DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理(基础篇)
📘

DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理(基础篇)

第 1/3 篇

「给标准图形对象加扩展属性的坑」

DoEasy 函数库在标准图形对象的基础上做了扩展,但原生的 ObjectSetInteger/ObjectGetInteger 对扩展属性支持并不完整,直接拿来用会在回测里漏掉部分对象状态。

  • 年 6 月 14 日发布的这套示例里,作者把图形对象处理拆到第一百部分,说明前面九十九次迭代都在补标准库的短板。MT5 里用 OBJ_RECTANGLE 这类对象做区域标记时,若不加自定义属性索引,EA 在切换时间帧后可能读不到之前写的扩展字段。

要验证也很简单:开 MT5 新建一个 EA,挂上 DoEasy 的图形类,画两个矩形分别写扩展属性,切到 M1 再切回 H1,打印属性看是否丢失。外汇和贵金属品种波动大,这类对象状态错乱会直接误导信号判定,属于高风险细节。

为什么又回头改画布类

前几篇里我先把扩展标准图形对象的复合创建做出来了,但路线并不顺:先是在 CCanvas 类上搭对象,中途又切去写标准图形对象的基础操作,因为函数库要统一接管它们。等回过头补高级图形对象时,发现基于画布的底层类还得再改,否则撑不住。 这一篇不铺新功能,只补两件事:一是消除画布上同时处理扩展图形对象、标准图形对象和表单对象时暴露的明显缺陷;二是修掉上一篇测试跑出来的 bug。外汇与贵金属相关的程序化界面开发高风险,底层类不稳会让策略面板直接崩。 本篇收掉函数库说明的这一段。下一篇开新线——在画布上模拟 MS Visual Studio 的 Windows 窗体,那是继续做扩展标准图形对象和复合对象的必经路。

◍ 用 ZOrder 把 GUI 控件钉在图表最上层

在 MT5 里手动画一条趋势线,或让 EA 以编程方式挂一个标准图形对象,都会默认盖在画布 GUI 控件之上。原因为图形对象创建时 ZOrder 默认是 0,而控件若不加处理也停在 0,后创建的对象就压在前者头顶,点选和 CHARTEVENT_CLICK 全乱套。 解决思路是给基于画布的对象加一个整数属性 ZOrder,把它从 23 扩到 24,并在基类里把读写方法写成 virtual。ZOrder 不只管点击事件优先级,更被用来排控件之间的前后次序,以及相对于图表上其它对象的层级。 把 GUI 推到前景不能简单隐藏再显示——直接遍历集合重设 OBJPROP_TIMEFRAMES 会丢顺序,列表第一个反而跑到最底。正确做法是建一个关掉内存管理标志的 CArrayObj 副本,按 ZOrder 升序 Sort,再依次隐藏(OBJ_NO_PERIODS)后显示(OBJ_ALL_PERIODS),后画的永远高于前一个。 三个关键点要记:副本必须 FreeMode 后手动 delete,否则大量对象会撑爆终端内存;取最大 ZOrder 用排序后取尾元素,若没对象返回 -1;选中某控件时把它 ZOrder 设为当前最大值+1,其余按对象总数做 0~N-1 的循环偏移,避免数值无限涨。 坐标边界也得顺手修。原先轴心点位移相对中心算,一正一负带来有限误差,对象贴边会变形。改成先算中心轴点坐标,位移用无符号值,界限按图表尺寸算就不再歪。外汇和贵金属图表多品种同屏时这种层级冲突更高频,手动验证前建议先单品种测。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Integer properties of the graphical element on the canvas          |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = class="num">0,              class=class="str">"cmt">// Element ID

「Canvas 图元属性的枚举清单」

在 MT5 的 Canvas 自定义控件体系里,每一个图元(按钮、面板、标签等)都靠一组枚举属性来描述自身状态与位置。下面这段枚举定义了 25 个属性常量,从类型、归属到坐标、尺寸、可交互区域偏移,覆盖了图元在图表子窗口中的完整几何与行为描述。 其中 CANV_ELEMENT_PROP_ZORDER 决定图元接收图表点击事件的优先级——多个控件叠放时,Z 序高的先吃到鼠标消息,这点直接关系自定义面板的命中测试逻辑。 开 MT5 新建一个 Canvas 面板时,建议先打印这几个坐标类属性(COORD_X/Y、WIDTH/HEIGHT、ACT_SHIFT_*)的实际返回值,确认你的 DPI 缩放下坐标没偏移,否则点击事件可能对不准。

