English Русский Español Deutsch 日本語 Português
preview
DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理

DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理

MetaTrader 5示例 | 14 六月 2022, 09:37
569 0
Artyom Trishkin
Artyom Trishkin

内容


概述

上一篇文章中覆盖的内容,我已经开发了基于扩展标准图形对象来创建复合图形对象的处理功能。 我们在以往的逐篇文章里梯次推进创建此功能。 有时,我被迫从一个主题转移到另一个主题:首先,我在 CCanvas 类的基础上创建了对象,然后开始创建图形对象,因为将图形对象引入所有函数库对象的计划,至少要求包括标准图形对象的部分操作工作功能。 之后,我又回到了基于 CCanvas 的窗体对象,因为高级图形对象需要改进基于画布的类。 现在我们需要继续开发画布基础上的对象。

因此,在本文中,我将消除在同时处理画布上的扩展(和标准)图形对象和表单对象方面的明显缺陷,并修复在前一篇文章中执行的测试期间检测到的错误。 本文总结了函数库说明的这一部分。 下一篇文章将开启一个新的部分,在这一部分中,我着手开发画布基础上的图形对象,模拟在 MS Visual Studio 中的 Windows 窗体。 我需要这些对象才能继续开发扩展的标准图形对象,以及基于它们的复合对象。


改进库类

如果图表里因创建 GUI 元素而包含任何附于画布上的图形对象 — 元素、形状、窗口(尚未实现)或其它类似控件元素,安置在图表上的标准图形对象、以及基于它们的其它函数库对象(手动或编程),都会导致这些对象绘制在控件之上,这很不方便。 故此,我们需要开发一种机制来跟踪图表上的新图形对象,并将所有 GUI 元素移动到前景。 为了实现这一点,我们可以使用 ZOrder 图形对象属性(图形对象接收单击图表事件的优先级(CHARTEVENT_CLICK)。

以下是帮助信息:

创建对象时会设置默认的零值,但如有必要,可以增加优先级。 当应用一个对象覆盖另一个对象时,只有其中具有最高优先级的那个对象会将收到 CHARTEVENT_CLICK 事件。

但我将更广泛地使用此属性 — 此属性的值作为指示 GUI 元素相对于彼此、以及相对于其它图形对象的排列顺序。

在 \MQL5\Include\DoEasy\Defines.mqh 中,并在基于画布的图形对象整数型枚举里,加入新的属性,并增加整数型属性的数量,从 23 至 24:

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type
   CANV_ELEMENT_PROP_BELONG,                          // Graphical element affiliation
   CANV_ELEMENT_PROP_NUM,                             // Element index in the list
   CANV_ELEMENT_PROP_CHART_ID,                        // Chart ID
   CANV_ELEMENT_PROP_WND_NUM,                         // Chart subwindow index
   CANV_ELEMENT_PROP_COORD_X,                         // Form's X coordinate on the chart
   CANV_ELEMENT_PROP_COORD_Y,                         // Form's Y coordinate on the chart
   CANV_ELEMENT_PROP_WIDTH,                           // Element width
   CANV_ELEMENT_PROP_HEIGHT,                          // Element height
   CANV_ELEMENT_PROP_RIGHT,                           // Element right border
   CANV_ELEMENT_PROP_BOTTOM,                          // Element bottom border
   CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,                  // Active area offset from the left edge of the element
   CANV_ELEMENT_PROP_ACT_SHIFT_TOP,                   // Active area offset from the upper edge of the element
   CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,                 // Active area offset from the right edge of the element
   CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,                // Active area offset from the bottom edge of the element
   CANV_ELEMENT_PROP_MOVABLE,                         // Element moveability flag
   CANV_ELEMENT_PROP_ACTIVE,                          // Element activity flag
   CANV_ELEMENT_PROP_INTERACTION,                     // Flag of interaction with the outside environment
   CANV_ELEMENT_PROP_COORD_ACT_X,                     // X coordinate of the element active area
   CANV_ELEMENT_PROP_COORD_ACT_Y,                     // Y coordinate of the element active area
   CANV_ELEMENT_PROP_ACT_RIGHT,                       // Right border of the element active area
   CANV_ELEMENT_PROP_ACT_BOTTOM,                      // Bottom border of the element active area
   CANV_ELEMENT_PROP_ZORDER,                          // Priority of a graphical object for receiving the event of clicking on a chart
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (24)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


在基于画布的图形元素的可能排序标准列表中添加新属性

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical elements on the canvas    |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                         // Sort by graphical element type
   SORT_BY_CANV_ELEMENT_BELONG,                       // Sort by a graphical element affiliation
   SORT_BY_CANV_ELEMENT_NUM,                          // Sort by form index in the list
   SORT_BY_CANV_ELEMENT_CHART_ID,                     // Sort by chart ID
   SORT_BY_CANV_ELEMENT_WND_NUM,                      // Sort by chart window index
   SORT_BY_CANV_ELEMENT_COORD_X,                      // Sort by the element X coordinate on the chart
   SORT_BY_CANV_ELEMENT_COORD_Y,                      // Sort by the element Y coordinate on the chart
   SORT_BY_CANV_ELEMENT_WIDTH,                        // Sort by the element width
   SORT_BY_CANV_ELEMENT_HEIGHT,                       // Sort by the element height
   SORT_BY_CANV_ELEMENT_RIGHT,                        // Sort by the element right border
   SORT_BY_CANV_ELEMENT_BOTTOM,                       // Sort by the element bottom border
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT,               // Sort by the active area offset from the left edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP,                // Sort by the active area offset from the top edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT,              // Sort by the active area offset from the right edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM,             // Sort by the active area offset from the bottom edge of the element
   SORT_BY_CANV_ELEMENT_MOVABLE,                      // Sort by the element moveability flag
   SORT_BY_CANV_ELEMENT_ACTIVE,                       // Sort by the element activity flag
   SORT_BY_CANV_ELEMENT_INTERACTION,                  // Sort by the flag of interaction with the outside environment
   SORT_BY_CANV_ELEMENT_COORD_ACT_X,                  // Sort by X coordinate of the element active area
   SORT_BY_CANV_ELEMENT_COORD_ACT_Y,                  // Sort by Y coordinate of the element active area
   SORT_BY_CANV_ELEMENT_ACT_RIGHT,                    // Sort by the right border of the element active area
   SORT_BY_CANV_ELEMENT_ACT_BOTTOM,                   // Sort by the bottom border of the element active area
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Sort by the priority of a graphical object for receiving the event of clicking on a chart
//--- Sort by real properties

//--- Sort by string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Sort by the graphical resource name
  };
