DoEasy. 控件 (第 10 部分): WinForms 对象  动画界面(基础篇)
📘

DoEasy. 控件 (第 10 部分): WinForms 对象 动画界面(基础篇)

第 1/3 篇

◍ 给 WinForms 控件加动画前先理清库结构

在 DoEasy 库里做 WinForms 风格控件,第 10 步的重点是把动画界面接进现有对象体系,而不是另起一套绘制逻辑。原文给出的示例发布于 2022 年 10 月 12 日,在 MT5 社区里累计 1 446 次查看、2 条讨论,说明这类自定义 UI 需求虽小众但有人盯。 动手前建议先翻库的「改进库类」那段:动画帧的刷新必须挂在原有消息循环上,否则在 MT5 图表上会出现控件闪退或占用主线程。外汇与贵金属图表多品种同开时,这种卡顿会被放大,属于高风险下的体验坑。 验证方式很直接——开 MT5 载入 DoEasy 示例,把其中一个静态按钮改成位移动画,观察 CPU 占用是否随帧率线性上升。能复现,就说明你摸到了这一节的落点。

给界面对象加上鼠标交互的视觉反馈

早先搭出来的 WinForms 对象本质都是静态图,只能在屏幕上拖着走,没法跟鼠标真正互动。这一步要补上动画化的图形界面能力,让控件能响应用户操作,同时也为后面拼更复杂的复合对象打底。 拿最普通的按钮说:鼠标悬停时它得轻微变色,提示“我能点”;按下时再变一次色,但此时还没触发逻辑;只有在控件范围内松开鼠标键,事件才真正触发。要是按下去后把光标移出控件再松手,状态应回退到按下前,不触发——这和 Windows 原生控件的行为一致,我们在 MQL5 里也照此实现。 由于同一控件现在要区分三种视觉状态,就得在对象属性里多存几组颜色:基准色、悬停色、按下色。提前把这几个字段写进属性结构,后面做可视化编辑器时能直接把全部属性列在面板上改;否则只能对每个对象手写颜色,既丑又难维护。外汇/贵金属 EA 界面开发属高风险定制,参数错乱可能让面板失灵,建议在 MT5 脚本里先单控件验证再扩展。

「给 DoEasy 库补一套鼠标交互的状态骨架」

在 \MQL5\Include\DoEasy\Defines.mqh 里给 WinForms 对象加默认颜色宏,复选框用 BorderColor 代替 ForeColor 表示边框色,悬停时随背景一起变。同时把“光标在活动区且鼠标键按下后释放”这个新状态塞进窗体鼠标状态枚举,靠它捕捉鼠标松开那一刻,作为图形对象改状态的触发点。 为了响应用户操作,新增一个鼠标事件枚举 ENUM_MOUSE_EVENT,插在图表事件和对象事件之间,于是图形对象首个事件枚举值要重算为“最后一个鼠标事件+1”。整数型属性列表同步加了 10 个新属性,总数从 71 扩到 81,画布排序准则也多了依据新属性挑对象的能力。 图形元素基类 GCnvElement 里声明了初始背景色数组——因为背景可能是渐变,必须存整组色而非单个变量。设置背景色时靠一个标志位决定是否把当前色存进数组,后续恢复就直接从数组取。两个构造函数和对象结构读写方法都接上了这个新属性,CheckBox 的 ForceColor 直接吃 BorderColor 值。 Form 类作为交互基准,在保护段加了“最后鼠标事件”和“初始边框色”两个变量,所有鼠标事件处理程序都写成虚函数。鼠标事件进来先按 ID 分发到对应虚方法,再把事件写进最后事件变量;光标移出对象时遍历所有 WinForms 对象,把还停在“悬停”状态的强制复位成“窗体外”并还原颜色。 CheckBox 和 RadioButton 继承了这套机制:CheckBox 在“左键按下并释放且光标仍在区内”时翻转选中状态、改字段/边框/勾选色,光标移出则还原;RadioButton 只重写左键释放方法且不翻转自身标志,靠同组对象互斥。按钮对象则预留了“点按复位”和“点按保持 Toggle”两类状态的颜色与触发标志,等下一步填充。

◍ 按钮状态色与窗体事件拆分的实操改法

