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

 
nicholi shen:

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. 

You've already crushed my dreams ;) but apart from kidding, we are talking technically and according to the recent policy of Metaquotes, development issues are to be discussed in forum.

  • It's not an abstract class, from the documentation:

Abstract classes are used for creating generic entities, that you expect to use for creating more specific derived classes. An abstract class can only be used as the base class for some other class, that is why it is impossible to create an object of the abstract class type.

A class which contains at least one pure virtual function in it is abstract. Therefore, classes derived from the abstract class must implement all its pure virtual functions, otherwise they will also be abstract classes.

  • The member can have a name other than "m_type", for example "m_object_type" to avoid subclass member declaration issues.
  • What would restrict having protected members in a base class?
Suggestions and bug reports would help the development process, that's why I created this topic, but coding a subclass would be the easiest way.
 
nicholi shen:

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


What about private members of derivatives of CWndContainer and CWndClient class?

For example CCheckGroup has a private "m_rows" array of CCheckBox type, this way one should clone CCheckGroup as UiCheckGroup and declare the "m_rows" array as a UiCheckBox private member, this goes to all standard library base and derivative classes.

 
nicholi shen:

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. 

Your custom classes that follow the logic in won't fail if a "m_type" protected member is introduced in a future update, since they override the Type() method and they ignore a "m_type" member.

 
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.

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

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:

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.

Then we can simply cast it to the correct class,

I agree. But have you tried just using typename() instead? It is a less optimal solution since the function returns a string. However, if your primary aim is to simply identify the instance of the control you are dealing with, I think this is the way to go without having to clone the standard library or even insert additional protected members.
 
Enrico Lambino:
I agree. But have you tried just using typename() instead? It is a less optimal solution since the function returns a string. However, if your primary aim is to simply identify the instance of the control you are dealing with, I think this is the way to go without having to clone the standard library or even insert additional protected members.

The type is lost when casting, it always return the type which the variable is declared.

   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.
      string type = typename(obj); //"CObject*" is assigned
      if(type == "CChartObjectEdit*") { //false
         CChartObjectEdit *edit=(CChartObjectEdit*) obj;
      }
      else if(type == "CArrayString*") { //false
         CArrayString *string_array=(CArrayString*) obj;
      }
   }
 
Mohammad Hossein Sadeghi:

The type is lost when casting, it always return the type which the variable is declared.

I see. My bad. The controls are stored in CObject*, so it would return "CObject*" as well.

