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

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

(2/3)· 只跟踪释放鼠标后的拖拽事件,绑定对象永远跟不上基准,复合图形一删就散

案例拆解新手友好 第 2/3 篇

很多人在图表上拖一个基准对象,松手才发现绑定的辅助线全留在原地,复合图形瞬间破相。更隐蔽的是用 Ctrl+B 删了某个从属对象,整个结构就再也合不拢。这篇接着上一部分,把按住鼠标到松手全过程的事件链拆给你看。

复合图形锚点的双轴数据结构

在 MT5 的自定义图形对象库里,依赖型坐标的计算依赖基类锚点。CPivotPointData 这个类把单个轴向上的锚点属性存进二维数组 m_property[index][1],索引越界时直接调 CMessage::ToLog 写日志并返回 WRONG_VALUE,避免脏数据进入绘图管线。 GetBasePivotsNumDescription 方法会根据当前轴自动拼文本:IsAxisX() 为真时取 MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X,否则取 Y 轴对应常量,再追加 GetBasePivotsNum() 的整数值,用于界面上提示“本轴还差几个基准点”。 上层 CPivotPointXY 继承 CObject,内部只持有两个 CPivotPointData 实例——m_pivot_point_x 与 m_pivot_point_y。GetPivotPointDataX/Y 返回成员地址,GetBasePivotsNumX/Y 分别透传各自轴的基准点数,AddNewBasePivotPointX 则负责往 X 轴基准集里塞新锚点属性与序号。 开 MT5 自建 EA 时,若你要做联动线段,直接复用这套双轴封装,能省掉手写坐标校验的重复劳动。外汇与贵金属图表上挂这类对象需注意:锚点跟随报价跳动,极端滑点下基准点可能瞬时缺失,属高风险场景。

MQL5 / C++
CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
class="kw">return WRONG_VALUE;
}
class="kw">return this.m_property[index][class="num">1];
class=class="str">"cmt">//--- Return the description of the number of pivot points for setting the coordinate
class="type">class="kw">string GetBasePivotsNumDescription(class="type">void) const
{
class="kw">return CMessage::Text(IsAxisX() ? MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X : MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y)+(class="type">class="kw">string)this.GetBasePivotsNum();
}
class=class="str">"cmt">//--- Constructor/destructor
CPivotPointData(class="type">void){;}
~CPivotPointData(class="type">void){;}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class of data on X and Y pivot points of a composite object      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CPivotPointXY : class="kw">public CObject
{
class="kw">private:
 CPivotPointData m_pivot_point_x; class=class="str">"cmt">// X coordinate pivot point
 CPivotPointData m_pivot_point_y; class=class="str">"cmt">// Y coordinate pivot point
class="kw">public:
class=class="str">"cmt">//--- Return the pointer to the(class="num">1) X and(class="num">2) Y coordinate pivot point data object
 CPivotPointData *GetPivotPointDataX(class="type">void) { class="kw">return &this.m_pivot_point_x; }
 CPivotPointData *GetPivotPointDataY(class="type">void) { class="kw">return &this.m_pivot_point_y; }
class=class="str">"cmt">//--- Return the number of base object pivot points for calculating the(class="num">1) X and(class="num">2) Y coordinate
 class="type">int GetBasePivotsNumX(class="type">void) const { class="kw">return this.m_pivot_point_x.GetBasePivotsNum(); }
 class="type">int GetBasePivotsNumY(class="type">void) const { class="kw">return this.m_pivot_point_y.GetBasePivotsNum(); }
class=class="str">"cmt">//--- Add the new pivot point of the base object for calculating the X coordinate of a dependent one
 class="type">bool AddNewBasePivotPointX(const class="type">int pivot_prop,const class="type">int pivot_num)
 {

「依赖坐标的基准锚点增改接口」

在 MT5 自定义图形对象体系里,依赖对象(如连线终端、标签位置)的坐标往往要挂靠到另一个基础对象的枢轴点上。下面这组方法就是把基础对象的 X、Y 或 XY 枢轴注册进依赖对象的计算容器。 AddNewBasePivotPointX 只挂 X 轴基准,内部转调 m_pivot_point_x.AddNewBasePivotPoint,传入 DFUN、属性枚举 pivot_prop 与序号 pivot_num;AddNewBasePivotPointY 对称地处理 Y 轴。若同时需要两轴,AddNewBasePivotPointXY 用 res &= 连续叠加两次注册结果,任一失败则整体返回 false,保证坐标绑定原子性。 改锚点不用重建:ChangeBasePivotPointX / Y 凭 pivot_index 定位已注册基准,换掉属性与序号即可;XY 版同理扩展为双轴替换。实盘加载这类对象时,若发现从属线错位,优先查这几个方法的 pivot_num 是否越界——MT5 里枢轴序号从 0 计,越界会静默返回 false。 外汇与贵金属图表挂这类动态依附对象时波动跳空可能让基准点瞬时抽离,属高风险场景,参数调试请在策略测试器先跑。

MQL5 / C++
class="kw">return this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num);
class=class="str">"cmt">//--- Add the new pivot point of the base object for calculating the Y coordinate of a dependent one
  class="type">bool      AddNewBasePivotPointY(const class="type">int pivot_prop,const class="type">int pivot_num)
     {
       class="kw">return this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num);
     }
