DoEasy 函数库中的图形(第九十九部分):依据单个控制点移动扩展图形对象

Artyom Trishkin | 24 五月, 2022

内容


概述

在之前的文章中,我实现了依据控件窗体移动扩展图形对象轴点的功能。 不过,我还未完成将这种图形对象作为一个整体进行移动的功能。 任何标准图形对象在移动其中心点时都可以整体移动。 类似地,这里我将制作一个管控图形对象的单一中心点,从而能够通过移动该点来移动整个图形对象(而非其轴点)。 为了执行测试,我选择了一个复合图形对象,该对象由一条趋势线组成,其末端带有价格标签对象。 考虑到这一点,整个工作将针对图形对象完成,而该图形对象有两个轴点用于重新定位其端点,单个中心点用于整体移动图形对象(两个点用于修改对象端点,一个中心点用于移动对象)。 稍后,我将为拥有三个以上控制点的图形对象创建含有相同控制点的窗体。

此外,我还会略微优化计算图形对象轴点的屏幕坐标的代码,将其划分到不同的方法,从而简化对于基础逻辑的理解。 毕竟,阅读调用方法返回某个值的代码(反过来,里面还有另一个方法,它也会计算一些东西),比之将这些方法代码整个放置在主计算模块中要容易得多,而后者会令代码庞大且难以理解。

并非所有在此实现的东西都会像预期的那样工作。 但本文的目标是讲述为了获得必要结果,所进行的开发和创建代码的过程。 我相信,从规划功能到实现,几乎所有的过程都比阅读一篇枯燥的关于“最终一切如何如何”的演讲要生动有趣得多。

由于获取屏幕坐标的 ChartTimePriceToXY() 函数仅返回图表可视部分的坐标,因此我们无法计算图表界限之外点线的屏幕坐标。 如果我们请求位于可视图表左侧以外的屏幕时间像素中的 X 坐标,该函数始终返回 0。 由此,当沿屏幕移动复合图形对象时,若其左侧超出屏幕的左边框时,对象的左枢轴点将保持在图表像素坐标 0 处。 这将导致图形对象失真。 这同样适用于图形对象右侧和图表屏幕右侧(以及顶部和底部)部分。 因此,我将为复合图形对象引入一个限制,限制把图形对象移到图表的可视区域之外。 这样做是为了防止图形对象的任何边缘在移动时“撞到”屏幕边框产生变形。


改进库类

由于用来显示管理扩展图形对象轴点的管控点的对象窗体是函数库对象中的一个重要对象,但这些窗体未包含在图形对象集合当中,故此我们需要为此类窗体定义一种新类型。 所有基准函数库对象都有自己的函数库对象类型名称,据其我们就能定义当前处于活动状态的对象。 我们来为管理函数库扩展图形对象中管控点的窗体对象定义类型。

在 \MQL5\Include\DoEasy\Defines.mqh 里,在函数库对象类型的枚举里添加新的类型:

//+------------------------------------------------------------------+
//| List of library object types                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                         // "Single animation frame" object type
   OBJECT_DE_TYPE_GFRAME_TEXT,                                    // "Single text animation frame" object type
   OBJECT_DE_TYPE_GFRAME_QUAD,                                    // "Single rectangular animation frame" object type
   OBJECT_DE_TYPE_GFRAME_GEOMETRY,                                // "Single geometric animation frame" object type
   OBJECT_DE_TYPE_GANIMATIONS,                                    // "Animations" object type
//--- Managing graphical objects
   ...
   ...
   ...
  }


在 \MQL5\Include\DoEasy\Data.mqh 中,添加函数库新消息索引

   MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY,            // Request outside long array
   MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY,          // Request outside double array
   MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY,          // Request outside string array
   MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY,                 // Request outside the array
   MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY,    // Failed to convert graphical object coordinates to screen ones
   MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY,    // Failed to convert time/price coordinates to screen ones

与新增索引相对应的消息

   {"Запрос за пределами long-массива","Data requested outside the long-array"},
   {"Запрос за пределами double-массива","Data requested outside the double-array"},
   {"Запрос за пределами string-массива","Data requested outside the string-array"},
   {"Запрос за пределами массива","Data requested outside the array"},
   {"Не удалось преобразовать координаты графического объекта в экранные","Failed to convert graphics object coordinates to screen coordinates"},
   {"Не удалось преобразовать координаты время/цена в экранные","Failed to convert time/price coordinates to screen coordinates"},


