DoEasy 函数库中的图形(第八十六部分):图形对象集合 - 管理属性修改

Artyom Trishkin | 22 十一月, 2021

内容


概述

上一篇文章中,我实现了把新创建的标准图形对象存储在图形对象集合类中的能力 — 我们检测到新的图表对象,创建与图表上对象类型相对应的类对象,并将它们添加到集合列表当中。 然而,这对于全面管理图形对象还是不够的。 我们需要管控图表上图形对象属性的所有变更,以及它们的删除或重命名。

鉴于来自 ObjectGetXXX 系列的函数被用来读取图形对象的属性,我们不能持续在定时器中检查每个图形对象的值,因为这些函数是同步的。 这意味着它们会等待命令被执行。 在含有大量图形对象的情况下,这可能会占用大量资源。

在此,我们面临着一个选择。 我们既可以用计时器来排查每个图形对象的每个属性,这需要利用属性请求函数同步得到所有后续结果;或是在 OnChartEvent() 处理程序中响应所应用的事件模型,然而,不幸的是,在策略测试器中这不起作用(您可能还记得,测试器中的计时器操作在函数库中会由 OnTick()OnCalculate() 处理程序来处理)。

在权衡了所有利弊之后,我决定在图表事件处理程序中跟踪图形对象属性的变化。 换言之,我将运用事件模型,它能简化了代码,但在测试器中其操作被施加了限制。 测试器不允许我们处理图形对象(至少目前是这样)。 我们不能将它们添加到测试器窗口,并随后更改它们的属性。 这意味着我们应该只能在“实时”图表的事件处理程序里处理图形对象。

在本文中,我仅会实现一个处理当前图表(启动程序的图表)上图形对象事件的测试版本。 一旦我确任一切操作正常,我就会为每个打开的图表开发成熟的事件处理程序。 处理程序将事件发送到程序的主图表,而函数库将把图形对象收集到集合中,并处理它们。


跟踪图形对象属性的修改

OnChartEvent() 处理程序中,我们对以下的事件感兴趣:

我在上一篇文章中准备了图形对象创建事件,没有调用 OnChartEvent() 处理程序。
我们需要图形对象属性变化事件,并通过终端属性对话框来手动控制对象属性的变更。
我们已经有了图形对象删除事件 — 该函数库跟踪终端图表上所有图形对象的数量,并为每个打开的图表提供事件标志 — 如果图表对象的数量减少,我们能够找出从图表中被删除对象的数量,并处理。
我们需要图形对象移动事件来控制整个图形对象位置的变化,特别是它的各自锚点。

手动创建对象时也会激活移动事件。 当我们单击图表为对象设置属性,但尚未释放鼠标按钮时,该对象必须已被创建,且函数库能够看到其相应类对象,并将其添加到集合当中。 并非所有对象属性值都会被正确设置。 鼠标按钮尚未释放,如果对象用到了多个锚点,那么我们就可以移动对象,或为其设置其余的锚点。 但是当我释放鼠标按钮时,如果所有对象锚点都已被设置,则会创建图形对象移动事件。 通过跟踪事件,并根据所创建图形对象的完整设置参数来更改已创建类对象的属性值,我们为新创建的对象的所有属性设置正确的值。

更改对象名称一次性蕴含三个事件 — 移除对象、创建对象和更改对象属性。 通过跟踪这三个事件来检测现有对象之一的名称更改。 但我将使用更简单的方法。 当我们更改对象名称时, CHARTEVENT_OBJECT_CHANGE 事件总是最后处理。 鉴于所有终端对象都是按名称和图表 ID 来选择的,由此我们就可以检查图表上存在的对象中哪个没在集合列表之中。 然后我们在图表上找到对象名称,若集合 (1) 中不存在该类对象,则找不到相应命名的图表对象 (2),那么就将该名称添加到 (1) 中检测到的类对象集合列表之中。 这可能看起来很复杂。 然而,一切其实都很简单。

