DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理(基础篇)
「给标准图形对象加扩展属性的坑」
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 的循环偏移,避免数值无限涨。 坐标边界也得顺手修。原先轴心点位移相对中心算,一正一负带来有限误差,对象贴边会变形。改成先算中心轴点坐标,位移用无符号值,界限按图表尺寸算就不再歪。外汇和贵金属图表多品种同屏时这种层级冲突更高频,手动验证前建议先单品种测。
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 缩放下坐标没偏移,否则点击事件可能对不准。
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 连续覆盖整型属性段,方便后续做点击事件命中测试时按坐标重排。
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 是谁,就能验证事件分发倾向。外汇贵金属图表做这种交互控件仍属高风险环境,参数错了也只是图形不响应,不影响报价。
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) {