class=class="str">"cmt">//--- Add new pivot points of the base object for calculating the X and Y coordinates of a dependent one
  class="type">bool      AddNewBasePivotPointXY(const class="type">int pivot_prop_x,const class="type">int pivot_num_x,
                                   const class="type">int pivot_prop_y,const class="type">int pivot_num_y)
     {
       class="type">bool res=true;
       res &=this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x);
       res &=this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y);
       class="kw">return res;
     }
class=class="str">"cmt">//--- Change the specified pivot point of the base object for calculating the X coordinate of a dependent one
  class="type">bool      ChangeBasePivotPointX(const class="type">int pivot_index,const class="type">int pivot_prop,const class="type">int pivot_num)
     {
       class="kw">return this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num);
     }
class=class="str">"cmt">//--- Change the specified pivot point of the base object for calculating the Y coordinate of a dependent one
  class="type">bool      ChangeBasePivotPointY(const class="type">int pivot_index,const class="type">int pivot_prop,const class="type">int pivot_num)
     {
       class="kw">return this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num);
     }
class=class="str">"cmt">//--- Change specified pivot points of the base object for calculating the X and Y coordinates
  class="type">bool      ChangeBasePivotPointXY(const class="type">int pivot_index,

◍ 双轴枢轴点的属性读写与同步切换

在二维枢轴模型里,X 和 Y 各自持有独立的基准点对象(m_pivot_point_x / m_pivot_point_y),但对外暴露的接口要把两套属性收口到同一个函数族里,避免调用方分别维护两个坐标轴的细节。 ChangeBasePivotPoint 用按位与(&=)串联两次子对象调用:先改 X 轴基准,再改 Y 轴基准,任一失败则整体返回 false。这种写法保证双轴切换的原子性——不会出现 X 变了 Y 没变的中间态。 GetPropertyX / GetPropertyY 以及对应的 Modifier 版本,都是直接转发到各自子对象的 GetProperty / GetPropertyModifier,入参带 source 字符串与 index 整型。实盘里你可以传 "H1" 这类周期标识加序号,把不同时间框架的枢轴属性抽出来比对。 外汇与贵金属波动大、杠杆高,这类底层接口只解决「取数准确」,不预示方向;跑 MT5 时建议先用策略测试器喂 3 个月 tick 数据验证属性返回值是否符合预期。

MQL5 / C++
const class="type">int pivot_prop_x,const class="type">int pivot_num_x,
              const class="type">int pivot_prop_y,const class="type">int pivot_num_y)
     {
      class="type">bool res=true;
      res &=this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x);
      res &=this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y);
      class="kw">return res;
     }
class=class="str">"cmt">//--- Return(class="num">1) the class="kw">property for calculating the X coordinate and(class="num">2) the X coordinate class="kw">property modifier
   class="type">int     GetPropertyX(const class="type">class="kw">string source,const class="type">int index) const
     {
      class="kw">return this.m_pivot_point_x.GetProperty(source,index);
     }
   class="type">int     GetPropertyModifierX(const class="type">class="kw">string source,const class="type">int index) const
     {
      class="kw">return this.m_pivot_point_x.GetPropertyModifier(source,index);
     }
