Graphics in DoEasy library (Part 94): Moving and deleting composite graphical objects
Contents
- Concept
- Improving library classes
- Moving and deleting a composite graphical object
- Test
- What's next?
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.
*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
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use