//+------------------------------------------------------------------+

现在,我们可以通过该属性在画布上选择和排序图形元素。


函数库中的所有图形对象都派生自函数库中所有图形对象的基准对象 — 基于画布的标准图形对象和图形元素。 在基准图形对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh 中,把处理 ZOrder 属性的方法变为虚拟:

//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
                          {
                           this.m_hidden=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set the priority of a graphical object for receiving the event of clicking on a chart 
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                          {
                           this.m_zorder=value;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set object visibility on all timeframes
   bool              SetVisible(const bool flag,const bool only_prop)   
                       {
                        long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
                          {
                           this.m_visible=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set visibility flags on timeframes specified as flags

...

//--- Return the values of class variables
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)        const { return this.m_type_element;       }
   ENUM_GRAPH_OBJ_BELONG   Belong(void)                  const { return this.m_belong;             }
   ENUM_GRAPH_OBJ_SPECIES  Species(void)                 const { return this.m_species;            }
   ENUM_OBJECT       TypeGraphObject(void)               const { return this.m_type_graph_obj;     }
   datetime          TimeCreate(void)                    const { return this.m_create_time;        }
   string            Name(void)                          const { return this.m_name;               }
   long              ChartID(void)                       const { return this.m_chart_id;           }
   long              ObjectID(void)                      const { return this.m_object_id;          }
   virtual long      Zorder(void)                        const { return this.m_zorder;             }
   int               SubWindow(void)                     const { return this.m_subwindow;          }
   int               ShiftY(void)                        const { return this.m_shift_y;            }
   int               VisibleOnTimeframes(void)           const { return this.m_timeframes_visible; }
   int               Digits(void)                        const { return this.m_digits;             }
   int               Group(void)                         const { return this.m_group;              }
   bool              IsBack(void)                        const { return this.m_back;               }
   bool              IsSelected(void)                    const { return this.m_selected;           }
   bool              IsSelectable(void)                  const { return this.m_selectable;         }
   bool              IsHidden(void)                      const { return this.m_hidden;             }
   bool              IsVisible(void)                     const { return this.m_visible;            }

//--- Return the graphical object type (ENUM_OBJECT) calculated from the object type (ENUM_OBJECT_DE_TYPE) passed to the method

所有函数库图形对象的基准对象类中都存在相同的方法。
这些方法也都是虚拟的

在抽象标准图形对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh 中:

//--- Background object
   bool              Back(void)                    const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);                          }
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagBack(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag);
                        return true;
                       }
//--- Priority of a graphical object for receiving the event of clicking on a chart
   virtual long      Zorder(void)                  const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0);                              }
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        if(!CGBaseObj::SetZorder(value,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value);
                        return true;
                       }
//--- Disable displaying the name of a graphical object in the terminal object list
   bool              Hidden(void)                  const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);                        }
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagHidden(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag);
                        return true;
                       }
//--- Object selection


在图形元素对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中,添加相同的虚拟方法:

//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true,false);                                    }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false,false);                                   }
   
//--- Priority of a graphical object for receiving the event of clicking on a chart
   virtual long      Zorder(void)                        const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                    }
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        if(!CGBaseObj::SetZorder(value,only_prop))
                           return false;
                        this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value);
                        return true;
                       }
//+------------------------------------------------------------------+
//| The methods of receiving raster data                             |
//+------------------------------------------------------------------+

由于图形元素对象类已经拥有保存和恢复其对象属性的结构,故我们应该为新的整数型属性添加字段:

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
private:
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type
      int            number;                                   // Element index in the list
      long           chart_id;                                 // Chart ID
      int            subwindow;                                // Chart subwindow index
      int            coord_x;                                  // Form's X coordinate on the chart
      int            coord_y;                                  // Form's Y coordinate on the chart
      int            width;                                    // Element width
      int            height;                                   // Element height
      int            edge_right;                               // Element right border
      int            edge_bottom;                              // Element bottom border
      int            act_shift_left;                           // Active area offset from the left edge of the element
      int            act_shift_top;                            // Active area offset from the top edge of the element
      int            act_shift_right;                          // Active area offset from the right edge of the element
      int            act_shift_bottom;                         // Active area offset from the bottom edge of the element
      uchar          opacity;                                  // Element opacity
      color          color_bg;                                 // Element background color
      bool           movable;                                  // Element moveability flag
      bool           active;                                   // Element activity flag
      bool           interaction;                              // Flag of interaction with the outside environment
      int            coord_act_x;                              // X coordinate of the element active area
      int            coord_act_y;                              // Y coordinate of the element active area
      int            coord_act_right;                          // Right border of the element active area
      int            coord_act_bottom;                         // Bottom border of the element active area
      long           zorder;                                   // Priority of a graphical object for receiving the event of clicking on a chart
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
     };
   SData             m_struct_obj;                             // Object structure


在创建对象结构的方法中,添加保存新对象属性

