DoEasy. 控件 (第 9 部分): 重新编排 WinForms 对象方法、RadioButton 和 Button 控件(基础篇)
「给 DoEasy 库补上 RadioButton 与 Button 的窗体方法」
在 DoEasy 控件的第九篇里,库结构做了一次方法重排:原本散落在各处的 WinForms 对象接口被统一收敛,RadioButton 和 Button 两个控件的底层调用方式随之改变。这次改动不触及渲染逻辑,主要解决的是上层调用时方法命名不一致、继承关系混乱的问题。 重排后,RadioButton 作为单选控件,状态切换只走单一入口方法,避免了旧版里多处直接改私有变量的写法。Button 则把点击事件绑定从构造函数里抽离,改成可后期挂载,方便动态创建按钮后再指定行为。 原文示例发布于 2022-09-26,在 MT5 里以标准 EA 形式挂载后,能在图表上看到两个控件的实例化和基础交互。想验证改动效果,直接拉取库后在测试 EA 里 new 一个 RadioButton 和 Button,对比旧版头文件的方法签名差异即可。外汇与贵金属标的波动剧烈,这类 UI 库仅用于辅助分析面板搭建,不构成任何交易信号。
◍ 把属性收编进统一框架
这一节动手把原来散落在各个类变量里的 WinForms 属性,统一收编到函数库主流概念下:每个对象持有一组整数、实数、字符串三类属性,并带“是否由对象自维护”的标志位。不支持的属性就清掉标志,对象构造时直接跳过,不再占用内存和逻辑分支。 这么做的好处很实在——可视化构造函数只要写一套循环,就能列出任意图形对象当前支持的属性名,或者批量改属性元素。以前每加一种控件就得重写一遍面板逻辑,现在只维护一张“对象支持属性清单”就行。 顺带复活了静态 WinForms 对象的命中检测:容器面板叠了多个可交互元素时,函数库现在能识别光标悬停到哪一个。鼠标事件和响应后面再接,这节先把“看得见光标底下是谁”的基础打通。 本节约新增 Button 和 RadioButton 两个标准控件,归到标准 WinForms 类别。它们暂只搭了基础结构,鼠标交互和视觉效果留待后续章节。提醒:MT5 自定义库开发涉及底层对象生命周期管理,改属性框架时若清错标志位可能导致控件不渲染,属高风险调试操作,建议先在模拟账户环境验证。
重构 DoEasy 图形控件底层类
这一节动的是 DoEasy 函数库里画布控件的地基。先在 Defines.mqh 给 CheckBox 补了颜色宏,并在图形元素类型枚举里加了两个新类型,同时把整数型属性枚举的总数从 48 改到 71——这意味着底层属性槽位扩了近五成,后续悬停、点击的上色逻辑才有地方存。 GBaseObj 里补了新类型描述返回,GCnvElement 中把背景色从单个变量改成颜色数组,删掉旧变量后,所有渐变背景(含悬停、点击态)都走对象属性。类里还加了 IsMain()/IsBase() 这类标志方法,靠指针是否 NULL 判定对象在层次里的位置。 Form 类是解决交互穿透的关键:以前面板悬停没法识别光标是否落在附加按钮上,现在建了“交互对象”列表,递归遍历附加层次,从列表尾往回找带悬停标志的那个对象,指针直接喂给处理程序。 相对坐标也改了算法——不再手填缩进,而是用基准对象屏幕坐标减绑定对象屏幕坐标实时算,绑定对象乱动也不会偏。WinFormBase 里把 FrameWidth 调用全换成 BorderSize,并写了从枚举名抠子串、大写转小写的字体宽度描述函数(例如 FW_TYPE_REGULAR 取位 8 起始子串,非首字母加 0x20 偏移)。 这些改动没有动用户层逻辑,但如果你在 MT5 里自己扩展了 DoEasy 控件,直接覆盖 Include 前务必 diff 一下 GCnvElement 和 Form 的头文件,否则旧代码里写死的 ColorBackground() 会编译报错。
「把 RadioButton 从 CheckBox 继承下来重画选取区」
DoEasy 的 WinForms 控件里,RadioButton 和 CheckBox 内部逻辑高度相似,区别只在选取区形状:前者是圆形,后者是方形的复选标记。直接从 CheckBox 类继承、只重定义绘制方法,是最省事的做法,但要把原本私有的变量移到 protected 段,否则后代类访问不到。 自动调整宽高的虚方法原先是 void,现在统一改成 bool。这样每个衍生对象能回报自己是否真改了尺寸——没改的就直接返回 true 不做操作,逻辑更干净。 文本标签类之前在重绘方法里现算坐标,太重了。把计算挪到独立的 SetTextParamsByAlign(),Label 和它的子类都能调。RadioButton 里重复的 SetTextCoords() 直接删掉,改调父类的这个方法。 复选框绘制改了底色:不再是透明背景,而是先画填充矩形加边框,再在上面画三条线模拟勾选,每条中心线比前一条高 1 像素。作者自己也说从伸缩性看不严谨,后续会改相对坐标。 下面这组宏定义了控件各状态的颜色与透明度,直接贴出来方便你抄进 Include 里改皮肤。CLR_DEF_CHECK_BACK_COLOR 默认是 C'0xD9,0xEC,0xEB',点击态变成 C'0xBA,0xEB,0xF5',悬停态 C'0xCE,0xE0,0xE3';勾选标记色 CLR_DEF_CHECK_FLAG_COLOR 为 C'0x04,0x7B,0x0D'。外汇与贵金属自动化面板涉及高风险,参数改动请在策略测试器先验证。
class="macro">#define CLR_DEF_SHADOW_OPACITY(class="num">127) class=class="str">"cmt">// Default class="type">color non-transparency for canvas objects class="macro">#define DEF_SHADOW_BLUR(class="num">4) class=class="str">"cmt">// Default blur for canvas object shadows class="macro">#define CLR_DEF_CHECK_BACK_COLOR(C&class="macro">#x27;0xD9,0xEC,0xEB&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox background class="macro">#define CLR_DEF_CHECK_BACK_OPACITY(class="num">255) class=class="str">"cmt">// Non-transparency of the control checkbox background class="type">color class="macro">#define CLR_DEF_CHECK_BACK_MOUSE_DOWN(C&class="macro">#x27;0xBA,0xEB,0xF5&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox background when clicking on the control class="macro">#define CLR_DEF_CHECK_BACK_MOUSE_OVER(C&class="macro">#x27;0xCE,0xE0,0xE3&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox background when hovering the mouse over the control class="macro">#define CLR_DEF_CHECK_FORE_COLOR(C&class="macro">#x27;0x2D,0x43,0x48&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox frame class="macro">#define CLR_DEF_CHECK_FORE_OPACITY(class="num">255) class=class="str">"cmt">// Non-transparency of the control checkbox frame class="type">color class="macro">#define CLR_DEF_CHECK_FORE_MOUSE_DOWN(C&class="macro">#x27;0x06,0x0B,0xAA&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox frame when clicking on the control class="macro">#define CLR_DEF_CHECK_FORE_MOUSE_OVER(C&class="macro">#x27;0x06,0x0B,0xAA&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox frame when hovering the mouse over the control class="macro">#define CLR_DEF_CHECK_FLAG_COLOR(C&class="macro">#x27;0x04,0x7B,0x0D&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox class="macro">#define CLR_DEF_CHECK_FLAG_OPACITY(class="num">255) class=class="str">"cmt">// Non-transparency of the control checkbox class="type">color class="macro">#define CLR_DEF_CHECK_FLAG_MOUSE_DOWN(C&class="macro">#x27;0x0E,0x9B,0x0B&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox when clicking on the control
◍ 控件配色与图形元素类型的底层枚举
在 MT5 自定义面板开发里,先固化一套默认视觉参数能省掉后续大量散落硬编码。下面这组宏把鼠标悬停勾选框色、标准控件背景色、透明度、字体及字号一次性定义清楚:悬停色为 C'0x0E,0xC7,0x2E'(RGB 14,199,46 的墨绿),标准背景 C'0xCD,0xD8,0xDA'(205,216,218 浅灰蓝),背景不透明度直接给 255 即完全不透明,默认字体 Calibri、字号 8。 真正决定对象归属的是 ENUM_GRAPH_ELEMENT_TYPE 这个枚举。它从 GRAPH_ELEMENT_TYPE_STANDARD 普通图形对象,一路铺到 GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON 单选按钮,中间穿插了阴影对象、窗体、容器以及 WinForms 系的 Panel、GroupBox、Label、Button、Checkbox 等。 开 MT5 新建一个 include 头文件,把这段枚举原样贴进去,编译后就能在自有库里用 GRAPH_ELEMENT_TYPE_WF_BUTTON 精准区分按钮实例。外汇与贵金属图表挂这类自定义控件属高风险操作环境,参数误用可能导致面板无响应,建议先在模拟账户验证。
class="macro">#define CLR_DEF_CHECK_FLAG_MOUSE_OVER(C&class="macro">#x27;0x0E,0xC7,0x2E&class="macro">#x27;) class=class="str">"cmt">// Color of control checkbox when hovering the mouse over the control class="macro">#define CLR_DEF_CONTROL_STD_BACK_COLOR(C&class="macro">#x27;0xCD,0xD8,0xDA&class="macro">#x27;) class=class="str">"cmt">// Standard controls background class="type">color class="macro">#define CLR_DEF_CONTROL_STD_OPACITY(class="num">255) class=class="str">"cmt">// Non-transparency of standard controls background class="type">color class="macro">#define DEF_FONT("Calibri") class=class="str">"cmt">// Default font class="macro">#define DEF_FONT_SIZE(class="num">8) class=class="str">"cmt">// Default font size class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The list of graphical element types | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, class=class="str">"cmt">// Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, class=class="str">"cmt">// Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, class=class="str">"cmt">// Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, class=class="str">"cmt">// Element GRAPH_ELEMENT_TYPE_FORM, class=class="str">"cmt">// Form GRAPH_ELEMENT_TYPE_WINDOW, class=class="str">"cmt">// Window class=class="str">"cmt">//--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, class=class="str">"cmt">// Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, class=class="str">"cmt">// Windows Forms Base GRAPH_ELEMENT_TYPE_WF_CONTAINER, class=class="str">"cmt">// Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, class=class="str">"cmt">// Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, class=class="str">"cmt">// Windows Forms GroupBox GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, class=class="str">"cmt">// Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, class=class="str">"cmt">// Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, class=class="str">"cmt">// Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, class=class="str">"cmt">// Windows Forms ChackBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, class=class="str">"cmt">// Windows Forms RadioButton };
画布图元整数属性的枚举清单
在 MT5 的 Canvas 自定义界面开发里,图元(按钮、标签、面板等)的位置、尺寸和交互状态都靠一组整数属性来描述。系统用 ENUM_CANV_ELEMENT_PROP_INTEGER 枚举把这些属性编了号,从 0 开始依次排布,便于用 CanvasGetInteger/CanvasSetInteger 按 ID 读写。 下面这段枚举定义了 24 个整数属性,覆盖了图元身份、归属、坐标和命中区。比如 CANV_ELEMENT_PROP_ID 是图元自身编号,CANV_ELEMENT_PROP_COORD_X / Y 是图表上的绘制坐标,CANV_ELEMENT_PROP_ACT_SHIFT_LEFT 这类四个偏移量则框出可点击的活跃区域,比图元本身更内缩。 实际写 EA 面板时,最常用的是 CANV_ELEMENT_PROP_MOVABLE(能否拖拽)和 CANV_ELEMENT_PROP_ACTIVE(是否响应事件)。把 MOVABLE 设 0 可锁死控件,避免误拖;ACTIVE 切 1 才进事件队列。外汇与贵金属图表上挂这类 UI 属高风险操作,参数误写可能导致面板失灵。 打开 MT5 的 MetaEditor,搜 ENUM_CANV_ELEMENT_PROP_INTEGER 能直接看到官方头文件定义,对照下面代码核对你本地版本是否缺项即可。
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 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">// Element X coordinate on the chart CANV_ELEMENT_PROP_COORD_Y, class=class="str">"cmt">// Element 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 };