如果在开发移动图形对象的功能时,检测到转换时间/价格坐标为屏幕坐标出错,则会报告该错误,以将链从逻辑错误搜索中排除。

ChartTimePriceToXY() 函数,这可能会导致坐标转换错误,它在 ChartWnd 中的图表窗口对象类 \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh 中也可能会被用到。 我们在 TimePriceToXY() 方法中加入在日志里显示坐标转换错误消息:

//+------------------------------------------------------------------+
//| Convert chart coordinates from the time/price representation     |
//| to X and Y coordinates                                           |
//+------------------------------------------------------------------+
bool CChartWnd::TimePriceToXY(const datetime time,const double price)
  {
   ::ResetLastError();
   if(!::ChartTimePriceToXY(this.m_chart_id,this.WindowNum(),time,price,this.m_wnd_coord_x,this.m_wnd_coord_y))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY);
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

首先,显示“未能将时间/价格坐标转换为屏幕坐标”消息,然后显示 错误描述和错误代码


由于我现在已经为管理扩展标准图形对象轴点的管控点声明了新的函数库对象类型,故我需要创建继承自窗体对象类的对象类。 在该类中,我将添加一些变量和方法,从而简化针对该类对象的处理。

在 \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh 中的扩展标准图形对象的工具箱中可以设置它

//+------------------------------------------------------------------+
//|                                      CGStdGraphObjExtToolkit.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Class of the form for managing pivot points of a graphical object|
//+------------------------------------------------------------------+
class CFormControl : public CForm
  {
private:
   bool              m_drawn;                   // Flag indicating that the pivot point is drawn on the form
   int               m_pivot_point;             // Pivot point managed by the form
public:
//--- (1) Return and (2) set the drawn point flag
   bool              IsControlAlreadyDrawn(void)               const { return this.m_drawn;                       }
   void              SetControlPointDrawnFlag(const bool flag)       { this.m_drawn=flag;                         }
//--- (1) Return and (2) set the pivot point managed by the form
   int               GraphObjPivotPoint(void)                  const { return this.m_pivot_point;                 }
   void              SetGraphObjPivotPoint(const int index)          { this.m_pivot_point=index;                  }
//--- Constructor
                     CFormControl(void)                              { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;  }
                     CFormControl(const long chart_id,const int subwindow,const string name,const int pivot_point,const int x,const int y,const int w,const int h) :
                        CForm(chart_id,subwindow,name,x,y,w,h)
                          {
                           this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;
                           this.m_pivot_point=pivot_point;
                          }
  };
//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject

私密类成员变量 m_drawn 存储已在窗体上画点的指示标志。 为什么我们需要这样一个变量? 如果从管理图形对象的管控点窗体的活动区域中移走鼠标光标,则需要从窗体上删除已画点。 当前,如果鼠标光标未悬停在窗体的活动区域上,则会持续删除所有该窗体上的已画点。 如果我们能够首先查看窗体重画标志,那为什么我们还要不惜加重系统的负担来持续重画所有这些窗体? 该标志通知我们正在画点或删除已画点。 在未来,我将开发一些视觉效果(以及其它一些东西),故需画出这些点;由此,最好在运行视觉效果处理程序后立即设置标志,而不是等画点完成后再尝试。

私密类成员变量 m_pivot_point 存储窗体管理的数据透视点索引。 图形对象会拥有多个控制点。 例如,趋势线有三个点 — 其中两个点位于线的末端,能独立更改线的末端位置,另一个点则用于移动整个对象。 存储在窗体对象中的索引会与线轴点的索引相对应:0 和 1 — 这是线的端点;2 — 这是中心点。 其它图形对象可能拥有完全不同的控制点,但所有索引都对应于对象枢轴点 + 额外的一个控制点(虽然并非在所有情况下,这将在后续文章中讨论),以便移动整个对象。

类的公开方法用于设置/返回上述变量的值。 该类还有两个构造函数。 在默认构造函数中,在本文中的对象类型中设置添加了新的 OBJECT_DE_TYPE_GFORM_CONTROL 类型。
参数型构造函数把所有参数值传递给父类构造函数,再加上一个额外变量 — 由所创建窗体管理的图形对象轴点索引

