English Русский Español Deutsch 日本語 Português
preview
DoEasy 函数库中的图形(第九十八部分):移动扩展的标准图形对象的轴点

DoEasy 函数库中的图形(第九十八部分):移动扩展的标准图形对象的轴点

MetaTrader 5示例 | 9 五月 2022, 10:25
948 0
Artyom Trishkin
Artyom Trishkin

内容


概述

文章第九十三部分,我启动了针对函数库里复合图形对象的开发。 然后,基于CCanvas 类改进必要的窗体对象功能分散了我的大部分注意力。 我们用复合图形对象中的窗体对象来创建包含在扩展的标准图形对象中的图形对象的轴点控件。 故此,我们就需要新的窗体对象功能。

在上一篇文章中,我基于 CCanvas 类完成了对象改进。 今天,我将继续开发扩展的标准图形对象,即复合图形对象。

本文的目标不是开发新类。 取而代之,我将研究改进已准备好的功能,从而为重新定位标准图形对象轴心点创建便捷的工具。 本文将讲述一个复合图形对象原型的开发。 我们在之前的文章中已经创建了它。 这是一条普通的趋势线,末端有额外的价格标签对象:


今天,我将讨论移动趋势线轴点的问题,以及重新定位趋势线一侧的价格标签。 该线的轴点包含需要重新定位的窗体对象。 通过抓取和移动对象,我们也会移动相应趋势线的一端。 当光标远离趋势线轴心点时,窗体对象仍不可见。 不过,当光标接近轴心点一定距离时(进入完全透明窗体的区域),窗体上会出现带圆圈的点:


这就是趋势线轴心点控件在图表上的显示方式。 窗体的尺寸大于其活动区域。 窗体的活动区域是可以拖动并移动窗体的区域。 相比之下,窗体本身的区域可以用来安排其它类型的交互,如鼠标按钮和鼠标滚轮。

因此,如果鼠标光标位于窗体内,但在其活动区域之外,我们就可以实现,例如,在按下鼠标右键时激活复合图形对象的关联菜单。 如果光标位于活动区域之内,那么,除了关联菜单之外,我们还可以用鼠标抓取该窗体,并移动它。 在这种情况下,附着在窗体里的线端点也会随之移动。

当然,这只是一个尝试复合图形对象新功能的测试。 在创建了创建复合图形对象和处理窗体对象所需的所有工具之后,我将创建一组用于开发自定义对象的标准库复合图形对象。 创造它们是作为一个示例,并讲述如何创建自己的这类对象。

目前,我只是在开发函数库的功能,并为制作自定义对象“添砖加瓦”。 在我的文章中讲述、分析和实现的步骤,可作为“按原样”使用的基础,而无需从头开始实现一切。


改进库类

打开 \MQL5\Include\DoEasy\Defines.mqh,并实现一些修改。

轴心点控件窗体包含一个点和一个圆圈。 所应用颜色之前已在代码中正确指定。 我们添加含有默认颜色的宏替换

//--- Pending request type IDs
#define PENDING_REQUEST_ID_TYPE_ERR    (1)                        // Type of a pending request created based on the server return code
#define PENDING_REQUEST_ID_TYPE_REQ    (2)                        // Type of a pending request created by request
//--- Timeseries parameters
#define SERIES_DEFAULT_BARS_COUNT      (1000)                     // Required default amount of timeseries data
#define PAUSE_FOR_SYNC_ATTEMPTS        (16)                       // Amount of pause milliseconds between synchronization attempts
#define ATTEMPTS_FOR_SYNC              (5)                        // Number of attempts to receive synchronization with the server
//--- Tick series parameters
#define TICKSERIES_DEFAULT_DAYS_COUNT  (1)                        // Required number of days for tick data in default series
#define TICKSERIES_MAX_DATA_TOTAL      (200000)                   // Maximum number of stored tick data of a single symbol
//--- Parameters of the DOM snapshot series
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // The default required number of days for DOM snapshots in the series
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Maximum number of stored DOM snapshots of a single symbol
//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
//--- Graphical object parameters
#define PROGRAM_OBJ_MAX_ID             (10000)                    // Maximum value of an ID of a graphical object belonging to a program
#define CTRL_POINT_RADIUS              (5)                        // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_POINT_COLOR               (clrDodgerBlue)            // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_FORM_SIZE                 (40)                       // Size of the control point form for managing graphical object pivot points
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+

将 CTRL_POINT_SIZE 宏替换重命名为 CTRL_POINT_RADIUS,因为这不是圆圈的完整尺寸,而是圆圈的半径。 在计算窗体对象的活动区域时,宏替换名称有点误导。

在图形元素对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中,稍微改进创建图形元素对象的方法。 不幸的是,在调用 CCanvas 类的 CreateBitmapLabel() 方法时,没有返回错误代码。 因此,在调用方法之前重置最后一个错误代码。 如果无法创建图形标签,在日志中会显示该错误代码。 这略微改善了调试。

//+------------------------------------------------------------------+
//| Create the graphical element object                              |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Chart ID
                          const int wnd_num,       // Chart subwindow
                          const string name,       // Element name
                          const int x,             // X coordinate
                          const int y,             // Y coordinate
                          const int w,             // Width
                          const int h,             // Height
                          const color colour,      // Background color
                          const uchar opacity,     // Opacity
                          const bool redraw=false) // Flag indicating the need to redraw
                         
  {
   ::ResetLastError();
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(CLR_CANV_NULL);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   CMessage::ToLog(DFUN,::GetLastError(),true);
   return false;
  }
