DoEasy. 控件 (第 9 部分): 重新编排 WinForms 对象方法、RadioButton 和 Button 控件(基础篇)
📘

DoEasy. 控件 (第 9 部分): 重新编排 WinForms 对象方法、RadioButton 和 Button 控件(基础篇)

第 1/3 篇

「给 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'。外汇与贵金属自动化面板涉及高风险,参数改动请在策略测试器先验证。

MQL5 / C++
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 精准区分按钮实例。外汇与贵金属图表挂这类自定义控件属高风险操作环境,参数误用可能导致面板无响应,建议先在模拟账户验证。

MQL5 / C++
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 能直接看到官方头文件定义,对照下面代码核对你本地版本是否缺项即可。

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
   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
  };

常见问题

把 RadioButton 从 CheckBox 继承下来,只重写选取区绘制逻辑,属性统一收进框架基类,避免重复代码。
用底层枚举统一管理配色与图形元素类型,画布图元整数属性也列成枚举清单,改色只动枚举不改散代码。
可以,把控件类结构贴给小布,它会按统一框架比对方法收编情况,标出未重排的窗体对象和继承异常。
只要属性收编进统一框架且保留原接口名,旧调用倾向兼容;建议跑一遍基础点击与重绘验证。
重画时把选取区改成圆点加外环,并在枚举里分图形元素类型,混排也不会误认。