
DoEasy 函数库中的图形(第九十五部分):复合图形对象控件
内容
概述
在本文中,我将继续开发复合图形对象。 这些是标准的图形对象,由多个图形对象组成,并组合成单一的图形对象。 在函数库中,复合图形中包含的图形对象被定义为扩展标准图形对象。 此类对象拥有一些附加属性和功能,令它们能够合并为其它图形对象。
复合图形对象的概念要求在父对象变更或重新定位时,将对象保持在其所附对象上,并调整其相对位置。
在上一篇文章中,我开始创建复合图形对象事件的处理程序,它实现了复合图形对象的移除处理,并启动开发其重新定位处理程序。
今天,我从复合图形对象重新定位的内容稍微离题 ,并实现图表上复合图形对象的变更事件处理。 此外,我将重点讲解管理复合图形对象的控件。
为什么呢? 我将实现复合图形对象的实时创建 — 通过把从属对象拖拽到基准对象上,实现把从属对象附加到基准对象。 如果用鼠标拖动另一个对象,则基准图形对象将跟踪该对象。 在距离某个图表定位点一定距离处启用对象附着机制。 连接附着对象定位点与基准对象定位点的连线会被直观地显示,表明拖动的对象已准备好附着到基准对象。 为达成这一点,图形对象的每个定位点都应该拥有一个大小确定的窗体对象。 进入窗体对象区域将激活附着机制,而指示对象已准备好进行交互的线条则会显示在窗体本身上。 这样的窗体在图形对象的每个轴点上都是不可见的。 只能在调试时,通过启用沿窗体边缘绘制矩形,来查看区域大小:
此外,窗体显示图形对象定位点,这些定位点仅在鼠标光标悬停在窗体活动区域上时才会出现。 因此,我们就能够通过把鼠标光标悬停在窗体区域上,而不必鼠标单击高亮显示来移动和修改扩展图形对象。 一旦我们将光标悬停在窗体的活动区域(上面的图像上用矩形标记)上时,标签就会出现在图形对象定位点(圆圈中心的蓝点)中。 如果我们开始用鼠标拖动窗体,图形对象的相应轴点将跟随光标,其所附复合图形对象会一起修改。
如果按住鼠标按钮并把光标拖入窗体活动区域,这意味着(如果验证)我们将另一个图形对象附着于窗体,从而激活把一个对象绑定到另一个对象的机制。 因此,这些窗体允许我们一次性完成几个目标。
我不打算在这里实现将一个对象连接到另一个对象,因为准备工作还没有完成。 取而代之,我将创建窗体,将它们附加到图形对象定位点,并在更改图表时实现沿对象轴点坐标移动它们的机制 — 重新定位图表、或更改其显示比例。 如此做,是因为窗体对象的坐标以屏幕像素为单位,而大多数图形对象以时间/价格值显示。
改进库类
在 \MQL5\Include\DoEasy\Data.mqh 里,加入新的消息索引:
//--- 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: //--- CGStdGraphObjExtToolkit MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA, // Failed to change the size of the pivot point time data array MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA, // Failed to change the size of the pivot point price data array MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM, // Failed to create a form object to manage a pivot point }; //+------------------------------------------------------------------+
以及与新添加的索引对应的消息文本 :
//--- 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: "}, //--- CGStdGraphObjExtToolkit {"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"}, {"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"}, {"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"}, }; //+---------------------------------------------------------------------+
替换 \MQL5\Include\DoEasy\Defines.mqh 中的宏替换
#define CLR_DEFAULT (0xFF000000) // Default symbol background color in the navigator
如此更容易理解
#define CLR_MW_DEFAULT (0xFF000000) // Default symbol background color in the Market Watch
以及宏替换
#define NULL_COLOR (0x00FFFFFF) // Zero for the canvas with the alpha channel
如此更容易理解
#define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel
并添加新的宏替换,为此处欲创建窗体对象设置默认值:
//--- Graphical object parameters #define PROGRAM_OBJ_MAX_ID (10000) // Maximum value of an ID of a graphical object belonging to a program #define CTRL_POINT_SIZE (5) // Radius of the control point on the form for managing graphical object pivot points #define CTRL_FORM_SIZE (40) // Size of the control point form for managing graphical object pivot points //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+
替换所有文件中的旧宏替换名称。
简单地按下 Ctrl+Shift+H,输入以下值,并选中如下框:
单击“在文件中替换”。 编辑器会在所有函数库文件中进行替换。
以相同方式替换 NULL_COLOR 为 CLR_CANV_NULL。
新的宏替换名称更具说明性,即可表明它们的功能,亦能减少潜在错误数量(举例来说,我输入 CLR_DEFAULT 是为了设置透明画布背景,但花费了大量时间试图理解它为什么不起作用)。
此外,我还对基准图形对象衍生子体的所有类文件进行了略微修改(只需在日志中显示对象简述的方法的代码里添加一个逗号即可):
//+------------------------------------------------------------------+ //| 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) ); } //+------------------------------------------------------------------+
这纯粹是一种“装饰性”的改进。
它已在 \MQL5\Include\DoEasy\Objects\Graph\Standard\ 中所有文件里实现。
扩展标准图形对象工具箱类
我们启动创建一款处理扩展图形对象的工具包。 这将是一个拥有创建窗体对象,并操控它们的所有必要方法的类。 每个扩展图形对象都有一个指向相应类对象的指针。 如有必要(如果这是复合图形对象中的基准对象),则在创建扩展对象时动态创建类对象,并在删除后者时将其删除。
对象接收基准图形对象的必要参数(其坐标、类型、名称、等等)。 跟踪基准对象坐标,并在对象内部调整窗体对象坐标。 最终,窗体对象将启用基准对象的管理。
但首先要做的是...
在 \MQL5\Include\DoEasy\Objects\Graph\ 里,创建新的 Extend\ 文件夹,包含 CGStdGraphObjExtToolkit.mqh 文件,内有 CGStdGraphObjExtToolkit 类, 继承自基准 CObject 类,可用于构建 MQL5 标准函数库:
//+------------------------------------------------------------------+ //| CGStdGraphObjExtToolkit.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Graph\Form.mqh" //+------------------------------------------------------------------+ //| Extended standard graphical | //| object toolkit class | //+------------------------------------------------------------------+ class CGStdGraphObjExtToolkit : public CObject { }
类文件中应包含窗体对象类文件。
在类的私密部分,声明存储全部所需基准对象属性的变量,构造窗体的属性,存储它们的列表,以及创建窗体对象、并返回其屏幕坐标的方法:
//+------------------------------------------------------------------+ //| Extended standard graphical | //| object toolkit class | //+------------------------------------------------------------------+ class CGStdGraphObjExtToolkit : public CObject { private: long m_base_chart_id; // Base graphical object chart ID int m_base_subwindow; // Base graphical object chart subwindow ENUM_OBJECT m_base_type; // Base object type string m_base_name; // Base object name int m_base_pivots; // Number of base object reference points datetime m_base_time[]; // Time array of base object reference points double m_base_price[]; // Price array of base object reference points int m_base_x; // Base object X coordinate int m_base_y; // Base object Y coordinate int m_ctrl_form_size; // Size of forms for managing reference points int m_shift; // Shift coordinates for adjusting the form location CArrayObj m_list_forms; // List of form objects for managing reference points //--- Create a form object on a base object reference point CForm *CreateNewControlPointForm(const int index); //--- Return X and Y screen coordinates of the specified reference point of the graphical object bool GetControlPointCoordXY(const int index,int &x,int &y); public:
我们用数组来存储价格和时间坐标,因为单一的图形对象可能有多个轴点。 因此,每个轴点的坐标将存储在相应的数组单元之中。 第一个轴点坐标 — 位于数组索引 0,第二个轴点坐标 — 位于数组索引 1,第三个轴点坐标 — 位于索引 2,等等。
平移窗体坐标允许我们把窗体精确地定位在对象轴点的中心。 这个平移覆盖了轴点尺寸的一半。 如果窗体尺寸是 2 的倍数,例如 10,则需加 1 进行调整,即 11。 如此即可将窗体精确地放置在图形对象轴点的中心,哪怕只有一个像素,且这样就不会有哪一侧比之另一侧更宽。
窗体列表用于存储所有创建的窗体。 若要访问它们,可由指针授权。 通过计算屏幕坐标的方法,我们可以知道窗体所处的屏幕坐标。 这样可以将其精确放置在图形对象轴点上。
在类的公开部分,声明处理类的所有必要方法:
public: //--- Set the parameters of the base object of a composite graphical object void SetBaseObj(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]); //--- Set the base object (1) time, (2) price, (3) time and price coordinates void SetBaseObjTime(const datetime time,const int index); void SetBaseObjPrice(const double price,const int index); void SetBaseObjTimePrice(const datetime time,const double price,const int index); //--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates void SetBaseObjCoordX(const int value) { this.m_base_x=value; } void SetBaseObjCoordY(const int value) { this.m_base_y=value; } void SetBaseObjCoordXY(const int value_x,const int value_y) { this.m_base_x=value_x; this.m_base_y=value_y; } //--- (1) Set and (2) return the size of the form of pivot point management control points void SetControlFormSize(const int size); int GetControlFormSize(void) const { return this.m_ctrl_form_size; } //--- Return the pointer to the pivot point form by (1) index and (2) name CForm *GetControlPointForm(const int index) { return this.m_list_forms.At(index); } CForm *GetControlPointForm(const string name,int &index); //--- Return the number of the base object pivot points int GetNumPivotsBaseObj(void) const { return this.m_base_pivots; } //--- Create form objects on the base object pivot points bool CreateAllControlPointForm(void); //--- Remove all form objects from the list void DeleteAllControlPointForm(void); //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor/destructor CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]) { this.m_list_forms.Clear(); this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price); this.CreateAllControlPointForm(); } CGStdGraphObjExtToolkit(){;} ~CGStdGraphObjExtToolkit(){;} }; //+------------------------------------------------------------------+
在类构造函数中,清除窗体对象列表,为类变量设置所有必要的基本对象值(传入构造函数参数),并创建窗体对象,以便可根据基准图形对象的每个轴点管理基准对象。
该方法设置复合图形对象的基准对象参数:
//+------------------------------------------------------------------+ //| Set the base object parameters of the | //| composite graphical object | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObj(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]) { this.m_base_chart_id=base_chart_id; // Base graphical object chart ID this.m_base_subwindow=base_subwindow; // Base graphical object chart subwindow this.m_base_type=base_type; // Base object type this.m_base_name=base_name; // Base object name this.m_base_pivots=base_pivots; // Number of base object reference points this.m_base_x=base_x; // Base object X coordinate this.m_base_y=base_y; // Base object Y coordinate this.SetControlFormSize(ctrl_form_size); // Size of forms for managing reference points if(this.m_base_type==OBJ_LABEL || this.m_base_type==OBJ_BUTTON || this.m_base_type==OBJ_BITMAP_LABEL || this.m_base_type==OBJ_EDIT || this.m_base_type==OBJ_RECTANGLE_LABEL || this.m_base_type==OBJ_CHART) return; if(::ArraySize(base_time)==0) { CMessage::ToLog(DFUN+"base_time: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY); return; } if(::ArraySize(base_price)==0) { CMessage::ToLog(DFUN+"base_price: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY); return; } if(::ArrayResize(this.m_base_time,this.m_base_pivots)!=this.m_base_pivots) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA); return; } if(::ArrayResize(this.m_base_price,this.m_base_pivots)!=this.m_base_pivots) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA); return; } for(int i=0;i<this.m_base_pivots;i++) { this.m_base_time[i]=base_time[i]; // Time (i) of the base object pivot point this.m_base_price[i]=base_price[i]; // Price (i) of the base object pivot point } } //+------------------------------------------------------------------+
该方法接收基准图形对象属性的所有必要值。 接下来,检查基准对象类型。 如果这是一个非基于价格/时间坐标的对象,则退出该方法。 我们不必处理这样的对象。 接下来,检查传递给该方法的基准对象坐标数组的大小。 如果它们的大小为零(该方法已收到一个空数组),则发送通知,并退出该方法。 接下来,根据传递的数组修改坐标内部数组的大小。 如果更改数组失败,则发通知,并退出。 在结尾,简单地把输入数组逐元素复制到内部数组即可。
该方法设置管理轴点的参考点大小:
//+------------------------------------------------------------------+ //|Set the size of reference points for managing pivot points | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetControlFormSize(const int size) { this.m_ctrl_form_size=(size>254 ? 255 : size<5 ? 5 : size%2==0 ? size+1 : size); this.m_shift=(int)ceil(m_ctrl_form_size/2)+1; } //+------------------------------------------------------------------+
该方法接收所需窗体的大小。 如果大小超过 254,则将其设置为 255(奇数值),如果传递的大小小于 5,则将其设置为 5(这应是最小的窗体大小值)。 否则,如果传递的大小等于 2,则为其加一,再用它。 如果检查的数值均为空,则使用传递给该方法的大小。
接下来,因为应该设置窗体,故计算坐标偏移,以便图形对象的轴点精确位于其中心。 为达此目标,我们应该从坐标值中减去偏移值。 将计算窗体大小分成两个,取最接近的较高整数值,并加一。
该方法设置基准对象的时间坐标:
//+------------------------------------------------------------------+ //| Set the time coordinate of the base object | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObjTime(const datetime time,const int index) { if(index>this.m_base_pivots-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return; } this.m_base_time[index]=time; } //+------------------------------------------------------------------+
该方法接收轴点时间和对象轴点索引。 如果索引超过了对象的轴点数量,则发送通知“请求超出数组范围”,并退出。 结果则是,传递给方法的时间值,会设置到与时间数组中的索引对应的单元格里。
当对象发生变化时,该方法对于在类对象中为基准对象指定时间是必需的。
该方法设置基准对象的价格坐标:
//+------------------------------------------------------------------+ //| Set the coordinate of the base object price | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObjPrice(const double price,const int index) { if(index>this.m_base_pivots-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return; } this.m_base_price[index]=price; } //+------------------------------------------------------------------+
该方法与上面研究过的方法雷同,除了此处我们把依据索引指定的基准对象轴点的价格添加到类价格数组当中。
该方法设置基准对象的时间和价格坐标:
//+------------------------------------------------------------------+ //| Set the time and price coordinates of the base object | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObjTimePrice(const datetime time,const double price,const int index) { if(index>this.m_base_pivots-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return; } this.m_base_time[index]=time; this.m_base_price[index]=price; } //+------------------------------------------------------------------+
该方法与上述两种方法雷同,除了价格和时间都会设置在类的数组当中。
该方法返回屏幕坐标里指定 X 和 Y 坐标轴点处的图形对象:
//+------------------------------------------------------------------+ //| Return the X and Y coordinates of the specified pivot point | //| of the graphical object in screen coordinates | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y) { switch(this.m_base_type) { case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : x=this.m_base_x; y=this.m_base_y; break; default: if(!::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y)) { x=0; y=0; return false; } } return true; } //+------------------------------------------------------------------+
该方法接收基准图形对象所需轴点的索引。 这是轴点的屏幕坐标(相对屏幕左上角的像素为单位)和两个变量(通过链接),用于接收窗体的屏幕坐标。 如果对象位于屏幕坐标之内,则返回这些坐标。
如果对象位于价格/时间坐标内,调用 ChartTimePriceToXY() 函数计算它们。 如果坐标转换为屏幕坐标失败,则将坐标设置为零,并返回 false。
如果一切正常,返回 true。
按名称返回指向轴点窗体的指针的方法:
//+------------------------------------------------------------------+ //| Return the pointer to the pivot point form by name | //+------------------------------------------------------------------+ CForm *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index) { index=WRONG_VALUE; for(int i=0;i<this.m_list_forms.Total();i++) { CForm *form=this.m_list_forms.At(i); if(form==NULL) continue; if(form.Name()==name) { index=i; return form; } } return NULL; } //+------------------------------------------------------------------+
该方法接收所需窗体的名称,和链接的变量,返回在窗体对象列表中检测到的该窗体索引。
在循环中遍历窗体对象列表,获取下一个对象。 如果其名称与所需名称匹配,则将循环索引写入变量,并返回指向检测到的对象指针。 循环完毕,返回 NULL — 未找到窗体,索引等于 -1。 该值已在循环开始预设。
该方法在基准对象参照点上创建一个窗体对象:
//+------------------------------------------------------------------+ //| Create a form object on a base object reference point | //+------------------------------------------------------------------+ CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index) { string name=this.m_base_name+"_TKPP_"+(index<this.m_base_pivots ? (string)index : "X"); CForm *form=this.GetControlPointForm(index); if(form!=NULL) return NULL; int x=0, y=0; if(!this.GetControlPointCoordXY(index,x,y)) return NULL; return new CForm(this.m_base_chart_id,this.m_base_subwindow,name,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize()); } //+------------------------------------------------------------------+
该方法接收应创建窗体对象所需的轴点索引。
创建窗体对象名,包括基准对象名 + “ToolKit Pivot Point”(_TKPP) 缩写 + 轴点索引。 当创建索引描述时,检查其值。 如果它小于基准对象轴点的数量(计算从零开始),则使用传递给该方法的索引的字符串表示形式。 否则,使用 X 图标。 我们为什么需要这个? 稍后之后,可以将从属对象附着于基准对象,不仅在其轴点,且可位于它们之间。 此外,我们需要在基准对象的线中心创建一个控件窗体,在该控件窗体之外重新定位整个对象。 因此,窗体名称的特点是不仅可以作为轴点创建表单,还可以作为其它用途。
接下来,通过传递给方法的索引检查窗体在列表中是否存在。 如果窗体对象已由列表中的索引显示(指向它的指针不等于 NULL),则返回 NULL。
接下来,将轴点坐标依据其索引转换为屏幕坐标,返回所获坐标处创建的窗体对象结果。 从两个坐标中减去偏移值,以便在轴点上精确定位窗体中心。
我能够简单地设置窗体锚定点的值,但我们的函数库约定规定,所有窗体的锚定点都保持不变 — 在其左上角。 因此,只在必要时才会使用窗体对象定位的位移。
该方法在基准对象轴点上创建窗体对象:
//+------------------------------------------------------------------+ //| Create form objects on the base object pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void) { bool res=true; //--- In the loop by the number of base object pivot points for(int i=0;i<this.m_base_pivots;i++) { //--- Create a new form object on the current pivot point corresponding to the loop index CForm *form=this.CreateNewControlPointForm(i); //--- If failed to create the form, inform of that and add 'false' to the final result if(form==NULL) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM); res &=false; } //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result if(!this.m_list_forms.Add(form)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); delete form; res &=false; } //--- Set all the necessary properties for the created form object form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM); // Object is created programmatically form.SetActive(true); // Form object is active form.SetMovable(true); // Movable object form.SetActiveAreaShift(0,0,0,0); // Object active area - the entire form form.SetFlagSelected(false,false); // Object is not selected form.SetFlagSelectable(false,false); // Object cannot be selected by mouse form.Erase(CLR_CANV_NULL,0); // Fill in the form with transparent color and set the full transparency //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver); // Draw an outlining rectangle for visual display of the form location form.DrawCircle((int)floor(form.Width()/2),(int)floor(form.Height()/2),CTRL_POINT_SIZE,clrDodgerBlue); // Draw a circle in the form center form.DrawCircleFill((int)floor(form.Width()/2),(int)floor(form.Height()/2),2,clrDodgerBlue); // Draw a point in the form center form.Done(); // Save the initial form object state (its appearance) } //--- Redraw the chart for displaying changes (if successful) and return the final result if(res) ::ChartRedraw(this.m_base_chart_id); return res; } //+------------------------------------------------------------------+
代码注释中描述了此处的整个逻辑。 简而言之,在循环中,根据基准对象轴点的数量,为每个轴点创建一个新的窗体对象,将其添加到窗体对象列表中,并为每个窗体设置相应的基准对象轴点的坐标。 每个窗体的中心都有一个圆和一个点,表明这是受管控的基准对象的对象。
这样的对象最初是不可见的,只有当鼠标光标悬停在窗体区域上时才会出现。 就现在而言,我将令它们可见,以便测试它们在图表更改期间的行为。 在后续的文章中,我将坚持这一计划 — 对象最初是不可见的,只有当鼠标悬停在其活动区域(即整个窗体对象尺寸)上时才会显现。
该方法从列表中删除所有窗体对象:
//+------------------------------------------------------------------+ //| Remove all form objects from the list | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::DeleteAllControlPointForm(void) { this.m_list_forms.Clear(); } //+------------------------------------------------------------------+
简单地调用 Clear() 方法完全清除整个列表。
在事件处理程序中,根据发生的事件处理窗体对象事件:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { if(id==CHARTEVENT_CHART_CHANGE) { for(int i=0;i<this.m_list_forms.Total();i++) { CForm *form=this.m_list_forms.At(i); if(form==NULL) continue; int x=0, y=0; if(!this.GetControlPointCoordXY(i,x,y)) continue; form.SetCoordX(x-this.m_shift); form.SetCoordY(y-this.m_shift); form.Update(); } ::ChartRedraw(this.m_base_chart_id); } } //+------------------------------------------------------------------+
目前,我们只处理图表更改事件。 循环遍历所有窗体对象时,从列表中获取下一个窗体。 如果未能根据绘制的轴点接收其屏幕坐标,则转到下一个窗体。 为窗体设置新的屏幕坐标,并更新窗体。 循环完毕后,重绘图表来显示变化。
由于扩展标准图形对象的工具箱对象存储在标准图形对象类的对象中,我们需要改进 \MQL5\Include\DoEasy\Objects\Graph\standard\GStdGraphObj.mqh 中的类。
首先,将窗体对象的文件和新创建的扩展标准图形对象的工具箱对象包含到文件中:
//+------------------------------------------------------------------+ //| GStdGraphObj.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\GBaseObj.mqh" #include "..\..\..\Services\Properties.mqh" #include "..\..\Graph\Form.mqh" #include "..\..\Graph\Extend\CGStdGraphObjExtToolkit.mqh" //+------------------------------------------------------------------+ //| Class of the dependent object pivot point data | //+------------------------------------------------------------------+
在抽象标准图形对象类的私密部分,声明指向扩展标准图形对象的工具箱对象的指针:
//+------------------------------------------------------------------+ //| The class of the abstract standard graphical object | //+------------------------------------------------------------------+ class CGStdGraphObj : public CGBaseObj { private: CArrayObj m_list; // List of subordinate graphical objects CProperties *Prop; // Pointer to the property object CLinkedPivotPoint m_linked_pivots; // Linked pivot points CGStdGraphObjExtToolkit *ExtToolkit; // Pointer to the extended graphical object toolkit int m_pivots; // Number of object reference points //--- Read and set (1) the time and (2) the price of the specified object pivot point void SetTimePivot(const int index); void SetPricePivot(const int index); //--- Read and set (1) color, (2) style, (3) width, (4) value, (5) text of the specified object level void SetLevelColor(const int index); void SetLevelStyle(const int index); void SetLevelWidth(const int index); void SetLevelValue(const int index); void SetLevelText(const int index); //--- Read and set the BMP file name for the "Bitmap Level" object. Index: 0 - ON, 1 - OFF void SetBMPFile(const int index); public:
在公开部分,编写返回工具箱对象指针的方法:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Curr.SetLong(property,index,value); } void SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value) { this.Prop.Curr.SetDouble(property,index,value); } void SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value) { this.Prop.Curr.SetString(property,index,value); } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index) const { return this.Prop.Curr.GetLong(property,index); } double GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index) const { return this.Prop.Curr.GetDouble(property,index); } string GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index) const { return this.Prop.Curr.GetString(property,index); } //--- Set object's previous (1) integer, (2) real and (3) string properties void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.SetLong(property,index,value); } void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.SetDouble(property,index,value); } void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.SetString(property,index,value); } //--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array long GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index) const { return this.Prop.Prev.GetLong(property,index); } double GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index) const { return this.Prop.Prev.GetDouble(property,index); } string GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index) const { return this.Prop.Prev.GetString(property,index); } //--- Return (1) itself, (2) properties and (3) the change history CGStdGraphObj *GetObject(void) { return &this; } CProperties *Properties(void) { return this.Prop; } CChangeHistory *History(void) { return this.Prop.History;} CGStdGraphObjExtToolkit *GetExtToolkit(void) { return this.ExtToolkit; } //--- 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; }
在类的公开部分,声明图形对象事件的处理程序。 在构造函数中,为指向工具箱对象的指针设置默认值 NULL,而在类的析构函数中,检查指针的有效性,首先,从工具箱对象中删除所有窗体,然后删除对象本身:
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: //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Default constructor CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; this.ExtToolkit=NULL; } //--- Destructor ~CGStdGraphObj() { if(this.Prop!=NULL) delete this.Prop; if(this.ExtToolkit!=NULL) { this.ExtToolkit.DeleteAllControlPointForm(); delete this.ExtToolkit; } } 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| //+--------------------------------------------------------------------+
在简化访问和设置图形对象属性的方法模块中,编写返回图形对象轴点数量的方法:
public: //+--------------------------------------------------------------------+ //|Methods of simplified access and setting graphical object properties| //+--------------------------------------------------------------------+ //--- Number of object reference points int Pivots(void) const { return this.m_pivots; } //--- Object index in the list int Number(void) const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM,0); } void SetNumber(const int number) { this.SetProperty(GRAPH_OBJ_PROP_NUM,0,number); }
在受保护的参数化构造函数中,检查图形元素类型。 如果这是一个扩展图形对象,则创建一个新的工具箱对象,并将指向它的指针保存在 ExtToolkit 变量中。 在构造函数清单的末尾,初始化工具箱对象:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::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) { //--- Create the property object with the default values this.Prop=new CProperties(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL); this.ExtToolkit=(elm_type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? new CGStdGraphObjExtToolkit() : NULL); //--- Set the number of pivot points and object levels this.m_pivots=pivots; int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS); //--- Set the property array dimensionalities according to the number of pivot points and levels this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2); //--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits this.m_type=obj_type; this.SetName(name); CGBaseObj::SetChartID(chart_id); CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type)); CGBaseObj::SetTypeElement(elm_type); CGBaseObj::SetBelong(belong); CGBaseObj::SetSpecies(species); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save the integer properties inherent in all graphical objects but not present in the current one this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID()); // Chart ID this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow()); // Chart subwindow index this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject()); // Graphical object type (ENUM_OBJECT) this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement()); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong()); // Graphical object affiliation this.SetProperty(GRAPH_OBJ_PROP_SPECIES,0,CGBaseObj::Species()); // Graphical object species this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,0); // Graphical object group this.SetProperty(GRAPH_OBJ_PROP_ID,0,0); // Object ID this.SetProperty(GRAPH_OBJ_PROP_BASE_ID,0,0); // Base object ID this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0); // Object index in the list this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,false); // Flag of storing the change history this.SetProperty(GRAPH_OBJ_PROP_BASE_NAME,0,this.Name()); // Base object name //--- Save the properties inherent in all graphical objects and present in a graphical object this.PropertiesRefresh(); //--- Save basic properties in the parent object this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME,0); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0); //--- Initialize the extended graphical object toolkit if(this.GraphElementType()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { datetime times[]; double prices[]; if(::ArrayResize(times,this.Pivots())!=this.Pivots()) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA); if(::ArrayResize(prices,this.Pivots())!=this.Pivots()) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA); for(int i=0;i<this.Pivots();i++) { times[i]=this.Time(i); prices[i]=this.Price(i); } this.ExtToolkit.SetBaseObj(this.TypeGraphObject(),this.Name(),this.ChartID(),this.SubWindow(),this.Pivots(),CTRL_FORM_SIZE,this.XDistance(),this.YDistance(),times,prices); this.ExtToolkit.CreateAllControlPointForm(); this.SetFlagSelected(false,false); this.SetFlagSelectable(false,false); } //--- Save the current properties to the previous ones this.PropertiesCopyToPrevData(); } //+-------------------------------------------------------------------+
初始化工具箱对象时,首先声明时间和价格属性的数组,根据图形对象轴点的数量调整它们的大小,并依据与循环索引对应的对象轴点,逐一设置价格和时间值。
接下来,调用工具箱对象的初始化方法,并将必要的图形对象参数与新填充的价格和时间属性数组一起传递给它。 初始化后,调用基于图形对象轴点上创建窗体对象的方法。 最后,为图形对象设置未选定对象的状态,并禁用用鼠标选择它的功能。
在检查对象属性变更的方法中,即在处理扩展标准图形对象的代码模块中,在更改扩展标准图形对象的轴点位置时,添加将控制点(窗体对象)移动到新屏幕坐标的代码模块:
//+------------------------------------------------------------------+ //| 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); } } dep.PropertiesCopyToPrevData(); } //--- Move reference control points to new coordinates if(ExtToolkit!=NULL) { for(int i=0;i<this.Pivots();i++) { ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); long lparam=0; double dparam=0; string sparam=""; ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } //--- 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(); } } //+------------------------------------------------------------------+
如果移动一个图形对象轴点或整个对象,其轴点的屏幕坐标亦会发生变化。 这意味着,我们还需要将工具箱类窗体对象移动到新的屏幕坐标处。 因此,我首先将新的图形对象坐标传递给工具箱对象(依据价格/时间坐标的轴点数循环遍历,以及按照像素定位的单独坐标)。 然后,我依据所传递变更事件的图表 ID, 调用工具箱对象事件的处理程序。 这会令工具箱对象事件的处理程序重新计算所有窗体的屏幕坐标,并根据图形对象的新价格和时间坐标将它们重新定位到新位置。
在把从属标准图形对象添加到列表中的方法中,修复了一个小错误 — 从属图形对象更改其自身属性,因此应立即将新属性保存为以前的属性,从而避免了在单击这些图形对象时,生成更改这些图形对象的新事件:
//+------------------------------------------------------------------+ //| 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); obj.PropertiesCopyToPrevData(); return true; } //+------------------------------------------------------------------+
抽象标准图形对象事件的处理程序:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGStdGraphObj::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(GraphElementType()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) return; if(id==CHARTEVENT_CHART_CHANGE) { if(ExtToolkit==NULL) return; for(int i=0;i<this.Pivots();i++) { ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); ExtToolkit.OnChartEvent(id,lparam,dparam,sparam); } } //+------------------------------------------------------------------+
就目前而言,处理程序只处理图表变更事件。
如果对象是非扩展对象,则退出处理程序。 如果检测到图表变更事件,则检查指向扩展标准图形对象的工具箱对象指针的有效性。 如果没有创建工具箱,则退出。 接下来,依据图形对象轴点的数量循环遍历,,把新的图形对象价格/时间坐标设置到工具箱对象。 接下来,设置其新的屏幕坐标,并调用工具箱对象事件的处理程序,在此过程中,所有窗体都会根据传递给工具箱对象的新价格/时间坐标重新定位到新的屏幕坐标。
当从图表中删除扩展标准图形对象时,我们还需要删除其t工具箱对象的窗体对象,以防再次创建这样的对象。 从图表中删除图形对象是在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 中的图形元素集合类中进行处理 。
在处理删除扩展图形对象的方法中,添加从工具箱对象中删除所有窗体对象的代码模块:
//+------------------------------------------------------------------+ //| 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) { CGStdGraphObjExtToolkit *toolkit=obj.GetExtToolkit(); if(toolkit!=NULL) { toolkit.DeleteAllControlPointForm(); } //--- 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); } //+------------------------------------------------------------------+
在此,我们简单地提取指向扩展图形对象的工具箱对象的指针。 如果指针有效,则调用我之前曾研究过的扩展标准图形对象的工具箱对象里删除所有已创建窗体的方法。
在图形元素集合类事件的处理程序中,添加对扩展标准图形对象的图表变更的处理:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj=NULL; ushort idx=ushort(id-CHARTEVENT_CUSTOM); if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CLICK) { //--- Calculate the chart ID //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID //--- If the event ID corresponds to a user event, the chart ID is received from lparam //--- Otherwise, the chart ID is assigned to -1 long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE); long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param); //--- Get the object, whose properties were changed or which was relocated, //--- from the collection list by its name set in sparam obj=this.GetStdGraphObject(sparam,chart_id); //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed if(obj==NULL) { //--- Let's search the list for the object that is not on the chart obj=this.FindMissingObj(chart_id); //--- If failed to find the object here as well, exit if(obj==NULL) return; //--- Get the name of the renamed graphical object on the chart, which is not in the collection list string name_new=this.FindExtraObj(chart_id); //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- and send an event with the new name of the object to the control program chart if(obj.SetNamePrev(obj.Name()) && obj.SetName(name_new)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),obj.TimeCreate(),obj.Name()); } //--- Update the properties of the obtained object //--- and check their change obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } //--- Handle chart changes for extended standard objects if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE) { CArrayObj *list=this.GetListStdGraphObjectExt(); if(list!=NULL) { for(int i=0;i<list.Total();i++) { obj=list.At(i); if(obj==NULL) continue; obj.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
在此,如果检测到图表变更事件,则获取所有扩展标准图形对象的列表。 在循环中,按编号获取下一个对象,调用其事件处理程序,并将 “Chart changed” 事件值传递给它。
测试
为了执行测试,我们借用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part95\,命名为 TestDoEasyPart95.mq5。
唯一的变化是附着于基准趋势线对象上的价格标签对象的名称略有不同。 创建 EA 事件处理程序的复合图形对象模块中的从属对象名称现在拥有 “Ext” 词缀,该名称对应于扩展图形对象类型:
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="PriceLeftExt"; 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="PriceRightExt"; 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); } } engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
我们来创建一个复合图形对象。 在创建过程中,将把窗体对象设置到其轴点。
这些窗体对象的坐标以屏幕左上角的像素为单位。 因此,如果我们移动图表,这些屏幕坐标应被重新计算,从而将对象设置在相应的图形对象轴点上。 这就是我们要检查的。
编译 EA,并在图表上启动它:
我们可以看到,当图表发生变化时,这些对象会被放置到它们的指定位置。 不过,这发生时略有滞后。
当删除图形对象时,也会删除对应的窗体对象。
对于滞后我们能做什么? 事实上,我们不需要现场观看这些动作 — 当移动图表时,这些动作将始终保持隐藏状态(现在显示它们是为了处理事件响应)。 当用鼠标拖动窗体对象时,图形对象轮廓线本身会移动。 与窗体的任何交互都在固定的图表上执行。 所以这个结果非常充分,特别是考虑到图表只在循环完成后更新,而非在每次循环迭代时都会更新。 为了减少负载,我们可以控制图表变更完成后,再显示变化,并随后显示对象(只有当光标悬停在窗体对象活动区域上时,才可能显示更改)。
下一步是什么?
在下一篇文章中,我将继续研究复合图形对象事件。
*该系列的前几篇文章:
DoEasy 函数库中的图形(第九十三部分):准备创建复合图形对象的功能
DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10387