MQL5 / C++
  CANV_ELEMENT_PROP_TYPE,                                                                       class=class="str">"cmt">// Graphical element type
  CANV_ELEMENT_PROP_BELONG,                                                                     class=class="str">"cmt">// Graphical element affiliation
  CANV_ELEMENT_PROP_NUM,                                                                        class=class="str">"cmt">// Element index in the list
  CANV_ELEMENT_PROP_CHART_ID,                                                                   class=class="str">"cmt">// Chart ID
  CANV_ELEMENT_PROP_WND_NUM,                                                                    class=class="str">"cmt">// Chart subwindow index
  CANV_ELEMENT_PROP_COORD_X,                                                                    class=class="str">"cmt">// Form&class="macro">#x27;s X coordinate on the chart
  CANV_ELEMENT_PROP_COORD_Y,                                                                    class=class="str">"cmt">// Form&class="macro">#x27;s Y coordinate on the chart
  CANV_ELEMENT_PROP_WIDTH,                                                                      class=class="str">"cmt">// Element width
  CANV_ELEMENT_PROP_HEIGHT,                                                                     class=class="str">"cmt">// Element height
  CANV_ELEMENT_PROP_RIGHT,                                                                      class=class="str">"cmt">// Element right border
  CANV_ELEMENT_PROP_BOTTOM,                                                                     class=class="str">"cmt">// Element bottom border
  CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,                                                             class=class="str">"cmt">// Active area offset from the left edge of the element
  CANV_ELEMENT_PROP_ACT_SHIFT_TOP,                                                              class=class="str">"cmt">// Active area offset from the upper edge of the element
  CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,                                                            class=class="str">"cmt">// Active area offset from the right edge of the element
  CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,                                                           class=class="str">"cmt">// Active area offset from the bottom edge of the element
  CANV_ELEMENT_PROP_MOVABLE,                                                                    class=class="str">"cmt">// Element moveability flag
  CANV_ELEMENT_PROP_ACTIVE,                                                                     class=class="str">"cmt">// Element activity flag
  CANV_ELEMENT_PROP_INTERACTION,                                                                class=class="str">"cmt">// Flag of interaction with the outside environment
  CANV_ELEMENT_PROP_COORD_ACT_X,                                                                class=class="str">"cmt">// X coordinate of the element active area
  CANV_ELEMENT_PROP_COORD_ACT_Y,                                                                class=class="str">"cmt">// Y coordinate of the element active area
  CANV_ELEMENT_PROP_ACT_RIGHT,                                                                  class=class="str">"cmt">// Right border of the element active area
  CANV_ELEMENT_PROP_ACT_BOTTOM,                                                                 class=class="str">"cmt">// Bottom border of the element active area
  CANV_ELEMENT_PROP_ZORDER,                                                                     class=class="str">"cmt">// Priority of a graphical object for receiving the event of clicking on a chart
  };

画布元素排序的整型属性边界

在 MT5 自定义画布上管理大量图形对象时,排序依据的枚举必须先划定整型属性的总量。代码里把整数属性总数定为 24,跳过不参与排序的设为 0,这意味着全部 24 个整型字段都可作为潜在排序键。 从元素 ID、类型、所属图表 ID 到坐标 X/Y、宽高、活动区四向偏移,枚举 ENUM_SORT_CANV_ELEMENT_MODE 把前 19 项都映射为整型排序模式。实际写 UI 层时,若你自己扩展属性,记得 FIRST_CANV_ELEMENT_DBL_PROP 是用 24 减跳过数算出来的,双精度与字符串属性的起始偏移也依赖这个基数。 开 MT5 新建脚本把下面枚举贴进去编译,能直接看到排序模式从 0 到 18 连续覆盖整型属性段,方便后续做点击事件命中测试时按坐标重排。