现在,CGStdGraphObjExtToolkit 类中的所有轴点管控窗体都是 CFormControl 类型,因此我们需要把 CForm 窗体对象的类型替换为 CFormControl,并添加处理管控图形对象轴点的控件窗体的新方法

//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
private:
   long              m_base_chart_id;           // Base graphical object chart ID
   int               m_base_subwindow;          // Base graphical object chart subwindow
   ENUM_OBJECT       m_base_type;               // Base object type
   string            m_base_name;               // Base object name
   int               m_base_pivots;             // Number of base object reference points
   datetime          m_base_time[];             // Time array of base object reference points
   double            m_base_price[];            // Price array of base object reference points
   int               m_base_x;                  // Base object X coordinate
   int               m_base_y;                  // Base object Y coordinate
   int               m_ctrl_form_size;          // Size of forms for managing reference points
   int               m_shift;                   // Shift coordinates for adjusting the form location
   CArrayObj         m_list_forms;              // List of form objects for managing reference points
//--- Create a form object on a base object reference point
   CFormControl     *CreateNewControlPointForm(const int index);
//--- Return X and Y (1) screen coordinates of the specified reference point of the graphical object
   bool              GetControlPointCoordXY(const int index,int &x,int &y);
//--- Set the parameters of a form object for managing pivot points
   void              SetControlFormParams(CFormControl *form,const int index);
public:
//--- Set the parameters of the base object of a composite graphical object
   void              SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                const long base_chart_id,const int base_subwindow,
                                const int base_pivots,const int ctrl_form_size,
                                const int base_x,const int base_y,
                                const datetime &base_time[],const double &base_price[]);
//--- Set the base object (1) time, (2) price, (3) time and price coordinates
   void              SetBaseObjTime(const datetime time,const int index);
   void              SetBaseObjPrice(const double price,const int index);
   void              SetBaseObjTimePrice(const datetime time,const double price,const int index);
//--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates
   void              SetBaseObjCoordX(const int value)                        { this.m_base_x=value;                          }
   void              SetBaseObjCoordY(const int value)                        { this.m_base_y=value;                          }
   void              SetBaseObjCoordXY(const int value_x,const int value_y)   { this.m_base_x=value_x; this.m_base_y=value_y; }
//--- (1) Set and (2) return the size of the form of pivot point management control points
   void              SetControlFormSize(const int size);
   int               GetControlFormSize(void)                           const { return this.m_ctrl_form_size;                 }
//--- Return the pointer to the pivot point form by (1) index and (2) name
   CFormControl     *GetControlPointForm(const int index)                     { return this.m_list_forms.At(index);           }
   CFormControl     *GetControlPointForm(const string name,int &index);
//--- Return the number of (1) base object pivot points and (2) newly created form objects for managing control 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);
//--- (1) Draw a control point on the form, (2) draw a control point on the form and delete it on all other forms
   void              DrawControlPoint(CFormControl *form,const uchar opacity,const color clr);
   void              DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR);
//--- (1) Draw using a default color, (remove) a control point on the form
   void              DrawControlPoint(CFormControl *form)                     { this.DrawControlPoint(form,255,CTRL_POINT_COLOR);}
   void              ClearControlPoint(CFormControl *form)                    { this.DrawControlPoint(form,0,CTRL_POINT_COLOR);  }
//--- Remove all form objects from the list
   void              DeleteAllControlPointForm(void);
   
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructor/destructor
                     CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name,
                                             const long base_chart_id,const int base_subwindow,
                                             const int base_pivots,const int ctrl_form_size,
                                             const int base_x,const int base_y,
                                             const datetime &base_time[],const double &base_price[])
                       {
                        this.m_list_forms.Clear();
                        this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price);
                        this.CreateAllControlPointForm();
                       }
                     CGStdGraphObjExtToolkit(){;}
                    ~CGStdGraphObjExtToolkit(){;}
  };
//+------------------------------------------------------------------+


改进 GetControlPointCoordXY() 方法,返回图形对象指定轴点的屏幕 X 和 Y 坐标。