//+------------------------------------------------------------------+
//| Create the object structure                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                            // Element ID
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                        // Graphical element type
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                       // Eleemnt ID in the list
   this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID);                     // Chart ID
   this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM);                // Chart subwindow index
   this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X);                  // Form's X coordinate on the chart
   this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y);                  // Form's Y coordinate on the chart
   this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH);                      // Element width
   this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT);                    // Element height
   this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT);                 // Element right edge
   this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM);               // Element bottom edge
   this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);    // Active area offset from the left edge of the element
   this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);      // Active area offset from the top edge of the element
   this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);  // Active area offset from the right edge of the element
   this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element
   this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);                 // Element moveability flag
   this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);                   // Element activity flag
   this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION);         // Flag of interaction with the outside environment
   this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X);          // X coordinate of the element active area
   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);          // Y coordinate of the element active area
   this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT);        // Right border of the element active area
   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);      // Bottom border of the element active area
   this.m_struct_obj.color_bg=this.m_color_bg;                                                  // Element background color
   this.m_struct_obj.opacity=this.m_opacity;                                                    // Element opacity
   this.m_struct_obj.zorder=this.m_zorder;                                                      // Priority of a graphical object for receiving the on-chart mouse click event
//--- Save real properties

//--- Save string properties
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name
   //--- Save the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

从结构恢复对象的方法中,加入读取属性到对象中

//+------------------------------------------------------------------+
//| Create the object from the structure                             |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                 // Element ID
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                             // Graphical element type
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                            // Element index in the list
   this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id);                     // Chart ID
   this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow);                     // Chart subwindow index
   this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x);                       // Form's X coordinate on the chart
   this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y);                       // Form's Y coordinate on the chart
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width);                           // Element width
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height);                         // Element height
   this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right);                      // Element right edge
   this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom);                    // Element bottom edge
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left);         // Active area offset from the left edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top);           // Active area offset from the upper edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right);       // Active area offset from the right edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom);     // Active area offset from the bottom edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable);                       // Element moveability flag
   this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active);                         // Element activity flag
   this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction);               // Flag of interaction with the outside environment
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x);               // X coordinate of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y);               // Y coordinate of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right);             // Right border of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);           // Bottom border of the element active area
   this.m_color_bg=this.m_struct_obj.color_bg;                                                  // Element background color
   this.m_opacity=this.m_struct_obj.opacity;                                                    // Element opacity
   this.m_zorder=this.m_struct_obj.zorder;                                                      // Priority of a graphical object for receiving the on-chart mouse click event
//--- Save real properties

//--- Save string properties
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name
  }
//+------------------------------------------------------------------+

我们需要所有这些来正确地将对象保存到磁盘,并在将来还原函数库对象时从磁盘文件读取。 这是必要的,如此才能在终端重新启动后,函数库可以恢复程序的状态及其数据,进而恢复正常工作。 但所有这些都将在稍后实现。 与此同时,我们简单地准备必要的功能。


我们回到抽象标准图形对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh

鉴于将来从扩展的标准图形对象构建复合图形对象时,我们需要知道整个对象的最小和最大坐标,现在是时候来添加查找和返回标准图形对象的最小和最大 X 和 Y 坐标值的方法了。 稍后,我们就能够基于这些方法获得整个组合图形对象的最小/最大坐标。

在类的公开部分声明两个新方法

//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects
   CArrayObj        *GetListDependentObj(void)        { return &this.m_list;           }
   CGStdGraphObj    *GetDependentObj(const int index) { return this.m_list.At(index);  }
   int               GetNumDependentObj(void)         { return this.m_list.Total();    }
//--- Return the name of the dependent object by index
   string            NameDependent(const int index);
//--- Add the dependent graphical object to the list
   bool              AddDependentObj(CGStdGraphObj *obj);
//--- Change X and Y coordinates of the current and all dependent objects
   bool              ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false);
//--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object
   bool              SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj);

//--- Return (1) the minimum and (2) maximum XY screen coordinate of the specified pivot point
   bool              GetMinCoordXY(int &x,int &y);
   bool              GetMaxCoordXY(int &x,int &y);
   
//--- Return the pivot point data object
   CLinkedPivotPoint*GetLinkedPivotPoint(void)        { return &this.m_linked_pivots;  }

我们在类主体之外编写它们的实现。

该方法返回所有轴点的最小 XY 屏幕坐标:

//+------------------------------------------------------------------+
//| Return the minimum XY screen coordinate                          |
//| from all pivot points                                            |
//+------------------------------------------------------------------+
bool CGStdGraphObj::GetMinCoordXY(int &x,int &y)
  {
//--- Declare the variables storing the minimum time and maximum price
   datetime time_min=LONG_MAX;
   double   price_max=0;
//--- Depending on the object type
   switch(this.TypeGraphObject())
     {
      //--- Objects constructed based on screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
         //--- Write XY screen coordinates to x and y variables and return 'true'
         x=this.XDistance();
         y=this.YDistance();
         return true;
      
      //--- Objects constructed in time/price coordinates
      default                    :
         //--- In the loop by all pivot points
         for(int i=0;i<this.Pivots();i++)
           {
            //--- Define the minimum time
            if(this.Time(i)<time_min)
               time_min=this.Time(i);
            //--- Define the maximum price
            if(this.Price(i)>price_max)
               price_max=this.Price(i);
           }
         //--- Return the result of converting time/price coordinates into XY screen coordinates
         return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_min,price_max,x,y) ? true : false);
     }
   return false;
  }
//+------------------------------------------------------------------+