MQL5 / C++
class="macro">#define CANV_ELEMENT_PROP_INTEGER_TOTAL(class="num">24)                 class=class="str">"cmt">// Total number of integer properties
class="macro">#define CANV_ELEMENT_PROP_INTEGER_SKIP(class="num">0)                     class=class="str">"cmt">// Number of integer properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Possible sorting criteria of graphical elements on the canvas    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define FIRST_CANV_ELEMENT_DBL_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
class="macro">#define FIRST_CANV_ELEMENT_STR_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
class=class="str">"cmt">//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = class="num">0,                                     class=class="str">"cmt">// Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                                       class=class="str">"cmt">// Sort by graphical element type
   SORT_BY_CANV_ELEMENT_BELONG,                                     class=class="str">"cmt">// Sort by a graphical element affiliation
   SORT_BY_CANV_ELEMENT_NUM,                                        class=class="str">"cmt">// Sort by form index in the list
   SORT_BY_CANV_ELEMENT_CHART_ID,                                   class=class="str">"cmt">// Sort by chart ID
   SORT_BY_CANV_ELEMENT_WND_NUM,                                    class=class="str">"cmt">// Sort by chart window index
   SORT_BY_CANV_ELEMENT_COORD_X,                                    class=class="str">"cmt">// Sort by the element X coordinate on the chart
   SORT_BY_CANV_ELEMENT_COORD_Y,                                    class=class="str">"cmt">// Sort by the element Y coordinate on the chart
   SORT_BY_CANV_ELEMENT_WIDTH,                                      class=class="str">"cmt">// Sort by the element width
   SORT_BY_CANV_ELEMENT_HEIGHT,                                     class=class="str">"cmt">// Sort by the element height
   SORT_BY_CANV_ELEMENT_RIGHT,                                      class=class="str">"cmt">// Sort by the element right border
   SORT_BY_CANV_ELEMENT_BOTTOM,                                     class=class="str">"cmt">// Sort by the element bottom border
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT,                             class=class="str">"cmt">// Sort by the active area offset from the left edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP,                              class=class="str">"cmt">// Sort by the active area offset from the top edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT,                            class=class="str">"cmt">// Sort by the active area offset from the right edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM,                           class=class="str">"cmt">// Sort by the active area offset from the bottom edge of the element
   SORT_BY_CANV_ELEMENT_MOVABLE,                                    class=class="str">"cmt">// Sort by the element moveability flag
   SORT_BY_CANV_ELEMENT_ACTIVE,                                     class=class="str">"cmt">// Sort by the element activity flag
   SORT_BY_CANV_ELEMENT_INTERACTION,                                class=class="str">"cmt">// Sort by the flag of interaction with the outside environment

◍ 画布元素排序与点击优先级怎么落代码

Canvas 库给元素排序提供了多套枚举:按激活区 X/Y 坐标、按激活区右边/下边界、按对象名或资源名,还有一项 SORT_BY_CANV_ELEMENT_ZORDER 决定图表点击事件的分发先后。 ZORDER 不是装饰参数。图表上叠了多个图形对象时,鼠标点击只会先发给 ZORDER 值更高的那个,低层对象在重叠区可能永远收不到事件。 下面这段 SetZorder 是虚函数,value 传 long 型优先级,only_prop 为 true 时只改内存标记不碰终端对象属性: ::ResetLastError();

if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value))only_prop)

{ this.m_hidden=flag; return true; } 注意上面片段里误把 m_hidden 赋成了 flag,真实实现应写 this.m_zorder=value;复制时自己改掉,否则优先级没存进实例。 开 MT5 建个 EA 把两个矩形 canvas 按钮叠一起,分别设 ZORDER=0 和 ZORDER=1,点重叠处看 ChartEvent 里 id 是谁,就能验证事件分发倾向。外汇贵金属图表做这种交互控件仍属高风险环境,参数错了也只是图形不响应,不影响报价。

MQL5 / C++
   SORT_BY_CANV_ELEMENT_COORD_ACT_X,                     class=class="str">"cmt">// Sort by X coordinate of the element active area
   SORT_BY_CANV_ELEMENT_COORD_ACT_Y,                     class=class="str">"cmt">// Sort by Y coordinate of the element active area
   SORT_BY_CANV_ELEMENT_ACT_RIGHT,                      class=class="str">"cmt">// Sort by the right border of the element active area
   SORT_BY_CANV_ELEMENT_ACT_BOTTOM,                     class=class="str">"cmt">// Sort by the bottom border of the element active area
   SORT_BY_CANV_ELEMENT_ZORDER,                         class=class="str">"cmt">// Sort by the priority of a graphical object for receiving the event of clicking on a chart
class=class="str">"cmt">//--- Sort by real properties
class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,class=class="str">"cmt">// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                       class=class="str">"cmt">// Sort by the graphical resource name
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag
   class="type">bool                SetFlagHidden(class="kw">const class="type">bool flag,class="kw">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 class="kw">false;
                      }
class=class="str">"cmt">//--- Set the priority of a graphical object for receiving the event of clicking on a chart 
   class="kw">virtual class="type">bool       SetZorder(class="kw">const class="type">long value,class="kw">const class="type">bool only_prop)
                      {
                       ::ResetLastError();
                       if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                         {

常见问题

扩展属性若直接写进原对象字段会冲掉默认样式,应单独建扩展结构挂载,避免改到基础属性。
用 ZOrder 整型属性把控件值设到最大,图表会按该值降序渲染,值越大越靠前。
可以,小布能扫描当前图表对象的 ZOrder 分布,标出层级异常并提示哪些控件该调上层。
排序属性超出整型上下限会导致渲染回绕,建议约束在合理中段区间,别用极值。
点击优先级应复用同一排序字段,渲染与命中检测都读 ZOrder,避免两套值不同步。