之前,该方法简单地返回指定图形对象轴心点的计算坐标。 现在,我们需要考虑图形对象可能拥有不同数量的轴点,和不同位置的中心点。 因此,我们要在 switch() 中计算不同类型的对象。 此外,我们应该考虑我们想得到哪个轴点的坐标 — 位于对象边缘的坐标之一、或中心坐标之一。 如果传递给方法的轴点索引小于图形对象轴点的总数,则会请求轴点坐标。 否则,将请求中心点的坐标。

现在,我将只为边缘有两个枢轴点和一个中心点的图形对象实现接收 X 和 Y 坐标:

//+------------------------------------------------------------------+
//| Return the X and Y coordinates of the specified pivot point      |
//| of the graphical object in screen coordinates                    |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y)
  {
//--- 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             :
      case OBJ_EVENT             :
        //--- Write object screen coordinates and return 'true'
        x=this.m_base_x;
        y=this.m_base_y;
        return true;
      
      //--- Lines (vertical and horizontal)
      case OBJ_VLINE             : break;
      case OBJ_HLINE             : break;
      //--- 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;
      //--- Graphical objects with time/price coordinates
      case OBJ_TEXT              : break;
      case OBJ_BITMAP            : break;
      //---
      default                    : break;
     }
   return false;
  }
//+------------------------------------------------------------------+

执行轴点计算时,取自线轴点坐标的 m_base_timem_base_price 数组中设置的值。 为了计算中心点坐标,依据附着到线边缘枢轴点的窗体对象的坐标。 如果坐标计算成功,该方法将立即返回 true。 在所有其它情况下,它要么返回 false,亦或应用 break 来停止 switch 模块里 case 代码的执行,并移到方法的末尾,返回 false

在按名称返回指向轴点窗体指针的方法中,把 CForm 替换为 CFormControl

//+------------------------------------------------------------------+
//| Return the pointer to the pivot point form by name               |
//+------------------------------------------------------------------+
CFormControl *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index)
  {
   index=WRONG_VALUE;
   for(int i=0;i<this.m_list_forms.Total();i++)
     {
      CFormControl *form=this.m_list_forms.At(i);
      if(form==NULL)
         continue;
      if(form.Name()==name)
        {
         index=i;
         return form;
        }
     }
   return NULL;
  }
//+------------------------------------------------------------------+

依据基准对象轴点创建窗体对象的方法中, 将 CForm 替换为 CFormControl,并设置参数以便成功创建窗体对象

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


在依据基准对象轴点创建窗体对象的方法中,将 CForm 替换为 CFormControl,并删除为已创建窗体对象设置参数的代码,因为在上述方法中创建对象时会立即设置参数:

//+------------------------------------------------------------------+
//| 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
      CFormControl *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;
        }
     }
//--- Redraw the chart for displaying changes (if successful) and return the final result
   if(res)
      ::ChartRedraw(this.m_base_chart_id);
   return res;
  }
//+------------------------------------------------------------------+

现在,按基准对象轴点的数量,再加上额外的一个运行循环。 换句话说,所创建窗体数量比图形对象的轴点数量要多一个。 最后一个窗体是中心窗体,用于移动整个图形对象。

该方法为窗体对象的管控点设置参数:

//+------------------------------------------------------------------+
//| Set the parameters of a form object for managing pivot points    |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetControlFormParams(CFormControl *form,const int index)
  {
   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);// Shift the active area from the form edge
   form.SetActiveAreaShift(x,x,x,x);                        // Object active area is located at the center of the form, its size is equal to 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
   form.SetID(index+1);                                     // Set the form ID
   form.SetControlPointDrawnFlag(false);                    // Set the flag that the pivot point is not drawn on the form
   form.Done();                                             // Save the initial form object state (its appearance)
  }
//+------------------------------------------------------------------+

此处,我们已有参考上述方法转换的代码串。 此外,还有一个在窗体上已画点的标志,和窗体 ID

在窗体上画点的方法中,将窗体中心的计算重新放置到单独的代码里,从而避免重复计算。 直至方法完成,在窗体上设置图形点已画点的标志:

//+------------------------------------------------------------------+
//| Draw a control point on the form                                 |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DrawControlPoint(CFormControl *form,const uchar opacity,const color clr)
  {
   if(form==NULL)
      return;
   int c=int(::floor(form.Width()/2));                      // Form center (coordinates)
   form.DrawCircle(c,c,CTRL_POINT_RADIUS,clr,opacity);      // Draw a circle in the form center
   form.DrawCircleFill(c,c,2,clr,opacity);                  // Draw a circle in the form center
   form.SetControlPointDrawnFlag(opacity>0 ? true : false); // Set the flag that the pivot point is drawn on the form
  }