为了掌握哪个对象属性已更改(因为我们得到的事件含有已更改对象的名称,但没有所更改属性的指示),我们需要将所有对象属性的当前值与收到对象属性更改事件之前保存的值进行比较。 因此,我们需要再创建三个数组来存储“以前的”对象属性值。

打开抽象标准图形对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh,并在在私密部分添加存储“以前的”对象属性值的新数组

//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   long              m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL];         // Integer properties
   double            m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL];        // Real properties
   string            m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL];        // String properties
   
   long              m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL];    // Integer properties before change
   double            m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL];   // Real properties before change
   string            m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL];   // String properties before change

//--- Return the index of the array the (1) double and (2) string properties are actually located at
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL;                              }
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_DOUBLE_TOTAL;  }

public:

在类的私密部分,加入设置方法,和返回“以前的”对象属性值的方法

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                         }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;       }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;       }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property)        const { return this.m_long_prop[property];                        }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];      }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];      }
   
//--- Set object's previous (1) integer, (2) real and (3) string properties
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop_prev[property]=value;                  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value){ this.m_double_prop_prev[this.IndexProp(property)]=value;}
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,string value){ this.m_string_prop_prev[this.IndexProp(property)]=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)    const { return this.m_long_prop_prev[property];                   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property)     const { return this.m_double_prop_prev[this.IndexProp(property)]; }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property)     const { return this.m_string_prop_prev[this.IndexProp(property)]; }
   
//--- Return itself
   CGStdGraphObj    *GetObject(void)                                                { return &this;}


我们已有了“对象 ID”属性来描述图形对象的类对象。 该属性允许我们设置一个唯一的对象标签,我们可其识别它。
该属性不应参与定义对象事件。 因此,在为对象设置 ID 的方法中,我们给方法一次性传递两个属性设置值 — 当前值(已开发),和以前值(待现在添加)

public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+
//--- Object index in the list
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM);                              }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,number);                                   }
//--- Object ID
   long              ObjectID(void)                const { return this.GetProperty(GRAPH_OBJ_PROP_ID);                                    }
   void              SetObjectID(const long obj_id)
                       {
                        CGBaseObj::SetObjectID(obj_id);
                        this.SetProperty(GRAPH_OBJ_PROP_ID,obj_id);
                        this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,obj_id);
                       }
//--- Graphical object type


事件响应算法如下:在接收到事件后,我们需要更新描述图形对象的类对象的所有数据,如此其所有属性都会有相关值。 由于我们不知道哪个属性发生了变化,故我们更新所有对象属性,并将对象当前属性值与接收更改事件之前的属性值进行比较。 我将在三个循环中把所有三个对象属性数组与相应的以前属性数组进行比较。 比较时,若当前属性值不等于以前值时,属性更改的消息会临时(因为迄今为止这只是测试版本)发送到日志。 对于每个对象属性数组进行数值比较时,每个检测到的差异也是如此。 稍后,在实现覆盖每个打开图表的属性更改控制时,我将介绍另一种方法。 所有更改的属性都将发送到事件对象。 每个这样的对象都将会收入集合类中,以示该打开图表上某个对象的属性发生变化。

在类的公开部分,声明覆盖所有对象属性的方法。 它需要一次性遍历图形对象的所有属性来检测属性更改事件,并将它们输入到类对象属性中。
检查对象属性变化的方法 将所有当前对象属性与其以前的状态进行比较。

在类的私密部分,声明三个方法,从图形对象接收所有属性,并把它们设置到类对象属性
检测到事件时,将当前属性复制到以前属性的方法,在下次检查时会取属性与更改的属性进行比较:

//--- Return the description of the object visibility on timeframes
   string            VisibleOnTimeframeDescription(void);

//--- Re-write all graphical object properties
   void              PropertiesRefresh(void);
//--- Check object property changes
   void              PropertiesCheckChanged(void);
   
private:
//--- Get and save (1) integer, (2) real and (3) string properties
   void              GetAndSaveINT(void);
   void              GetAndSaveDBL(void);
   void              GetAndSaveSTR(void);
//--- Copy the current data to the previous one
   void              PropertiesCopyToPrevData(void);
   
  };
//+------------------------------------------------------------------+

