Type() method of CObject derivatives in Standard library, always return 0 when casting to CObject.

To add comments, please log in or register
Mohammad Hossein Sadeghi
2044
Mohammad Hossein Sadeghi  

There is no clean way to detect the object type of the CObject derivatives, this is highly needed when iterating through items of a CObject array or a CArrayObj instance.

The "Type(void)" public method is defined virtually in the base class CObject.

class CObject
  {
public:
   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(0);      }
  };

The method is defined for all derivatives in standard library, here are some examples:

class CChartObjectLabel : public CChartObjectText
  {
public:
   //--- method of identifying the object
   virtual int       Type(void) const { return(OBJ_LABEL); }
  };

class CChartObjectEdit : public CChartObjectLabel
  {
public:
   //--- method of identifying the object
   virtual int       Type(void) const { return(OBJ_EDIT); }
  };

class CArrayString : public CArray
  {
public:
   //--- method of identifying the object
   virtual int       Type(void) const { return(TYPE_STRING); }
  };

Suppose that we have created a CAppDialog and we want to iterate through its controls, first we may assign controls to local pointer by the following code:

   int total=ControlsTotal();
   for(int i=0;i<total;i++) {
      CWnd *obj=Control(i); //CWnd is base class of the control object that creates control panels and indicator panels
   }

But there is no clean way to detect the control type, so we can't cast it to the correct class.

If the Type() method is called for the pointer, it will always return 0, because CWnd has not a Type() method and the Type() method of CObject always return 0.


The work around would be a protected member, m_type, in CObject, then all constructors should initialize it by their unique identifier, and the virtual Type() method of derivatives calls the parent method and returns the value of m_type.

class CObject
  {
protected:
   int               m_type;

public:
   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(m_type);      }
  };