无需搜索依据屏幕坐标构建的对象的最大/最小坐标,因为此类对象只拥有单个图表定位点,并且已经作为屏幕坐标 — 我们只是简单地返回它们即可。
相反,对于使用时间/价格坐标构建的对象,我们需要通过 ChartTimePriceToXY() 函数获取这些坐标。 但首先必须从对象的所有轴心点找到最小时间坐标,并在相同轴心点找出最大价格坐标。 如果我们需要的是图表上的最小坐标,为什么我们要寻找最大的价格坐标? 答案很简单:图表上的价格从下到上递增,而图表上以像素为单位的坐标是从左上角开始计算的。 因此,价格越高,该价位的像素坐标值越小。
在遍历所有轴心点的循环中,找到最小时间和最大价格,最终我们将其传递给 ChartTimePriceToXY() 函数,来获取以图表像素为单位的坐标。

类似的方法用于从所有轴点获取最大屏幕 XY 坐标:

//+------------------------------------------------------------------+
//| Return the maximum XY screen coordinate                          |
//| from all pivot points                                            |
//+------------------------------------------------------------------+
bool CGStdGraphObj::GetMaxCoordXY(int &x,int &y)
  {
//--- Declare the variables that store the maximum time and minimum price
   datetime time_max=0;
   double   price_min=DBL_MAX;
//--- Depending on the object type
   switch(this.TypeGraphObject())
     {
      //--- Objects constructed based on screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
         //--- Write XY screen coordinates to x and y variables and return 'true'
         x=this.XDistance();
         y=this.YDistance();
         return true;
      
      //--- Objects constructed in time/price coordinates
      default                    :
         //--- In the loop by all pivot points
         for(int i=0;i<this.Pivots();i++)
           {
            //--- Define the maximum time
            if(this.Time(i)>time_max)
               time_max=this.Time(i);
            //--- Define the maximum price
            if(this.Price(i)<price_min)
               price_min=this.Price(i);
           }
         //--- Return the result of converting time/price coordinates into XY screen coordinates
         return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_max,price_min,x,y) ? true : false);
     }
   return false;
  }
//+------------------------------------------------------------------+

该方法与前一种方法类似,但在此我们寻找最大时间和最低价格。

正如我曾提到的,稍后在处理复合图形对象时,我将需要这些方法。


在扩展标准图形对象工具包类 \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh 中,稍微调整返回屏幕坐标中图形对象指定轴点的 X 和 Y 坐标的方法。 其原因是,我们需要通过 “switch” 操作符的 "case" 来分派图形对象,从而每种情况都包含由时间/价格坐标构建的拥有若干对象轴心点数量的图形对象。 我们按正确的顺序来分派它们:

//+------------------------------------------------------------------+
//| 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)
  {
//--- Declare form objects, from which we are to receive their screen coordinates
   CFormControl *form0=NULL, *form1=NULL;
//--- Set X and Y to zero - these values will be received in case of a failure
   x=0;
   y=0;
//--- Depending on the graphical object type
   switch(this.m_base_type)
     {
      //--- Objects drawn using screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        //--- Write object screen coordinates and return 'true'
        x=this.m_base_x;
        y=this.m_base_y;
        return true;
      
      //--- One pivot point (price only)
      case OBJ_HLINE             : break;
      
      //--- One pivot point (time only)
      case OBJ_VLINE             :
      case OBJ_EVENT             : break;
      
      //--- One reference point (time/price)
      case OBJ_TEXT              :
      case OBJ_BITMAP            : break;
      
      //--- Two pivot points and a central one
      //--- Lines
      case OBJ_TREND             :
      case OBJ_TRENDBYANGLE      :
      case OBJ_CYCLES            :
      case OBJ_ARROWED_LINE      :
      //--- Channels
      case OBJ_CHANNEL           :
      case OBJ_STDDEVCHANNEL     :
      case OBJ_REGRESSION        :
      //--- Gann
      case OBJ_GANNLINE          :
      case OBJ_GANNGRID          :
      //--- Fibo
      case OBJ_FIBO              :
      case OBJ_FIBOTIMES         :
      case OBJ_FIBOFAN           :
      case OBJ_FIBOARC           :
      case OBJ_FIBOCHANNEL       :
      case OBJ_EXPANSION         :
        //--- Calculate coordinates for forms on the line pivot points
        if(index<this.m_base_pivots)
           return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false);
        //--- Calculate the coordinates for the central form located between the line pivot points
        else
          {
           form0=this.GetControlPointForm(0);
           form1=this.GetControlPointForm(1);
           if(form0==NULL || form1==NULL)
              return false;
           x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2;
           y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2;
           return true;
          }

      //--- Channels
      case OBJ_PITCHFORK         : break;
      
      //--- Gann
      case OBJ_GANNFAN           : break;
      
      //--- Elliott
      case OBJ_ELLIOTWAVE5       : break;
      case OBJ_ELLIOTWAVE3       : break;
      
      //--- Shapes
      case OBJ_RECTANGLE         : break;
      case OBJ_TRIANGLE          : break;
      case OBJ_ELLIPSE           : break;
      
      //--- Arrows
      case OBJ_ARROW_THUMB_UP    : break;
      case OBJ_ARROW_THUMB_DOWN  : break;
      case OBJ_ARROW_UP          : break;
      case OBJ_ARROW_DOWN        : break;
      case OBJ_ARROW_STOP        : break;
      case OBJ_ARROW_CHECK       : break;
      case OBJ_ARROW_LEFT_PRICE  : break;
      case OBJ_ARROW_RIGHT_PRICE : break;
      case OBJ_ARROW_BUY         : break;
      case OBJ_ARROW_SELL        : break;
      case OBJ_ARROW             : break;
      
      //---
      default                    : break;
     }
   return false;
  }
//+------------------------------------------------------------------+

最初在屏幕坐标中绘制的对象只返回其基准对象(它们所附着对象)的屏幕坐标。

实现了返回有两个轴心点屏幕坐标的对象。 计算所有对象轴点和中心点的屏幕坐标。