我们简化受保护的参数型构造函数。 以前,它从对象接收所有图形对象固有的所有属性,并在类对象中设置

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- 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;
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save integer properties
   //--- properties inherent in all graphical objects but not present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Chart ID
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Chart subwindow index
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Graphical object type (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Graphical object affiliation
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Graphical object group
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Object ID
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Object index in the list
   //--- Properties inherent in all graphical objects and present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(chart_id,name,OBJPROP_CREATETIME);  // Object creation time
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(chart_id,name,OBJPROP_TIMEFRAMES);  // Object visibility on timeframes
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(chart_id,name,OBJPROP_BACK);        // Background object
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(chart_id,name,OBJPROP_ZORDER);      // Priority of a graphical object for receiving the event of clicking on a chart
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(chart_id,name,OBJPROP_HIDDEN);      // Disable displaying the name of a graphical object in the terminal object list
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTED);    // Object selection
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTABLE);  // Object availability
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(chart_id,name,OBJPROP_TIME);        // First point time coordinate
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(chart_id,name,OBJPROP_COLOR);       // Color
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(chart_id,name,OBJPROP_STYLE);       // Style
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(chart_id,name,OBJPROP_WIDTH);       // Line width
   //--- Properties belonging to different graphical objects
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]                          = 0;  // Object color filling
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]                      = 0;  // Ability to edit text in the Edit object
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]                        = 0;  // Number of levels
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]                    = 0;  // Level line color
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]                    = 0;  // Level line style
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]                    = 0;  // Level line width
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]                         = 0;  // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]                      = 0;  // Font size
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]                      = 0;  // Ray goes to the left
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]                     = 0;  // Ray goes to the right
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]                           = 0;  // Vertical line goes through all windows of a chart
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]                       = 0;  // Display the full ellipse of the Fibonacci Arc object
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]                     = 0;  // Arrow code for the "Arrow" object
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]                        = 0;  // Position of the binding point of the graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]                     = 0;  // Distance from the base corner along the X axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]                     = 0;  // Distance from the base corner along the Y axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]                     = 0;  // Gann object trend
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]                        = 0;  // Elliott wave marking level
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]                     = 0;  // Display lines for Elliott wave marking
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]                         = 0;  // Button state (pressed/released)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]            = 0;  // Chart object ID (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]              = 0;  // Chart object period<
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE]          = 0;  // Time scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]         = 0;  // Price scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]         = 0;  // Chart object scale
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]                         = 0;  // Object width along the X axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]                         = 0;  // Object height along the Y axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]                       = 0;  // X coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]                       = 0;  // Y coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]                       = 0;  // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]                        = 0;  // Chart corner for binding a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE]                   = 0;  // Border type for "Rectangle border"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]                  = 0;  // Border color for OBJ_EDIT and OBJ_BUTTON
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(chart_id,name,OBJPROP_PRICE);  // Price coordinate
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = 0;                                               // Level value
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = 0;                                               // Scale (property of Gann objects and Fibonacci Arcs objects)
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = 0;                                               // Angle
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = 0;                                               // Deviation of the standard deviation channel
   
//--- Save string properties
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_NAME)]        = name;                                            // Object name
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]        = ::ObjectGetString(chart_id,name,OBJPROP_TEXT);   // Object description (the text contained in the object)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]     = ::ObjectGetString(chart_id,name,OBJPROP_TOOLTIP);// Tooltip text
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]   = "";                                              // Level description
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]        = "";                                              // Font
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]     = "";                                              // BMP file name for the "Bitmap Level" object
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]= "";                                          // Chart object symbol 
   
//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);
   this.m_name=this.GetProperty(GRAPH_OBJ_PROP_NAME);

  }
//+-------------------------------------------------------------------+