class CArrayString : public CArray
  {
public:
   //--- method of identifying the object
   virtual int       Type(void) const { return(TYPE_STRING); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CArrayString::CArrayString(void)
  {
   m_type = TYPE_STRING;
//--- initialize protected data
   m_data_max=ArraySize(m_data);
  }



class CChartObjectEdit : public CChartObjectLabel
  {
public:
   //--- method of identifying the object
   virtual int       Type(void) const { return(OBJ_EDIT); }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CChartObjectEdit::CChartObjectEdit(void)
  {
   m_type = OBJ_EDIT;
  }

Then we can simply cast it to the correct class,

   int total=ControlsTotal();
   for(int i=0;i<total;i++) {
      CObject *obj=Control(i); //CObject is the base pointer type, to allow casting to all derivatives.
      if(obj.Type() == OBJ_EDIT) {
         CChartObjectEdit *edit=(CChartObjectEdit*) obj;
      }
      else if(obj.Type() == TYPE_STRING) {
         CArrayString *string_array=(CArrayString*) obj;
      }
   }
Mohammad Hossein Sadeghi
2044
Mohammad Hossein Sadeghi  

@MetaQuotes Software Corp. I created this topic, following this post because "Servicedesk goes to work only with financial issues."

I have tried to describe in full with code samples, and "it can be reproduced."

nicholi shen
2256
nicholi shen  

The problem is that (int)ENUM_DATATYPES and OBJ_X can overlap. Also, I'm not sure how adding a private member to store the type ID is a workaround. The workaround is to subclass controls and implement the Type method in your own classes. 

#include <Controls\Button.mqh>
#define TYPE_BUTTON 1001
class Button : public CButton
{
   public: virtual int Type() const override {
      return TYPE_BUTTON;
   }
};

void OnStart()
{
   Button button;
   CObject *obj = &button;
   printf("%d == %d", button.Type(), obj.Type()); //1001 == 1001
}
Mohammad Hossein Sadeghi
2044
Mohammad Hossein Sadeghi  
nicholi shen:

The problem is that (int)ENUM_DATATYPES and OBJ_X can overlap. Also, I'm not sure how adding a private member to store the type ID is a workaround. The workaround is to subclass controls and implement the Type method in your own classes. 

Not a private member, but a protected member, to avoid overlap, there could be unique definitions.

Your code works fine, even no subclass is needed for those derivatives that implement Type method and they return the correct type, others will return the parent class, typically 0 because of CObject::Type().

But, we can't always assign an object to a pointer by reference, since we don't have direct access to all objects and some times all we have is a pointer to it!

For example "CWnd* CWndContainer::Control(const int ind)" always returns a pointer, and there is no other solution to access the object itself by reference.

nicholi shen
2256
nicholi shen  
Mohammad Hossein Sadeghi:

Not a private member, but a protected member, to avoid overlap, there could be unique definitions.

Your code works fine, even no subclass is needed for those derivatives that implement Type method and they return the correct type, others will return the parent class, typically 0 because of CObject::Type().

But, we can't always assign an object to a pointer by reference, since we don't have direct access to all objects and some times all we have is a pointer to it!

For example "CWnd* CWndContainer::Control(const int ind)" always returns a pointer, and there is no other solution to access the object itself by reference.

Well, if you want to use Type() then you need to subclass and implement. Otherwise, you can use dynamic casting. 

//+------------------------------------------------------------------+
//|                                                       TestUI.mq5 |
//|                                                      nicholishen |
//|                                                    interwebs.com |
//+------------------------------------------------------------------+
#property copyright "nicholishen"
#property link      "interwebs.com"
#property version   "1.00"
#include <Controls\Button.mqh>
#include <Controls\Label.mqh>
#include <Controls\Dialog.mqh>

class MyApp : public CAppDialog
{
   CButton     m_button;
   CLabel      m_label;
   CArrayObj   m_ui_controls;
public:  
   bool Create(){
      return (
            CAppDialog::Create(0, "MyApp", 0, 10, 10, 150, 150);
         && m_button.Create(m_chart_id, m_name+"button", m_subwin, 2, 2, 22, 22)
         && m_label.Create(m_chart_id, m_name+"label", m_subwin, 23, 2, 0, 0)
         && Add(m_button)
         && Add(m_label)
      );
   }
   bool Add(CWnd &control){
      return (m_ui_controls.Add(&control) && CAppDialog::Add(control));
   }
   void print_identity(){
      CWnd *obj = m_ui_controls.At(0);
      for(int i=0; CheckPointer(obj); obj=m_ui_controls.At(++i)){
         CButton *button = dynamic_cast<CButton*>(obj);
         if(CheckPointer(button)){
            Print(i, " I am a button");
            continue;
         }
         CLabel *label = dynamic_cast<CLabel*>(obj);
         if(CheckPointer(label)){
            Print(i, " I am a label");
            continue;
         }
         Print("I don't know what I am!");
      }
   }
};

MyApp app;
int OnInit()
{
   if(!app.Create() || !app.Run())
      return INIT_FAILED;   
   app.print_identity();
   return(INIT_SUCCEEDED);
}
void OnTick(){}
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   app.ChartEvent(id, lparam, dparam, sparam);
}
void OnDeinit(const int reason)
{
   app.Destroy(reason);
}
Mohammad Hossein Sadeghi
2044
Mohammad Hossein Sadeghi  
nicholi shen:

Well, if you want to use Type() then you need to subclass and implement. Otherwise, you can use dynamic casting. 

It's great, I enjoyed it, thanks (highly appreciated).

The only remaining issue would be the efficiency, since a pointer is allocated and a dynamic_cast is performed per intended class.

But it is worth, meanwhile I believe if it is modified in the standard library and my work around is applied for a future release of MQL5, it's worth too.

Alain Verleyen
37245
Alain Verleyen  
Mohammad Hossein Sadeghi:

It's great, I enjoyed it, thanks (highly appreciated).

The only remaining issue would be the efficiency, since a pointer is allocated and a dynamic_cast is performed per intended class.

But it is worth, meanwhile I believe if it is modified in the standard library and my work around is applied for a future release of MQL5, it's worth too.

That's a typical weakness of OOP when you have to use a library with structural weaknesses, bad design and not heavily supported. Rather than using solution that could lead to serious performance issue if you need to build a GUI using a lot of different controls, I would suggest you to clone the "standard" library and to apply your solution rather than waiting Metaquotes (good luck with that). Not ideal but seems to me the best choice.
Mohammad Hossein Sadeghi
2044
Mohammad Hossein Sadeghi  
Alain Verleyen:
That's a typical weakness of OOP when you have to use a library with structural weaknesses, bad design and not heavily supported. Rather than using solution that could lead to serious performance issue if you need to build a GUI using a lot of different controls, I would suggest you to clone the "standard" library and to apply your solution rather than waiting Metaquotes (good luck with that). Not ideal but seems to me the best choice.

I agree, a good idea for MQL4, as its development is stopped, but there's a chance for MQL5.

nicholi shen
2256
nicholi shen  
Mohammad Hossein Sadeghi:

I agree, a good idea for MQL4, as its development is stopped, but there's a chance for MQL5.

I'm not trying to crush your dreams but there's no chance for this to happen in MQL5, and it isn't a matter of whether or not it could be implemented, it shouldn't be implemented. You should never have protected mutable members in an abstract base class. Do you know how many descendant classes already contain a "m_type" member? I think half of all my custom classes would break if MQ ever did such a thing. Your only chance here is if MQ ever releases an update to allow for multiple inheritance, and even then you'd still have to roll your own Control classes. So if you are concerned with "performance" issues associated with dynamic casting (which I can assure you, you shouldn't be) then you should K.S.S. and subclass each control, assign it a unique int ID, and implement the `Type` method because after-all, that is the "proper" way to do it. 

nicholi shen
2256
nicholi shen  

I had a few minutes waiting for a trade setup... Merry Xmas. 


//+------------------------------------------------------------------+
//|                                                   uicontrols.mqh |
//|                                                      nicholishen |
//|                         https://www.forexfactory.com/nicholishen |
//+------------------------------------------------------------------+
#property copyright "nicholishen"
#property link      "https://www.forexfactory.com/nicholishen"
#ifdef __MQL4__
   #property strict
#endif 
#include <Controls\BmpButton.mqh>
#include <Controls\Button.mqh>
#include <Controls\CheckBox.mqh>
#include <Controls\CheckGroup.mqh>
#include <Controls\ComboBox.mqh>
#include <Controls\DateDropList.mqh>
#include <Controls\DatePicker.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Label.mqh>
#include <Controls\ListView.mqh>
#include <Controls\Panel.mqh>
#include <Controls\Picture.mqh>
#include <Controls\RadioButton.mqh>
#include <Controls\RadioGroup.mqh>
#include <Controls\Scrolls.mqh>
#include <Controls\SpinEdit.mqh>
#include <Controls\Dialog.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
#define TYPE_UI_BMPBUTTON     1001 
#define TYPE_UI_BUTTON        1002 
#define TYPE_UI_CHECKBOX      1003 
#define TYPE_UI_CHECKGROUP    1004 
#define TYPE_UI_COMBOBOX      1005 
#define TYPE_UI_DATEDROPLIST  1006 
#define TYPE_UI_DATEPICKER    1007 
#define TYPE_UI_EDIT          1008 
#define TYPE_UI_LABEL         1009 
#define TYPE_UI_LISTVIEW      1010 
#define TYPE_UI_PANEL         1011 
#define TYPE_UI_PICTURE       1012 
#define TYPE_UI_RADIOBUTTON   1013 
#define TYPE_UI_RADIOGROUP    1014 
#define TYPE_UI_SCROLL        1015
#define TYPE_UI_SPINEDIT      1016
//+------------------------------------------------------------------+
//| DESCENDANTS                                                      |
//+------------------------------------------------------------------+
class UiBmpButton    :public CBmpButton   {public:virtual int Type()const{return TYPE_UI_BMPBUTTON;  }};
class UiButton       :public CButton      {public:virtual int Type()const{return TYPE_UI_BUTTON;     }};
class UiCheckBox     :public CCheckBox    {public:virtual int Type()const{return TYPE_UI_CHECKBOX;   }};
class UiCheckGroup   :public CCheckGroup  {public:virtual int Type()const{return TYPE_UI_CHECKGROUP; }};
class UiComboBox     :public CComboBox    {public:virtual int Type()const{return TYPE_UI_COMBOBOX;   }};
class UiDateDropList :public CDateDropList{public:virtual int Type()const{return TYPE_UI_DATEDROPLIST;}};
class UiDatePicker   :public CDatePicker  {public:virtual int Type()const{return TYPE_UI_DATEPICKER; }};
class UiEdit         :public CEdit        {public:virtual int Type()const{return TYPE_UI_EDIT;       }};
class UiLabel        :public CLabel       {public:virtual int Type()const{return TYPE_UI_LABEL;      }};
class UiListView     :public CListView    {public:virtual int Type()const{return TYPE_UI_LISTVIEW;   }};
class UiPanel        :public CPanel       {public:virtual int Type()const{return TYPE_UI_PANEL;      }};
class UiPicture      :public CPicture     {public:virtual int Type()const{return TYPE_UI_PICTURE;    }};
class UiRadioButton  :public CRadioButton {public:virtual int Type()const{return TYPE_UI_RADIOBUTTON;}};
class UiRadioGroup   :public CRadioGroup  {public:virtual int Type()const{return TYPE_UI_RADIOGROUP; }};
class UiScroll       :public CScroll      {public:virtual int Type()const{return TYPE_UI_SCROLL;     }};
class UiSpinEdit     :public CSpinEdit    {public:virtual int Type()const{return TYPE_UI_SPINEDIT;   }};
nicholi shen
2256
nicholi shen  
Alain Verleyen:
That's a typical weakness of OOP when you have to use a library with structural weaknesses, bad design and not heavily supported. Rather than using solution that could lead to serious performance issue if you need to build a GUI using a lot of different controls, I would suggest you to clone the "standard" library and to apply your solution rather than waiting Metaquotes (good luck with that). Not ideal but seems to me the best choice.

No need to clone. You just need to subclass and implement the virtual method `Type`. That way if MQ ever updates the stdlib then all fixes will automatically apply to the subclass as well (if code recompiled). See solution in post #9

To add comments, please log in or register