Русский 中文 Español Deutsch 日本語 Português
Graphics in DoEasy library (Part 94): Moving and deleting composite graphical objects

Graphics in DoEasy library (Part 94): Moving and deleting composite graphical objects

MetaTrader 5Examples | 11 March 2022, 10:06
7 289 0
Artyom Trishkin
Artyom Trishkin

Contents


Concept

In the previous article, I started the development of composite graphical objects. To define a composite graphical object, I introduced a new type of graphical element — an extended standard graphical object. All graphical objects participating in the creation of a composite graphical object are to be of that type. For now, I create no classes for creating certain composite graphical objects. Instead, I am going to implement the functionality allowing for creation of predefined composite graphical objects which, naturally, does not exclude the possibility of creating custom composite graphical objects both programmatically and "on the fly" — directly on the chart.


I will divide my work on the functionality into several parts. First, I will create the necessary toolkit for managing and creating composite graphical objects. Next, I will add predefined classes of such objects (in fact, here all depends on user's individual needs, the classes of predefined composite graphical objects are used only as an example here). Next, I will start implementing the functionality enabling us to create composite graphical objects visually, manually, in real time and directly on the chart.

In fact, here I am going to fine-tune the things I implemented in the previous article. I will introduce setting the coordinate anchor points to subordinate objects and receiving the coordinates. Besides, I will test moving the base object with attached subordinate ones (at this stage, it turns out that I also need the functionality for moving coordinate points of a composite object in a more complex form rather than simply tracking a single object event), as well as create the functionality for removing a composite graphical object.

Moving coordinate points of a graphical object leads to the CHARTEVENT_OBJECT_DRAG event only when the mouse button is released. Accordingly, when we track this event only, moving a base graphical object (while the mouse button is not released) leads to all objects attached to it remaining unchanged. When the button is released and the event appears, the bound objects are moved to their base object anchor points. This means we should track the mouse movement with the pressed mouse button. Besides, we need to know that the button was pressed on a base graphical object, namely in its coordinate (or central) anchor point. We should also be able to recalculate the location of the object coordinate points and the anchor points of its subordinate objects.
The CHARTEVENT_OBJECT_DRAG event should also be handled at the very end of the relocation in order to fix the end coordinates of the base object and use them to recalculate the coordinates of all subordinate graphical objects bound to it.

In the current article, I will implement handling the CHARTEVENT_OBJECT_DRAG event and recalculating coordinates of bound objects according to a new location of the base object coordinates. If the base object is removed, a composite graphical object is removed as well. In case of such an event, remove all graphical objects bound to it. For now, I am going to make it simple by disabling the ability to select all graphical objects, bound to a base one, with a mouse. Thus, we need to select the base object and delete it in order to remove a composite graphical object. We will no longer be able to select any of the bound object using a mouse. This is the first and the simplest way to protect against the destruction of a composite graphical object.
However, it is possible to open the object list (Ctrl+B), select the properties of any bound object and allow it to select, or remove from the graphical object list window immediately. Later, I will also implement handling of an intended destruction of a composite graphical object. When removing any graphical object bound to the base one, we will remove all objects participating in constructing a composite graphical object. In other words, I will make so that the entire composite object is deleted when any of the objects forming it is removed. In the coming articles, I will also introduce the functionality for detaching a bound graphical object from the base one.


Improving library classes

As usual, let's implement the new library messages first.
In \MQL5\Include\DoEasy\Data.mqh, add the indices of the new messages:

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART,        // Failed to remove a graphical object from the chart
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST,          // Failed to set a graphical object to the list of removed objects
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST,          // Failed to set a graphical object to the list of renamed objects

...

//--- CLinkedPivotPoint
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X,                // Not a single pivot point is set for the object along the X axis
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y,                // Not a single pivot point is set for the object along the Y axis
   MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE,             // The object is not attached to the basic graphical object
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ,       // Failed to create a data object for the X and Y pivot points
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X,            // Number of base object pivot points for calculating the X coordinate: 
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y,            // Number of base object pivot points for calculating the Y coordinate: 
   
  };
//+------------------------------------------------------------------+