现在,所有这些代码都被移到单独的方法中,这些方法可以一次性从 PropertiesRefresh() 方法调用
因此,我们来删除这些代码。 所以现在构造函数如下所示:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- 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(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save integer properties
   //--- properties inherent in all graphical objects but not present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Chart ID
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Chart subwindow index
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Graphical object type (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Graphical object affiliation
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Graphical object group
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Object ID
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Object index in the list

//--- 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);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);

//--- Save the current properties to the previous ones
   this.PropertiesCopyToPrevData();
   
  }
//+-------------------------------------------------------------------+

为了令 PropertiesRefresh() 方法正常工作,它应该知道从何处获取图形对象的名称数据。 以前,名称几乎写在末尾 — 在读取字符串参数的模块中。 现在,每当进入构造函数时,立即读取图形对象名称,并将其设置为类对象属性。 将所有属性添加到类对象后,调用 PropertiesCopyToPrevData() 方法,该方法把所有已经保存的对象属性设置到“以前的”属性数组,从而控制它们的变化。

该方法从图形对象接收整数型实数型字符串型属性,并将它们保存到类对象之中:

//+------------------------------------------------------------------+
//| Get and save the integer properties                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Properties inherent in all graphical objects and present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME); // Object creation time
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES); // Object visibility on timeframes
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK);       // Background object
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER);     // Priority of a graphical object for receiving the event of clicking on a chart
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN);     // Disable displaying the name of a graphical object in the terminal object list
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED);   // Object selection
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE); // Object availability
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME);       // First point time coordinate
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR);      // Color
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE);      // Style
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH);      // Line width
   //--- Properties belonging to different graphical objects
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL);       // Fill an object with color
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY);   // Ability to edit text in the Edit object
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS);     // Number of levels
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR); // Level line color
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE); // Level line style
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH); // Level line width
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN);      // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE);   // Font size
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT);   // Ray goes to the left
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT);  // Ray goes to the right
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]         = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY);        // Vertical line goes through all windows of a chart
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE);    // Display the full ellipse of the Fibonacci Arc object
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE);  // Arrow code for the "Arrow" object
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR);     // Position of the binding point of the graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE);  // Distance from the base corner along the X axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE);  // Distance from the base corner along the Y axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION);  // Gann object trend
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE);     // Elliott wave marking level
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES);  // Display lines for Elliott wave marking
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE);      // Button state (pressed/released)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID);   // Chart object ID (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD);     // Chart object period
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE); // Time scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE);// Price scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE);// Chart object scale
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE);      // Object width along the X axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE);      // Object height along the Y axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET);    // X coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET);    // Y coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR);    // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER);     // Chart corner for binding a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE);// Border type for "Rectangle border"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR);// Border color for OBJ_EDIT and OBJ_BUTTON
  }
//+------------------------------------------------------------------+
//| Get and save the real properties                                 |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveDBL(void)
  {
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE);       // Price coordinate
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE);  // Level value
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE);       // Scale (property of Gann objects and Fibonacci Arcs objects)
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE);       // Corner
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION);   // Deviation of the standard deviation channel
  }
//+------------------------------------------------------------------+
//| Get and save the string properties                               |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveSTR(void)
  {
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT);     // Object description (the text contained in the object)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP);  // Tooltip text
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]         = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT);// Level description
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT);     // Font
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE);  // BMP file name for the "Bitmap Level" object
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]  = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL);   // Chart object symbol 
  }
//+------------------------------------------------------------------+

覆盖所有图形对象属性的方法替换的类构造函数代码,被移至这三个方法:

//+------------------------------------------------------------------+
//| Overwrite all graphical object properties                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesRefresh(void)
  {
   this.GetAndSaveINT();
   this.GetAndSaveDBL();
   this.GetAndSaveSTR();
  }
//+------------------------------------------------------------------+

上面研究过的所有三种方法都在这里逐一调用。

将当前类对象属性复制到以前的方法:

//+------------------------------------------------------------------+
//| Copy the current data to the previous one                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCopyToPrevData(void)
  {
   ::ArrayCopy(this.m_long_prop_prev,this.m_long_prop);
   ::ArrayCopy(this.m_double_prop_prev,this.m_double_prop);
   ::ArrayCopy(this.m_string_prop_prev,this.m_string_prop);
  }
//+------------------------------------------------------------------+

