DoEasy 函数库中的图形(第九十部分):标准图形对象事件。 基本功能(基础篇)
📘

DoEasy 函数库中的图形(第九十部分):标准图形对象事件。 基本功能(基础篇)

第 1/3 篇

给标准图形对象挂上事件钩子

在 DoEasy 函数库里,标准图形对象(水平线、趋势线、矩形这类)现在能直接抛出事件,不用交易者再写轮询去判断对象有没有被拖拽或删除。这意味着 EA 可以监听对象变动并即时响应,比如线被挪动就重算支撑阻力。 原文示例标注于 2022 年 2 月 28 日发布,累计浏览 1572 次,说明这类底层封装在实际跑 MT5 的人里关注度不算低,但也不算爆款——属于硬核自用型功能。外汇和贵金属波动大、滑点频繁,用对象事件驱动下单前,务必在策略测试器里先验证事件触发延迟。 要验证这套机制,开 MT5 把库里标准图形对象事件类挂到一个空 EA 上,手动拖一根趋势线,看日志是否打出对象 ID 与变更类型。能跑通,再谈后续封装。

「用事件钩子盯住图形对象的变化」

在 MT5 里手动拖一条趋势线,程序若想实时知道它的价位变了,最笨的办法是每帧去读对象属性——这其实是资源密集型操作,官方文档也明确提示过频繁轮询会拖慢终端。 更好的落点是 OnChartEvent():终端本身就把图形对象的增删改都封装成了事件推给 EA。我们要做的,是在自己的函数库里补一层,让程序精确知道“哪个对象、哪个属性”动了,而不是瞎猜。 这一节先把对象级事件定下来:新建、改属性、重命名、删除、随图表窗口一起删除。这五类会直接进 OnChartEvent()。下一篇才逐个写处理器,现在只是把骨架立住。 重命名被单独拎出来,是因为它底层等价于“删旧对象+建新对象+改属性”一连串动作;拆成独立事件能省掉上层逻辑的判断分支。删除事件目前只能报“删了几个、叫什么名”,若连图表窗口一起关,暂时只知道被删对象的数量,具体属性暂不回溯——作者判断暂无强需求。

◍ 给图形对象事件追踪补上底层钩子

想在 MT5 里实时跟踪图形对象的选中、取消选中,光靠双击动作不够,得先在事件响应程序里注册新的事件 ID。当前图表直接加 ID 就能监听,跨图表则必须改事件控制指标,让新开的图表窗口自动挂上监听——否则你在副图上的对象操作主图指标根本收不到信号。 具体落地要动几个文件:EventControl.mq5 发新事件 ID;Data.mqh 加消息索引和文本;Defines.mqh 删掉多余的「对象重新定位事件」,因为属性更改枚举已覆盖;再补一个删除图形对象/图表的常数。注意枚举里下一个事件代码要跟着最后一个枚举值自动算,手改魔法数字迟早出 bug。 类构造上有个坑:CGStdGraphObj 构造函数从物理对象读属性写进类变量,但原有 Set 方法会无条件调 ObjectSetXXX 写回图表。图表同步慢,重复写是浪费。解决办法是在 CGBaseObj 的 Set 方法加 bool only_prop 参数——true 只写类变量,false 才先写图形对象再写变量。改完所有图形对象基类方法都照这个逻辑走,避免过度访问慢函数。 标准图形对象类里把设属性的方法全改成返回 bool,不然父类 bool 子类 void 在重载时提示会乱。以设子窗口索引为例:用已存的图表 ID 和对象名调基类方法,失败返 false,成功填属性返 true。设全周期可见性的方法也多接一个参数指定「仅属性还是连对象带属性」,逻辑一致。其余整数型属性接收方法同理,先写基类再填衍生类,避免取属性时读不到图表数据。 下面这段是事件控制指标的骨架,跨图表发事件时 InpChartSRC/InpChartDST 填图表 ID 即可验证:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                 EventControl.mq5 |
class=class="str">"cmt">//|                                  Copyright class="num">2021, MetaQuotes Ltd. |
class=class="str">"cmt">//|                             [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2021, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_plots class="num">0
class=class="str">"cmt">//--- input parameters
input class="type">long     InpChartSRC = class="num">0;
input class="type">long     InpChartDST = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- indicator shortname
   IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(class="type">class="kw">string)InpChartSRC+"_To#"+(class="type">class="kw">string)InpChartDST);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,

图表事件转发与对象控制指示器

在自定义指标里,OnCalculate 直接返回 rates_total 即可,系统用 prev_calculated 判断增量计算边界,省去手动维护柱数状态的麻烦。 OnChartEvent 负责把用户操作透传出去:当 id 等于 CHARTEVENT_OBJECT_CHANGE、CHARTEVENT_OBJECT_DRAG 或 CHARTEVENT_OBJECT_CLICK 时,调用 EventChartCustom 把事件转发到目标图表(InpChartDST),并带上源图表 ID(InpChartSRC)、数值 dparam 与字符串 sparam。这样跨图表联动画线、拖拽锚点就能被另一端的 EA 或指标捕获。 CGraphElementsCollection 的错误码暴露了内部链路:MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR 表示控制事件指示器已成功加载到图表,而 MSG_GRAPH_OBJ_FAILED_ADD_EVN_OBJ 意味着图形对象的事件对象没进列表。开 MT5 挂上这类指标后,从 Experts 日志搜这些前缀,能直接定位是建对象失败还是加指示器失败。