剩下的没有这类功能的对象则被简单地划分到它们的组一些对象是按类型而不是按轴心点的数量进行分组的,我将在后面实现它们)。


现在,我们需要确保图表上的所有 GUI 控件始终位于新加入到图表里的任何图形对象之上。 甚至,我们需要令 GUI 元素按照相同的顺序排列,就像图形对象被加到图表之前一样。

为了将图形对象带至前景,我们需要先将其隐藏,再按顺序重新显示。 这是经由重置然后安装图形对象可见性标志来完成的。 为了隐藏对象,我们需要调用 ObjectSetInteger() 函数为 OBJPROP_TIMEFRAMES 属性设置 OBJ_NO_PERIODS 标志。 OBJ_ALL_PERIODS 标志则是显示对象。 如果我们按图形元素集合列表中的顺序对所有 GUI 对象执行此操作,那么我们会丢失它们在图表中的位置顺序。 换言之,这种排列方式将令列表中排第一个的对象在图表上最低,而最后一个则是最高的。 但这些对象的顺序可能完全不同,在重画时会发生变化。 在此,我们来看看我们今天添加的新属性 — ZOrder。 我们需要按照 ZOrder 属性的升序针对图形对象列表进行排序。 然后将按正确的顺序重新绘制对象。

打开图形元素集合类文件 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh,并针对它进行所有必要的改进。

在类的私密部分声明三个新方法

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

private:
//--- Move all objects on canvas to the foreground
   void              BringToTopAllCanvElm(void);
//--- (1) Return the maximum ZOrder of all CCanvas elements,
//--- (2) Set ZOrde to the specified element and adjust it in all the remaining elements
   long              GetZOrderMax(void);
   bool              SetZOrderMAX(CGCnvElement *obj);
//--- Handle the removal of extended graphical objects
   void              DeleteExtendedObj(CGStdGraphObj *obj);

我们在类主体之外编写它们的实现。

该方法将画布上的所有对象移动至前景:

//+------------------------------------------------------------------+
//| Move all objects on canvas to the foreground                     |
//+------------------------------------------------------------------+
void CGraphElementsCollection::BringToTopAllCanvElm(void)
  {
//--- Create a new list, reset the flag of managing memory and sort by ZOrder
   CArrayObj *list=new CArrayObj();
   list.FreeMode(false);
   list.Sort(SORT_BY_CANV_ELEMENT_ZORDER);
//--- Declare the graphical element object
   CGCnvElement *obj=NULL;
//--- In the loop by the total number of all graphical elements,
   for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++)
     {
      //--- get the next object
      obj=this.m_list_all_canv_elm_obj.At(i);
      if(obj==NULL)
         continue;
      //--- and insert it to the list in the order of sorting by ZOrder
      list.InsertSort(obj);
     }
//--- In a loop by the created list sorted by ZOrder,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next object
      obj=list.At(i);
      if(obj==NULL)
         continue;
      //--- and move it to the foreground
      obj.BringToTop();
     }
//--- Remove the list sorted by 'new'
   delete list;
  }
//+------------------------------------------------------------------+

故我们在这里能看到什么。 我们声明新的 CArrayObj 列表对象,并重置其内存管理标志。 接下来,为排序标志列表设置 ZOrder 属性。 我们为什么要这样做? 这是为了能够按任何属性对 CObject 对象进行排序,我们需要将属性值设置为排序标志。 在这种情况下,从 CObject 类派生的所有对象里需实现 virtual Compare() 方法,其返回两个对象的类似属性值(设置为排序模式的属性)的比较结果,而这是 Sort() 方法操作所必需的。

需要重置内存管理标志,如此我们最终就可从位于所创建数组中的集合列表中获取对象的副本,而非指向集合对象的指针。 这一点很重要,因为如果我们处理指针,那么在新列表中对它们的任何更改都将自动修改集合列表中的对象,因为这些指针指向同一集合列表的对象,虽然它们分处两个列表。 我正在创建一个独立的列表副本,更改它不会影响原件,从而避免此类“讶异”。 一旦该方法完成后,我们应该删除所创建列表,从而避免内存泄漏。 标准库的帮助(FreeMode)中也提到了这一点:

使用 CArrayObj 类时,设置内存管理标志是其中一个重要部分。 由于数组元素是指向动态对象的指针,因此从数组中删除时判定如何处理它们很重要。

如果设置了该标志,则从数组中删除元素时,该元素将由 delete 操作符自动删除。 如果未设置该标志,则假定指向已删除对象的指针仍在用户程序中的某个位置,并将在之后由程序释放。

如果用户程序重置内存管理标志,则用户必须在程序终止之前负有将其从数组里删除的责任。 否则,不会释放由 new 操作符为元素分配的内存。

对于大量数据,这最终会导致终端崩溃。 如果用户不重置内存管理标志,则还存在另一个陷阱。

当数组中的指针元素存储在局部变量的某个位置时,删除数组将导致严重错误,且令用户程序崩溃。 默认情况下,会设置内存管理标志,即由类负责释放数组的内存元素。

接下来,我们循环遍历图形元素的集合列表,并取下一个对象按排序顺序插入到新列表之中

直至循环完成后,我们将按照所有图形元素 ZOrder 属性的升序重新排序新列表。
在遍历列表的循环中,获取下一个元素并将其置于前景。 随后的每一个都将始终高于前一个。
直至循环完成后,确保删除新创建的列表

该方法返回所有 CCanvas 元素的最大 ZOrder:

//+------------------------------------------------------------------+
//| Return the maximum ZOrder of all CCanvas elements                |
//+------------------------------------------------------------------+
long CGraphElementsCollection::GetZOrderMax(void)
  {
   this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ZORDER);
   int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ZORDER);
   CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index);
   return(obj!=NULL ? obj.Zorder() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

在此,我们按照 ZOrder 属性对列表进行排序,并从列表中获得属性值最大的元素索引
依据所得的索引获取指向元素的指针,并返回元素的 ZOrder 属性
如果尚未收到元素,则该方法返回 -1

例如,如果我们有三个 GUI 对象,则它们有三个 ZOrder 值是合理的。 所有对象最初的 ZOrder 值为零 — 它们都位于最底层。 一旦我们捕获任何对象,它的 ZOrder 就会增加1。 但首先,我们应该看看是否有任何其它对象的 ZOrder 值等于或超过 1,因为最后选定的对象应该比所有其它对象的 ZOrder 值高出 1。 当然,我们可以得到最大 ZOrder,然后简单地将其加 1。 但这并非最优雅的解决方案。 取而代之,我会这样做,在这三个对象中,我们的 ZOrder 值只能在 0 - 2 范围内。

因此,对于最后一个选定的对象,我们需要将 ZOrder 增加1,或者将其保持尽可能高(从零开始的所有对象的数量),而将其余的 ZOrder 减 1。 与此同时,如果对象位于最底层,并且其 ZOrder 已经为零,则我们就不必再减少它。 因此,ZOrder 值的变更是根据 GUI 对象的数量“循环”完成的。

该方法为指定元素设置 ZOrder,并在其它元素中调整它:

//+------------------------------------------------------------------+
//| Set ZOrde to the specified element                               |
//| and adjust it in other elements                                  |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj)
  {
//--- Get the maximum ZOrder of all graphical elements
   long max=this.GetZOrderMax();
//--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false'
   if(obj==NULL || max<0)
      return false;
//--- Declare the variable for storing the method result
   bool res=true;
//--- If the maximum ZOrder is zero, ZOrder is equal to 1,
//--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1,
//--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1
   long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1);
//--- If failed to set ZOrder for an object passed to the method, return 'false'
   if(!obj.SetZorder(value,false))
      return false;
//--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder
   CForm *form=obj;
//--- and draw a text specifying ZOrder on the form
   form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
//--- Sort the list of graphical elements by an element ID
   this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
//--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL);
//--- If failed to obtain the list and the list size exceeds one,
//--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false'
   if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1)
      return false;
//--- In the loop by the obtained list of remaining graphical element objects
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next object
      CGCnvElement *elm=list.At(i);
      //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object
      //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one
      if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0)
         continue;
      //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value
      if(!elm.SetZorder(elm.Zorder()-1,false))
         res &=false;
      //--- Temporarily (for the test purpose), if the element is a form,
      if(elm.Type()==OBJECT_DE_TYPE_GFORM)
        {
         //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form 
         form=elm;
         form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
        }
     }
//--- Upon the loop completion, return the result set in 'res'
   return res;
  }
//+------------------------------------------------------------------+

每段代码都附带了注释,故可澄清方法的逻辑。 出于测试目的,添加了在窗体上显示文本及其 ZOrder 值的代码,以便我们可以在测试期间直观地看到每个窗体对象属性的变化。

在两种情况下,我们需要将 GUI 对象移动到新构建的图形对象之上:

  1. 手动将标准图形对象添加到图表之中,
  2. 以编程方式添加图形对象(标准或扩展复合对象)。

这些情况均可由函数库独立处理。
因此,我们需要添加一个方法调用,该方法可把 GUI 对象提升到不同位置的更高级别。

当以编程方式创建图形对象时,我们可以在私密方法中直接调用重绘 GUI 对象,该方法会把所创建标准图形对象添加到列表当中。 这正是创建图形对象成功与否的最终决定因素,我们可以添加一个方法调用,将所有 GUI 对象移动到前景,就在末尾:

//--- Add a newly created standard graphical object to the list
   bool              AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj)
                       {

                        if(!this.m_list_all_graph_obj.Add(obj))
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           ::ObjectDelete(chart_id,name);
                           delete obj;
                           return false;
                          }
                        //--- Move all the forms above the created chart object and redraw the chart
                        this.BringToTopAllCanvElm();
                        ::ChartRedraw(chart_id);
                        return true;
                       }

调用该方法后,我们需要更新图表,从而立即显示变化。


如果是手动声明图形对象,则在更新所有图形对象列表的方法中定义此事件。

在添加新图形对象的代码模块中,加入把 GUI 对象移动到前景台的方法调用:

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


在执行前一篇文章中的测试时,我发现复合图形对象的坐标并不总是正确地受限于图表边界。 考虑到对象轴心点的不同位置,对象在到达图表边缘时可能会“变形”。
我在测试后立即明晰了原因所在:

... 轴心点位移是相对于中心点进行计算的,这意味着有一个点将发生正位移,而第二个点将发生负位移。 当改变轴点相对于中心点的位置时,我们会面临有限的计算误差。

我们来进行必要的修改,从而修复此问题。 我们将立即计算中心点坐标,以及从图形对象轴心点到其中心点的位移。 按图表大小计算界限时,使用无符号的移位值。 在这种情况下,位移的符号不会影响计算。