此处利用数组复制函数将整数型、实数型和字符串型属性的数组逐一复制到以前属性的相应数组当中。

检查对象属性变化的方法:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   bool changed=false;
   int beg=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+

此处,在三个循环中(分别针对整数型、实数型和字符串型属性),从相应的数组中获取下一个属性,并将其与以前属性数组中的相同属性进行比较。 如果当前属性和以前属性的比较值不相等,则该属性已变化。 在对象属性中设置已变化标志,并在日志中显示消息

这是一种测试方法,仅用于检查搜索对象属性变化的概念。 在接下来的文章中,我将改进这个概念,令其功能更齐全。 我将跟踪所有打开图表上所有图形对象的变化,并将对象属性更改事件发送到控制程序图表,以便函数库能够进一步处理它们。


跟踪图形对象删除

由于图形对象属性属于对象,因此在对象类中跟踪更改图形对象属性。 它们在对象类中设置,并在那里进行检查。 然而,上面创建的对象属性变化及验证的所有方法都将从图形对象集合类中调用。 相比之下,在图表中添加和删除图形对象只能在图形对象集合类中进行跟踪。 该类管理所有打开图表上所有对象的完整列表,并在其自己的集合列表中跟踪它们。

打开图形对象集合类 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 的文件,并在其中进行所有必要的改进。

在位于同一文件的图表对象管理类中,声明私有方法,设置跟踪鼠标和图形对象事件的权限。 这允许设置权限,从而跟踪每个打开的图表的该类形事件。
在类构造函数中,调用这些方法来为其创建控件对象的图表设置权限。 在类清单的最后,声明事件处理程序(我将在下一篇文章中实现它):

//+------------------------------------------------------------------+
//| Chart object management class                                    |
//+------------------------------------------------------------------+
class CChartObjectsControl : public CObject
  {
private:
   CArrayObj         m_list_new_graph_obj;      // List of added graphical objects
   ENUM_TIMEFRAMES   m_chart_timeframe;         // Chart timeframe
   long              m_chart_id;                // Chart ID
   string            m_chart_symbol;            // Chart symbol
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_last_objects;            // Number of graphical objects during the previous check
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   
//--- Return the name of the last graphical object added to the chart
   string            LastAddedGraphObjName(void);
//--- Set the permission to track mouse events and graphical objects
   void              SetMouseEvent(void);
   
public:
//--- Return the variable values
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_chart_timeframe;    }
   long              ChartID(void)                             const { return this.m_chart_id;           }
   string            Symbol(void)                              const { return this.m_chart_symbol;       }
   bool              IsEvent(void)                             const { return this.m_is_graph_obj_event; }
   int               TotalObjects(void)                        const { return this.m_total_objects;      }
   int               Delta(void)                               const { return this.m_delta_graph_obj;    }
//--- Create a new standard graphical object
   CGStdGraphObj    *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name);
//--- Return the list of newly added objects
   CArrayObj        *GetListNewAddedObj(void)                        { return &this.m_list_new_graph_obj;}
//--- Check the chart objects
   void              Refresh(void);
//--- Constructors
                     CChartObjectsControl(void)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=::ChartID();
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     CChartObjectsControl(const long chart_id)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=chart_id;
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     
//--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CChartObjectsControl *obj_compared=node;
                        return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0);
                       }

//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

  };
//+------------------------------------------------------------------+

在类主体之外,实现设置权限从而跟踪鼠标和图形对象事件的方法:

//+------------------------------------------------------------------+
//| Set the permission                                               |
//| to track mouse and graphical object events for the chart         |
//+------------------------------------------------------------------+
void CChartObjectsControl::SetMouseEvent(void)
  {
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_CREATE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_DELETE,true);
  }
//+------------------------------------------------------------------+


现在我们把注意力移到图形对象集合类。