//+------------------------------------------------------------------+

我为什么要这么做? 在创建窗体对象时,我花了很多时间试图理解为什么我无法创建图形资源。 最后,事实证明一个图形资源的创建名称超过了 63 个字符。 如果创建 CCanvas 类的图形标签的方法报告了错误,我们就不必搜索其它任何内容。 取而代之,我们会立即得到错误代码,我不必遍历所有循环调用不同类中的各种方法。

不过,如果资源的名称长度超过 63 个字符,这种改进将毫无帮助:

ERR_RESOURCE_NAME_IS_TOO_LONG     4018     The resource name exceeds 63 characters

返回错误代码

ERR_RESOURCE_NOT_FOUND    4016    Resource with such a name is not found in EX5

但这依旧是较好的,因为这立刻引发了一个问题:“为什么没有创建图形资源?”...


我们来改进 \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh 文件中的扩展标准图形对象工具箱类。

每个图形对象都有一个或多个轴点,用于在图表上定位对象。 每个轴点上都会附着一个窗体对象。 标准图形对象有自己的重新定位点。 它们在选择图形对象时出现。 我们不打算在函数库中管理扩展的标准图形对象。 为了实现该功能,我们用窗体对象来移动图形对象的轴心点更便捷。 这样的窗体对象也许比控制点更多。 因此,我们不能通过图形对象的轴点数量来定义窗体对象的数量。 然而,我们需要知道这个数量。 因此,在类的公开部分,添加返回附着于所管理轴点上的已创建窗体对象数量的方法,并声明在完全透明的窗体对象上绘制所管理轴点的方法

//--- (1) Set and (2) return the size of the form of pivot point management control points
   void              SetControlFormSize(const int size);
   int               GetControlFormSize(void)                           const { return this.m_ctrl_form_size;                 }
//--- Return the pointer to the pivot point form by (1) index and (2) name
   CForm            *GetControlPointForm(const int index)                     { return this.m_list_forms.At(index);           }
   CForm            *GetControlPointForm(const string name,int &index);
//--- Return the number of (1) base object pivot points and (2) newly created form objects for managing reference points
   int               GetNumPivotsBaseObj(void)                          const { return this.m_base_pivots;                    }
   int               GetNumControlPointForms(void)                      const { return this.m_list_forms.Total();             }
//--- Create form objects on the base object pivot points
   bool              CreateAllControlPointForm(void);
//--- Draw a reference point on the form
   void              DrawControlPoint(CForm *form,const uchar opacity,const color clr);
//--- Remove all form objects from the list
   void              DeleteAllControlPointForm(void);


在基于对象轴心点创建窗体对象的方法中,截短已创建对象的名称 替换 "_TKPP_" 为 "_CP_":

//+------------------------------------------------------------------+
//| Create a form object on a base object reference point            |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index)
  {
   string name=this.m_base_name+"_CP_"+(index<this.m_base_pivots ? (string)index : "X");
   CForm *form=this.GetControlPointForm(index);
   if(form!=NULL)
      return NULL;
   int x=0, y=0;
   if(!this.GetControlPointCoordXY(index,x,y))
      return NULL;
   return new CForm(this.m_base_chart_id,this.m_base_subwindow,name,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize());
  }
//+------------------------------------------------------------------+

在此(以及在测试 EA 文件中),我必须缩短所创建窗体对象的名称,因为图形资源的名称超过了 63 个字符,故平台没有创建任何对象。 原因在于 CCanvas 类中的动态资源创建方法,其中所创建资源的名称由 “::“ 符号 + 传递给方法的名称(并在上述方法中指定)+ 图表 ID + 自系统启动以来经历的毫秒数 + 伪随机数

//+------------------------------------------------------------------+
//| Create dynamic resource                                          |
//+------------------------------------------------------------------+
bool CCanvas::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt)
  {
   Destroy();
//--- prepare data array
   if(width>0 && height>0 && ArrayResize(m_pixels,width*height)>0)
     {
      //--- generate resource name
      m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());
      //--- initialize data with zeros
      ArrayInitialize(m_pixels,0);
      //--- create dynamic resource
      if(ResourceCreate(m_rcname,m_pixels,width,height,0,0,0,clrfmt))
        {
         //--- successfully created
         //--- complete initialization
         m_width =width;
         m_height=height;
         m_format=clrfmt;
         //--- succeed
         return(true);
        }
     }
//--- error - destroy object
   Destroy();
   return(false);
  }
//+------------------------------------------------------------------+

不幸的是,所有这些都严重限制了为所创建对象选择一个可理解的名称。

在基准对象轴点上创建窗体对象的方法中,计算窗体对象每侧的缩进量,以便指定位于窗体中心的活动区域的位置和大小,而其大小应等于 CTRL_POINT_RADIUS 宏替换中设置的两个值。 由于我们要处理半径,我们需要用到两个半径值,从窗体宽度(窗体高度与宽度相等)中减去它们,然后将获得的值除以 2,这样窗体活动区域就等于在其中心绘制的圆圈
在 SetActiveAreaShift() 方法中指定从窗体边缘开始的活动区域边框缩进