//+------------------------------------------------------------------+


当前,如果我们将鼠标悬停在窗体上以便管控图形对象轴点,则会在其上出现一个点。 只有在光标离开窗体后,才会删除该点。 但如果我们把对象的所有控制点拉近,那么窗体会构建在图形对象末端,且中心窗体开始相互重叠,那么把光标从一个窗体移开会导致光标移到附近的另一个窗体之中。 因此,我们可把所有窗体对象的所有点都显示出来:

如果我们用鼠标抓取窗体并开始移动它,对象轴点也会随之开始移动。 由于一个错误,导致窗体移动并重定位后,原来的位置依旧残留。 这种行为显然不正确。 因此,我们需要一个方法,在一个图形对象窗体对象上画点,同时删除同一对象的其它窗体对象上的已画点。

该方法在窗体上画点,并在所有其余窗体上清理残留:

//+------------------------------------------------------------------+
//| Draw a control point on the form,                                |
//| remove it on all other forms                                     |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR)
  {
   this.DrawControlPoint(form,opacity,clr);
   for(int i=0;i<this.GetNumControlPointForms();i++)
     {
      CFormControl *ctrl=this.GetControlPointForm(i);
      if(ctrl==NULL || ctrl.ID()==form.ID())
         continue;
      this.ClearControlPoint(ctrl);
     }
  }
//+------------------------------------------------------------------+

在此处,方法接收指向光标悬停处窗体的指针。 在窗体上画点. 然后,循环遍历所有窗体对象选择窗体如果窗体尚未传递给方法并则从其中删除已画点

在事件处理程序中,用 CFormControl 替换 CForm 窗体类型

//+------------------------------------------------------------------+
//| 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++)
        {
         CFormControl *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         int x=0, y=0;
         if(!this.GetControlPointCoordXY(i,x,y))
            continue;
         form.SetCoordX(x-this.m_shift);
         form.SetCoordY(y-this.m_shift);
         form.Update();
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
  }
//+------------------------------------------------------------------+


我们针对 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh. 中抽象标准图形对象类的方法代码进行一些优化改进。 在不同的方法中我们有相似的代码片段,因此将这些代码块集中到单独的方法实现,并在需要时调用它们更有意义,这也令代码更易于阅读。

在类的公开私密部分,声明新方法,包含所有重复调用的代码片段:

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

...

private:
//--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set X and Y coordinates into the appropriate pivot point of a specified subordinate object
   void              SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index);
//--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property
   void              SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);

public:
//--- Event handler


在检查对象属性变更的方法中,删除指定的代码块(代码将移到单独的方法模块里):

      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- get the data object of its pivot points,
            CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
            if(pp==NULL)
               continue;
            //--- get the number of coordinate points the object is attached to
            int num=pp.GetNumLinkedCoords();
            //--- In the loop by the object coordinate points,
            for(int j=0;j<num;j++)
              {
               //--- get the number of coordinate points of the base object for setting the X coordinate
               int numx=pp.GetBasePivotsNumX(j);
               //--- In the loop by each coordinate point for setting the X coordinate,
               for(int nx=0;nx<numx;nx++)
                 {
                  //--- get the property for setting the X coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyX(j,nx);
                  int modifier_from=pp.GetPropertyModifierX(j,nx);
                  this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
                 }
               //--- Get the number of coordinate points of the base object for setting the Y coordinate
               int numy=pp.GetBasePivotsNumY(j);
               //--- In the loop by each coordinate point for setting the Y coordinate,
               for(int ny=0;ny<numy;ny++)
                 {
                  //--- get the property for setting the Y coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyY(j,ny);
                  int modifier_from=pp.GetPropertyModifierY(j,ny);
                  this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
                 }
              }
            dep.PropertiesCopyToPrevData();
           }
         //--- Move reference control points to new coordinates
         if(ExtToolkit!=NULL)
           {
            for(int i=0;i<this.Pivots();i++)
              {
               ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
              }
            ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
            long   lparam=0;
            double dparam=0;
            string sparam="";
            ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }

添加调用新方法替代删除的模块:

      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- Set X and Y coordinates to all pivot points of a subordinate object and
            //--- save the current properties of a subordinate graphical object as the previous ones
            if(this.SetCoordsXYtoDependentObj(dep))
               dep.PropertiesCopyToPrevData();
           }
         //--- Move reference control points to new coordinates
         if(this.ExtToolkit!=NULL)
           {
            for(int i=0;i<this.Pivots();i++)
              {
               this.ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
              }
            this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
            long   lparam=0;
            double dparam=0;
            string sparam="";
            this.ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }


由于在当前点位置删除的逻辑意味着,如果光标未悬浮在任何窗体上,则会不断重新绘制每个窗体(这是次优且资源密集型的),故我们来实现检查,仅在窗体需要重绘,但控制点仍然存在时,才会实际删除控制点,并重绘窗体。 此外,我将用新的对象类型替换窗体对象类型

//+------------------------------------------------------------------+
//| Redraw the form for managing a control point                     |
//| of an extended standard graphical object                         |
//+------------------------------------------------------------------+
void CGStdGraphObj::RedrawControlPointForms(const uchar opacity,const color clr)
  {
//--- Leave if the object has no toolkit of an extended standard graphical object
   if(this.ExtToolkit==NULL)
      return;
//--- Get the number of pivot point management 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
      CFormControl *form=this.ExtToolkit.GetControlPointForm(i);
      if(form==NULL)
         continue;
      //--- Draw a point and a circle with a specified non-transparency and color
      //--- If a point should be completely transparent (deleted)
      //--- and the form still has the point, delete the point,
      if(opacity==0 && form.IsControlAlreadyDrawn())
         this.ExtToolkit.DrawControlPoint(form,0,clr);
      //--- otherwise, draw the point with a specified non-transparency and color
      else
         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 new coordinates for the pivot point specified in 'modifier'
   if(!this.SetTimePrice(x,y,modifier))
      return false;
//--- If the object is not a composite graphical object
//--- or if subordinate graphical objects are not attached to the object,
//--- there is nothing else to 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 pivot point data of the bound 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 subordinate 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 subordinate 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 a subordinate graphical object as the previous ones
   dep.PropertiesCopyToPrevData();
//--- Move a reference control point to new coordinates
   this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier);
   this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
//--- If the flag is active, redraw the chart
   if(redraw)
      ::ChartRedraw(m_chart_id);
//--- All is successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+

现在,该方法更加简洁:

//+----------------------------------------------------------------------+
//| 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 new coordinates for the pivot point specified in 'modifier'
   if(!this.SetTimePrice(x,y,modifier))
      return false;
//--- If the object is a composite graphical object,
//--- and subordinate graphical objects are attached to the object
   if(this.ExtToolkit!=NULL && this.m_list.Total()>0)
     {
      //--- Get the graphical object bound to the 'modifier' point
      CGStdGraphObj *dep=this.GetDependentObj(modifier);
      if(dep==NULL)
         return false;
      //--- Set X and Y coordinates to all pivot points of a subordinate object and
      //--- save the current properties of a subordinate graphical object as the previous ones
      if(this.SetCoordsXYtoDependentObj(dep))
         dep.PropertiesCopyToPrevData();
     }
//--- Move a reference control point to new coordinates
   this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier);
   this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
//--- If the flag is active, redraw the chart
   if(redraw)
      ::ChartRedraw(m_chart_id);
//--- All is successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+


实现为指定从属对象的相应轴点设置 X 和 Y 坐标的方法:

//+------------------------------------------------------------------+
//| Set X and Y coordinates to the associated pivot point            |
//| of a specified subordinate object by index                       |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index)
  {
//--- get the number of coordinate points of the base object for setting the X coordinate
   int numx=pivot_point.GetBasePivotsNumX(index);
//--- 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 subordinate graphical object attached to the 'index' point
      int prop_from=pivot_point.GetPropertyX(index,nx);
      int modifier_from=pivot_point.GetPropertyModifierX(index,nx);
      this.SetCoordXToDependentObj(dependent_obj,prop_from,modifier_from,nx);
     }
//--- Get the number of coordinate points of the base object for setting the Y coordinate
   int numy=pivot_point.GetBasePivotsNumY(index);
   //--- 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 subordinate graphical object attached to the 'index' point
      int prop_from=pivot_point.GetPropertyY(index,ny);
      int modifier_from=pivot_point.GetPropertyModifierY(index,ny);
      this.SetCoordYToDependentObj(dependent_obj,prop_from,modifier_from,ny);
     }
  }