在类的私密部分,声明新方法,并在其说明中明确阐明其功能:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
class CGraphElementsCollection : public CBaseObj
  {
private:
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   
//--- Return the flag indicating the graphical element object presence in the collection list of graphical elements
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
//--- Return the flag indicating the graphical element object presence in the collection list of graphical objects
   bool              IsPresentGraphObjInList(const long chart_id,const string name);
//--- Return the flag indicating the presence of a graphical object on a chart by name
   bool              IsPresentGraphObjOnChart(const long chart_id,const string name);
//--- Return the pointer to the object of managing objects of the specified chart
   CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id);
//--- Create a new object of managing graphical objects of a specified chart and add it to the list
   CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id);
//--- Update the list of graphical objects by chart ID
   CChartObjectsControl *RefreshByChartID(const long chart_id);
//--- Return the first free ID of the graphical (1) object and (2) element on canvas
   long              GetFreeGraphObjID(void);
   long              GetFreeCanvElmID(void);
//--- Add a graphical object to the collection
   bool              AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control);
//--- Find an object present in the collection but not on a chart
   CGStdGraphObj    *FindMissingObj(const long chart_id);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object from the graphical object collection list
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   
public:

在类的公开部分,声明通过图表名称和 ID 返回图形对象的方法,和图表事件处理程序

public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Return the full collection list of standard graphical objects "as is"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Return the full collection list of graphical elements on canvas "as is"
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
//--- Return the list of graphical elements by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
//--- Return the list of graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)    { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
//--- Return the number of new graphical objects, (3) the flag of the occurred change in the list of graphical objects
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   bool              IsEvent(void) const                                                                 { return this.m_is_graph_obj_event;    }
//--- Return a graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
//--- Constructor
                     CGraphElementsCollection();
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);

//--- Create the list of chart management objects and return the number of charts
   int               CreateChartControlList(void);
//--- 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);
  };
//+------------------------------------------------------------------+

在更新所有图形对象列表的方法中,添加处理从图表中删除图形对象的模块

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
//--- 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(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            // Find an extra object in the list
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);
            if(obj!=NULL)
              {
               //--- Display a short description of a detected object deleted from a chart in the journal
               obj.PrintShort();
               //--- Remove the class object of a removed graphical object from the collection list
               if(!this.DeleteGraphObjFromList(obj))
                  CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
              }
           }
         //--- otherwise
         else
           {
            
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+

在该方法中,我们按照图表控件对象的总数来循环,逐一检查每个控件对象中是否存在事件。 如果存在事件,则检查图表(由图表控制对象管理)上已变化对象的数值。 我已在上一篇文章中实现了添加对象的处理。 此处我引入处理图表对象数量变化的负值。
这一切都很简单:首先,我们在集合列表里搜索图表上没有的图形对象,并将其从集合列表中删除。

该方法搜索存在于集合中,但图表上不存在的对象:

//+------------------------------------------------------------------+
//|Find an object present in the collection but not on a chart       |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   if(list==NULL)
      return NULL;
   for(int i=0;i<list.Total();i++)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name()))
         return obj;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

此处,我们得到所有对象的列表,其图表 ID 等于方法参数中指定的 ID
在所得列表里循环得到标准图形对象类的下一个对象如果图表内不含这个名称的对象,则返回指向该对象的指针
直至循环完毕,返回 NULL

该方法搜索存在于图表上,但不在集合当中的对象:

//+------------------------------------------------------------------+
//|Find an object present on a chart but not in the collection       |
//+------------------------------------------------------------------+
string CGraphElementsCollection::FindExtraObj(const long chart_id)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
     {
      string name=::ObjectName(chart_id,i);
      if(!this.IsPresentGraphObjInList(chart_id,name))
         return name;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

在此,依据终端列表中的所有对象循环得到下一个对象的名字如果集合列表中不含有这个名称及图表 ID 的对象,则返回图形对象名称。 直至循环完毕,返回 NULL

该方法返回图形对象集合列表中存在该图形对象类的指示标志:

//+------------------------------------------------------------------------------+
//| Return the flag indicating the presence of the graphical object class object |
//| in the graphical object collection list                                      |
//+------------------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list==NULL || list.Total()==0 ? false : true);
  }
//+------------------------------------------------------------------+