//+------------------------------------------------------------------+
//| Create form objects on the base object pivot points              |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void)
  {
   bool res=true;
   //--- In the loop by the number of base object pivot points
   for(int i=0;i<this.m_base_pivots;i++)
     {
      //--- Create a new form object on the current pivot point corresponding to the loop index
      CForm *form=this.CreateNewControlPointForm(i);
      //--- If failed to create the form, inform of that and add 'false' to the final result
      if(form==NULL)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM);
         res &=false;
        }
      //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result
      if(!this.m_list_forms.Add(form))
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
         delete form;
         res &=false;
        }
      //--- Set all the necessary properties for the created form object
      form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM);                // Object is created programmatically
      form.SetActive(true);                                    // Form object is active
      form.SetMovable(true);                                   // Movable object
      int x=(int)::floor((form.Width()-CTRL_POINT_RADIUS*2)/2);// Active area shift from the form edge
      form.SetActiveAreaShift(x,x,x,x);                        // Object active area is located in the center of the form, its size is equal to the two CTRL_POINT_RADIUS values
      form.SetFlagSelected(false,false);                       // Object is not selected
      form.SetFlagSelectable(false,false);                     // Object cannot be selected by mouse
      form.Erase(CLR_CANV_NULL,0);                             // Fill in the form with transparent color and set the full transparency
      //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver);    // Draw an outlining rectangle for visual display of the form location
      //form.DrawRectangle(x,x,form.Width()-x-1,form.Height()-x-1,clrSilver);// Draw an outlining rectangle for visual display of the form active area location
      this.DrawControlPoint(form,0,CTRL_POINT_COLOR);          // Draw a circle and a point in the form center
      form.Done();                                             // Save the initial form object state (its appearance)
     }
   //--- Redraw the chart for displaying changes (if successful) and return the final result
   if(res)
      ::ChartRedraw(this.m_base_chart_id);
   return res;
  }
//+------------------------------------------------------------------+

创建窗体时,我们可以绘制矩形,显示窗体的大小及其活动区域,以便调试 — 这些代码已被注释掉。 使用下面研究的新方法在窗体的中心绘制完全透明的圆圈(不过绘制它们究竟是不是一个好主意呢?)。

在窗体上绘制参考点的方法:

//+------------------------------------------------------------------+
//| Draw a reference point on the form                               |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DrawControlPoint(CForm *form,const uchar opacity,const color clr)
  {
   if(form==NULL)
      return;
   form.DrawCircle((int)::floor(form.Width()/2),(int)::floor(form.Height()/2),CTRL_POINT_RADIUS,clr,opacity);// Draw a circle at the center of the form
   form.DrawCircleFill((int)::floor(form.Width()/2),(int)::floor(form.Height()/2),2,clr,opacity);            // Draw a point at the center of the form
  }
//+------------------------------------------------------------------+

该方法包含两段取自上述方法的代码。 为什么呢? 我们需要在不同的时间点来显示或隐藏在窗体中心的一个带圆圈的点。 为达此目的,我们依据指定所绘制形状的必要非透明度和颜色来调用该方法。

从事件处理程序中删除鼠标光标移动的处理 ,因为不再需要它:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      for(int i=0;i<this.m_list_forms.Total();i++)
        {
         CForm *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         int x=0, y=0;
         if(!this.GetControlPointCoordXY(i,x,y))
            continue;
         form.SetCoordX(x-this.m_shift);
         form.SetCoordY(y-this.m_shift);
         form.Update();
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      for(int i=0;i<this.m_list_forms.Total();i++)
        {
         CForm *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         form.OnChartEvent(id,lparam,dparam,sparam);
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
  }
//+------------------------------------------------------------------+


改进 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh 中的抽象标准图形对象类。

在类的公开部分,声明方法,允许我们同时更改图形对象轴点和附着于其上的对象坐标:

//--- 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);
//--- Return the object of data on pivot points
   CLinkedPivotPoint*GetLinkedPivotPoint(void)        { return &this.m_linked_pivots;  }

声明三种方法用于访问管理图形对象轴点的窗体:

//--- Return the number of base object pivot points for calculating the coordinates in the (1) current and (2) specified object
   int               GetLinkedCoordsNum(void)               const { return this.m_linked_pivots.GetNumLinkedCoords();      }
   int               GetLinkedPivotsNum(CGStdGraphObj *obj) const { return(obj!=NULL ? obj.GetLinkedCoordsNum() : 0);      }
//--- Return the form for managing an object pivot point
   CForm            *GetControlPointForm(const int index);
//--- Return the number of form objects for managing reference points
   int               GetNumControlPointForms(void);
//--- Redraw the form for managing a reference point of an extended standard graphical object
   void              RedrawControlPointForms(const uchar opacity,const color clr);

private:


添加依据屏幕坐标设置时间和价格的方法

//--- Symbol for the Chart object 
   string            ChartObjSymbol(void)          const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0);                    }
   bool              SetChartObjSymbol(const string symbol)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0,symbol);
                        return true;
                       }
//--- Set the time and price by screen coordinates
   bool              SetTimePrice(const int x,const int y,const int modifier)
                       {
                        bool res=true;
                        ENUM_OBJECT type=this.GraphObjectType();
                        if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL)
                          {
                           res &=this.SetXDistance(x);
                           res &=this.SetYDistance(y);
                          }
                        else
                          {
                           int subwnd=0;
                           datetime time=0;
                           double price=0;
                           if(::ChartXYToTimePrice(this.ChartID(),x,y,subwnd,time,price))
                             {
                              res &=this.SetTime(time,modifier);
                              res &=this.SetPrice(price,modifier);
                             }
                          }
                        return res;
                       }
  