在处理扩展标准图形对象窗体的事件处理程序模块中,添加新的界限计算,并在图表上显示调试数据

                           //--- If the form is central for managing all pivot points of a graphical object
                           else
                             {
                              //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure
                              if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point))
                                {
                                 //--- In the loop by the number of object pivot points,
                                 for(int i=0;i<(int)this.m_data_pivot_point.Size();i++)
                                   {
                                    //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders
                                    //--- By X coordinate
                                    if(x+shift-::fabs(this.m_data_pivot_point[i].ShiftX)<0)
                                       x=-shift+::fabs(m_data_pivot_point[i].ShiftX);
                                    if(x+shift+::fabs(this.m_data_pivot_point[i].ShiftX)>chart_width)
                                       x=chart_width-shift-::fabs(this.m_data_pivot_point[i].ShiftX);
                                    //--- By Y coordinate
                                    if(y+shift-::fabs(this.m_data_pivot_point[i].ShiftY)<0)
                                       y=-shift+::fabs(this.m_data_pivot_point[i].ShiftY);
                                    if(y+shift+::fabs(this.m_data_pivot_point[i].ShiftY)>chart_height)
                                       y=chart_height-shift-::fabs(this.m_data_pivot_point[i].ShiftY);
                                    //--- set the calculated coordinates to the current object pivot point
                                    ext.ChangeCoordsExtendedObj(x+shift+this.m_data_pivot_point[i].ShiftX,y+shift+this.m_data_pivot_point[i].ShiftY,i);
                                   }
                                }
                                
                              //--- Display debugging comments on the chart
                              if(m_data_pivot_point.Size()>=2)
                                {
                                 int max_x,min_x,max_y,min_y;
                                 if(ext.GetMinCoordXY(min_x,min_y) && ext.GetMaxCoordXY(max_x,max_y))
                                    Comment
                                      (
                                       "MinX=",min_x,", MaxX=",max_x,"\n",
                                       "MaxY=",min_y,", MaxY=",max_y
                                      );
                                }
                                
                             }


一旦单击 GUI 对象,我们就需要将其移动到前景,并为其设置最大 ZOrder。

在激活区域内按下鼠标按钮后,处理光标的事件处理程序中,添加为对象设置最大 ZOrder 的代码模具哎:

            //--- 'The cursor is inside the active area,  any mouse button is clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move)
              {
               pressed_form=true;   // the flag of holding the mouse button on the form
               //--- If the left mouse button is pressed
               if(this.m_mouse.IsPressedButtonLeft())
                 {
                  //--- Set flags and form parameters
                  move=true;                                            // movement flag
                  form.SetInteraction(true);                            // flag of the form interaction with the environment
                  form.BringToTop();                                    // form on the background - above all others
                  form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate
                  form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  
                  //--- Get the maximum ZOrder
                  long zmax=this.GetZOrderMax();
                  //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero
                  if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0))
                    {
                     //--- If the form is not a control point for managing an extended standard graphical object,
                     //--- set the form's ZOrder above all others
                     if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL)
                        this.SetZOrderMAX(form);
                    }
                 }
              }

此处,我为非扩展标准图形对象控件的窗体对象设置 ZOrder。

图形元素集合类的事件处理程序非常庞大。 故此,在这里提供其全部代码是没有意义的。 您可以在附件中找到所有更改(如上所述)。

我们在返回每个图形对象轴心点屏幕坐标的方法中修改位移计算。
现在,我们将立即计算中心轴点的坐标(用于移动对象),并基于该点计算位移。 更准确地说,我们会计算从每个轴点到中心点的位移

//+------------------------------------------------------------------+
//| Return screen coordinates                                        |
//| of each graphical object pivot point                             |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[])
  {
//--- Central point coordinates
   int xc=0, yc=0;
//--- If failed to increase the array of structures to match the number of pivot points,
//--- inform of that in the journal and return 'false'
   if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots())
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      return false;
     }
//--- In the loop by the number of graphical object pivot points
   for(int i=0;i<obj.Pivots();i++)
     {
      //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false'
      if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y))
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY);
         return false;
        }
     }
//--- Depending on the graphical object type
   switch(obj.TypeGraphObject())
     {
      //--- One pivot point in screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             : break;
      
      //--- One pivot point (price only)
      case OBJ_HLINE             : break;
      
      //--- One pivot point (time only)
      case OBJ_VLINE             :
      case OBJ_EVENT             : break;
      
      //--- Two pivot points and a central one
      //--- Lines
      case OBJ_TREND             :
      case OBJ_TRENDBYANGLE      :
      case OBJ_CYCLES            :
      case OBJ_ARROWED_LINE      :
      //--- Channels
      case OBJ_CHANNEL           :
      case OBJ_STDDEVCHANNEL     :
      case OBJ_REGRESSION        :
      //--- Gann
      case OBJ_GANNLINE          :
      case OBJ_GANNGRID          :
      //--- Fibo
      case OBJ_FIBO              :
      case OBJ_FIBOTIMES         :
      case OBJ_FIBOFAN           :
      case OBJ_FIBOARC           :
      case OBJ_FIBOCHANNEL       :
      case OBJ_EXPANSION         :
        //--- Calculate the central point coordinates
        xc=(array_pivots[0].X+array_pivots[1].X)/2;
        yc=(array_pivots[0].Y+array_pivots[1].Y)/2;
        //--- Calculate the shifts of all pivot points from the central one and write them to the structure array
        array_pivots[0].ShiftX=array_pivots[0].X-xc;  // X shift from 0 to the central point
        array_pivots[1].ShiftX=array_pivots[1].X-xc;  // X shift from 1 to the central point
        array_pivots[0].ShiftY=array_pivots[0].Y-yc;  // Y shift from 0 to the central point
        array_pivots[1].ShiftY=array_pivots[1].Y-yc;  // Y shift from 1 to the central point
        return true;

      //--- Channels
      case OBJ_PITCHFORK         : break;
      //--- Gann
      case OBJ_GANNFAN           : break;
      //--- Elliott
      case OBJ_ELLIOTWAVE5       : break;
      case OBJ_ELLIOTWAVE3       : break;
      //--- Shapes
      case OBJ_RECTANGLE         : break;
      case OBJ_TRIANGLE          : break;
      case OBJ_ELLIPSE           : break;
      //--- Arrows
      case OBJ_ARROW_THUMB_UP    : break;
      case OBJ_ARROW_THUMB_DOWN  : break;
      case OBJ_ARROW_UP          : break;
      case OBJ_ARROW_DOWN        : break;
      case OBJ_ARROW_STOP        : break;
      case OBJ_ARROW_CHECK       : break;
      case OBJ_ARROW_LEFT_PRICE  : break;
      case OBJ_ARROW_RIGHT_PRICE : break;
      case OBJ_ARROW_BUY         : break;
      case OBJ_ARROW_SELL        : break;
      case OBJ_ARROW             : break;
      
      //--- Graphical objects with time/price coordinates
      case OBJ_TEXT              : break;
      case OBJ_BITMAP            : break;
      //---
      default: break;
     }
   return false;
  }