获取含有指定图表 ID 的对象列表从获取的列表中获取指向名称与所需对象匹配的对象指针如果获取列表失败,或者列表为空,返回 false — 没有找到对象;否则 — 返回 true

该方法返回图表上存在依据名称指示的图形对象的标志:

//+----------------------------------------------------------------------------------+
//| Return the flag indicating the presence of a graphical object on a chart by name |
//+----------------------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjOnChart(const long chart_id,const string name)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
      if(::ObjectName(chart_id,i)==name)
         return true;
   return false;
  }
//+-------------------------------------------------------------------+

在依据所有图形对象总数的循环中在指定 ID 的图表上获取下一个对象的名称。 如果名称匹配查找的名称,则返回 true直至循环完成,返回 false

该方法从图形对象集合列表中删除图形对象:

//+---------------------------------------------------------------------+
//|Remove the graphical object from the graphical object collection list|
//+---------------------------------------------------------------------+
bool CGraphElementsCollection::DeleteGraphObjFromList(CGStdGraphObj *obj)
  {
   this.m_list_all_graph_obj.Sort();
   int index=this.m_list_all_graph_obj.Search(obj);
   return(index==WRONG_VALUE ? false : this.m_list_all_graph_obj.Delete(index));
  }
//+------------------------------------------------------------------+

该方法接收指向要从列表中删除的对象的指针
为列表设置已排序标志
(仅在排序列表中执行搜索),调用标准库的 Search() 方法获取对象索引。
如果没有找到对象索引,返回 false
否则返回调用标准库 Delete() 方法从列表中删除对象的结果 。

该方法依据图表名称和 ID 返回指向图形对象的指针:

//+------------------------------------------------------------------+
//| Return a graphical object by chart name and ID                   |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id)
  {
   CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,chart_id);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

获取图表 ID 与传递给该方法的对象相同的对象列表。 从获取的列表中获取包含名称与所需对象匹配的对象的列表(应该只有一个这样的对象)。 如果获取列表失败,且列表不为空,则返回指向列表中第一个(也是唯一那个)对象的指针否则,返回 NULL

事件处理程序。

在本文中,我将实现事件处理程序的测试版本,该版本仅处理当前图表上的图形对象事件。 处理程序将处理更改、或重新定位图形对象的事件。 该方法足以定义上面命名的事件。 此外,我们还修复了针对非通过单击鼠标构建的对象,属性填充不完整的问题。 我之前曾提到过,第一次单击图表会创建一个对象创建事件,而函数库会立即创建相应的事件。

与此同时,并非所有属性都会在其中正确设置,因为该对象是经由多次单击鼠标构建的。 完成对象构建后,将创建移动事件。 对它的响应会重写对象属性(因为我们想实时跟踪事件,但当创建一个带有多个锚点的对象时,我们也会得到一个导致对象属性更新的移动事件,如此它们就会被正确的数据重写)。

整个逻辑在方法代码注释中进行了描述。 我们来研究一下该方法:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- If the object, whose properties were changed or which was relocated,
      //--- is successfully received from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,::ChartID());
      if(obj!=NULL)
        {
         //--- Update the properties of the obtained object
         //--- and check their change
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      else
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(::ChartID());
         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(::ChartID());
         //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- update the chart properties and check their change
         obj.SetName(name_new);
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
     }
  }
//+------------------------------------------------------------------+


此外,我们需要从基于函数库的程序访问图形对象集合。
为此,在 \MQL5\Include\DoEasy\Engine.mqh 内的主库对象中,创建返回指向函数库图形对象集合类指针的方法

//--- Launch the new pause countdown
   void                 Pause(const ulong pause_msc,const datetime time_start=0)
                          {
                           this.PauseSetWaitingMSC(pause_msc);
                           this.PauseSetTimeBegin(time_start*1000);
                           while(!this.PauseIsCompleted() && !::IsStopped()){}
                          }

//--- Return the graphical object collection
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects; }

//--- Constructor/destructor
                        CEngine();
                       ~CEngine();

private:


测试

为了执行测试,我们借用上一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part86\,命名为 TestDoEasyPart86.mq5