//--- Return the flags indicating object visibility on timeframes

我们需要将屏幕坐标重新换算为时间/价格坐标,以便能够在 X 和 Y 屏幕坐标系中处理图形对象。 该方法首先检查当前对象类型。 如果它是基于屏幕坐标构建的,其屏幕坐标会被立即转换。 如果图形对象基于时间/价格坐标,我们首先需要将传递给该方法的屏幕坐标转换为相应的时间和价格数值。 然后在图形对象参数中设置获得的值。

该方法返回所管理对象轴点的窗体:

//+------------------------------------------------------------------+
//| Return the form for managing an object pivot point               |
//+------------------------------------------------------------------+
CForm *CGStdGraphObj::GetControlPointForm(const int index)
  {
   return(this.ExtToolkit!=NULL ? this.ExtToolkit.GetControlPointForm(index) : NULL);
  }
//+------------------------------------------------------------------+

这里一切都很简单:如果存在属于扩展标准图形对象的工具箱对象则返回窗体对象的索引否则,返回 NULL

该方法返回所管理参考点的窗体对象数量:

//+------------------------------------------------------------------+
//| Return the number of form objects                                |
//| for managing control points                                      |
//+------------------------------------------------------------------+
int CGStdGraphObj::GetNumControlPointForms(void)
  {
   return(this.ExtToolkit!=NULL ? this.ExtToolkit.GetNumControlPointForms() : 0);
  }
//+------------------------------------------------------------------+

该方法与上述方法类似:如果存在属于扩展标准图形对象的工具箱对象则返回窗体对象的数量否则,返回 0

该方法依据所管理的扩展标准图形对象的参考点重绘窗体:

//+------------------------------------------------------------------+
//| Redraw the form for managing the control point                   |
//| of the extended standard graphical object                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::RedrawControlPointForms(const uchar opacity,const color clr)
  {
//--- If the object has no extended standard graphical object toolkit, exit
   if(this.ExtToolkit==NULL)
      return;
//--- Get the number of pivot point handling forms
   int total_form=this.GetNumControlPointForms();
//--- In the loop by the number of pivot point management forms
   for(int i=0;i<total_form;i++)
     {
      //--- get the next form object
      CForm *form=this.ExtToolkit.GetControlPointForm(i);
      if(form==NULL)
         continue;
      //--- draw a point and a circle with the specified non-transparency and color
      this.ExtToolkit.DrawControlPoint(form,opacity,clr);
     }
   
//--- Get the total number of bound graphical objects
   int total_dep=this.GetNumDependentObj();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<total_dep;i++)
     {
      //--- get the next graphical object from the list
      CGStdGraphObj *dep=this.GetDependentObj(i);
      if(dep==NULL)
         continue;
      //--- call the method for it
      dep.RedrawControlPointForms(opacity,clr);
     }
  }
//+------------------------------------------------------------------+

该方法附有详细的注释。 简而言之,其思路如下:首先,我们重新绘制附着于当前对象的所有窗体对象。 如我们所知,从属图形对象可以附着到对象之上。 这些从属对象也有自己的窗体对象。 因此,我们在循环中遍历所有从属对象,为每个从属对象调用该方法。 反过来,从属对象循环遍历自己挂靠的对象列表,并为它们调用相同的方法。 所有窗体对象链接的全部图形对象得到重绘之后,这一切才完结。

该方法修改当前和所有从属对象的 X 和 Y 坐标:

//+----------------------------------------------------------------------+
//| Change X and Y coordinates of the current and all dependent objects  |
//+----------------------------------------------------------------------+
bool CGStdGraphObj::ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false)
  {
//--- Set the new coordinates for the pivot point set in 'modifier'
   if(!this.SetTimePrice(x,y,modifier))
      return false;
//--- If the object is not a composite graphical object
//--- or dependent graphical objects are not attached to the object,
//--- there is nothing we can do here, return 'true'
   if(this.ExtToolkit==NULL || this.m_list.Total()==0)
      return true;
//--- Get the graphical object bound to the 'modifier' point
   CGStdGraphObj *dep=this.GetDependentObj(modifier);
   if(dep==NULL)
      return false;
//--- Get the object of the pivot point data of the attached graphical object
   CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
   if(pp==NULL)
      return false;
//--- get the number of coordinate points the object is attached to
   int num=pp.GetNumLinkedCoords();
//--- In the loop by the object coordinate points,
   for(int j=0;j<num;j++)
     {
      //--- get the number of coordinate points of the base object for setting the X coordinate
      int numx=pp.GetBasePivotsNumX(j);
      //--- In the loop by each coordinate point for setting the X coordinate,
      for(int nx=0;nx<numx;nx++)
        {
         //--- get the property for setting the X coordinate, its modifier
         //--- and set it to the dependent graphical object attached to the 'modifier' point
         int prop_from=pp.GetPropertyX(j,nx);
         int modifier_from=pp.GetPropertyModifierX(j,nx);
         this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
        }
      //--- Get the number of coordinate points of the base object for setting the Y coordinate
      int numy=pp.GetBasePivotsNumY(j);
      //--- In the loop by each coordinate point for setting the Y coordinate,
      for(int ny=0;ny<numy;ny++)
        {
         //--- get the property for setting the Y coordinate, its modifier
         //--- and set it to the dependent graphical object attached to the 'modifier' point
         int prop_from=pp.GetPropertyY(j,ny);
         int modifier_from=pp.GetPropertyModifierY(j,ny);
         this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
        }
     }
//--- Save the current properties of the dependent graphical object as the previous ones
   dep.PropertiesCopyToPrevData();
//--- Move the management control point to new coordinates
   this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier);
   this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
//--- Redraw the chart, if the flag is enabled
   if(redraw)
      ::ChartRedraw(m_chart_id);
//--- If all is successful, return 'true'
   return true;
  }
//+------------------------------------------------------------------+

该方法附有详细的注释。 长话短说,我们首先修改当前对象的指定轴点的坐标。 如果对象有绑定的从属图形对象,亦应将其移动到新坐标。 这正是该方法中发生的情况。

从事件处理程序中删除处理鼠标移动的操作因为不再需要它:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(GraphElementType()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
      return;
   string name=this.Name();
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      if(ExtToolkit==NULL)
         return;
      for(int i=0;i<this.Pivots();i++)
        {
         ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
        }
      ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
      ExtToolkit.OnChartEvent(id,lparam,dparam,name);
     }
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      if(ExtToolkit!=NULL)
         ExtToolkit.OnChartEvent(id,lparam,dparam,name);
     }
  }
//+------------------------------------------------------------------+


我们来改进 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 中图形对象的集合类。

当我们在图表上移动光标时,而图表内含对象时,鼠标光标悬停处的对象会在事件处理程序中定义。 然后启动鼠标相对于对象的状态处理程序。 当我们将光标悬停在对象上,以便处理扩展标准图形对象的轴点时,可以在处理程序中看到窗体本身、窗体的索引、以及窗体所附着的图形对象。 为了避免在处理程序之外再次查找此窗体和图形对象,我们需要简单地将窗体欲附着的图形对象 ID 和光标悬停处窗体索引保存在变量当中。 通过这些数据,我们可以快速从列表中选择必要的对象,了解光标位于哪个窗体上方 — 依据这些变量的值。

在方法声明中插入以下变量,该方法将返回光标所在处的窗体指针:

//--- Return the pointer to the form located under the cursor
   CForm            *GetFormUnderCursor(const int id, 
                                        const long &lparam, 
                                        const double &dparam, 
                                        const string &sparam,
                                        ENUM_MOUSE_FORM_STATE &mouse_state,
                                        long &obj_ext_id,
                                        int &form_index);

变量将通过引用链接传递给方法。 换言之,我们只需在方法中设置必要的值,然后将它们保存在相应的变量之中,供以后使用。

在类的公开部分中,声明两个方法,依据 ID 返回扩展和标准图形对象

//--- Return an (1) existing and (2) removed graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
   CGStdGraphObj    *GetStdDelGraphObject(const string name,const long chart_id);
//--- Return the existing (1) extended and (2) standard graphical object by its ID
   CGStdGraphObj    *GetStdGraphObjectExt(const long id,const long chart_id);
   CGStdGraphObj    *GetStdGraphObject(const long id,const long chart_id);
//--- Return the list of (1) chart management objects and (2) removed graphical objects
   CArrayObj        *GetListChartsControl(void)                                                          { return &this.m_list_charts_control;  }
   CArrayObj        *GetListDeletedObj(void)                                                             { return &this.m_list_deleted_obj;     }

我们需要这些方法来依据其 ID 接收指向图形对象的指针。 我们来看看这些方法的实现。

该方法依据 ID 返回存在的扩展标准图形对象:

//+------------------------------------------------------------------+
//| Return the existing extended standard                            |
//| graphical object by its ID                                       |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObjectExt(const long id,const long chart_id)
  {
   CArrayObj *list=this.GetListStdGraphObjectExt();
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_ID,0,id,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

在此,我们接收扩展标准图形对象的列表列表中只保留含有指定图表 ID 的对象
从获得的列表中,选择含有指定对象 ID 的对象如果得到的列表有效,且不为空,返回指向列表中所包含的必要对象的指针。 否则,返回 NULL

该方法依据 ID 返回存在的标准图形对象:

//+------------------------------------------------------------------+
//| Return the existing standard                                     |
//| graphical object by its ID                                       |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const long id,const long chart_id)
  {
   CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_ID,0,id,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

在此,我们依据图表 ID 接收图形对象列表在获取的列表中,保留含有指定对象 ID 的对象
如果得到的列表有效,且不为空,返回列表中所包含的指向所需对象的指针
否则,返回 NULL

我们来改进返回位于光标处窗体指针的方法。 我们需要添加并初始化两个新变量,用于存储扩展标准图形对象 ID 和处理所需的窗体定位点索引,以及插入用于处理扩展标准图形对象的代码模块搜索附着于这些对象的窗体(鼠标光标悬停处):

//+------------------------------------------------------------------+
//| Return the pointer to the form located under the cursor          |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, 
                                                    const long &lparam, 
                                                    const double &dparam, 
                                                    const string &sparam,
                                                    ENUM_MOUSE_FORM_STATE &mouse_state,
                                                    long &obj_ext_id,
                                                    int &form_index)
  {
//--- Set the ID of the extended standard graphical object to -1 
//--- and the index of the anchor point managed by the form to -1
   obj_ext_id=WRONG_VALUE;
   form_index=WRONG_VALUE;
//--- Initialize the mouse status relative to the form
   mouse_state=MOUSE_FORM_STATE_NONE;
//--- Declare the pointers to graphical element collection class objects
   CGCnvElement *elm=NULL;
   CForm *form=NULL;
//--- Get the list of objects the interaction flag is set for (there should be only one object)
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL);
//--- If managed to obtain the list and it is not empty
   if(list!=NULL && list.Total()>0)
     {
      //--- Get the only graphical element there
      elm=list.At(0);
      //--- If the element is a form object
      if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object with a specified interaction flag,
//--- in the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next element
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL)
         continue;
      //--- if the obtained element is a form object
      if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object from the collection list
//--- Get the list of extended standard graphical objects
   list=this.GetListStdGraphObjectExt();
   if(list!=NULL)
     {
      //--- in the loop by all extended standard graphical objects
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next graphical object,
         CGStdGraphObj *obj_ext=list.At(i);
         if(obj_ext==NULL)
            continue;
         //--- get the object of its toolkit,
         CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit();
         if(toolkit==NULL)
            continue;
         //--- handle the event of changing the chart for the current graphical object
         obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
         //--- Get the total number of form objects created for the current graphical object
         total=toolkit.GetNumControlPointForms();
         //--- In the loop by all form objects
         for(int j=0;j<total;j++)
           {
            //--- get the next form object,
            form=toolkit.GetControlPointForm(j);
            if(form==NULL)
               continue;
            //--- get the mouse status relative to the form
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- If the cursor is inside the form,
            if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
              {
               //--- set the object ID and form index
               //--- and return the pointer to the form
               obj_ext_id=obj_ext.ObjectID();
               form_index=j;
               return form;
              }
           }
        }
     }