在 DoEasy 的 Button.mqh 私密段里,先声明一个数组存新按钮各状态的颜色,受保护段重写父类的鼠标事件 handler,公开段暴露触发器标志与状态的 get/set 以及颜色处理方法。构造函数里把悬停、单击时的背景色和不透明度设成默认值,普通按钮 State() 恒为 false,切换钮初始也是 false。 鼠标事件拆成三块重写:“区域内无点击”“区域内任意键按下”“区域内左键按下”,注释里逻辑已经写清,不必复述。最后一个事件 handler 只管光标移出窗体且原状态是“上方无点击”的情况,按 State() 把背景刷回初始色。 GraphElementsCollection.mqh 里把 GetFormUnderCursor() 中两段重复的“找光标下窗体对象”代码抽成独立方法,方法长度肉眼缩短。所有建图形元素的方法在设色时都传 true 保存初始色,例如画布上建窗体对象那一类调用都已改完。 搜索交互对象时,原逻辑在 GetFormUnderCursor() 内,现在改为调用抽出的方法。处理“前一个活动窗体鼠标事件”时,会遍历所有 form 及以上元素,对每个绑定对象调一次“光标上方无点击”的 handler,再把末事件置为窗体外——保证同时只有一个对象在被处理。 可移动窗体按下鼠标会置移动标志,但点在绑定子对象上不应移动窗体。原来移动标志和鼠标移动事件混验,现在把 MOUSE_FORM_STATE_NONE 换成新事件 MOUSE_FROM_STATE_INSIDE_ACTIVE_AREA_RELEASED,单独验移动标志解除。这样能在释放移动标志时立刻捕捉“光标离开活动对象”的事件。 OnChartEvent() 里调用对象 handler 时传入已找到的事件,并补一个“释放鼠标按钮”的 handler。设置滚动/上下文菜单/十字线标志的方法加了已设标志的检查,避免重复置位。下面这段宏定义了画布默认色:悬停文字 0x1467F1、按下文字 0x0E1198、悬停边框 0x93ADC8、按下边框 0x6188C9,画布刷新间隔 16ms。

MQL5 / C++
class=class="str">"cmt">//--- Canvas parameters
class="macro">#define PAUSE_FOR_CANV_UPDATE(class="num">16)                 class=class="str">"cmt">// Canvas update frequency
class="macro">#define CLR_CANV_NULL(0x00FFFFFF)         class=class="str">"cmt">// Zero for the canvas with the alpha channel
class="macro">#define CLR_DEF_FORE_COLOR(C&class="macro">#x27;0x2D,0x43,0x48&class="macro">#x27;)  class=class="str">"cmt">// Default class="type">color for texts of objects on canvas
class="macro">#define CLR_DEF_FORE_COLOR_MOUSE_DOWN(C&class="macro">#x27;0x0E,0x11,0x98&class="macro">#x27;)  class=class="str">"cmt">// Default class="type">color for texts of objects on canvas when clicking the mouse on the control
class="macro">#define CLR_DEF_FORE_COLOR_MOUSE_OVER(C&class="macro">#x27;0x14,0x67,0xF1&class="macro">#x27;)  class=class="str">"cmt">// Default class="type">color for texts of objects on canvas when hovering the mouse over the control
class="macro">#define CLR_DEF_FORE_COLOR_OPACITY(class="num">255)                class=class="str">"cmt">// Default class="type">color non-transparency for canvas object texts
class="macro">#define CLR_DEF_BORDER_COLOR(C&class="macro">#x27;0x2D,0x43,0x48&class="macro">#x27;)  class=class="str">"cmt">// Default class="type">color for object frames on canvas
class="macro">#define CLR_DEF_BORDER_MOUSE_DOWN(C&class="macro">#x27;0x61,0x88,0xC9&class="macro">#x27;)  class=class="str">"cmt">// Default class="type">color for object frames on canvas when clicking the mouse on the control
class="macro">#define CLR_DEF_BORDER_MOUSE_OVER(C&class="macro">#x27;0x93,0xAD,0xC8&class="macro">#x27;)  class=class="str">"cmt">// Default class="type">color for object frames on canvas when hovering the mouse over the control
class="macro">#define CLR_DEF_BORDER_COLOR_OPACITY(class="num">255)                class=class="str">"cmt">// Default class="type">color non-transparency for canvas object frames
class="macro">#define CLR_DEF_BORDER_COLOR_DARKNESS(-class="num">2.0)               class=class="str">"cmt">// Default class="type">color opacity for canvas object frames(when class="kw">using the background class="type">color)

