DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象
内容
概述
在上一篇文章中,我启动开发复合图形对象。 为了定义复合图形对象,我引入了一种新型的图形元素 — 扩展标准图形对象。 参与创建复合图形对象的所有图形对象均应属于该类型。 至于目前,我尚未开发创建某些复合图形对象的类。 取而代之,我将实现允许创建预定义复合图形对象的功能,这自然不会排除通过编程和“动态”创建自定义复合图形对象的可能性 — 直接在图表上。
我将把自己的工作从功能方面划分成几个部分。 首先,我将创建必要的工具箱,用于管理和创建复合图形对象。 接下来,我将添加此类对象的预定义类(事实上,所有这些都取决于用户的个人需求,预定义复合图形对象类在这里仅用作示例)。 再接下来,我将开始实现该功能,令我们能够直观、手动、实时地在图表上直接创建复合图形对象。
实际上,在此,我还会把前一篇文章中实现的东西进行微调。 我将讲解如何为从属对象设置并接收定位点坐标。 此外,我还将测试移动带有从属对象的基准对象(在此阶段,我还需要以更复杂的形式移动复合对象坐标点的功能,而非简单地跟踪单一对象事件),以及创建移除复合图形对象的功能。
仅当松开鼠标按钮时,移动图形对象的坐标点才会触发 CHARTEVENT_OBJECT_DRAG 事件。 相应地,当我们仅跟踪此事件时,移动基准图形对象(且不释放鼠标按钮)会导致所有附加于它的对象保持不变。 释放按钮时事件出现,绑定的对象将移动到其基准对象的定位点。 这意味着我们应该跟踪按住鼠标按钮并移动鼠标。 此外,我们需要知道按钮是在基准图形对象上按下的,即在其坐标(或中心)定位点上。 我们还应该能够重新计算对象坐标点,及其从属对象定位点的位置
CHARTEVENT_OBJECT_DRAG 事件也应该在重新定位的最后进行处理,以便固定基准对象的最终坐标,并用它们重新计算绑定于其上的所有从属图形对象的坐标。
在本文中,我将实现处理 CHARTEVENT_OBJECT_DRAG 事件,并根据基准对象坐标的新位置重新计算其余绑定对象的坐标。 如果删除了基准对象,一个复合图形对象也会被删除。 如果发生此类事件,需删除绑定于其上的所有图形对象。 现在,我禁用鼠标选择所有图形对象(绑定到基准对象)的功能来简化操作。 因此,我们需要选择基准对象并将其删除,以便删除复合图形对象。 我们将无法再使用鼠标选择任何绑定对象。 这是防止复合图形对象被破坏的首选也是最简单的方式。
不过,可以打开对象列表(Ctrl+B),选择任何绑定对象的属性,并立即允许其可选择、或从图形对象列表窗口中删除。 稍后,我还将实现针对复合图形对象的有意破坏的处理。 当移除任何绑定到基准图形对象上的图形对象时,我们将移除参与构建复合图形对象的所有对象。 换言之,我将把整个复合对象构成时的任何对象都删除。 在后续的文章中,我还将讲解如何从基准对象里删除绑定的图形对象功能
改进库类
如往常一样,我们先实现新的函数库消息。
在 \MQL5\Include\DoEasy\Data.mqh 里,添加新消息的索引:
//--- 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: }; //+------------------------------------------------------------------+
和与新添加的索引对应的文本消息:
//--- 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: "}, }; //+---------------------------------------------------------------------+
我们在 \MQL5\Include\DoEasy\Objects\Graph\Standard\ 中抽象标准图形对象的所有衍生后代类文件里完成下面的小改进,即在其显示对象简述的方法中:
//+------------------------------------------------------------------+ //| 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) ); } //+------------------------------------------------------------------+
由于所有显示对象短名的虚拟方法都应该拥有与父类方法类似的输入集,故此这些方法具有(且仍然具有)未使用的输入。 我刚刚实现了其中一个 — 在方法返回文本之前显示一个连字符。 如果该方法收到的破折号标志等于 true,则在显示对象短名之前会设置一个连字符(本文后面提供了一个示例)。 如果我们想写一个题头并在其下显示对象名枚举,这样就会很方便。
在抽象标准图形对象类派生的所有类文件中都完成了此类修改(与所研究的修改完全相同)。 您可在文后的附件中找到它们。
此处研究的所有基本修改都与抽象标准图形对象 \MQL5\Include\DoEasy\Objects\Graph\standard\GStdGraphObj.mqh 有关。
在位于同一文件中的从属对象轴点数据类中,坐标:数组声明为:m_property_x[][2],进行实验后,在 X 和 Y 坐标的单个类中保留了两个数组。 随后,我放弃了这个想法,数组名仍然不正确。 因此,它被重新命名为 m_property[][2]。
该类的公开部分,现在提供了显示存储在该类中的坐标轴名称的方法,返回属性的方法,数组中存储的属性修饰符,以及返回多个基准对象轴点描述的方法,这些轴点用于计算从属图形对象所附着的坐标点 — 该方法用于调试:
//+------------------------------------------------------------------+ //| 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){;} }; //+------------------------------------------------------------------+
所有方法都非常简单。 它们的逻辑从代码中应该清晰易懂。 我不打算在这里赘述。
在复合对象的轴点 X 和 Y 的数据类中,添加方法返回调用我刚刚研究的新方法结果:
//+------------------------------------------------------------------+ //| 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){;} }; //+------------------------------------------------------------------+
这些方法均返回调用类中相应同名方法的结果,该类将数据存储在 X 轴和 Y 轴坐标上。
现在这些方法的名称里已指明该方法返回其数据的确切坐标,例如 GetPropertyX 或 GetPropertyY。
复合对象轴点的绑定数据类已经有了很大的改进,主要是在方法名称方面。 在调试过程中,我一开始混淆了方法的名称,导致它们不能自我解析。 因此,为了更加清晰明了,我已将它们重新命名。 例如,CreateNewLinkedPivotPoint() 方法,是根据 X 和 Y 坐标添加从属对象的新定位点,该方法的名称非常混乱,因为轴点是用于设置基准对象的 X 或 Y 坐标的定位点,它用于计算从属对象所需附着的坐标。 可以使用若干个轴点来计算坐标点本身。 因此,该方法被重命名为 CreateNewLinkedCord(),表示添加了一个新的坐标点。
添加了三元运算符,以便缩短方法的代码。 例如,方法
CPivotPointData *GetBasePivotPointDataX(const int index) const { CPivotPointXY *obj=this.GetLinkedPivotPointXY(index); if(obj==NULL) return NULL; return obj.GetPivotPointDataX(); }
现在看来如下:
CPivotPointData *GetBasePivotPointDataX(const int index_coord_point) const { CPivotPointXY *obj=this.GetLinkedCoord(index_coord_point); return(obj!=NULL ? obj.GetPivotPointDataX() : NULL); }
尽管代码更短,但完全相同。
此外,类的公开部分现在提供了若干方法,返回所需坐标对应的同名类方法的调用结果,从而简化了所需数据的获取:
//--- 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){;} }; //+------------------------------------------------------------------+
在返回抽象标准图形对象类的属性描述的方法中 ,添加所需属性的索引:
//--- 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); }
这令我们能够运用这些方法显示所需图形对象属性的列表,而不仅只是显示所有这些属性。
我来解释一下。 假设趋势线有两个锚定点。 属性修饰符(上述方法中的索引)用于设置时间(X 坐标)或价格(Y 坐标),为的是指示欲获取定位点(左或右)的坐标。 此刻,该方法将显示所有属性的完整列表 — 标题后面是两个定位点的值:
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
但如果我们需要显示单一定位点,目前没有办法。 我们需要记下属性名和它的值。 稍后,我将实现一种简单的方法,可依据索引显示所需锚点的名称和值。 现在,我将为索引设置默认值。 这样做是为了防止出现多个错误,并令插入修改更容易;因为我们只需要删除默认值,并添加必要的错误处理,就可以显示完整描述(如现在),或单个定位点的选择性描述。
在类的公开部分,添加将附加对象的数量返回到基准对象的方法,并修复方法名称:
//--- 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); }
重命名 GetLinkedPivotsNum() 方法,并声明新的私密方法,用于设置下级图形对象的坐标:
//--- 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| //+--------------------------------------------------------------------+
在基准图形对象绑定的对象列表里添加从属标准图形对象的方法中,加入设置属性:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
为对象设置选择标志 false,如此来避免选择新添加的对象。 通过将相应的标志设置为 false,可即刻禁用对象可用性。 接下来,为对象设置“扩展标准图形对象”类型。 通过鼠标选择对象的功能被禁用,它们在扩展标准图形对象列表中可用,从而可通过编程方式按基准图形对象的类型和名称选择它们。
该方法取来自基准对象指定属性的 X 坐标,并设置到指定从属对象:
//+--------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+
根据对象类型选择必要的属性。 这也许是时间坐标或屏幕像素坐标。 接下来,设置属性,其坐标从方法输入中传递,而欲设置的对象坐标属性则以指针形式传递给方法 — 属性本身及其修饰符。 最后,在对象本身中指定属性集合的修饰符。 结果则为,图形对象含有锚定点的必要坐标,且其参数已传递给该方法。
该方法从基准对象的指定属性提取 Y 坐标,并设置给指定的从属对象:
//+--------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+
此处的所有方法都类似于设置 X 坐标的方法。 不过,有一个例外:X 坐标始终是整数 — 时间或像素数,而 Y 坐标可以是整数(像素数),或实数值(价格)。 因此,此处我们应该检查要设置的结果属性。 根据这一点,我们设置整数型属性值,或实数型属性值。
方法设置指定从属对象的整数型值:
//+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+
如果所传递指向该对象的指针无效,或者该对象不是从属对象(未绑定到基准对象)— 退出。 接下来,简单地把传递给方法的参数赋值给属性。 某些对象属性不可更改。 这就是为什么它们位于 “switch” 序列的末尾,并且不会以任何方式加以处理。
为指定的从属对象设置实数值的方法:
//+------------------------------------------------------------------+ //|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; } } //+------------------------------------------------------------------+
为指定的从属对象设置字符串属性的方法:
//+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+
这两种方法都与设置整数值属性的方法雷同。
移动和删除复合图形对象
在移动复合图形对象时(只有在移动基准图形对象时才会移动),我们还需要重新定位附加到基准图形对象的所有从属图形对象。 正如我曾提过的,这不能通过简单的事件跟踪来实现 — 当拖动图形对象之后再释放鼠标按钮时,就会生成鼠标事件。 对象将接收其最终更改的属性,这些属性应设置给绑定其上的对象,以便它们也能移动到与其位置锚点坐标相对应的位置。 这是移动复合图形对象的最后阶段。 而当我们用鼠标拖动一个对象时,在没有释放它之前,我们还需要跟踪图表上图形对象的位置变化,并以交互方式跟踪其坐标,以及相应地移动绑定到基准对象上的从属对象。 我以后再来完成。 目前,我将在复合图形对象中的基准对象重定位后,重新计算从属对象的位置点。
为了实现这一点,我们将以下代码块添加 到同一抽象图形对象类中,以便检查对象属性的变化:
//+------------------------------------------------------------------+ //| 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(); } } //+------------------------------------------------------------------+
如果在图形对象中检测到有变化,则检查该对象是否拥有从属对象。 如果是(列表不为空),则在循环中移动每个从属对象,在其对象位置坐标里设置指定的新值,并识别基准对象的坐标。 根据这些坐标,我们获得其数值,并设置到从属对象的坐标中。 循环完成后,立即更新图表,从而显示所有更改,无需等待新的即时报价到达。
删除绑定到的基准对象的所有从属对象之后,就可以从图表中删除复合图形对象。
这种情况(删除基准对象)是在图形元素集合类 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 当中处理。
在类的私密部分,声明处理移除标准扩展图形对象的方法:
//--- 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
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
整个方法逻辑在代码的注释中均有讲述。 长话短说,如果基准对象已被删除(其列表包含绑定对象),需从图表中删除所有与其绑定的对象。 如果从属图形对象被移除,我们需要知道它所绑定到的对象(找到复合图形对象的基准对象),然后顺着列表依次找到绑定到它的从属对象,并移除所有对象。
在处理图形对象删除的模块中,更新所有图形对象列表的方法中会调用该方法:
//+------------------------------------------------------------------+ //| 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++; } } //+------------------------------------------------------------------+
这些足以处理删除复合标准图形对象的操作。
我们测试一下结果。
测试
为了执行测试,我们借用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part94\,命名为 TestDoEasyPart94.mq5。
EA 不会有任何修改,除了在图表点击 OnChartEvent() 处理程序模块里,删除了创建复合图形对象时有关的日志显示:
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); } }
我们创建的“左价格标签”和“右价格标签”对象作为非扩展的对象,这一事实不会引起任何关注,因为所有附加对象现在都在 AddDependentObj() 方法中获得扩展图形对象状态。
编译 EA,并在图表上启动它:
正如我们所见,只有当鼠标按钮被释放时,从属对象才会被摆放到它们的目标位置。 我将在后续文章中解决这个问题。 移除一个对象操作正常 — 所有从属对象也会被移除。 特意删除一个从属对象会导致删除整个组合图形对象。
下一步是什么?
在下一篇文章中,我将继续研究复合图形对象。
*该系列的前几篇文章:
DoEasy 函数库中的图形(第八十九部分):标准图形对象编程。 基本功能
DoEasy 函数库中的图形(第九十部分):标准图形对象事件。 基本功能
DoEasy 函数库中的图形(第九十一部分):标准图形对象事件。 对象名称变更历史记录
DoEasy 函数库中的图形(第九十二部分):标准图形对象记忆类。 对象属性变更历史记录
DoEasy 函数库中的图形(第九十三部分):准备创建复合图形对象的功能
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10356