//--- Nothing is found - return NULL
   return NULL;
  }
//+------------------------------------------------------------------+

所加代码模块的整个逻辑都有注释描述。 简而言之,我们需要找到鼠标光标悬停处的窗体对象。 首先,我们要查找存储在图形元素集合类列表中的窗体对象。 如果没有找到单个窗体,我们应该在搜索它们的窗体时传递所有扩展的标准图形对象 — 光标可以在其中任何一个窗体上。 如果是这种情况,则在传递给方法的变量引用链接中设置窗体所附加的扩展标准图形对象 ID。 窗体索引也要设置好,这样我们就可以知道轴点和窗体管理的图形对象。

现在,我们应该在事件处理程序中处理鼠标光标与扩展标准图形对象的窗体对象的交互。 此外,我们要实现针对窗体对象重新定位的控制,这样它们就不能进入右上角的图表区域,其位置是一键交易模式激活按钮的所在。 此按钮始终位于所有对象的顶部,因此重新定位的窗体不应进入其区域,从而避免意外单击按钮。 如果一键交易面板已激活,则窗体也不应进入其区域,因为这可能会给处理窗体造成不便。

我们来看看事件处理程序中的改进和修改

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj_std=NULL;  // Pointer to the standard graphical object
   CGCnvElement  *obj_cnv=NULL;  // Pointer to the graphical element object on canvas
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj_std=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj_std==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj_std=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         if(obj_std==NULL)
            return;
         //--- Get the name of the renamed graphical object on the chart, which is not in the collection list
         string name_new=this.FindExtraObj(chart_id);
         //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- and send an event with the new name of the object to the control program chart
         if(obj_std.SetNamePrev(obj_std.Name()) && obj_std.SetName(name_new))
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj_std.ChartID(),obj_std.TimeCreate(),obj_std.Name());
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj_std.PropertiesRefresh();
      obj_std.PropertiesCheckChanged();
     }

//--- Handle standard graphical object events in the collection list
   for(int i=0;i<this.m_list_all_graph_obj.Total();i++)
     {
      //--- Get the next graphical object and
      obj_std=this.m_list_all_graph_obj.At(i);
      if(obj_std==NULL)
         continue;
      //--- call its event handler
      obj_std.OnChartEvent((id<CHARTEVENT_CUSTOM ? id : idx),lparam,dparam,sparam);
     }