//+------------------------------------------------------------------+

事实上,这些正是从类方法中删除的代码块。 在之前的文章中,我已研究过该代码逻辑。 此外,在代码注释中也对其进行了说明。 因此,我认为,这不需要任何解释。

为指定从属对象的关联轴点设置 X 和 Y坐标的方法实现

//+------------------------------------------------------------------+
//| Set X and Y coordinates to associated pivot points               |
//| of the specified subordinate object                              |
//+------------------------------------------------------------------+
bool CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj)
  {
//--- Get the object of pivot point data of the bound graphical object
   CLinkedPivotPoint *pp=dependent_obj.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,
//--- set X and Y to all pivot points of a subordinate object
   for(int j=0;j<num;j++)
      this.SetCoordsXYtoDependentObj(dependent_obj,pp,j);
   return true;
  }
//+------------------------------------------------------------------+

该方法能够为从属对象的所有轴点设置坐标。 如果把其它图形对象添加到复合图形对象中,该方法可为它们设置指定的坐标。


我们在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh中的图形元素集合类里加入改进。

由于 ChartTimePriceToXY() 标准函数一次性返回两个坐标 — X 和 Y,我将在私密部分中创建结构来存储它们。 除了坐标外,结构还将存储相对于中心点的坐标偏移。 鉴于图形对象也许会有多个轴点,故需声明一个所创建结构类型的数组,从而能存储所有图形对象的每个轴点坐标。 在这种情况下,数组的每个单元都含有从“时间/价格”坐标转换而来的 X 和 Y 屏幕坐标,以及相对于图形对象中心点的轴点坐标的偏移值。

在类的私密部分,创建结构,并声明所需数组:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
class CGraphElementsCollection : public CBaseObj
  {
private:
   //--- Pivot point data structure
   struct SDataPivotPoint
     {
      public:
         int         X;                         // Pivot point X coordinate
         int         Y;                         // Pivot point Y coordinate
         int         ShiftX;                    // Pivot point X coordinate shift from the central one
         int         ShiftY;                    // Pivot point Y coordinate shift from the central one
     };
   SDataPivotPoint   m_data_pivot_point[];      // Pivot point data structure array
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   CArrayObj         m_list_deleted_obj;        // List of removed graphical objects
   CMouseState       m_mouse;                   // "Mouse status" class object
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check

在类的私密部分中,声明返回图形对象每个轴点的屏幕坐标的方法

private:
//--- Find an object present in the collection but not on a chart
   CGStdGraphObj    *FindMissingObj(const long chart_id);
   CGStdGraphObj    *FindMissingObj(const long chart_id,int &index);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object class object from the graphical object collection list: (1) specified object, (2) by chart ID
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   void              DeleteGraphObjectsFromList(const long chart_id);
//--- Move the graphical object class object to the list of removed graphical objects: (1) specified object, (2) by index
   bool              MoveGraphObjToDeletedObjList(CGStdGraphObj *obj);
   bool              MoveGraphObjToDeletedObjList(const int index);
//--- Move all objects by chart ID to the list of removed graphical objects
   void              MoveGraphObjectsToDeletedObjList(const long chart_id);
//--- Remove the object of managing charts from the list
   bool              DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj);
//--- Set the flags of scrolling the chart with the mouse, context menu and crosshairs tool for the specified chart
   void              SetChartTools(const long chart_id,const bool flag);
//--- Return the screen coordinates of each pivot point of the graphical object
   bool              GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]);
public:

在类主体之外,实现以下方法:

//+------------------------------------------------------------------+
//| Return screen coordinates                                        |
//| of each graphical object pivot point                             |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[])
  {
//--- 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             : break;
      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 shifts of all pivot points from the central one and write them to the structure array
        array_pivots[0].ShiftX=(array_pivots[1].X-array_pivots[0].X)/2; 
        array_pivots[0].ShiftY=(array_pivots[1].Y-array_pivots[0].Y)/2;
        array_pivots[1].ShiftX=(array_pivots[0].X-array_pivots[1].X)/2;
        array_pivots[1].ShiftY=(array_pivots[0].Y-array_pivots[1].Y)/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;
      //--- Graphical objects with time/price coordinates
      case OBJ_TEXT              : break;
      case OBJ_BITMAP            : break;
      //---
      default: break;
     }
   return false;
  }