and the text messages corresponding to newly added indices:

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   {"Не удалось удалить графический объект из списка","Failed to delete graphic object from the list"},
   {"Не удалось удалить графический объект с графика","Failed to delete graphic object from the chart"},
   {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"},
   {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"},

...

//--- CLinkedPivotPoint
   {"Для объекта не установлено ни одной опорной точки по оси X","The object does not have any pivot points set along the x-axis"},
   {"Для объекта не установлено ни одной опорной точки по оси Y","The object does not have any pivot points set along the y-axis"},
   {"Объект не привязан к базовому графическому объекту","The object is not attached to the base graphical object"},
   {"Не удалось создать объект данных опорной точки X и Y.","Failed to create X and Y reference point data object"},
   {"Количество опорных точек базового объекта для расчёта координаты X: ","Number of reference points of the base object to set the X coordinate: "},
   {"Количество опорных точек базового объекта для расчёта координаты Y: ","Number of reference points of the base object to set the Y coordinate: "},
   
  };
//+---------------------------------------------------------------------+


Let's make the following small improvement in all descendant class files of the abstract standard graphical object stored in \MQL5\Include\DoEasy\Objects\Graph\Standard\, namely in its methods for displaying a brief object description:

//+------------------------------------------------------------------+
//| Display a short description of the object in the journal         |
//+------------------------------------------------------------------+
void CGStdArrowBuyObj::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print
     (
      (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(string)this.GetProperty(GRAPH_OBJ_PROP_ID,0),
      ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
     );
  }
//+------------------------------------------------------------------+

Since all virtual methods displaying a short object name should have the set of inputs similar to the parent class method, these methods featured (and still feature) unused inputs. I have just implemented one of them — displaying a hyphen before the text returned from the method. If the method receives the dash flag equal to true, a hyphen is set before displaying a short object name (an example is provided in the current article further below). This is convenient if we want to write a header and display the object name enumeration under it.
Such changes (completely identical to the considered one) have already been made in all class files derived from the class of the abstract standard graphical object. You can find them in the files attached below.

All basic changes considered here have to do with the class of the abstract standard graphical object \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.

In the class of the dependent object pivot point data located in the same file, the array was declared with the name having the coordinate: m_property_x[][2] remaining after the experiments with two arrays in a single class for X and Y coordinates. Later, I abandoned this idea, while the array name remained incorrect. Therefore, it was renamed into m_property[][2].

The public section of the class now features the method for displaying the name of the axis whose coordinates are stored in the class, the method for returning a property and the modifier of a property stored in the array, as well as the method returning the description of a number of the base object pivot points used to calculate the point of the coordinate the dependent graphical object is attached to — the method is used for debugging:

//+------------------------------------------------------------------+
//| Class of the dependent object pivot point data                   |
//+------------------------------------------------------------------+
class CPivotPointData
  {
private:
   bool              m_axis_x;
   int               m_property[][2];
public:
//--- (1) Set and (2) return the flag indicating that the pivot point belongs to the X coordinate
   void              SetAxisX(const bool axis_x)         { this.m_axis_x=axis_x;             }
   bool              IsAxisX(void)                 const { return this.m_axis_x;             }
   string            AxisDescription(void)         const { return(this.m_axis_x ? "X" : "Y");}
//--- Return the number of base object pivot points for calculating the coordinate of the dependent one
   int               GetBasePivotsNum(void)  const { return ::ArrayRange(this.m_property,0);  }
//--- Add the new pivot point of the base object for calculating the coordinate of a dependent one
   bool              AddNewBasePivotPoint(const string source,const int pivot_prop,const int pivot_num)
                       {
                        //--- Get the array size 
                        int pivot_index=this.GetBasePivotsNum();
                        //--- if failed to increase the array size, inform of that and return 'false'
                        if(::ArrayResize(this.m_property,pivot_index+1)!=pivot_index+1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
                           return false;
                          }
                        //--- Return the result of changing the values of a newly added new array dimension
                        return this.ChangeBasePivotPoint(source,pivot_index,pivot_prop,pivot_num);
                       }
//--- Change the specified pivot point of the base object for calculating the coordinate of a dependent one
   bool              ChangeBasePivotPoint(const string source,const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        //--- Get the array size. If it is zero, inform of that and return 'false'
                        int n=this.GetBasePivotsNum();
                        if(n==0)
                          {
                           CMessage::ToLog(source,(this.IsAxisX() ? MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X : MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y));
                           return false;
                          }
                        //--- If the specified index goes beyond the array range, inform of that and return 'false'
                        if(pivot_index<0 || pivot_index>n-1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
                           return false;
                          }
                        //--- Set the values, passed to the method, in the specified array cells by index
                        this.m_property[pivot_index][0]=pivot_prop;
                        this.m_property[pivot_index][1]=pivot_num;
                        return true;
                       }

//--- Return(1) a property and (2) a modifier of the property from the array
   int               GetProperty(const string source,const int index)     const
                       {
                        if(index<0 || index>this.GetBasePivotsNum()-1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
                           return WRONG_VALUE;
                          }
                        return this.m_property[index][0];   
                       }
   int               GetPropertyModifier(const string source,const int index)  const
                       {
                        if(index<0 || index>this.GetBasePivotsNum()-1)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
                           return WRONG_VALUE;
                          }
                        return this.m_property[index][1];   
                       }

//--- Return the description of the number of pivot points for setting the coordinate
   string            GetBasePivotsNumDescription(void) const
                       {
                        return CMessage::Text(IsAxisX() ? MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X : MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y)+
                               (string)this.GetBasePivotsNum();
                       }

//--- Constructor/destructor
                     CPivotPointData(void){;}
                    ~CPivotPointData(void){;}
  };
//+------------------------------------------------------------------+

All methods are very simple. Their logic should be clear from the code. I am not going to dwell on them here.


In the class of data on X and Y pivot points of a composite object, add the methods returning the result of calling the new methods I have just considered:

//+------------------------------------------------------------------+
//| Class of data on X and Y pivot points of a composite object      |
//+------------------------------------------------------------------+
class CPivotPointXY : public CObject
  {
private:
   CPivotPointData   m_pivot_point_x;            // X coordinate pivot point
   CPivotPointData   m_pivot_point_y;            // Y coordinate pivot point
public:
//--- Return the pointer to the (1) X and (2) Y coordinate pivot point data object
   CPivotPointData  *GetPivotPointDataX(void)      { return &this.m_pivot_point_x;                    }
   CPivotPointData  *GetPivotPointDataY(void)      { return &this.m_pivot_point_y;                    }
//--- Return the number of base object pivot points for calculating the (1) X and (2) Y coordinate
   int               GetBasePivotsNumX(void) const { return this.m_pivot_point_x.GetBasePivotsNum();  }
   int               GetBasePivotsNumY(void) const { return this.m_pivot_point_y.GetBasePivotsNum();  }
//--- Add the new pivot point of the base object for calculating the X coordinate of a dependent one
   bool              AddNewBasePivotPointX(const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num);
                       }
//--- Add the new pivot point of the base object for calculating the Y coordinate of a dependent one
   bool              AddNewBasePivotPointY(const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num);
                       }
//--- Add new pivot points of the base object for calculating the X and Y coordinates of a dependent one
   bool              AddNewBasePivotPointXY(const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        bool res=true;
                        res &=this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x);
                        res &=this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y);
                        return res;
                       }
//--- Change the specified pivot point of the base object for calculating the X coordinate of a dependent one
   bool              ChangeBasePivotPointX(const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num);
                       }
//--- Change the specified pivot point of the base object for calculating the Y coordinate of a dependent one
   bool              ChangeBasePivotPointY(const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        return this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num);
                       }
//--- Change specified pivot points of the base object for calculating the X and Y coordinates
   bool              ChangeBasePivotPointXY(const int pivot_index,
                                            const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        bool res=true;
                        res &=this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x);
                        res &=this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y);
                        return res;
                       }