画布控件默认配色与透明度宏

在 MT5 自定义面板开发里,先把画布对象的默认视觉参数用宏固化,能省掉后续每个控件重复传参的麻烦。下面这组宏覆盖了 GroupBox 边框、阴影、复选框三态以及标准控件背景的色值与透明度。 阴影部分默认 blur 为 4、不透明度 127,而 GroupBox 边框色取 C'0xDC,0xDC,0xDC'、整体非透明值 200,这意味着阴影比主体更虚,层级感靠这两个数值差拉开。 复选框的交互反馈写得很细:常态边框 C'0x2D,0x43,0x48'、悬停变 C'0x00,0x78,0xD7'、按下变 C'0x00,0x54,0x99',背景也从白底在悬停时切到 C'0xD8,0xE6,0xF2'。直接把这些宏贴进 EA 的 Canvas 基类,开 MT5 挂个测试指标就能看到三态切换。 标准控件背景常态 C'0xF0,0xF0,0xF0',鼠标按下与悬停复用和复选框同一套浅蓝,保持 UI 一致性,不用另调。

MQL5 / C++
class="macro">#define CLR_DEF_FRAME_GBOX_COLOR(C&class="macro">#x27;0xDC,0xDC,0xDC&class="macro">#x27;)        class=class="str">"cmt">// Default class="type">color for GroupBox object frames on canvas
class="macro">#define CLR_DEF_OPACITY(class="num">200)                       class=class="str">"cmt">// Default class="type">color non-transparency for canvas objects
class="macro">#define CLR_DEF_SHADOW_COLOR(C&class="macro">#x27;0x6B,0x6B,0x6B&class="macro">#x27;)         class=class="str">"cmt">// Default class="type">color for canvas object shadows
class="macro">#define CLR_DEF_SHADOW_OPACITY(class="num">127)                       class=class="str">"cmt">// Default class="type">color opacity 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;0xFF,0xFF,0xFF&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">// Opacity of the control checkbox background class="type">color
class="macro">#define CLR_DEF_CHECK_BACK_MOUSE_DOWN(C&class="macro">#x27;0xC0,0xDC,0xF3&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;0xD8,0xE6,0xF2&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_BORDER_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_BORDER_OPACITY(class="num">255)                       class=class="str">"cmt">// Opacity of the control checkbox frame class="type">color
class="macro">#define CLR_DEF_CHECK_BORDER_MOUSE_DOWN(C&class="macro">#x27;0x00,0x54,0x99&class="macro">#x27;)       class=class="str">"cmt">// Color of control checkbox frame when clicking on the control
class="macro">#define CLR_DEF_CHECK_BORDER_MOUSE_OVER(C&class="macro">#x27;0x00,0x78,0xD7&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">// Opacity of the control checkbox class="type">color
class="macro">#define CLR_DEF_CHECK_FLAG_MOUSE_DOWN(C&class="macro">#x27;0x00,0x54,0x99&class="macro">#x27;)         class=class="str">"cmt">// Color of control checkbox when clicking on the control
class="macro">#define CLR_DEF_CHECK_FLAG_MOUSE_OVER(C&class="macro">#x27;0x00,0x78,0xD7&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;0xF0,0xF0,0xF0&class="macro">#x27;)        class=class="str">"cmt">// Standard controls background class="type">color
class="macro">#define CLR_DEF_CONTROL_STD_MOUSE_DOWN(C&class="macro">#x27;0xC0,0xDC,0xF3&class="macro">#x27;)        class=class="str">"cmt">// Color of standard control background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_STD_MOUSE_OVER(C&class="macro">#x27;0xD8,0xE6,0xF2&class="macro">#x27;)        class=class="str">"cmt">// Color of standard controls background when hovering the mouse over the control

「控件配色与鼠标状态枚举的落地定义」