//--- Handle chart changes for extended standard objects
   if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE)
     {
      CArrayObj *list=this.GetListStdGraphObjectExt();
      if(list!=NULL)
        {
         for(int i=0;i<list.Total();i++)
           {
            obj_std=list.At(i);
            if(obj_std==NULL)
               continue;
            obj_std.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
        }
     }
//--- Handling mouse events of graphical objects on canvas
//--- If the event is not a chart change
   else
     {
      //--- Check whether the mouse button is pressed
      bool pressed=(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)==MOUSE_BUTT_KEY_STATE_LEFT ? true : false);
      ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE;
      //--- Declare static variables for the active form and status flags
      static CForm *form=NULL;
      static bool pressed_chart=false;
      static bool pressed_form=false;
      static bool move=false;
      //--- Declare static variables for the form index of managing an extended standard graphical object and its ID
      static int  form_index=WRONG_VALUE;
      static long graph_obj_id=WRONG_VALUE;
      
      //--- If the button is not pressed on the chart and the movement flag is not set, get the form, above which the cursor is located
      if(!pressed_chart && !move)
         form=this.GetFormUnderCursor(id,lparam,dparam,sparam,mouse_state,graph_obj_id,form_index);
      
      //--- If the button is not pressed, reset all flags and enable the chart tools 
      if(!pressed)
        {
         pressed_chart=false;
         pressed_form=false;
         move=false;
         this.SetChartTools(::ChartID(),true);
        }
      
      //--- If this is a mouse movement event and the movement flag is active, move the form, above which the cursor is located (if the pointer to it is valid)
      if(id==CHARTEVENT_MOUSE_MOVE && move)
        {
         if(form!=NULL)
           {
            //--- calculate the cursor movement relative to the form coordinate origin
            int x=this.m_mouse.CoordX()-form.OffsetX();
            int y=this.m_mouse.CoordY()-form.OffsetY();
            //--- get the width and height of the chart the form is located at
            int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow());
            int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow());
            //--- If the form is outside the extended standard graphical object
            if(form_index==WRONG_VALUE)
              {
               //--- Adjust the calculated form coordinates if the form is out of the chart range
               if(x<0) x=0;
               if(x>chart_width-form.Width()) x=chart_width-form.Width();
               if(y<0) y=0;
               if(y>chart_height-form.Height()) y=chart_height-form.Height();
               //--- If the chart has no one-click trading panel,
               if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK))
                 {
                  //--- calculate the form coordinate so that the form does not enter the one-click trading button during relocation
                  if(y<17 && x<41)
                     y=17;
                 }
               //--- If the chart has the one-click trading panel enabled,
               else
                 {
                  //--- calculate the form coordinate so that the form does not enter the one-click trading panel area during relocation
                  if(y<80 && x<192)
                     y=80;
                 }
              }
            //--- If the form is included into the extended standard graphical object
            else
              {
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- Get the list of objects by the object ID (there should be one object)
                  CArrayObj *list_ext=CSelect::ByGraphicStdObjectProperty(GetListStdGraphObjectExt(),GRAPH_OBJ_PROP_ID,0,graph_obj_id,EQUAL);
                  //--- If managed to obtain the list and it is not empty,
                  if(list_ext!=NULL && list_ext.Total()>0)
                    {
                     //--- get the graphical object from the list
                     CGStdGraphObj *ext=list_ext.At(0);
                     //--- If the pointer to the object is received,
                     if(ext!=NULL)
                       {
                        //--- get the object type
                        ENUM_OBJECT type=ext.GraphObjectType();
                        //--- If the object is constructed based on screen coordinates, set the coordinates to the object
                        if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL)
                          {
                           ext.SetXDistance(x);
                           ext.SetYDistance(y);
                          }
                        //--- otherwise, if the object is based on time/price coordinates
                        else
                          {
                           //--- calculate the coordinate shift and limit the coordinates so that they are not out of the chart range
                           int shift=(int)::ceil(form.Width()/2)+1;
                           if(x+shift<0)
                              x=-shift;
                           if(x+shift>chart_width)
                              x=chart_width-shift;
                           if(y+shift<0)
                              y=-shift;
                           if(y+shift>chart_height)
                              y=chart_height-shift;
                           //--- set calculated coordinates in the object
                           ext.ChangeCoordsExtendedObj(x+shift,y+shift,form_index);
                          }
                       }
                    }
                 }
              }
            //--- Move the form by the obtained coordinates
            form.Move(x,y,true);
           }
        }
   
      //--- Display debugging comments on the chart
      Comment
        (
         (form!=NULL ? form.Name()+":" : ""),"\n",
         EnumToString((ENUM_CHART_EVENT)id),"\n",
         EnumToString(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)),
         "\n",EnumToString(mouse_state),
         "\npressed=",pressed,", move=",move,(form!=NULL ? ", Interaction="+(string)form.Interaction() : ""),
         "\npressed_chart=",pressed_chart,", pressed_form=",pressed_form,
         "\nform_index=",form_index,", graph_obj_id=",graph_obj_id
        );
      
      //--- If the cursor is not above the form
      if(form==NULL)
        {
         //--- If the mouse button is pressed
         if(pressed)
           {
            //--- If the button is still pressed and held on the form, exit
            if(pressed_form)
              {
               return;
              }
            //--- If the button hold flag is not enabled yet, set the flags and enable chart tools
            if(!pressed_chart)
              {
               pressed_chart=true;  // Button is held on the chart
               pressed_form=false;  // Cursor is not above the form
               move=false;          // movement disabled
               this.SetChartTools(::ChartID(),true);
              }
           }
         //--- If the mouse button is not pressed
         else
           {
            //--- Get the list of extended standard graphical objects
            CArrayObj *list_ext=GetListStdGraphObjectExt();
            //--- In the loop by all extended graphical objects,
            int total=list_ext.Total();
            for(int i=0;i<total;i++)
              {
               //--- get the next graphical object
               CGStdGraphObj *obj=list_ext.At(i);
               if(obj==NULL)
                  continue;
               //--- and redraw it without a point and a circle
               obj.RedrawControlPointForms(0,CTRL_POINT_COLOR);
              }
           }
        }
      //--- If the cursor is above the form
      else
        {
         //--- If the button is still pressed and held on the chart, exit
         if(pressed_chart)
           {
            return;
           }
         
         //--- If the flag of holding the button on the form is not set yet
         if(!pressed_form)
           {
            pressed_chart=false;    // The button is not pressed on the chart
            this.SetChartTools(::ChartID(),false);
            
            //--- 'The cursor is inside the form, no mouse buttons are clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED)
              {
               //--- If the cursor is above the extended graphical object pivot point control form,
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by the object and chart IDs
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of the extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a circle with a point on the form
                        toolkit.DrawControlPoint(form,255,CTRL_POINT_COLOR);
                       }
                    }
                 }
              }
            //--- 'The cursor is inside the form, a mouse button is clicked (any)' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED)
              {
               this.SetChartTools(::ChartID(),false);
               //--- If the flag of holding the form is not set yet
               if(!pressed_form)
                 {
                  pressed_form=true;      // set the flag of pressing on the form
                  pressed_chart=false;    // remove the flag of pressing on the chart
                 }
              }
            //--- 'The cursor is inside the form, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_WHEEL)
              {
               
              }
            
            
            //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED)
              {
               //--- Set the cursor shift relative to the form initial coordinates
               form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX());
               form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY());
               //--- If the cursor is above the active area of the extended graphical object pivot point control form
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by the object and chart IDs
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of the extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a circle with a point on the form
                        toolkit.DrawControlPoint(form,255,CTRL_POINT_COLOR);
                       }
                    }
                 }
              }
            //--- '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
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  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
                 }
              }
            //--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL)
              {
               
              }
            
            
            //--- 'The cursor is inside the window scrolling area, no mouse buttons are clicked' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED)
              {
               
              }
            //--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED)
              {
               
              }
            //--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL)
              {
               
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

该方法的所有改进都在代码中附有详细注释。 如果您有任何疑问,请随时在下面的评论中提问。

这些都是我今天要做的改进。


测试

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

已实现的唯一修改是创建三个窗体对象

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart98.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define        FORMS_TOTAL (3)   // Number of created forms
#define        START_X     (4)   // Initial X coordinate of the shape
#define        START_Y     (4)   // Initial Y coordinate of the shape
#define KEY_LEFT           (188) // Left
#define KEY_RIGHT          (190) // Right
#define KEY_ORIGIN         (191) // Initial properties
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

与此有关,我们稍微调整每个已创建窗体的坐标计算。
窗体对象声明设置在循环之外

第一个窗体将在 Y 坐标为 100 处构建而剩下的那些 — 从上一个窗体的底边缩进 20 个像素

//+------------------------------------------------------------------+
//| 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();
      
      //--- 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.TextOnBG(0,TextByLanguage("Тест 0","Test 0")+string(i+1),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);
  }