//--- Return (1) the property for calculating the X coordinate and (2) the X coordinate property modifier
   int               GetPropertyX(const string source,const int index) const
                       {
                        return this.m_pivot_point_x.GetProperty(source,index);
                       }
   int               GetPropertyModifierX(const string source,const int index) const
                       {
                       return this.m_pivot_point_x.GetPropertyModifier(source,index);
                       }
//--- Return (1) the property for calculating the Y coordinate and (2) the Y coordinate property modifier
   int               GetPropertyY(const string source,const int index) const
                       {
                        return this.m_pivot_point_y.GetProperty(source,index);
                       }
   int               GetPropertyModifierY(const string source,const int index) const
                       {
                       return this.m_pivot_point_y.GetPropertyModifier(source,index);
                       }
//--- Return the description of the number of pivot points for setting the (1) X and (2) Y coordinates
   string            GetBasePivotsNumXDescription(void) const
                       {
                        return this.m_pivot_point_x.GetBasePivotsNumDescription();
                       }
   string            GetBasePivotsNumYDescription(void) const
                       {
                        return this.m_pivot_point_y.GetBasePivotsNumDescription();
                       }
                       
//--- Constructor/destructor
                     CPivotPointXY(void){ this.m_pivot_point_x.SetAxisX(true); this.m_pivot_point_y.SetAxisX(false); }
                    ~CPivotPointXY(void){;}
  };  
//+------------------------------------------------------------------+

Each of these methods returns the result of calling a same-name method of the appropriate class storing the data on X and Y axis coordinates.
The names of the methods now specify the exact coordinate whose data is returned by the method, for example GetPropertyX or GetPropertyY.

The class of the bound data of the composite object pivot points has been considerably improved mostly in terms of method names. During the debugging, I began confusing the names of the methods that were not self-explanatory enough. Therefore, I have renamed them for more clarity. For example the name of the CreateNewLinkedPivotPoint() method, which adds a new anchor point of a dependent object by X and Y coordinates, was pretty confusing since PivotPoint is an anchor point used to set the X or Y coordinates of the base object for calculating the coordinate the dependent object is to be attached to. The coordinate point itself can be calculated using several PivotPoint. Therefore, the method was renamed to CreateNewLinkedCoord() indicating the adding of a new coordinate point.

The ternary operators have been added to shorten the method code. For example, the method

   CPivotPointData  *GetBasePivotPointDataX(const int index) const
                       {
                        CPivotPointXY *obj=this.GetLinkedPivotPointXY(index);
                        if(obj==NULL)
                           return NULL;
                        return obj.GetPivotPointDataX();
                       }

now looks as follows:

   CPivotPointData  *GetBasePivotPointDataX(const int index_coord_point) const
                       {
                        CPivotPointXY *obj=this.GetLinkedCoord(index_coord_point);
                        return(obj!=NULL ? obj.GetPivotPointDataX() : NULL);
                       }

which is completely the same albeit shorter.

Also, the public section of the class now features the methods returning the result of calling the same-name class methods corresponding to the required coordinate, thus simplifying the obtaining of the required data:

//--- Add the new pivot point of the base object for calculating the X coordinate for a specified anchor point of the dependent one
   bool              AddNewBasePivotPointX(const int index_coord_point,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false);
                       }
//--- Add the new pivot point of the base object for calculating the Y coordinate for a specified anchor point of the dependent one
   bool              AddNewBasePivotPointY(const int index_coord_point,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false);
                       }
//--- Add the new pivot points of the base object for calculating the X and Y coordinates for a specified anchor point of the dependent one
   bool              AddNewBasePivotPointXY(const int index_coord_point,
                                            const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point);
                        if(objx==NULL)
                           return false;
                        CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point);
                        if(objy==NULL)
                           return false;
                        bool res=true;
                        res &=objx.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x);
                        res &=objy.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y);
                        return res;
                       }

//--- Change the specified pivot point of the base object for calculating the X coordinate for a specified anchor point of the dependent one
   bool              ChangeBasePivotPointX(const int index_coord_point,const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false);
                       }
//--- Change the specified pivot point of the base object for calculating the Y coordinate for a specified anchor point of the dependent one
   bool              ChangeBasePivotPointY(const int index_coord_point,const int pivot_index,const int pivot_prop,const int pivot_num)
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false);
                       }
//--- Change the specified pivot points of the base object for calculating the X and Y coordinates for a specified anchor point
   bool              ChangeBasePivotPointXY(const int index_coord_point,
                                            const int pivot_index,
                                            const int pivot_prop_x,const int pivot_num_x,
                                            const int pivot_prop_y,const int pivot_num_y)
                       {
                        CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point);
                        if(objx==NULL)
                           return false;
                        CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point);
                        if(objy==NULL)
                           return false;
                        bool res=true;
                        res &=objx.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x);
                        res &=objy.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y);
                        return res;
                       }

//--- Return the property for calculating the X coordinate for a specified anchor point
   int               GetPropertyX(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE);
                       }
//--- Return the modifier of the X coordinate property for a specified anchor point
   int               GetPropertyModifierX(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE);
                       }

//--- Return the property for calculating the Y coordinate for a specified anchor point
   int               GetPropertyY(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE);
                       }
//--- Return the modifier of the Y coordinate property for a specified anchor point
   int               GetPropertyModifierY(const int index_coord_point,const int index) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                       return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE);
                       }

//--- Return the description of the number of base object pivot points for calculating the X coordinate by index
   string            GetBasePivotsNumXDescription(const int index_coord_point) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
                        return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE");
                       }
//--- Return the description of the number of base object pivot points for calculating the Y coordinate by index
   string            GetBasePivotsNumYDescription(const int index_coord_point) const
                       {
                        CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
                        return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE");
                       }

//--- Constructor/destructor
                     CLinkedPivotPoint(void){;}
                    ~CLinkedPivotPoint(void){;}
  };  
//+------------------------------------------------------------------+


In the methods returning the property description of the class of the abstract standard graphical object, add the index of the required property:

//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property)  { return true;             }

//--- Get description of (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER property,const int index=0);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_DOUBLE property,const int index=0);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_STRING property,const int index=0);

//--- Return the description of the graphical object anchor point position
   virtual string    AnchorDescription(void)                const { return (string)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0); }