在 MT5 自定义面板开发里,先固化一套标准控件视觉参数,能省掉后期大量散落的魔法数字。下面这组宏把背景不透明度锁死为 255(完全不透明),开启态背景用 C'0xC9,0xDE,0xD0' 这种浅绿,按下和悬停分别压暗到 0xA6,0xC8,0xB0 与 0xB8,0xD3,0xC0,读者可直接拷进 EA 的面板库对照看色差。 字体默认走 Calibri、字号 8,勾选框边长 12,外边距单边 16,窗体边框宽 3——这几个数决定了小屏 MT5 终端里控件不会挤成一团。外汇与贵金属图表加载此类面板时波动刷新频繁,高重叠区建议把 OUTER_AREA_SIZE 调到 20 以上降低误触概率。 鼠标相对表单的状态用 ENUM_MOUSE_FORM_STATE 收口,从表单外无按键、表单外按下、表单外滚轮,到表单内对应三态,再到标题活动区三态,共 10 个枚举值(含 0 号未定义)。写拖拽逻辑时只判 INSIDE_ACTIVE_AREA_PRESSED 就能隔离出“抓标题移动窗口”这一条路径,不必堆坐标判断。

MQL5 / C++
class="macro">#define CLR_DEF_CONTROL_STD_OPACITY(class="num">255)                 class=class="str">"cmt">// Opacity of standard controls background class="type">color
class="macro">#define CLR_DEF_CONTROL_STD_BACK_COLOR_ON(C&class="macro">#x27;0xC9,0xDE,0xD0&class="macro">#x27;)   class=class="str">"cmt">// Background class="type">color of standard controls which are on
class="macro">#define CLR_DEF_CONTROL_STD_BACK_DOWN_ON(C&class="macro">#x27;0xA6,0xC8,0xB0&class="macro">#x27;)    class=class="str">"cmt">// Color of standard control background when clicking on the control when it is on
class="macro">#define CLR_DEF_CONTROL_STD_BACK_OVER_ON(C&class="macro">#x27;0xB8,0xD3,0xC0&class="macro">#x27;)    class=class="str">"cmt">// Color of standard control background when hovering the mouse over the control when it is on
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="macro">#define DEF_CHECK_SIZE(class="num">12)                           class=class="str">"cmt">// Verification flag class="kw">default size
class="macro">#define OUTER_AREA_SIZE(class="num">16)                           class=class="str">"cmt">// Size of one side of the outer area around the form workspace
class="macro">#define DEF_FRAME_WIDTH_SIZE(class="num">3)                            class=class="str">"cmt">// Default form/panel/window frame width
class=class="str">"cmt">//--- Graphical object parameters
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| The list of possible mouse states relative to the form            |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = class="num">0,                                 class=class="str">"cmt">// Undefined state
class=class="str">"cmt">//--- Outside the form
   MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED,                 class=class="str">"cmt">// The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,                     class=class="str">"cmt">// The cursor is outside the form, the mouse button(any) is clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,                       class=class="str">"cmt">// The cursor is outside the form, the mouse wheel is being scrolled
class=class="str">"cmt">//--- Within the form
   MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED,                  class=class="str">"cmt">// The cursor is inside the form, no mouse buttons are clicked
   MOUSE_FORM_STATE_INSIDE_FORM_PRESSED,                      class=class="str">"cmt">// The cursor is inside the form, the mouse button(any) is clicked
   MOUSE_FORM_STATE_INSIDE_FORM_WHEEL,                        class=class="str">"cmt">// The cursor is inside the form, the mouse wheel is being scrolled
class=class="str">"cmt">//--- Within the window header area
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,           class=class="str">"cmt">// The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,               class=class="str">"cmt">// The cursor is inside the active area,  any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,                 class=class="str">"cmt">// The cursor is inside the active area, the mouse wheel is being scrolled
  };

常见问题

先理清库的对象继承关系,把窗体事件和控件配色拆开,再在状态枚举里补鼠标交互字段,避免后期耦合。
在控件配色宏里定义不同鼠标状态对应的色值,并在画布重绘逻辑中读取状态枚举切换填充色即可。
可以,小布能按你贴出的控件结构和枚举定义,指出事件拆分遗漏点并给出配色宏的校验建议。
改透明度宏的数值降低默认不透明度,并确认该宏在控件初始化时被正确读取而非写死。
容易漏掉「离开控件」的复位状态,导致悬停色残留,需在枚举里显式定义并绑定鼠标移出事件。