MQL5 / C++
const class="type">int prev_calculated,
const class="type">class="kw">datetime &time[],
const class="type">class="kw">double &open[],
const class="type">class="kw">double &high[],
const class="type">class="kw">double &low[],
const class="type">class="kw">double &close[],
const class="type">long &tick_volume[],
const class="type">long &volume[],
const class="type">int &spread[])
{
 class="kw">return rates_total;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//---
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK)
     {
       EventChartCustom(InpChartDST,(class="type">class="kw">ushort)id,InpChartSRC,dparam,sparam);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
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_CREATE_EVN_CTRL_INDICATOR,        class=class="str">"cmt">// Indicator for controlling and sending events created
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR, class=class="str">"cmt">// Failed to create the indicator for controlling and sending events
   MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR,         class=class="str">"cmt">// Indicator for controlling and sending events successfully added to the chart
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR,    class=class="str">"cmt">// Failed to add the indicator for controlling and sending events
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                    class=class="str">"cmt">// Chart windows closed:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,         class=class="str">"cmt">// Objects removed together with charts:
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_OBJ,            class=class="str">"cmt">// Failed to create the event object for a graphical object
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_OBJ,               class=class="str">"cmt">// Failed to add the event object to the list

「图形对象事件枚举与提示文本映射」

在 MT5 的自定义图形对象事件框架里,系统用一组枚举值区分对象生命周期动作:新建、属性变更、重命名、删除,以及随图表关闭被连带清除。代码里还单独列了 MOVE 事件,说明对象被拖拽位移也会触发独立回调,不只是属性改写。 配套的多语言字符串表把每个技术状态翻成可读提示,例如「New graphic object created」对应新建、「Graphic object deleted」对应删除。若图表被关,会同时输出「Closed chart windows: 」和「Objects removed with them: 」两条计数,便于回溯资源释放是否干净。 实操上,开 MT5 新建 EA 把下面枚举抄进工程,挂一个监控指标,就能在对象被手动拖动或改名时收到事件,而不是轮询 ObjectGetInteger。外汇与贵金属图表对象变动频繁,这类事件驱动写法能降低 CPU 占用,但信号滑点和高波动下的对象竞态仍属高风险场景,需自行压测。

MQL5 / C++
enum ENUM_GRAPH_OBJ_EVENT
  {
  GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,class=class="str">"cmt">// No event
  GRAPH_OBJ_EVENT_CREATE,                              class=class="str">"cmt">// "Creating a new graphical object" event
  GRAPH_OBJ_EVENT_CHANGE,                              class=class="str">"cmt">// "Changing graphical object properties" event
  GRAPH_OBJ_EVENT_MOVE,                                class=class="str">"cmt">// "Moving graphical object" event
  GRAPH_OBJ_EVENT_RENAME,                              class=class="str">"cmt">// "Renaming graphical object" event

◍ 图形对象事件枚举与背景标志设置

在 MT5 自定义图形对象封装里,事件类型用枚举 ENUM_GRAPH_OBJ_EVENT 管理,从 CHART_OBJ_EVENTS_NEXT_CODE 起算,依次覆盖创建、属性变更、重命名、删除,以及随图表窗口一起被移除的 GRAPH_OBJ_EVENT_DEL_CHART。 末尾用宏 GRAPH_OBJ_EVENTS_NEXT_CODE 定义为最后一个事件码加 1,给后续扩展事件留出了连续编号空间,避免和平台内建对象事件撞码。 SetFlagBack 方法控制对象是否置于背景层:传入 flag 设 OBJPROP_BACK,only_prop 为 true 时只改内存成员 m_back 不碰图表,为 false 才真正调用 ObjectSetInteger 下发到图表。 调用前先 ResetLastError,失败则走 CMessage::ToLog 把错误码写日志;外汇与贵金属图表对象交互属高频操作,图形事件回调异常可能漏掉关键价位提示,实盘前建议在策略测试器里跑一遍删除与换图事件。

MQL5 / C++
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,class=class="str">"cmt">// No event
   GRAPH_OBJ_EVENT_CREATE,                              class=class="str">"cmt">// "Creating a new graphical object" event
   GRAPH_OBJ_EVENT_CHANGE,                              class=class="str">"cmt">// "Changing graphical object properties" event
   GRAPH_OBJ_EVENT_RENAME,                              class=class="str">"cmt">// "Renaming graphical object" event
   GRAPH_OBJ_EVENT_DELETE,                              class=class="str">"cmt">// "Removing graphical object" event
   GRAPH_OBJ_EVENT_DEL_CHART,                           class=class="str">"cmt">// "Removing a graphical object together with the chart window" event
  };
class="macro">#define GRAPH_OBJ_EVENTS_NEXT_CODE(GRAPH_OBJ_EVENT_DEL_CHART+class="num">1) class=class="str">"cmt">// The code of the next event after the last graphical object event code
  class="type">bool SetFlagBack(const class="type">bool flag,const class="type">bool only_prop)
    {
     ::ResetLastError();
     if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_BACK,flag)) || only_prop)
       {
        this.m_back=flag;
        class="kw">return true;
       }
     else
       CMessage::ToLog(DFUN,::GetLastError(),true);
     class="kw">return false;
    }

常见问题

在对象创建后调用事件绑定函数把标准图形对象接入事件系统,拖动或修改时图表会触发对应事件,脚本里读事件类型即可实时响应。
用图形对象事件枚举里的删除事件做监听,触发时映射提示文本输出到日志或弹窗,就能在删除发生的那一刻收到通知。
可以,小布能接入图形对象事件钩子,把支撑线设为监听对象,触碰或变形时直接推送提醒,不用你自己盯盘。
多半是没开背景标志或事件转发没接好,检查对象事件枚举里的背景标志设置,并确认图表事件已转发到对象控制逻辑。
可以,用提示文本映射把事件枚举对应到中文描述,之后所有事件输出都会显示你设好的中文文案。