This will allow us to make so that the methods display the list of required graphical object properties rather than showing all of them.

Let me explain. Suppose that a trend line has two anchor points. The property modifier (the index in the methods considered above) is used to set time (X coordinate) or price (Y coordinate) in order to indicate the coordinates of which point (left or right) are to be obtained. At the moment, the method displays the full list of all properties — the header is followed by the values of both anchor points:

OnChartEvent: Time coordinate: 
 - Pivot point 0: 2022.01.24 20:59
 - Pivot point 1: 2022.01.26 22:00

...

OnChartEvent: Price coordinate: 
 - Pivot point 0: 1.13284
 - Pivot point 1: 1.11846

But if we need to display a single point, there is no way to do that at the moment. We need to write the property name and its value. Later, I will implement an easy way of displaying the name and value of a required anchor point when using the indices. For now, I will set the default values for indices. This will protect against multiple errors and make inserting changes easier since we simply need to remove a default value and add the necessary handling of arising errors for displaying either a full description (like now) or a selective one for a single anchor point.

In the public section, add the method returning the number of attached objects to the base one and fix the method names:

//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects
   CArrayObj        *GetListDependentObj(void)        { return &this.m_list;           }
   CGStdGraphObj    *GetDependentObj(const int index) { return this.m_list.At(index);  }
   int               GetNumDependentObj(void)         { return this.m_list.Total();    }
//--- Return the name of the dependent object by index
   string            NameDependent(const int index);
//--- Add the dependent graphical object to the list
   bool              AddDependentObj(CGStdGraphObj *obj);

//--- Return the object of data on pivot points
   CLinkedPivotPoint*GetLinkedPivotPoint(void)        { return &this.m_linked_pivots;  }
   
//--- Add a new pivot point for calculating X and Y coordinates to the current object
   bool              AddNewLinkedCoord(const int pivot_prop_x,const int pivot_num_x,const int pivot_prop_y,const int pivot_num_y)
                       {
                        //--- If the current object is not bound to the base one, display the appropriate message and return 'false'
                        if(this.BaseObjectID()==0)
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE);
                           return false;
                          }
                        //--- Return the result of adding a new connected pivot point from the CLinkedPivotPoint class to the current object
                        return this.m_linked_pivots.CreateNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y);
                       }
//--- Add a new pivot point for calculating X and Y coordinates to the specified object
   bool              AddNewLinkedCoord(CGStdGraphObj *obj,const int pivot_prop_x,const int pivot_num_x,const int pivot_prop_y,const int pivot_num_y)
                       {
                        //--- If the current object is not an extended one, display the appropriate message and return 'false'
                        if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_NOT_EXT_OBJ);
                           return false;
                          }
                        //--- If a zero pointer to the object is passed, return 'false'
                        if(obj==NULL)
                           return false;
                        //--- Return the result of adding a new connected pivot point from the CLinkedPivotPoint class to the specified object
                        return obj.AddNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y);
                       }


Rename the GetLinkedPivotsNum() methods and declare new private methods for setting the coordinates to the subordinate graphical objects:

//--- Return the number of base object pivot points for calculating the (1) X and (2) Y coordinate in the current object
   int               GetBasePivotsNumX(const int index)           { return this.m_linked_pivots.GetBasePivotsNumX(index);  }
   int               GetBasePivotsNumY(const int index)           { return this.m_linked_pivots.GetBasePivotsNumY(index);  }
//--- Return the number of base object pivot points for calculating the (1) X and (2) Y coordinate in the specified object
   int               GetBasePivotsNumX(CGStdGraphObj *obj,const int index) const { return(obj!=NULL ? obj.GetBasePivotsNumX(index): 0); }
   int               GetBasePivotsNumY(CGStdGraphObj *obj,const int index) const { return(obj!=NULL ? obj.GetBasePivotsNumY(index): 0); }
//--- Return the number of base object pivot points for calculating the coordinates in the (1) current (2) object
   int               GetLinkedCoordsNum(void)               const { return this.m_linked_pivots.GetNumLinkedCoords();      }
   int               GetLinkedPivotsNum(CGStdGraphObj *obj) const { return(obj!=NULL ? obj.GetLinkedCoordsNum() : 0);      }
   
private:
//--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property
   void              SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);

public:
//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                       }
protected:
//--- Protected parametric constructor
                     CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                                   const ENUM_GRAPH_ELEMENT_TYPE elm_type,
                                   const ENUM_GRAPH_OBJ_BELONG belong,
                                   const ENUM_GRAPH_OBJ_SPECIES species,
                                   const long chart_id, const int pivots,
                                   const string name);
                     
public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+


In the method adding the subordinate standard graphical object to the list of objects bound to the base one, add setting the properties:

//+------------------------------------------------------------------+
//| Add a subordinate standard graphical object to the list          |
//+------------------------------------------------------------------+
bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj)
  {
   //--- If the current object is not an extended one, inform of that and return 'false'
   if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ);
      return false;
     }
   //--- If failed to add the pointer to the passed object into the list, inform of that and return 'false'
   if(!this.m_list.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST);
      return false;
     }
   //--- Object added to the list - set its number in the list,
   //--- name and ID of the current object as the base one,
   //--- set the flags of object availability and selection
   //--- and the graphical element type - standard extended graphical object
   obj.SetNumber(this.m_list.Total()-1);
   obj.SetBaseName(this.Name());
   obj.SetBaseObjectID(this.ObjectID());
   obj.SetFlagSelected(false,false);
   obj.SetFlagSelectable(false,false);
   obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED);
   return true;
  }
//+------------------------------------------------------------------+

Set the object selection flag to false to avoid selection of a newly added object. Disable the object availability right away by setting the appropriate flag to false as well. Next, set the "extended standard graphical object" type for the object. The ability to select the objects by mouse is disabled and they become available in the list of extended standard graphical objects making it possible to programmatically select them by type and name of a base graphical object.

The method setting the X coordinate from the specified property of the base object to the specified subordinate object:

//+--------------------------------------------------------------------+
//| Set the X coordinate from the specified property of the base object|
//| to the specified subordinate object                                |
//+--------------------------------------------------------------------+
void CGStdGraphObj::SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to)
  {
   int prop=WRONG_VALUE;
   switch(obj.TypeGraphObject())
     {
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        prop=GRAPH_OBJ_PROP_XDISTANCE; 
        break;
      default:
        prop=GRAPH_OBJ_PROP_TIME;
        break;
     }
   if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL)
     {
      this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL)
     {
      //--- Assigning a real property value to the integer value of the X coordinate is a bad idea unless you know what you are doing
      this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL+GRAPH_OBJ_PROP_STRING_TOTAL)
     {
      //--- Assigning a string property value to the integer value of the X coordinate is a bad idea unless you know what you are doing
      this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to);
     }
  }
//+------------------------------------------------------------------+

Select the necessary property depending on the object type. This may be a time coordinate or a coordinate in screen pixels. Next, set the property, whose coordinates have been passed in the method inputs, to the coordinate property of the object passed to the method by the pointer — the property itself and its modifier. Finally, specify the modifier of the property set in the object itself. As a result, the graphical object features the necessary coordinates of the anchor point whose parameters were passed to the method.

The method setting the Y coordinate from the specified property of the base object to the specified subordinate object:

//+--------------------------------------------------------------------+
//| Set the Y coordinate from the specified property of the base object|
//| to the specified subordinate object                                |
//+--------------------------------------------------------------------+
void CGStdGraphObj::SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to)
  {
   int prop=WRONG_VALUE;
   switch(obj.TypeGraphObject())
     {
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        prop=GRAPH_OBJ_PROP_YDISTANCE;
        break;
      default:
        prop=GRAPH_OBJ_PROP_PRICE;
        break;
     }
   if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL)
     {
      if(prop==GRAPH_OBJ_PROP_YDISTANCE)
         this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
      else
         //--- Assigning an integer property value to the real value of the Y coordinate is allowed only if you know what you are doing
         this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL)
     {
      if(prop==GRAPH_OBJ_PROP_YDISTANCE)
         //--- Assigning a real property value to the integer value of the Y coordinate is a bad idea unless you know what you are doing
         this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to);
      else
         this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to);
     }
   else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL+GRAPH_OBJ_PROP_STRING_TOTAL)
     {
      //--- Assigning a string property value to the integer or real value of the Y coordinate is a bad idea unless you know what you are doing
      if(prop==GRAPH_OBJ_PROP_YDISTANCE)
         this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to);
      else
         this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,(double)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to);
     }
  }
//+------------------------------------------------------------------+

Here all is similar to the method for setting the X coordinate. However, there is an exception: the X coordinate is always integer — either time or the number of pixels, while the Y coordinate can be either an integer (the number of pixels), or a real value (price). Therefore, here we should check the resulting property to be set. Depending on that, we set the value either to an integer property, or to a real one.

The method setting an integer value to the specified subordinate object:

//+------------------------------------------------------------------+
//| Set the integer property                                         |
//| to the specified dependent object                                |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier)
  {
   if(obj==NULL || obj.BaseObjectID()==0)
      return;
   switch(prop)
     {
      case GRAPH_OBJ_PROP_TIMEFRAMES            :  obj.SetVisibleOnTimeframes((int)value,false);         break;   // Object visibility on timeframes
      case GRAPH_OBJ_PROP_BACK                  :  obj.SetFlagBack(value,false);                         break;   // Background object
      case GRAPH_OBJ_PROP_ZORDER                :  obj.SetZorder(value,false);                           break;   // Priority of a graphical object for receiving the event of clicking on a chart
      case GRAPH_OBJ_PROP_HIDDEN                :  obj.SetFlagHidden(value,false);                       break;   // Disable displaying the name of a graphical object in the terminal object list
      case GRAPH_OBJ_PROP_SELECTED              :  obj.SetFlagSelected(value,false);                     break;   // Object selection
      case GRAPH_OBJ_PROP_SELECTABLE            :  obj.SetFlagSelectable(value,false);                   break;   // Object availability
      case GRAPH_OBJ_PROP_TIME                  :  obj.SetTime(value,modifier);                          break;   // Time coordinate
      case GRAPH_OBJ_PROP_COLOR                 :  obj.SetColor((color)value);                           break;   // Color
      case GRAPH_OBJ_PROP_STYLE                 :  obj.SetStyle((ENUM_LINE_STYLE)value);                 break;   // Style
      case GRAPH_OBJ_PROP_WIDTH                 :  obj.SetWidth((int)value);                             break;   // Line width
      case GRAPH_OBJ_PROP_FILL                  :  obj.SetFlagFill(value);                               break;   // Filling an object with color
      case GRAPH_OBJ_PROP_READONLY              :  obj.SetFlagReadOnly(value);                           break;   // Ability to edit text in the Edit object
      case GRAPH_OBJ_PROP_LEVELS                :  obj.SetLevels((int)value);                            break;   // Number of levels
      case GRAPH_OBJ_PROP_LEVELCOLOR            :  obj.SetLevelColor((color)value,modifier);             break;   // Level line color
      case GRAPH_OBJ_PROP_LEVELSTYLE            :  obj.SetLevelStyle((ENUM_LINE_STYLE)value,modifier);   break;   // Level line style
      case GRAPH_OBJ_PROP_LEVELWIDTH            :  obj.SetLevelWidth((int)value,modifier);               break;   // Level line width
      case GRAPH_OBJ_PROP_ALIGN                 :  obj.SetAlign((ENUM_ALIGN_MODE)value);                 break;   // Horizontal text alignment in the Edit object (OBJ_EDIT)
      case GRAPH_OBJ_PROP_FONTSIZE              :  obj.SetFontSize((int)value);                          break;   // Font size
      case GRAPH_OBJ_PROP_RAY_LEFT              :  obj.SetFlagRayLeft(value);                            break;   // Ray goes to the left
      case GRAPH_OBJ_PROP_RAY_RIGHT             :  obj.SetFlagRayRight(value);                           break;   // Ray goes to the right
      case GRAPH_OBJ_PROP_RAY                   :  obj.SetFlagRay(value);                                break;   // Vertical line goes through all windows of a chart
      case GRAPH_OBJ_PROP_ELLIPSE               :  obj.SetFlagEllipse(value);                            break;   // Display the full ellipse of the Fibonacci Arc object
      case GRAPH_OBJ_PROP_ARROWCODE             :  obj.SetArrowCode((uchar)value);                       break;   // Arrow code for the Arrow object
      case GRAPH_OBJ_PROP_ANCHOR                :  obj.SetAnchor((int)value);                            break;   // Position of the binding point of the graphical object
      case GRAPH_OBJ_PROP_XDISTANCE             :  obj.SetXDistance((int)value);                         break;   // Distance from the base corner along the X axis in pixels
      case GRAPH_OBJ_PROP_YDISTANCE             :  obj.SetYDistance((int)value);                         break;   // Distance from the base corner along the Y axis in pixels
      case GRAPH_OBJ_PROP_DIRECTION             :  obj.SetDirection((ENUM_GANN_DIRECTION)value);         break;   // Gann object trend
      case GRAPH_OBJ_PROP_DEGREE                :  obj.SetDegree((ENUM_ELLIOT_WAVE_DEGREE)value);        break;   // Elliott wave markup level
      case GRAPH_OBJ_PROP_DRAWLINES             :  obj.SetFlagDrawLines(value);                          break;   // Display lines for Elliott wave markup
      case GRAPH_OBJ_PROP_STATE                 :  obj.SetFlagState(value);                              break;   // Button state (pressed/released)
      case GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID    :  obj.SetChartObjChartID(value);                        break;   // Chart object ID (OBJ_CHART)
      case GRAPH_OBJ_PROP_CHART_OBJ_PERIOD      :  obj.SetChartObjPeriod((ENUM_TIMEFRAMES)value);        break;   // Chart object period
      case GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE  :  obj.SetChartObjChartScale((int)value);                break;   // Time scale display flag for the Chart object
      case GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE :  obj.SetFlagChartObjPriceScale(value);                 break;   // Price scale display flag for the Chart object
      case GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE :  obj.SetFlagChartObjDateScale(value);                  break;   // Chart object scale
      case GRAPH_OBJ_PROP_XSIZE                 :  obj.SetXSize((int)value);                             break;   // Object distance along the X axis in pixels
      case GRAPH_OBJ_PROP_YSIZE                 :  obj.SetYSize((int)value);                             break;   // Object height along the Y axis in pixels
      case GRAPH_OBJ_PROP_XOFFSET               :  obj.SetXOffset((int)value);                           break;   // X coordinate of the upper-left corner of the visibility area
      case GRAPH_OBJ_PROP_YOFFSET               :  obj.SetYOffset((int)value);                           break;   // Y coordinate of the upper-left corner of the visibility area
      case GRAPH_OBJ_PROP_BGCOLOR               :  obj.SetBGColor((color)value);                         break;   // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
      case GRAPH_OBJ_PROP_CORNER                :  obj.SetCorner((ENUM_BASE_CORNER)value);               break;   // Chart corner for binding a graphical object
      case GRAPH_OBJ_PROP_BORDER_TYPE           :  obj.SetBorderType((ENUM_BORDER_TYPE)value);           break;   // Border type for "Rectangle border"
      case GRAPH_OBJ_PROP_BORDER_COLOR          :  obj.SetBorderColor((color)value);                     break;   // Border color for the OBJ_EDIT and OBJ_BUTTON objects
      case GRAPH_OBJ_PROP_BASE_ID               :  obj.SetBaseObjectID(value);                           break;   // Base object ID
      case GRAPH_OBJ_PROP_GROUP                 :  obj.SetGroup((int)value);                             break;   // Graphical object group
      case GRAPH_OBJ_PROP_CHANGE_HISTORY        :  obj.SetAllowChangeMemory((bool)value);                break;   // Flag of storing the change history
      case GRAPH_OBJ_PROP_ID                    :  // Object ID
      case GRAPH_OBJ_PROP_TYPE                  :  // Graphical object type (ENUM_OBJECT)
      case GRAPH_OBJ_PROP_ELEMENT_TYPE          :  // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
      case GRAPH_OBJ_PROP_SPECIES               :  // Graphical object species (ENUM_GRAPH_OBJ_SPECIES)
      case GRAPH_OBJ_PROP_BELONG                :  // Graphical object affiliation
      case GRAPH_OBJ_PROP_CHART_ID              :  // Chart ID
      case GRAPH_OBJ_PROP_WND_NUM               :  // Chart subwindow index
      case GRAPH_OBJ_PROP_NUM                   :  // Object index in the list
      case GRAPH_OBJ_PROP_CREATETIME            :  // Object creation time
      default  : break;
     }
  }
//+------------------------------------------------------------------+

If an invalid pointer to the object has been passed or this is not a subordinate object (not bound to the base one) — exit. Next, simply set the property passed to the method for the object. Some object properties cannot be changed. This is why they are located at the end of the 'switch' list and are not handled in any way.

The method setting a real property to the specified subordinate object:

//+------------------------------------------------------------------+
//|Set a real property to the specified subordinate object           |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier)
  {
   if(obj==NULL || obj.BaseObjectID()==0)
      return;
   switch(prop)
     {
      case GRAPH_OBJ_PROP_PRICE                 : obj.SetPrice(value,modifier);        break;   // Price coordinate
      case GRAPH_OBJ_PROP_LEVELVALUE            : obj.SetLevelValue(value,modifier);   break;   // Level value
      case GRAPH_OBJ_PROP_SCALE                 : obj.SetScale(value);                 break;   // Scale (property of Gann objects and Fibonacci Arcs objects)
      case GRAPH_OBJ_PROP_ANGLE                 : obj.SetAngle(value);                 break;   // Angle
      case GRAPH_OBJ_PROP_DEVIATION             : obj.SetDeviation(value);             break;   // Deviation of the standard deviation channel
      default: break;
     }
  }
//+------------------------------------------------------------------+

The method setting a string property to the specified subordinate object:

//+------------------------------------------------------------------+
//| Set a string property to the specified subordinate object        |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier)
  {
   if(obj==NULL || obj.BaseObjectID()==0)
      return;
   obj.SetProperty(prop,modifier,value);
   switch(prop)
     {
      case GRAPH_OBJ_PROP_TEXT                  : obj.SetText(value);                  break;   // Object description (the text contained in the object)
      case GRAPH_OBJ_PROP_TOOLTIP               : obj.SetTooltip(value);               break;   // Tooltip text
      case GRAPH_OBJ_PROP_LEVELTEXT             : obj.SetLevelText(value,modifier);    break;   // Level description
      case GRAPH_OBJ_PROP_FONT                  : obj.SetFont(value);                  break;   // Font
      case GRAPH_OBJ_PROP_BMPFILE               : obj.SetBMPFile(value,modifier);      break;   // BMP file name for the "Bitmap Level" object
      case GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL      : obj.SetChartObjSymbol(value);        break;   // Chart object symbol
      case GRAPH_OBJ_PROP_BASE_NAME             : obj.SetBaseName(value);              break;   // Base object name
      case GRAPH_OBJ_PROP_NAME                  : // Object name
      default :  break;
     }
  }
//+------------------------------------------------------------------+

Both methods are identical to the method setting an integer property.


Moving and deleting a composite graphical object

When moving a composite graphical object (which can be moved only if the base one is moved as well), we also need to relocate all subordinate graphical objects attached to the base one. As I have already mentioned, this cannot be done by simple event tracking — the event occurs when the mouse button is released after dragging the graphical object. The object receives its final changed properties which should be set to the objects bound to it so that they are also moved to the positions corresponding to their position anchor coordinates. This is to be the final stage of moving a composite graphical object. While we drag an object with a mouse and have not released it yet, we also need to track the change in the location of a graphical object on the chart to interactively track its coordinates and move subordinate objects bound to the base one accordingly. I will do this later. Currently, I will implement recalculation of location point of subordinate objects after relocating the base one in a composite graphical object.

To achieve this, let's add the following code block to the same abstract graphical object class checking the changes in the object properties:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   CGBaseObj::ClearEventsList();
   bool changed=false;
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME)
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      if(this.AllowChangeHistory())
        {
         int total=HistoryChangesTotal();
         if(this.CreateNewChangeHistoryObj(total<1))
            ::Print
              (
               DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total),
               ": ",this.HistoryChangedObjTimeChangedToString(total-1)
              );
        }
      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- get the data object of its pivot points,
            CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
            if(pp==NULL)
               continue;
            //--- get the number of coordinate points the object is attached to
            int num=pp.GetNumLinkedCoords();
            //--- In the loop by the object coordinate points,
            for(int j=0;j<num;j++)
              {
               //--- get the number of coordinate points of the base object for setting the X coordinate
               int numx=pp.GetBasePivotsNumX(j);
               //--- In the loop by each coordinate point for setting the X coordinate,
               for(int nx=0;nx<numx;nx++)
                 {
                  //--- get the property for setting the X coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyX(j,nx);
                  int modifier_from=pp.GetPropertyModifierX(j,nx);
                  this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
                 }
               //--- Get the number of coordinate points of the base object for setting the Y coordinate
               int numy=pp.GetBasePivotsNumY(j);
               //--- In the loop by each coordinate point for setting the Y coordinate,
               for(int ny=0;ny<numy;ny++)
                 {
                  //--- get the property for setting the Y coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyY(j,ny);
                  int modifier_from=pp.GetPropertyModifierY(j,ny);
                  this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
                 }
              }
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }
      //--- Save the current properties as the previous ones
      this.PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

If a change is detected in a graphical object, check if the object has subordinate objects. If yes (the list is not empty), move in the loop along each subordinate object and set new values for its location coordinates specified in the object and identifying the coordinates of the base one. Using these coordinates, we obtain the values and set them in the coordinates of the subordinate object. After the loop is complete, update the chart to display all the changes right away rather than waiting for a new tick.

The composite graphical object can be removed from the chart by removing the base object all subordinate objects are bound to.
This case (removing a base object) is handled in the collection class of graphical elements in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

In the private section of the class, declare the method handling the removal of a standard extended graphical object:

//--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag
   void              Refresh(void);
   void              Refresh(const long chart_id);
//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

private:
//--- Handle the removal of extended graphical objects
   void              DeleteExtendedObj(CGStdGraphObj *obj);
//--- Create a new graphical object, return the pointer to the chart management object

Let's write its implementation outside the class body:

//+------------------------------------------------------------------+
//| Handle the removal of extended graphical objects                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj)
  {
   if(obj==NULL)
      return;
   //--- Save the ID of the graphical object chart and the number of subordinate objects in its list
   long chart_id=obj.ChartID();
   int total=obj.GetNumDependentObj();
   //--- If the list of subordinate objects is not empty (this is the base object)
   if(total>0)
     {
      //--- In the loop, move along all dependent objects and remove them
      for(int n=total-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=obj.GetDependentObj(n);
         if(dep==NULL)
            continue;
         //--- If failed to remove it from the chart, display the appropriate message in the journal
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
        }
      //--- Upon the loop completion, update the chart to display the changes and exit the method
      ::ChartRedraw(chart_id);
      return;
     }
   //--- If this is a subordinate object
   else if(obj.BaseObjectID()>0)
     {
      //--- Get the base object name and its ID
      string base_name=obj.BaseName();
      long base_id=obj.BaseObjectID();
      //--- Get the base object from the graphical object collection list
      CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id);
      if(base==NULL)
         return;
      //--- get the number of dependent objects in its list
      int count=base.GetNumDependentObj();
      //--- In the loop, move along all its dependent objects and remove them
      for(int n=count-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=base.GetDependentObj(n);
         //--- If failed to get the pointer or the object has already been removed from the chart, move on to the next one
         if(dep==NULL || !this.IsPresentGraphObjOnChart(dep.ChartID(),dep.Name()))
            continue;
         //--- If failed to delete the graphical object from the chart,
         //--- display the appropriate message in the journal and move on to the next one
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
           {
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
            continue;
           }
        }
      //--- Remove the base object from the chart and from the list
      if(!::ObjectDelete(base.ChartID(),base.Name()))
         CMessage::ToLog(DFUN+base.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
     }
   //--- Update the chart for displaying the changes
   ::ChartRedraw(chart_id);
  }