//+------------------------------------------------------------------+

对于目前,在结构中仅设置拥有两个轴心点和一个中心点的图形对象的屏幕坐标。

该方法接收指向图形对象的指针,该图形对象的轴点坐标应设置在结构数组之中,该结构数组也通过引用链接传递给该方法。 在坐标转换成功的情况下,该方法将返回 true ,且结构数组里会填充全部图形对象的每个轴点屏幕坐标。 如若失败,该方法将返回 false

在类的事件处理程序中,我们需要处理对象管理窗体的位移,以便该处是中心点的情况下整体移动对象。 为了达此目标,我们需要计算其窗体边缘相对于中心窗体的偏移(用于拖动对象),并依据在结构中计算和设置的偏移值重新定位两个轴点。 因此,其所有轴心点的移动值将与鼠标拖动的中心轴心点的移动值相同。

我们来添加相对此类中心控制点(窗体)移动事件的处理:

//+------------------------------------------------------------------+
//| 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 index of the form for 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 not within an 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 one-click trading panel is not present on the chart,
               if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK))
                 {
                  //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel button
                  if(y<17 && x<41)
                     y=17;
                 }
               //--- If the one-click trading panel is on the chart,
               else
                 {
                  //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel
                  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 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 has been received,
                     if(ext!=NULL)
                       {
                        //--- get the object type
                        ENUM_OBJECT type=ext.GraphObjectType();
                        //--- If the object is built using 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 built based on time/price coordinates,
                        else
                          {
                           //--- calculate the coordinate shift
                           int shift=(int)::ceil(form.Width()/2)+1;
                           //--- If the form is located on one of the graphical object pivot points,
                           if(form_index<ext.Pivots())
                             {
                              //--- limit the form coordinates so that they do not move beyond the chart borders
                              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 the calculated coordinates to the object
                              ext.ChangeCoordsExtendedObj(x+shift,y+shift,form_index);
                             }
                           //--- 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
                                    if(x+shift-this.m_data_pivot_point[i].ShiftX<0)
                                       x=-shift+m_data_pivot_point[i].ShiftX;
                                    if(x+shift+this.m_data_pivot_point[i].ShiftX>chart_width)
                                       x=chart_width-shift-this.m_data_pivot_point[i].ShiftX;
                                    if(y+shift+this.m_data_pivot_point[i].ShiftY<0)
                                       y=-shift-this.m_data_pivot_point[i].ShiftY;
                                    if(y+shift-this.m_data_pivot_point[i].ShiftY>chart_height)
                                       y=chart_height-shift+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);
                                   }
                                }
                             }
                          }
                       }
                    }
                 }
              }
            //--- 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 form for managing the pivot point of an extended graphical object,
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by its ID and by the chart ID
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of an extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a point with a circle on the form and delete it on all other forms
                        toolkit.DrawOneControlPoint(form);
                       }
                    }
                 }
              }
            //--- '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;    // disable the flag of pressing on the form
                 }
              }
            //--- '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 form for managing the pivot point of an extended graphical object,
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by its ID and by the chart ID
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of an extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a point with a circle on the form and delete it on all other forms
                        toolkit.DrawOneControlPoint(form);
                       }
                    }
                 }
              }
            //--- '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\Part99\ 当中,命名为 TestDoEasyPart99.mq5

我不必在 EA 里插入任何更改 — 针对目前,所有更改都只在函数库类中完成。

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


正如我们所见,如果我们在创建复合图形对象的窗体中移动该对象,那么超过图表界限部分,所有相关的限制都会正常工作。 但如果我们相对于其初始位置“反转”轴点位置,则当轴点超出图表界限时,对象“配置”就会失真。 这意味着超出图表右、左、上或下边缘的界限,则针对轴点限制计算不正确。
这并不奇怪,因为轴点偏移是相对于中心点进行计算的,这意味着该点将发生正偏移,而第二个点将产生负偏移。 当改变轴点相对于中心点的位置时,我们会面临有限的计算误差。 我将在下一篇文章中解决这个问题。


下一步是什么?

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

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

返回内容目录

*该系列的前几篇文章:

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