DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象(基础篇)
📘

DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象(基础篇)

第 1/3 篇

复合图形对象的拖拽与清除

DoEasy 函数库在 2022 年 4 月的迭代里,把复合图形对象(由多个基础图元组合的自定义对象)的移动与删除逻辑下沉到了库类层,调用方不再需要逐个遍历子对象做坐标偏移。 之前要在图表上拖动一组关联线条,得自己写循环去重设每个子对象的 anchor 点;现在库内部维护了复合对象的拓扑关系,移动时只改父容器坐标,子对象跟随刷新。 删除动作同理:对复合对象调一次销毁接口,库会递归清理所有派生图元,避免残留匿名对象占用图表资源。外汇与贵金属图表高频重绘场景下,这种封装能明显降低脚本卡顿概率,但 MT5 自定义对象本就存在多品种切换时状态不同步的高风险,验证前先在 demo 账户跑。

◍ 复合图形对象的坐标重算与防破坏机制

MQL5 里扩展标准图形对象构成复合体后,基准对象的坐标变化必须带动从属对象同步移动。原生 CHARTEVENT_OBJECT_DRAG 只在鼠标松开时触发一次,这意味着拖拽过程中若不额外跟踪鼠标按下和移动,从属对象会停在旧位置,直到松手才瞬间跳到新锚点。 要实时跟随,就得自己捕获鼠标按钮在基准对象锚点(或中心)按下的状态,并在移动过程中持续重算从属对象坐标;最终在 CHARTEVENT_OBJECT_DRAG 里固化基准坐标,再批量更新绑定对象。当前版本我直接禁用了从属对象的鼠标选择,删复合体只能选基准对象删,避免误拆。 风险点在 Ctrl+B 对象列表:用户仍能手动把某个绑定对象设为可选或直接删掉,复合体就会残缺。外汇与贵金属图表上跑这类脚本属于高风险操作,参数误设可能导致图形层错乱,建议先在模拟图表验证。 后续会补上“有意破坏”处理——任意绑定对象被删时,整体复合对象全部清除,而不是留半截。

「图形对象库的底层类名与绑定逻辑重整」

在 DoEasy 库的路径 \MQL5\Include\DoEasy\Objects\Graph\Standard\ 下,抽象标准图形对象类 GStdGraphObj.mqh 做了几处务实修改。所有衍生类在显示对象简述的方法里保留了未使用的输入参数,但新增了一个破折号标志:当传入 true 时,返回的短名前会加一个连字符,方便在题头下枚举对象名。 从属对象轴点数据类里,原本声明为 m_property_x[][2] 的坐标数组被改名为 m_property[][2]——作者实验后放弃分 X/Y 两个类各存数组的思路,但数组名残留不正确,这次顺手修正。该类公开方法现在能显示坐标轴名称、返回属性、返回属性修饰符,以及输出多个基准轴点描述用于调试。 复合对象轴点类把 CreateNewLinkedPivotPoint() 重命名为 CreateNewLinkedCord(),原因是原意混淆:轴点(pivot)只是基准对象 X 或 Y 的定位参考,可多个叠加算出一个坐标点(cord),方法实际是加坐标点而非加轴点。同时用三元运算符压缩了若干方法代码,逻辑不变。 绑定数据类给从属对象加了新的属性设置入口:取基准对象指定属性的 X 坐标(整数,时间或像素)设给从属对象;Y 坐标则需判断整数(像素)或实数(价格)分别赋值。新对象默认 selection=false 且类型标为“扩展标准图形对象”,鼠标不可选,只能按基准类型与名称编程选取。 消息索引里新增了两条删除失败提示与两条轴点数量提示(见代码高亮段),用于运行时排查从列表/图表删对象失败,以及输出计算 X/Y 所用的基准轴点数。趋势线有两个锚点,目前属性方法只能整列打印,作者预留了索引默认值,后续可删默认值并加错误处理来按锚点选择性显示。

MQL5 / C++
class=class="str">"cmt">//--- CGraphElementsCollection
  MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,       class=class="str">"cmt">// Failed to get the list of newly added objects
  MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,     class=class="str">"cmt">// Failed to remove a graphical object from the list
  MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_LIST,     class=class="str">"cmt">// Failed to remove a graphical object from the list
  MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART,    class=class="str">"cmt">// Failed to remove a graphical object from the chart
  MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST,      class=class="str">"cmt">// Failed to set a graphical object to the list of removed objects
  MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST,      class=class="str">"cmt">// Failed to set a graphical object to the list of renamed objects