OnInit() 处理程序中,删除设置权限代码,从而跟踪鼠标事件

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'246,244,244';     // Original ≈pale gray
   array_clr[1]=C'249,251,250';     // Final ≈pale gray-green
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

现在,在图表管理类中设置跟踪鼠标和图形对象事件的权限。 权限是为所有打开的图表设置的。

OnChartEvent() 处理程序的最后,加入调用图形对象集合类事件处理程序

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If working in the tester, exit
   if(MQLInfoInteger(MQL_TESTER))
      return;
//--- If the mouse is moved
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      CForm *form=NULL;
      datetime time=0;
      double price=0;
      int wnd=0;
      
      //--- If Ctrl is not pressed,
      if(!IsCtrlKeyPressed())
        {
         //--- clear the list of created form objects, allow scrolling a chart with the mouse and show the context menu
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,true);
         return;
        }
      
      //--- If X and Y chart coordinates are successfully converted into time and price,
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- get the bar index the cursor is hovered over
         int index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         if(index==WRONG_VALUE)
            return;
         
         //--- Get the bar index by index
         CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index);
         if(bar==NULL)
            return;
         
         //--- Convert the coordinates of a chart from the time/price representation of the bar object to the X and Y coordinates
         int x=(int)lparam,y=(int)dparam;
         if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y))
            return;
         
         //--- Disable moving a chart with the mouse and showing the context menu
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,false);
         
         //--- Create the form object name and hide all objects except one having such a name
         string name="FormBar_"+(string)index;
         HideFormAllExceptOne(name);
         
         //--- If the form object with such a name does not exist yet,
         if(!IsPresentForm(name))
           {
            //--- create a new form object
            form=bar.CreateForm(index,name,x,y,114,16);   
            if(form==NULL)
               return;
            
            //--- Set activity and unmoveability flags for the form
            form.SetActive(true);
            form.SetMovable(false);
            //--- Set the opacity of 200
            form.SetOpacity(200);
            //--- The form background color is set as the first color from the color array
            form.SetColorBackground(array_clr[0]);
            //--- Form outlining frame color
            form.SetColorFrame(C'47,70,59');
            //--- Draw the shadow drawing flag
            form.SetShadow(true);
            //--- Calculate the shadow color as the chart background color converted to the monochrome one
            color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
            //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
            //--- Otherwise, use the color specified in the settings for drawing the shadow
            color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
            //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
            //--- Set the shadow opacity to 200, while the blur radius is equal to 4
            form.DrawShadow(2,2,clr,200,3);
            //--- Fill the form background with a vertical gradient
            form.Erase(array_clr,form.Opacity());
            //--- Draw an outlining rectangle at the edges of the form
            form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
            //--- If failed to add the form object to the list, remove the form and exit the handler
            if(!list_forms.Add(form))
              {
               delete form;
               return;
              }
            //--- Capture the form appearance
            form.Done();
           }
         //--- If the form object exists,
         if(form!=NULL)
           {
            //--- draw a text with the bar type description on it and show the form. The description corresponds to the mouse cursor position
            form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21');
            form.Show();
           }
         //--- Redraw the chart
         ChartRedraw();
        }
     }
   
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
   
  }
//+------------------------------------------------------------------+

这就是全部改进。 编译 EA,并在图表上启动它。 每当创建/删除对象,或更改其属性时,客户端日志中会显示相应的事件记录:


到目前为止,这些只是日志记录,但稍后会改变。

下一步是什么?

在下一篇文章中,我将为每个打开的图表创建对象事件处理程序,并实现将这些事件发送到控制程序图表,从而程序能够完全处理它们。

以下是该函数库当前版本的所有文件,以及 MQL5 的测试 EA 文件,供您测试和下载。

在评论中留下您的问题、意见和建议。

返回内容目录

*该系列的前几篇文章:

DoEasy 函数库中的图形(第八十三部分):抽象标准图形对象类
DoEasy 函数库中的图形(第八十四部分):抽象标准图形对象的衍生后代类
DoEasy 函数库中的图形(第八十五部分):图形对象集合 - 添加新创建的对象