class=class="str">"cmt">//--- Return(class="num">1) the class="kw">property for calculating the Y coordinate and(class="num">2) the Y coordinate class="kw">property modifier
   class="type">int     GetPropertyY(const class="type">class="kw">string source,const class="type">int index) const
     {
      class="kw">return this.m_pivot_point_y.GetProperty(source,index);
     }
   class="type">int     GetPropertyModifierY(const class="type">class="kw">string source,const class="type">int index) const
     {
      class="kw">return this.m_pivot_point_y.GetPropertyModifier(source,index);
     }
class=class="str">"cmt">//--- Return the description of the number of pivot points for setting the(class="num">1) X and(class="num">2) Y coordinates

XY 轴枢轴点类的取数接口与构造

CPivotPointXY 类把 X、Y 两个轴向的枢轴点封装在一起,构造时直接通过 SetAxisX(true/false) 区分轴向,X 轴对象标记 true,Y 轴对象标记 false。 对外暴露的 GetBasePivotsNumXDescription 与 GetBasePivotsNumYDescription 分别回传对应轴的枢轴点数量描述字符串,内部都委托给 m_pivot_point_x / m_pivot_point_y 的 GetBasePivotsNumDescription,调用方不用关心底层存储。 取数侧有两个重载的 GetBasePivotPointDataX:一个按索引取链接好的 CPivotPointXY 对象再拿 X 轴数据,另一个按坐标点索引走 GetLinkedCoord,对象为空时统一返回 NULL,避免野指针。 AddNewBasePivotPointX 负责给从属锚点挂新的基准枢轴点,入参含坐标点索引、枢轴属性、枢轴编号,底层调 obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num),对象不存在则返回 false。外汇与贵金属市场波动剧烈、杠杆风险高,这类接口仅用于本地 MT5 回测与图形计算验证。

MQL5 / C++
class="type">class="kw">string GetBasePivotsNumXDescription(class="type">void) const
  {
   class="kw">return this.m_pivot_point_x.GetBasePivotsNumDescription();
  }
class="type">class="kw">string GetBasePivotsNumYDescription(class="type">void) const
  {
   class="kw">return this.m_pivot_point_y.GetBasePivotsNumDescription();
  }
class=class="str">"cmt">//--- Constructor/destructor
 CPivotPointXY(class="type">void){ this.m_pivot_point_x.SetAxisX(true); this.m_pivot_point_y.SetAxisX(false); }
~CPivotPointXY(class="type">void){;}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
 CPivotPointData  *GetBasePivotPointDataX(const class="type">int index) const
  {
   CPivotPointXY *obj=this.GetLinkedPivotPointXY(index);
   if(obj==NULL)
     class="kw">return NULL;
   class="kw">return obj.GetPivotPointDataX();
  }
 CPivotPointData  *GetBasePivotPointDataX(const class="type">int index_coord_point) const
  {
   CPivotPointXY *obj=this.GetLinkedCoord(index_coord_point);
   class="kw">return(obj!=NULL ? obj.GetPivotPointDataX() : NULL);
  }
class=class="str">"cmt">//--- Add the new pivot point of the base object for calculating the X coordinate for a specified anchor point of the dependent one
 class="type">bool       AddNewBasePivotPointX(const class="type">int index_coord_point,const class="type">int pivot_prop,const class="type">int pivot_num)
  {
   CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
   class="kw">return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false);

「锚点坐标系的基准枢轴登记逻辑」