//+------------------------------------------------------------------+

所有位移和界限现在都应该可以正确计算。 我们来进行测试,验证这一点。


测试

为了执行测试,我们延用来自前一篇文章里的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part100\, 当中,命名为 TestDoEasyPart100.mq5

由于我们希望在测试期间立即看到每个窗体上的 ZOrder 属性值,因此在 OnInit() 处理程序中,添加为窗体属性设置零值(最底层部的对象),并添加显示窗体对象 ID及其 ZOrder 属性值

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- 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
//--- Create form objects
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      //--- When creating an object, pass all the required parameters to it
      form=new CForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30);
      if(form==NULL)
         continue;
      //--- Set activity and moveability flags for the form
      form.SetActive(true);
      form.SetMovable(true);
      //--- Set the form ID and the index in the list of objects
      form.SetID(i);
      form.SetNumber(0);   // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them
      //--- Set the opacity of 200
      form.SetOpacity(245);
      //--- 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(clrDarkBlue);
      //--- Draw the shadow drawing flag
      form.SetShadow(false);
      //--- 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(3,3,clr,200,4);
      //--- Fill the form background with a vertical gradient
      form.Erase(array_clr,form.Opacity(),true);
      //--- Draw an outlining rectangle at the edges of the form
      form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
      form.Done();
      
      //--- Set ZOrder to zero, display the text describing the gradient type and update the form
      //--- Text parameters: the text coordinates and the anchor point in the form center
      //--- Create a new text animation frame with the ID of 0 and display the text on the form
      form.SetZorder(0,false);
      form.TextOnBG(0,TextByLanguage("Тест: ID ","Test: ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
      //--- Add the form to the list
      if(!engine.GraphAddCanvElmToCollection(form))
         delete form;
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


编译,并在图表上启动 EA:


正如我们所见,每个窗体对象的 ZOrder 值在创建后立即等于零,但图形对象仍然是“在它们下面”构建的。 每个对象的 ZOrder 值的变更是“在一个循环中”执行的 — 不超过图表上窗体对象的数量(从零开始计算)。 任何构造的图形对象始终显示在 GUI 对象的“下方”,其相对位置保持不变,这意味着它们根据其 ZOrder 属性的值在列表中正确派发。 最后,复合图形对象现在被正确地限定在屏幕的边缘,并且无论其轴点的位置如何,都不会扭曲。 与其它图形对象一样,它绘制在窗体对象下方。

下一步是什么?

下一篇文章将开始一个关于以 Windows 窗体样式创建 GUI 对象的大章节。
然而,这并不意味着不会进一步开发扩展图形对象和基于它们的复合对象。 我们只需要对它们的进一步开发进行全面的控制。 随着函数库下一部分的进展,我将继续逐步开发和完善扩展图形对象。

以下是 MQL5 的当前函数库版本、测试 EA,和图表事件控制指标的所有文件,供您测试和下载。 在评论中留下您的问题、意见和建议。

返回内容目录

*该系列的前几篇文章:

DoEasy 函数库中的图形(第九十三部分):准备创建复合图形对象的功能
DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象
DoEasy 函数库中的图形(第九十五部分):复合图形对象控件
DoEasy 函数库中的图形(第九十六部分):窗体对象中的图形和鼠标事件的处理
DoEasy 函数库中的图形(第九十七部分):独立处理窗体对象移动
DoEasy 函数库中的图形(第九十八部分):移动扩展的标准图形对象的轴点
DoEasy 函数库中的图形(第九十九部分):依据单个控制点移动扩展图形对象

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10634

附加的文件 |
MQL5.zip (4375.67 KB)
从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I) 从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I)
这是目前最强力的指标之一。 任何满怀信心尝试交易的人都必须在他们的图表上拥有这个指标。 最常用的指标都是那些喜欢在交易时“读磁带”的人所采用。 此外,而该指标则是那些交易时仅依据价格动作的人会采用。
一张图表上的多个指标(第 06 部分):将 MetaTrader 5 转变为 RAD 系统(II) 一张图表上的多个指标(第 06 部分):将 MetaTrader 5 转变为 RAD 系统(II)
在我的前一篇文章中,我向您展示了如何利用 MetaTrader 5 对象创建图表交易,从而将平台转变为 RAD 系统。 该系统运行良好,可以肯定的是,许多读者也许已经考虑过创建一个函数库,令其能够在拟议的系统中扩展功能。 有基于此,就有可能开发一款更直观的智能交易系统,其界面更友好、更易于使用。
DoEasy. 控件 (第 1 部分): 第一步 DoEasy. 控件 (第 1 部分): 第一步
本文开始延展话题,介绍如何利用 MQL5 仿照 Windows 窗体样式创建控件。 我感兴趣的第一个对象是创建面板(panel)类。 若是没有控件,那么管理就会变得越来越困难。 因此,我将仿照 Windows 窗体样式创建所有可能的控件。
数据科学与机器学习(第 01 部分):线性回归 数据科学与机器学习(第 01 部分):线性回归
我们作为交易员,现在是时候基于数字所言来培训我们的系统,并自行制定决策了。 尽管我们的眼睛看不到,但我们的勇气让我们相信,这是世界前进的方向,所以,让我们顶着波浪的方向移动。