Another way I am thinking atm is to use an overloaded method for adding objects. On the code below, it uses the AddControl() method rather than the Add() method of CWndContainer, where each specific type of control is mirrored to m_controls, and are stored in different instances of CArrayObj. It is not a pretty neat solution, but at least when you are iterating on each of the controls array, you already know what type of instance you are dealing with. And you no longer need to clone or remake the entire standard library. One of the problems though is ambiguous calls. You either need to create CArrayObj instances for those types, or use Add() in case you don't want to track them.
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CDialogCustom : public CAppDialog
  {
protected:
   CArrayObj         m_controls_obj;
   CArrayObj         m_controls_wnd;
   CArrayObj         m_controls_wndcontainer;
   CArrayObj         m_controls_client;
public:
                     CDialogCustom(void);
                    ~CDialogCustom(void);
   virtual bool      AddControl(CObject *control);
   virtual bool      AddControl(CWnd *control);
   virtual bool      AddControl(CWndContainer *control);
   virtual bool      AddControl(CWndClient *control);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CDialogCustom::CDialogCustom(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CDialogCustom::~CDialogCustom(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CDialogCustom::AddControl(CWndContainer *control)
  {
   Print(__FUNCTION__+" added as container");
   Add(control);
   return true;
  }
bool CDialogCustom::AddControl(CWnd *control)
  {
   Print(__FUNCTION__+" added as wnd");
   Add(control);
   return true;
  }
bool CDialogCustom::AddControl(CWndClient *control)
  {
   Print(__FUNCTION__+" added as client");
   Add(control);
   return true;
  }
bool CDialogCustom::AddControl(CObject *control)
  {
   Print(__FUNCTION__+" added as object");
   Add(control);
   return true;
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CWnd wnd;
   CWnd *wnd1=new CWnd();
   CWndContainer *wnd2=new CWndContainer();
   CWndClient *wnd3=new CWndClient();
   CButton *wnd4=new CButton();
   
   CDialogCustom appdialog;
   appdialog.AddControl(GetPointer(wnd));    //ok
   appdialog.AddControl(GetPointer(wnd1));   //ok
   appdialog.AddControl(GetPointer(wnd2));   //ok
   appdialog.AddControl(GetPointer(wnd3));   //ok
   //appdialog.AddControl(GetPointer(wnd4)); //ambiguous call
   
   delete wnd1;
   delete wnd2;
   delete wnd3;
   delete wnd4;
  }
//+------------------------------------------------------------------+

I'd be interested to hear other solutions some might have regarding this problem, without cloning or remaking the standard library, as this can impact some of my existing programs. My thanks to you for raising this issue.

 

My friends, I think we are confusing virtual methods for regular methods. Let us not forget what makes virtual methods special, polymorphism. Mohammad, type is not lost when casting. There's a reason the base class implements the abstraction of type identification through virtual methods and not via a protected member. First let's look at a basic example of polymorphism and how we actually do get the result we expect. 

#include <uicontrols.mqh>
void OnStart()
{
   CArrayObj arr;
   arr.Add(new UiButton);
   arr.Add(new UiCheckBox);
   arr.Add(new UiComboBox);
   arr.Add(new UiEdit);
   for(int i=0; i<arr.Total(); i++)
      Print(arr.At(i).Type());
}
//FOO EURUSD,H1: 1002
//FOO EURUSD,H1: 1003
//FOO EURUSD,H1: 1005
//FOO EURUSD,H1: 1008

As you can see, each dynamic object has been casted and stored as a CObject* pointer in the array. From the loop we call the virtual method Type on the CObject* pointers without casting the objects back to their original pointers, and we do in fact get the proper Type ID integer return. 


The real reason you are having so much trouble is because you are trying to access newly added control objects to CAppDialog via the Controls method, but they aren't stored there. They are instead added to m_client_area which is a private member of CDialog, which then becomes inaccessible as soon as you subclass CAppDialog. In order for this to work the way you need it to, you will need to overload the Add method in your Dialog subclass and add a protected CArrayObj member to store pointers to the controls for future iteration. I've taken the liberty to update the uicontrols.mqh so now all you have to do is replace CAppDialog with UiAppDialog in your code (*also replace all CControls with UiControls) and you can then iterate your newly added controls via the new m_ui_controls member. 

Example:

//+------------------------------------------------------------------+
//|                                                       TestUI.mq5 |
//|                                                      nicholishen |
//|                                                    interwebs.com |
//+------------------------------------------------------------------+
#property copyright "nicholishen"
#property link      "interwebs.com"
#property version   "1.00"
#property strict

#include <uicontrols.mqh>

class MyApp : public UiAppDialog
{
   UiButton     m_button;
   UiLabel      m_label;
public:  
   bool Create(){
      return (
            UiAppDialog::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)
         && this.Add(m_button)
         && this.Add(m_label)
      );
   }
   void DEBUG_identity(){
      for(int i=0; i<m_ui_controls.Total(); i++){
         CWnd *control = m_ui_controls.At(i);
         if(control.Type() == TYPE_UI_BUTTON){
            Print("I am a button and my name is ", control.Name());
         }
         if(control.Type() == TYPE_UI_LABEL){
            Print("I am a label and my name is ", control.Name());
         }
      }
   }
};

MyApp app;
int OnInit()
{
   if(!app.Create() || !app.Run())
      return INIT_FAILED;   
   app.DEBUG_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);
}

And of course... the new include

//+------------------------------------------------------------------+
//|                                                   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\TimePicker.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
#define TYPE_UI_APPDIALOG     1017
//+------------------------------------------------------------------+
//| 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;   }};

class UiAppDialog : public CAppDialog
{
protected:
   CArrayObj      m_ui_controls;
public:
   virtual int Type()const {return TYPE_UI_APPDIALOG; }
   bool Add(CWnd *control){
      return(m_ui_controls.Add(control) && CAppDialog::Add(control));
   }
   bool Add(CWnd &control){
      return(m_ui_controls.Add(&control) && CAppDialog::Add(control));
   }
};

 
nicholi shen:

My friends, I think we are confusing virtual methods for regular methods. Let us not forget what makes virtual methods special, polymorphism. Mohammad, type is not lost when casting. There's a reason the base class implements the abstraction of type identification through virtual methods and not via a protected member. First let's look at a basic example of polymorphism and how we actually do get the result we expect. 

As you can see, each dynamic object has been casted and stored as a CObject* pointer in the array. From the loop we call the virtual method Type on the CObject* pointers without casting the objects back to their original pointers, and we do in fact get the proper Type ID integer return. 


The real reason you are having so much trouble is because you are trying to access newly added control objects to CAppDialog via the Controls method, but they aren't stored there. They are instead added to m_client_area which is a private member of CDialog, which then becomes inaccessible as soon as you subclass CAppDialog. In order for this to work the way you need it to, you will need to overload the Add method in your Dialog subclass and add a protected CArrayObj member to store pointers to the controls for future iteration. I've taken the liberty to update the uicontrols.mqh so now all you have to do is replace CAppDialog with UiAppDialog in your code (*also replace all CControls with UiControls) and you can then iterate your newly added controls via the new m_ui_controls member. 

Example:

And of course... the new include

You're right. thanks for your time and describing in details. By the way, by the word "type is lost when casting" I meant the name returned by typename as following:

#include <Controls\Wnd.mqh>

void OnStart()
  {

   CWnd wnd;
   CObject *obj = &wnd;
   Print(typename(obj)); //"CObject*" is printed.
  }
 
Mohammad Hossein Sadeghi:

You're right. thanks for your time and describing in details. By the way, by the word "type is lost when casting" I meant the name returned by typename as following:

No worries. Fair enough, I thought you were saying that casting back to CObject would result in Type always returning 0 despite overriding the virtual method. I'm glad we cleared that up 😁

 
nicholi shen:

My friends, I think we are confusing virtual methods for regular methods. Let us not forget what makes virtual methods special, polymorphism. Mohammad, type is not lost when casting. There's a reason the base class implements the abstraction of type identification through virtual methods and not via a protected member. First let's look at a basic example of polymorphism and how we actually do get the result we expect. 

As you can see, each dynamic object has been casted and stored as a CObject* pointer in the array. From the loop we call the virtual method Type on the CObject* pointers without casting the objects back to their original pointers, and we do in fact get the proper Type ID integer return. 


The real reason you are having so much trouble is because you are trying to access newly added control objects to CAppDialog via the Controls method, but they aren't stored there. They are instead added to m_client_area which is a private member of CDialog, which then becomes inaccessible as soon as you subclass CAppDialog. In order for this to work the way you need it to, you will need to overload the Add method in your Dialog subclass and add a protected CArrayObj member to store pointers to the controls for future iteration. I've taken the liberty to update the uicontrols.mqh so now all you have to do is replace CAppDialog with UiAppDialog in your code (*also replace all CControls with UiControls) and you can then iterate your newly added controls via the new m_ui_controls member. 

Example:

And of course... the new include

With the help of your example, I realized that the real reason is that not all derivative classes have a virtual Type() method.

I searched in Include files and found that the method is defined only for chart objects, indicators and arrays.

That's why calling Type() method on controls always return 0.

Your logic fills the GAP, but not fully, because each controls has its own m_controls array of CArrayObj, keeping the pointer to the private child controls, the pointer to child controls are accessible through Control() public method, so we can iterate them.

Suppose that I have a subclass of CCheckGroup as UiCheckGroup, it has a private member CCheckBox, I have no way to find out the type of controls in the UiCheckGroup.

So I believe the work around would be defining a virtual Type() method for each control type in a future update of standard library.

Reason: