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

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

(2/3)· 在 MQL5 中持续测量图形对象属性是资源黑洞,事件化改造才是正解

进阶 第 2/3 篇
很多 EA 还在用定时器反复读取趋势线坐标,CPU 占用居高不下却毫无必要。终端早把图形对象变动封装成了 OnChartEvent 回调,只是多数人没在库层面接住它。本篇把这套事件骨架搭进 DoEasy,后续篇再填肉。

◍ 背景与选中状态的属性封装

在 MT5 自定义图形对象的类封装里,背景层级和可选中状态是两个高频切换的属性。把 OBJPROP_BACK 和 OBJPROP_SELECTED 用成员函数包一层,能避免每次手动调 ObjectSetInteger 时漏写 chart_id 和 name。 下面两段代码分别处理背景标志与选中标志,逻辑同构:先 ResetLastError 清错误码,再按 only_prop 决定要不要真去改图表对象。only_prop 为 true 时只改类内成员变量,不碰图表,适合批量预设。 调用失败会走 CMessage::ToLog 把 GetLastError 打进日志,返回 false;成功则同步 this.m_back / this.m_selected 并返回 true。实盘里若发现对象突然不能被鼠标点选,优先查 SetFlagSelected 的返回值而不是去图表上瞎找。 外汇与贵金属图表对象受品种波动影响,对象属性刷新存在毫秒级延迟,高频切换标志时建议只在 only_prop 模式下改内存态,择机一次性提交。

MQL5 / C++
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;
  }
class="type">bool SetFlagSelected(const class="type">bool flag,const class="type">bool only_prop)
  {
   ::ResetLastError();
   if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTED,flag)) || only_prop)
     {
      this.m_selected=flag;
      class="kw">return true;
     }
   else
     CMessage::ToLog(DFUN,::GetLastError(),true);
   class="kw">return false;
  }

「对象可选与隐藏标志的底层封装」

在 MT5 自定义图形对象类里,SetFlagSelectable 与 SetFlagHidden 这两个方法控制对象在终端里的交互属性。前者决定对象能否被鼠标点选,后者决定对象名是否出现在终端对象列表中,对清理图表 clutter 有直接作用。 两个方法都带 only_prop 参数。当 only_prop=true 时,函数跳过 ObjectSetInteger 的底层调用,只更新类内部成员变量(m_selectable / m_hidden),适合在对象尚未创建或批量预设属性时调用,避免无谓的终端报错。 注意 SetFlagHidden 的原文实现里有一处明显笔误:条件判断中写的是 OBJPROP_SELECTABLE 而非 OBJPROP_HIDDEN,这会导致隐藏标志实际未下发给终端。开 MT5 跑这段代码时,若发现对象名仍显示在列表里,优先查这一行。 ResetLastError 在每次调用前清零,失败时走 CMessage::ToLog 记录错误码并返回 false,调用方应检查返回值而非假设设置成功。外汇与贵金属图表对象受品种高波动影响,对象属性可能在 tick 密集时设置失败,需做容错。

MQL5 / C++
class="type">bool SetFlagSelectable(const class="type">bool flag,const class="type">bool only_prop)
  {
   ::ResetLastError();
   if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
     {
      this.m_selectable=flag;
      class="kw">return true;
     }
   else
     CMessage::ToLog(DFUN,::GetLastError(),true);
   class="kw">return false;
  }
class=class="str">"cmt">//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag
  class="type">bool SetFlagHidden(const class="type">bool flag,const class="type">bool only_prop)
  {
   ::ResetLastError();
   if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
     {
      this.m_hidden=flag;
      class="kw">return true;
     }
   else
     CMessage::ToLog(DFUN,::GetLastError(),true);
   class="kw">return false;
  }
class=class="str">"cmt">//--- Set the priority of a graphical object for receiving the event of clicking on a chart
  class="type">bool SetZorder(const class="type">long value,const class="type">bool only_prop)

图层叠放与多周期显隐的封装写法

在 MT5 自定义图形对象类里,Z 轴顺序和多周期可见性是两个容易被忽略、却直接决定图表观感的属性。下面这段封装把底层 ObjectSetInteger 调用包了一层,顺便用 only_prop 参数控制「只改内存成员、不碰图表」的轻量模式。 Z 轴顺序设置段:先 ResetLastError 清掉旧错误码,再判断——若不是仅改属性且 ObjectSetInteger 写 OBJPROP_ZORDER 成功,或处于 only_prop 模式,就把 m_zorder 赋值并返回 true;否则走 CMessage::ToLog 把 GetLastError 打进日志并返回 false。 多周期可见性那段逻辑同构:flag 为 true 时 value 取 OBJ_ALL_PERIODS(即 -1,覆盖全部周期),false 时取 OBJ_NO_PERIODS(0,全周期隐藏),通过 OBJPROP_TIMEFRAMES 写入。实盘里若只想让对象在 M5、H1 出现,应改用 SetVisibleOnTimeframes 传具体周期标志位,而不是这个全有全无的开关。 外汇与贵金属图表对象叠加密集时,Z 序乱掉会导致价格线被指标框吃掉,建议开 MT5 把 only_prop 先置 true 跑一遍单元测试,确认成员赋值无误再放开真实绘图。

MQL5 / C++
{
   ::ResetLastError();
   if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
     {
      this.m_zorder=value;
      class="kw">return true;
     }
   else
      CMessage::ToLog(DFUN,::GetLastError(),true);
   class="kw">return false;
   }
class=class="str">"cmt">//--- Set object visibility on all timeframes
   class="type">bool SetVisible(const class="type">bool flag,const class="type">bool only_prop)
   {
      class="type">long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
      ::ResetLastError();
      if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
        {
         this.m_visible=flag;
         class="kw">return true;
        }
      else
         CMessage::ToLog(DFUN,::GetLastError(),true);
      class="kw">return false;
   }
class=class="str">"cmt">//--- Set visibility flags on timeframes specified as flags
   class="type">bool SetVisibleOnTimeframes(const class="type">int flags,const class="type">bool only_prop)
   {

◍ 给图形对象挑哪些周期露脸

MT5 里画好的对象默认在所有周期都显示,实战中往往只想让它在 M5、H1 这类关键周期出现,避免小周期图表被线框糊满。下面这段封装函数就是按 timeframe 参数把对应周期可见位置位,再写进 OBJPROP_TIMEFRAMES。 调用前先用 ResetLastError() 清掉旧错误码,否则后面 GetLastError() 可能读到上一次调用的残留。若 only_prop 为 false 且 ObjectSetInteger 写属性成功,或 only_prop 本身为 true(只改内存标记不碰图表),就更新 m_timeframes_visible 并返回 true;失败则打日志返回 false。 SetVisibleOnTimeframe 内部先用 int flags=this.m_timeframes_visible 取出已有标志,再 switch 进对应周期做按位或。M1 到 M10 这几个分钟周期各自有独立宏:PERIOD_M1 对应 OBJ_PERIOD_M1,PERIOD_M2 对应 OBJ_PERIOD_M2,依次到 PERIOD_M10 对应 OBJ_PERIOD_M10,共覆盖 7 个非常规分钟周期(M1/M2/M3/M4/M5/M6/M10)。 开 MT5 新建 EA 把这段抄进对象类,传 PERIOD_M5 进去后切到 M1 图表,对象应当消失——外汇与贵金属杠杆高,对象误显示可能干扰信号判定,验证时先用模拟盘。

MQL5 / C++
ResetLastError();
if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,flags)) || only_prop)
  {
   this.m_timeframes_visible=flags;
   class="kw">return true;
  }
else
   CMessage::ToLog(DFUN,::GetLastError(),true);
class="kw">return false;
class=class="str">"cmt">//--- Add the visibility flag on a specified timeframe
class="type">bool SetVisibleOnTimeframe(const ENUM_TIMEFRAMES timeframe,const class="type">bool only_prop)
  {
   class="type">int flags=this.m_timeframes_visible;
   class="kw">switch(timeframe)
     {
      case PERIOD_M1     : flags |= OBJ_PERIOD_M1;  break;
      case PERIOD_M2     : flags |= OBJ_PERIOD_M2;  break;
      case PERIOD_M3     : flags |= OBJ_PERIOD_M3;  break;
      case PERIOD_M4     : flags |= OBJ_PERIOD_M4;  break;
      case PERIOD_M5     : flags |= OBJ_PERIOD_M5;  break;
      case PERIOD_M6     : flags |= OBJ_PERIOD_M6;  break;
      case PERIOD_M10    : flags |= OBJ_PERIOD_M10; break;

「用位或把多周期塞进对象属性」

这段 switch 把传入的图表周期常量逐一转成对应的 OBJ_PERIOD_* 标志,并用 flags |= 做按位或累加。从 M12 到 MN1 共 15 个周期分支,未匹配的周期直接走 default 返回 true,等于跳过设置。 注意 default 返回 true 的副作用:若调用方传了非法周期值,函数会静默成功,不会触发错误码,排错时容易误判为「已生效」。 最后一行判断里,only_prop 为真时跳过 ObjectSetInteger 调用,仅走属性准备逻辑;ResetLastError 放在设置前清场,避免上一次失败码污染本次结果。外汇与贵金属波动剧烈,多周期对象叠加显示可能带来视觉噪音,实盘使用前建议在 MT5 策略测试器里切几个非常规周期(如 M20、H3)验证 flags 拼接是否符合预期。

MQL5 / C++
case PERIOD_M12  : flags |= OBJ_PERIOD_M12; break;
case PERIOD_M15  : flags |= OBJ_PERIOD_M15; break;
case PERIOD_M20  : flags |= OBJ_PERIOD_M20; break;
case PERIOD_M30  : flags |= OBJ_PERIOD_M30; break;
case PERIOD_H1   : flags |= OBJ_PERIOD_H1;  break;
case PERIOD_H2   : flags |= OBJ_PERIOD_H2;  break;
case PERIOD_H3   : flags |= OBJ_PERIOD_H3;  break;
case PERIOD_H4   : flags |= OBJ_PERIOD_H4;  break;
case PERIOD_H6   : flags |= OBJ_PERIOD_H6;  break;
case PERIOD_H8   : flags |= OBJ_PERIOD_H8;  break;
case PERIOD_H12  : flags |= OBJ_PERIOD_H12; break;
case PERIOD_D1   : flags |= OBJ_PERIOD_D1;  break;
case PERIOD_W1   : flags |= OBJ_PERIOD_W1;  break;
case PERIOD_MN1  : flags |= OBJ_PERIOD_MN1; break;
class="kw">default          : class="kw">return true;
}
::ResetLastError();
if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,flags)) || only_prop)

画布元素的可见性与层级控制

在自定义图形库里,多周期面板常需要精确控制每个画布元素是否显示。上面这段把时间框架可见性标志写进成员变量后返回 true,失败则记日志并返回 false,调用方可以据此判断面板初始化是否完整。 ID()、Number()、IsShadow()、ChartColorBackground() 四个 const 方法直接透出元素属性:元素 ID、列表序号、是否带阴影、图表背景色。实测在 EURUSD 的 M5 图上,背景色取值与终端「属性」窗口显示的 RGB 完全一致,可用于动态重绘时对齐底色。 BringToTop() 先隐藏再显示对象,利用 CGBaseObj::SetVisible 的两次调用把元素提到最前层;Show() 则只做单次显示。外汇与贵金属波动剧烈,图形层叠错误可能导致信号被遮,建议开 MT5 把这两段粘进 EA 的调试工程,切换品种验证层级是否如预期。

MQL5 / C++
            {
             this.m_timeframes_visible=flags;
             class="kw">return true;
            }
            else
             CMessage::ToLog(DFUN,::GetLastError(),true);
            class="kw">return false;
            }
class=class="str">"cmt">//--- Return(class="num">1) the element ID, (class="num">2) element index in the list, (class="num">3) flag of the form shadow presence and(class="num">4) the chart background class="type">color
   class="type">int                ID(class="type">void)                const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ID);                }
   class="type">int                Number(class="type">void)            const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_NUM);              }
   class="type">bool               IsShadow(class="type">void)          const { class="kw">return this.m_shadow;                                                        }
   class="type">color              ChartColorBackground(class="type">void) const { class="kw">return this.m_chart_color_bg;                                            }
class=class="str">"cmt">//--- Set the object above all
   class="type">void               BringToTop(class="type">void)              { CGBaseObj::SetVisible(false,false); CGBaseObj::SetVisible(true,false);}
class=class="str">"cmt">//--- (class="num">1) Show and(class="num">2) hide the element
   class="kw">virtual class="type">void       Show(class="type">void)                    { CGBaseObj::SetVisible(true,false);                                       }
让小布替你跑这套
这些图形对象事件的触发诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到对象变动的实时日志,你只需关心策略怎么响应。

常见问题

持续测量是资源密集型操作,终端帮助已明确说明。用 OnChartEvent 推送的事件来感知变化,只在对象真变动时跑逻辑,开销更低。
可以,小布盯盘的 AIGC 模块已内置对象变动诊断,打开品种页就能看到创建、改属性、重命名、删除等事件的实时记录。
改名会连锁触发删旧对象、建新对象、改属性等多事件,拆出来单独处理能简化库内状态机,也方便上层程序精准响应。
目前只能知道该图表上被删对象的数量,具体属性记录尚未实现,作者暂未看到迫切需求,后续版本才可能补充。