//+------------------------------------------------------------------+

OnChartEvent() 处理程序中,截短鼠标单击创建的图形对象名称的长度(以前是 “TrendLineExt”),从而避免在创建图形资源时超过 63 个字符:

   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      //--- Get the chart click coordinates
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         //--- Get the right point coordinates for a trend line
         datetime time2=iTime(Symbol(),PERIOD_CURRENT,1);
         double price2=iOpen(Symbol(),PERIOD_CURRENT,1);
         
         //--- Create the "Trend line" object
         string name_base="TLineExt";
         engine.CreateLineTrend(name_base,0,true,time,price,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID
         CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID());
         
         //--- Create the "Left price label" object
         string name_dep="PriceLeftExt";
         engine.CreatePriceLabelLeft(name_dep,0,false,time,price);
         //--- Get the object from the list of graphical objects by chart name and ID and
         CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line left point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0);
         
         //--- Create the "Right price label" object
         name_dep="PriceRightExt";
         engine.CreatePriceLabelRight(name_dep,0,false,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and
         dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line right point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1);
        }
     }

//--- Handling graphical element collection events

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


正如我们所见,窗体不会进入一键交易面板按钮所在的区域,如果它已被激活,也不会进入面板区域。 用于处理扩展标准图形对象轴点的窗体按预期工作,没有离开图表限制区域
然而,也有瑕疵。 创建复合图形对象并移动其轴点后,它们位于窗体对象上方。 在某些情况下,这也许是不正确的。 例如,如果我们有一个面板,光标拖动的线应该穿过面板下方,而不是在面板上方绘制。 如果我们逐一点击每个窗体,它们将设置在复合图形对象上方,并且在重新定位期间不会在这些窗体上方绘制。 如果我们将三个窗体部分重叠在一起,那么当我们将鼠标悬停在第二个窗体上时,第一个窗体将被激活。 我会修复它的。 在此,我们需要用到所有窗体彼此间的相对“深度”,以及与图表上其它对象的相对位置。


下一步是什么?

在下一篇文章中,我将继续研究复合图形对象及其功能。

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

返回内容目录

*该系列的前几篇文章:

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

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

附加的文件 |
MQL5.zip (4233.69 KB)
一张图表上多个指标(第 04 部分):晋升为一款智能交易系统 一张图表上多个指标(第 04 部分):晋升为一款智能交易系统
在我之前的文章里,我已经解释了如何创建拥有多个子窗口的指标,在使用自定义指标时如此这般会变得很有趣。 这次,我们将看到如何为智能交易系统添加多个窗口。
来自专业程序员的提示(第三部分):日志。 连接到 Seq 日志收集和分析系统 来自专业程序员的提示(第三部分):日志。 连接到 Seq 日志收集和分析系统
Logger 类的实现能够统一和结构化打印到智能系统栏的日志消息。 连接到 Seq 日志收集和分析系统。 在线监视日志消息。
一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I) 一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)
有很多人不知道如何编程,但他们很有创造力,亦有杰出的想法。 然而,由于缺乏编程知识,他们无法实现这些想法。 我们一起看看如何利用 MetaTrader 5 平台本身创建图表交易,就如同它是一个 IDE。
在一张图表上的多个指标(第 03 部分):为用户开发定义 在一张图表上的多个指标(第 03 部分):为用户开发定义
今天,我们将首次更新指标系统的功能。 在“一张图表上的多个指标”的前一篇文章中,我们研究了允许在图表子窗口中加载多个指标的基本代码。 但其所代表的只是一个更大系统的起点。