//+------------------------------------------------------------------+

The entire method logic is described in the comments to the code. In short, if the base object is removed (its list features bound objects), remove all objects bound to it from the chart. If a subordinate graphical object is removed instead, we need to know the object it was bound to (find the base object of a composite graphical object), then move along the list of dependent objects bound to it and remove all of them.

This method is called in the method of updating the list of all graphical objects in the block handling the removal of a graphical object:

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
   this.RefreshForExtraObjects();
//--- Declare variables to search for charts
   long chart_id=0;
   int i=0;
//--- In the loop by all open charts in the terminal (no more than 100)
   while(i<CHARTS_MAX)
     {
      //--- Get the chart ID
      chart_id=::ChartNext(chart_id);
      if(chart_id<0)
         break;
      //--- Get the pointer to the object for managing graphical objects
      //--- and update the list of graphical objects by chart ID
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      //--- If failed to get the pointer, move on to the next chart
      if(obj_ctrl==NULL)
         continue;
      //--- If the number of objects on the chart changes
      if(obj_ctrl.IsEvent())
        {
         //--- If a graphical object is added to the chart
         if(obj_ctrl.Delta()>0)
           {
            //--- Get the list of added graphical objects and move them to the collection list
            //--- (if failed to move the object to the collection, move on to the next object)
            if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            int index=WRONG_VALUE;
            //--- In the loop by the number of removed graphical objects
            for(int j=0;j<-obj_ctrl.Delta();j++)
              {
               // Find an extra object in the list
               CGStdGraphObj *obj=this.FindMissingObj(chart_id,index);
               if(obj!=NULL)
                 {
                  //--- Get the removed object parameters
                  long   lparam=obj.ChartID();
                  string sparam=obj.Name();
                  double dparam=(double)obj.TimeCreate();
                  //--- If this is an extended graphical object
                  if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
                    {                            
                     this.DeleteExtendedObj(obj);
                    }                            
                  //--- Move the graphical object class object to the list of removed objects
                  //--- and send the event to the control program chart
                  if(this.MoveGraphObjToDeletedObjList(index))
                     ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam);
                 }
              }
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+

This is sufficient for handling the removal of a composite standard graphical object.

Let's test the results.


Test

To perform the test, I will use the EA from the previous article and save it in \MQL5\Experts\TestDoEasy\Part94\ as TestDoEasyPart94.mq5.

There will be no changes in the EA except for removing the display of journal entries concerning created objects forming the composite graphical object in the block of chart click handling in the OnChartEvent() handler:

   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      //--- Get the chart click coordinates
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         //--- Get the right point coordinates for a trend line
         datetime time2=iTime(Symbol(),PERIOD_CURRENT,1);
         double price2=iOpen(Symbol(),PERIOD_CURRENT,1);
         
         //--- Create the "Trend line" object
         string name_base="TrendLineExt";
         engine.CreateLineTrend(name_base,0,true,time,price,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and pass its properties to the journal
         CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID());
         
         //--- Create the "Left price label" object
         string name_dep="PriceLeft";
         engine.CreatePriceLabelLeft(name_dep,0,false,time,price);
         //--- Get the object from the list of graphical objects by chart name and ID and
         CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line left point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0);
         
         //--- Create the "Right price label" object
         name_dep="PriceRight";
         engine.CreatePriceLabelRight(name_dep,0,false,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and
         dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line right point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1);
        }
     }

The fact that we create the "Left price label" and "Right price label" objects as non-extended ones should be of no concern since all attached objects now get the extended graphical object status in the AddDependentObj() method.

Compile the EA and launch it on the chart:


As we can see, the subordinate objects are set in their target locations only when the mouse button is released. I will fix this in the coming articles. Removing an object works correctly — all subordinate objects are removed as well. Intentional removal of one of the subordinate objects leads to the removal of the entire composite graphical object.

What's next?

In the next article, I will continue my work on composite graphical objects.

All files of the current library version, test EA and chart event control indicator for MQL5 are attached below for you to test and download. Leave your questions, comments and suggestions in the comments.

Back to contents

*Previous articles within the series:

Graphics in DoEasy library (Part 89): Programming standard graphical objects. Basic functionality
Graphics in DoEasy library (Part 90): Standard graphical object events. Basic functionality
Graphics in DoEasy library (Part 91): Standard graphical object events. Object name change history
Graphics in DoEasy library (Part 92): Standard graphical object memory class. Object property change history
Graphics in DoEasy library (Part 93): Preparing functionality for creating composite graphical objects

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/10356

Attached files |
MQL5.zip (4202.7 KB)
Data Science and Machine Learning (Part 01): Linear Regression Data Science and Machine Learning (Part 01): Linear Regression
It's time for us as traders to train our systems and ourselves to make decisions based on what number says. Not on our eyes, and what our guts make us believe, this is where the world is heading so, let us move perpendicular to the direction of the wave.
The correct way to choose an Expert Advisor from the Market The correct way to choose an Expert Advisor from the Market
In this article, we will consider some of the essential points you should pay attention to when purchasing an Expert Advisor. We will also look for ways to increase profit, to spend money wisely, and to earn from this spending. Also, after reading the article, you will see that it is possible to earn even using simple and free products.
Graphics in DoEasy library (Part 95): Composite graphical object controls Graphics in DoEasy library (Part 95): Composite graphical object controls
In this article, I will consider the toolkit for managing composite graphical objects - controls for managing an extended standard graphical object. Today, I will slightly digress from relocating a composite graphical object and implement the handler of change events on a chart featuring a composite graphical object. Besides, I will focus on the controls for managing a composite graphical object.
Learn how to design a trading system by RSI Learn how to design a trading system by RSI
In this article, I will share with you one of the most popular and commonly used indicators in the world of trading which is RSI. You will learn how to design a trading system using this indicator.