class=class="str">"cmt">//--- CLinkedPivotPoint
  MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X,            class=class="str">"cmt">// Not a single pivot point is set for the object along the X axis
  MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y,            class=class="str">"cmt">// Not a single pivot point is set for the object along the Y axis
  MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE,         class=class="str">"cmt">// The object is not attached to the basic graphical object
  MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ,   class=class="str">"cmt">// Failed to create a data object for the X and Y pivot points
  MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X,        class=class="str">"cmt">// Number of base object pivot points for calculating the X coordinate: 
  MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y,        class=class="str">"cmt">// Number of base object pivot points for calculating the Y coordinate: 

  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- CGraphElementsCollection
  {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
  {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},

图形对象报错与枢轴点数据类的内部构造

在自定义图形对象库里,删除和重命名操作都有明确的失败回报文本。比如「Failed to delete graphic object from the list」「Failed to place graphic object in the list of renamed objects」,这些字符串直接进日志,方便定位是列表维护出错还是图表层出错。 CLinkedPivotPoint 相关提示暴露了依赖对象的硬约束:对象若没设 X 轴或 Y 轴枢轴点,会报「The object does not have any pivot points set along the x-axis」;没挂到基对象则报「The object is not attached to the base graphical object」。这类错误在 MT5 加载 EA 调对象时大概率静默拖慢逻辑,建议开终端看 Experts 标签逐条核对。

PrintShort 方法把对象头、名称、GRAPH_OBJ_PROP_ID 属性和创建时间拼成一行日志。时间用 TimeToString 带 DATEMINUTESSECONDS 格式,精确到秒,便于回看某箭头是何时画的。

CPivotPointData 类用 m_axis_x 标记该枢轴点是否属于 X 坐标,m_property[][2] 存属性键值对。SetAxisX / IsAxisX 是一对极简接口,外部靠 bool 标志区分轴向,避免 X/Y 混淆。外汇与贵金属图表对象多受报价跳空影响,枢轴点绑定失误可能让画线偏移到异常价位,属高风险操作。

MQL5 / C++
class="type">void CGStdArrowBuyObj::PrintShort(const class="type">bool dash=false,const class="type">bool symbol=false)
  {
  ::Print(
      (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(class="type">class="kw">string)this.GetProperty(GRAPH_OBJ_PROP_ID,class="num">0),
      ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
    );
  }

class CPivotPointData
  {
class="kw">private:
  class="type">bool              m_axis_x;
  class="type">int               m_property[][class="num">2];
class="kw">public:
  class="type">void              SetAxisX(const class="type">bool axis_x)            { this.m_axis_x=axis_x;            }
  class="type">bool              IsAxisX(class="type">void)                const { class="kw">return this.m_axis_x;            }
  };

◍ 依赖坐标的基准锚点怎么管

在 MT5 自定义图形对象里,依赖对象的坐标往往要挂在一个或多个基准对象的枢轴上算出来。下面这段类方法就是专门维护「基准枢轴点数组」的,直接决定了依赖点能不能正确跟着基准动。 AxisDescription 只返回 "X" 或 "Y",靠 m_axis_x 布尔判断当前对象是横轴还是纵轴相关;GetBasePivotsNum 用 ArrayRange(m_property,0) 取第一维长度,也就是当前已登记的基准枢轴数量。 AddNewBasePivotPoint 先拿现有数量当新索引,若 ArrayResize 没能扩出 +1 的空间就写日志返回 false,否则转交 ChangeBasePivotPoint 去填值。ChangeBasePivotPoint 里若数组长度为 0 会按横纵轴分别报 MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X / Y 的错——说明没基准就别想改枢轴。 开 MT5 建个测试指标,把这俩方法挂到你的图形扩展类里,故意先不 Add 就调 Change,能在专家日志看到对应_axis 的空枢轴报错,验证数组逻辑没写反。

MQL5 / C++
class="type">class="kw">string AxisDescription(class="type">void) const { class="kw">return(this.m_axis_x ? "X" : "Y");}
class=class="str">"cmt">//--- Return the number of base object pivot points for calculating the coordinate of the dependent one
class="type">int GetBasePivotsNum(class="type">void) const { class="kw">return ::ArrayRange(this.m_property,class="num">0); }
class=class="str">"cmt">//--- Add the new pivot point of the base object for calculating the coordinate of a dependent one
class="type">bool AddNewBasePivotPoint(const class="type">class="kw">string source,const class="type">int pivot_prop,const class="type">int pivot_num)
  {
   class=class="str">"cmt">//--- Get the array size 
   class="type">int pivot_index=this.GetBasePivotsNum();
   class=class="str">"cmt">//--- if failed to increase the array size, inform of that and class="kw">return &class="macro">#x27;false&class="macro">#x27;
   if(::ArrayResize(this.m_property,pivot_index+class="num">1)!=pivot_index+class="num">1)
     {
      CMessage::ToLog(source,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      class="kw">return false;
     }
   class=class="str">"cmt">//--- Return the result of changing the values of a newly added new array dimension
   class="kw">return this.ChangeBasePivotPoint(source,pivot_index,pivot_prop,pivot_num);
  }
class=class="str">"cmt">//--- Change the specified pivot point of the base object for calculating the coordinate of a dependent one
class="type">bool ChangeBasePivotPoint(const class="type">class="kw">string source,const class="type">int pivot_index,const class="type">int pivot_prop,const class="type">int pivot_num)
  {
   class=class="str">"cmt">//--- Get the array size. If it is zero, inform of that and class="kw">return &class="macro">#x27;false&class="macro">#x27;
   class="type">int n=this.GetBasePivotsNum();
   if(n==class="num">0)
     {
      CMessage::ToLog(source,(this.IsAxisX() ? MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X : MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y));

「枢轴属性读写里的越界拦截」

这段收尾代码处理的是枢轴点属性数组的写入与读取,核心动作就两件:按索引存属性与修饰值,以及按索引往外取。写方法里先判 pivot_index 是否落在 0 到 n-1 之间,越界就写日志并返回 false,不碰数组。 读方法 GetProperty 与 GetPropertyModifier 逻辑对称,用 index 和 GetBasePivotsNum()-1 比边界,越界返回 WRONG_VALUE(宏值通常为负常数),正常则返回 m_property[index][0] 或 [1]。 这种越界前置判断在 MT5 自建枢轴库里很实用——实盘跑 EA 时若传入了错位索引,日志会直接报 MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY,而不是让终端抛数组越界异常挂掉。复制下面片段到你的类里,把 CMessage 换成 Print 就能先验证逻辑。

MQL5 / C++
        class="kw">return false;
            }
            class=class="str">"cmt">//--- If the specified index goes beyond the array range, inform of that and class="kw">return &class="macro">#x27;false&class="macro">#x27;
            if(pivot_index<class="num">0 || pivot_index>n-class="num">1)
              {
               CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
               class="kw">return false;
              }
            class=class="str">"cmt">//--- Set the values, passed to the method, in the specified array cells by index
            this.m_property[pivot_index][class="num">0]=pivot_prop;
            this.m_property[pivot_index][class="num">1]=pivot_num;
            class="kw">return true;
            }
class=class="str">"cmt">//--- Return(class="num">1) a class="kw">property and(class="num">2) a modifier of the class="kw">property from the array
   class="type">int   GetProperty(const class="type">class="kw">string source,const class="type">int index)   const
            {
            if(index<class="num">0 || index>this.GetBasePivotsNum()-class="num">1)
              {
               CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
               class="kw">return WRONG_VALUE;
              }
            class="kw">return this.m_property[index][class="num">0];  
            }
   class="type">int   GetPropertyModifier(const class="type">class="kw">string source,const class="type">int index) const
            {
            if(index<class="num">0 || index>this.GetBasePivotsNum()-class="num">1)
              {

常见问题

多半是缺少坐标重算与防破坏机制,拖拽后未同步子对象锚点。需在移动事件中重算基准锚点并刷新从属对象坐标。
清除逻辑要遍历绑定列表逐个删除子对象。用底层类名管理的绑定关系,循环释放所有从属资源即可。
可以。小布能扫描当前品种图上的复合对象绑定状态,提示锚点偏移或残留子对象,省去手动核对。
是索引越界。在枢轴属性读写里加边界拦截,访问前判断索引范围,超界直接返回空值不执行。
基准锚点建议绑定到固定时间坐标或枢轴点而非动态极值。重算时以该锚点为参照偏移子对象即可。