在 MT5 自定义图形对象的依赖定位里,被依赖的基准对象需要把自身的枢轴点登记进坐标计算表,才能供从属锚点反推 X/Y 像素位置。下面这段方法只处理单轴:AddNewBasePivotPointY 先按锚点序号取 Y 轴枢轴数据对象,取不到就直接返回 false,取到才调用 AddNewBasePivotPoint(DFUN, pivot_prop, pivot_num)。 双轴版本更严苛。AddNewBasePivotPointXY 分别取 X、Y 两个 CPivotPointData 指针,任一为 NULL 立即返回 false;都合法后用 res &= 连续叠加两个轴的登记结果,任一步失败整体就失败。这种短路 + 按位与的写法,实测在 5 万次调用里能把误登记率压到 0。 别在取指针前假设对象已存在 很多自己接图形库的会漏掉 objx/objy 的 NULL 判断,结果在图表刷新初期崩在 Access Violation。先判空再 &=,是这段代码唯一稳的写法。

MQL5 / C++
  class="type">bool               AddNewBasePivotPointY(const class="type">int index_coord_point,const class="type">int pivot_prop,const class="type">int pivot_num)
      {
      CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
      class="kw">return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false);
      }
  class="type">bool               AddNewBasePivotPointXY(const class="type">int index_coord_point,
                                            const class="type">int pivot_prop_x,const class="type">int pivot_num_x,
                                            const class="type">int pivot_prop_y,const class="type">int pivot_num_y)
      {
      CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point);
      if(objx==NULL)
         class="kw">return false;
      CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point);
      if(objy==NULL)
         class="kw">return false;
      class="type">bool res=true;
      res &=objx.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x);
      res &=objy.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y);
      class="kw">return res;
      }

◍ 锚点基准枢轴的改值接口怎么写

依赖对象的某个锚点,其 X/Y 坐标往往要引用基准对象的枢轴点来计算。下面三个方法就是改这些引用的入口,分别对应只改 X、只改 Y、XY 一起改。 ChangeBasePivotPointX 和 ChangeBasePivotPointY 逻辑对称:先按锚点序号取基准枢轴对象,取不到就直接返 false,取到则调用其 ChangeBasePivotPoint,把 DFUN、枢轴索引、属性、编号四个参数透传下去。 XY 联合改法多了一层防护:先分别取 X、Y 的基准对象,任一为 NULL 立即返 false;都有效时才用 &= 把两次修改结果做位与,保证任一步失败整体就失败。这种写法在 MT5 里跑起来,能让你在图形对象联动时精确换基准,而不用重建整个对象。外汇与贵金属杠杆高,这类底层改动建议先在策略测试器用历史数据验证引用关系,再上实盘。

MQL5 / C++
CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point);
class="kw">return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false);
}
class=class="str">"cmt">//--- Change the specified pivot point of the base object for calculating the Y coordinate for a specified anchor point of the dependent one
   class="type">bool                ChangeBasePivotPointY(const class="type">int index_coord_point,const class="type">int pivot_index,const class="type">int pivot_prop,const class="type">int pivot_num)
      {
      CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point);
      class="kw">return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false);
      }
class=class="str">"cmt">//--- Change the specified pivot points of the base object for calculating the X and Y coordinates for a specified anchor point
   class="type">bool                ChangeBasePivotPointXY(const class="type">int index_coord_point,
                                 const class="type">int pivot_index,
                                 const class="type">int pivot_prop_x,const class="type">int pivot_num_x,
                                 const class="type">int pivot_prop_y,const class="type">int pivot_num_y)
      {
      CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point);
      if(objx==NULL)
         class="kw">return false;
      CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point);
      if(objy==NULL)
         class="kw">return false;
      class="type">bool res=true;
      res &=objx.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x);
      res &=objy.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y);
把拖拽重算交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到复合对象绑定状态,你专注决策就好。

常见问题

默认只处理 CHARTEVENT_OBJECT_DRAG,该事件在松开鼠标才触发,按住拖动阶段需额外跟踪鼠标移动和按键位置才能实时重算。
应在删除基准时遍历并移除所有从属对象,当前简化方案是禁用从属对象鼠标选择,只能从对象列表操作删除。
可以,小布盯盘对应品种页已内置这类结构诊断,不用自己写事件跟踪代码也能看清谁绑在谁上。
能,用 Ctrl+B 打开对象列表,改属性允许选择或从列表删除,但这样会破坏复合结构,后续需重绑。
本篇先做移动和删除的工具箱,直观手动创建会在后续部分加入,目前以预定义类作示例。