DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象(基础篇)
📘

DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象(基础篇)

第 1/3 篇

「给 WinForms 控件补上鼠标交互与 Tab 雏形」

在 DoEasy 库的第 13 次迭代里,核心动作是把已有 WinForms 控件和鼠标事件之间的耦合重新梳理了一遍。原先控件响应鼠标移动、点击的链路存在冗余判断,这次优化后,光标在控件边界内的命中测试改为单次矩形包含检测,MT5 里拖拽面板时的卡顿概率会下降。 这一版同步启动了 TabControl 对象的布局开发。目前只是在库内部定义了页签条(tab strip)与客区(client area)的占位矩形,还未接入实际子页切换逻辑,但已经能看出分页容器的基本坐标划分方式。 想验证优化效果,可在 MT5 用同一套库加载两个版本(第 12 版与第 13 版)的测试 EA,分别快速拖动含多个 WinForms 控件的面板,观察 Experts 日志里鼠标消息处理函数的调用频次差异。外汇与贵金属品种上跑这类 UI 测试不影响报价,但实盘加载自定义库存在平台稳定性高风险,建议只用模拟账户。

悬停残影与命名超长:GUI 库两个卡点

用图形对象搭 MT5 自定义面板时,有个很烦的现象:鼠标从下往上或从左往右离开对象,外观能复原;但反向划走,对象经常卡在悬停态不还原。两个对象贴太近时尤其明显,实测只要彼此间隔拉到至少 4 像素,行为就恢复正常。 根子在于对象要先把光标「吸」回所在窗体区域才会触发复原逻辑,所以组件布局不能照搬 VS 控件集的紧凑间距,得人为留缝。 另一个更隐蔽的坑是对象命名。当前规则把程序名、面板名、逐级 '_ElmXX' 后缀全拼进资源名,嵌套一层就加一长串。例如 Program_WFPanel1_Elm00_Elm00 这种。MT5 图形资源名上限 63 字符,而 CCanvas 的 Create() 还会在用户给的名字后追加上 ChartID、系统启动毫秒数、0–32767 伪随机数。 拼到最后留给我们的字符空间所剩无几。做 TabControl 时选项卡上再挂子元素,名字直接超长,CCanvas::Create() 返回 false,控件就建不出来。 这节先不深写 TabControl 类,而是用「即兴工具」搭个空白布局验证它未来要长什么样、挂哪些功能;正经的命名新机制留到下篇再落地。

MQL5 / C++
m_rcname="::"+name+(class="type">class="kw">string)ChartID()+(class="type">class="kw">string)(GetTickCount()+MathRand());

◍ 给图形库控件补上状态色与交互优化

悬停或点中 TabPage 选项卡时,元素该变颜色、标题该微涨,边框也要跟着状态走。之前库里硬编码了 4 像素高、6 像素宽的行距,修完鼠标交互的显示 bug 后,改成了用宏定义最小行间距,ListBox 行距不再写死。 枚举里新增了 TabControl、TabHeader、TabPage 三类图形元素,选项卡标题可放右/左/上/下,新建枚举管位置;原先 COLOR_TOGGLE 系列改名 COLOR_STATE_ON,整数型属性从 85 扩到 88,排序条件也多了三个新属性可用。 颜色设置方法前先比对:若目标色和当前色一致,直接退出不重绘。GCnvElement 里用 ArrayCompare() 比颜色数组,相等返回零就跳过,避免无谓重涂导致的界面闪烁。 CheckBox 原先按像素硬坐标画勾,字段一缩放就歪;改成相对字段大小的百分比坐标,实数算完转整数再交给 CCanvas,缩放后勾选标记位置就正了。Button 类给启用态加了三个正式颜色参数,按下/释放分别走 StateOnColor 系列,鼠标事件里实时换文本色。 ListBox 创建多行列表时新增列宽入参和自适应标志,只有标志置位容器才改自己的尺寸;CheckedListBox 建对象时高度多加 3 像素,悬停背景才能铺满整行而不是卡在严格高度内。外汇与贵金属 MT5 界面开发属高风险实验,改完建议开 MT5 编译库并拖一个 TabControl 实测悬停变色。

MQL5 / C++
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

「TabControl 配色宏的默认空值与页面底色」

在 MT5 自定义控件库里,TabControl 容器本身的背景、边框、悬停与按下态,默认全部走 CLR_CANV_NULL,透明度也设为 0。也就是说,如果不主动改宏,标签栏区域在画布上完全透明,不会盖住下层 K 线或指标。 真正有可见默认值的是 TabPage 页面:背景统一是 C'0xFF,0xFF,0xFF'(纯白),边框是 C'0xDD,0xDD,0xDD'(浅灰),页面透明度 255 即完全不透明。这一点在写多页面板时很关键——标签头隐形、内容页实底,视觉层级靠页面自己撑起来。 开 MT5 新建一个 CCanvas 面板,把上面这组宏原样贴进头文件,编译后拖到图表上,就能直接看到标签栏透明、页面白底灰框的效果。若要做暗色主题,只需把那几个 C'0xFF…' 改成深色调并调低 OPACITY 即可。

MQL5 / C++
class="macro">#define CLR_DEF_CONTROL_TAB_BACK_COLOR(CLR_CANV_NULL)              class=class="str">"cmt">// TabControl background class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_MOUSE_DOWN(CLR_CANV_NULL)              class=class="str">"cmt">// Color of TabControl background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_MOUSE_OVER(CLR_CANV_NULL)              class=class="str">"cmt">// Color of TabControl background when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_OPACITY(class="num">0)                        class=class="str">"cmt">// TabControl background opacity
class="macro">#define CLR_DEF_CONTROL_TAB_BACK_COLOR_ON(CLR_CANV_NULL)           class=class="str">"cmt">// Enabled TabControl background class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_BACK_DOWN_ON(CLR_CANV_NULL)            class=class="str">"cmt">// Color of enabled TabControl background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_BACK_OVER_ON(CLR_CANV_NULL)            class=class="str">"cmt">// Color of enabled TabControl background when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_BORDER_COLOR(CLR_CANV_NULL)            class=class="str">"cmt">// TabControl frame class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN(CLR_CANV_NULL)       class=class="str">"cmt">// Color of TabControl frame when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER(CLR_CANV_NULL)       class=class="str">"cmt">// Color of TabControl frame when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_BORDER_COLOR_ON(CLR_CANV_NULL)         class=class="str">"cmt">// Enabled TabControl frame class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_BORDER_DOWN_ON(CLR_CANV_NULL)          class=class="str">"cmt">// Color of enabled TabControl frame when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_BORDER_OVER_ON(CLR_CANV_NULL)          class=class="str">"cmt">// Color of enabled TabControl frame when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)     class=class="str">"cmt">// TabPage control background class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)     class=class="str">"cmt">// Color of TabPage control background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)     class=class="str">"cmt">// Color of TabPage control background when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_OPACITY(class="num">255)                 class=class="str">"cmt">// TabPage background opacity
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR_ON(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)  class=class="str">"cmt">// Color of the enabled TabPage control background
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BACK_DOWN_ON(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)   class=class="str">"cmt">// Color of the enabled TabPage control background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BACK_OVER_ON(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)   class=class="str">"cmt">// Color of the enabled TabPage control background when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;)   class=class="str">"cmt">// TabPage control frame class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;) class=class="str">"cmt">// Color of TabPage control background frame when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;) class=class="str">"cmt">// Color of TabPage control background frame when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR_ON(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;) class=class="str">"cmt">// Color of the enabled TabPage control frame
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_DOWN_ON(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;) class=class="str">"cmt">// Color of the enabled TabPage control frame when clicking on the control

TabPage 与列表控件的默认配色和间距常量

在 MT5 自定义面板开发里,TabPage 控件的视觉状态靠一组宏常量锁死。悬停启用态边框用 C'0xDD,0xDD,0xDD',普通表头背景与点击、悬停背景同为 C'0xF0,0xF0,0xF0',不区分交互态,只有启用态背景才跳到 C'0xFF,0xFF,0xFF'。 表头边框在非启用态统一为 C'0xD9,0xD9,0xD9',启用态变为 C'0xDD,0xDD,0xDD',透明度硬编码 255 不透明。若你做多 Tab 面板,直接改这几个宏就能整体换肤,不必动绘制逻辑。 ListBox 控件列间距 DEF_CONTROL_LIST_MARGIN_X 为 1 像素、行间距 DEF_CONTROL_LIST_MARGIN_Y 为 0,意味着行间无留白,密集数据表得自己加行高补偿。 全局字体默认 Calibri、尺寸 8,勾选框边长 12,窗体周边留白 OUTER_AREA_SIZE 单边 16、默认边框宽 3。把这些值抄进你的 EA 面板头文件,开 MT5 编译看渲染是否符合预期。

MQL5 / C++
class="macro">#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_OVER_ON(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;) class=class="str">"cmt">// Color of the enabled TabPage control frame when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;)   class=class="str">"cmt">// TabPage control header background class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;)   class=class="str">"cmt">// Color of TabPage control header background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;)   class=class="str">"cmt">// Color of TabPage control header background when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_OPACITY(class="num">255)                class=class="str">"cmt">// TabPage header background opacity
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)class=class="str">"cmt">// Color of the enabled TabPage control header background
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;) class=class="str">"cmt">// Color of the enabled TabPage control header background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;) class=class="str">"cmt">// Color of the enabled TabPage control header background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR(C&class="macro">#x27;0xD9,0xD9,0xD9&class="macro">#x27;)  class=class="str">"cmt">// TabPage control header frame class="type">color
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN(C&class="macro">#x27;0xD9,0xD9,0xD9&class="macro">#x27;) class=class="str">"cmt">// Color of TabPage control header frame when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER(C&class="macro">#x27;0xD9,0xD9,0xD9&class="macro">#x27;) class=class="str">"cmt">// Color of TabPage control header frame when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR_ON(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;)class=class="str">"cmt">// Color of the enabled TabPage control header frame
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_DOWN_ON(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;) class=class="str">"cmt">// Color of the enabled TabPage control header frame when clicking on the control
class="macro">#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_OVER_ON(C&class="macro">#x27;0xDD,0xDD,0xDD&class="macro">#x27;) class=class="str">"cmt">// Color of the enabled TabPage control header frame when hovering the mouse over the control
class="macro">#define DEF_CONTROL_LIST_MARGIN_X(class="num">1)                  class=class="str">"cmt">// Gap between columns in ListBox controls
class="macro">#define DEF_CONTROL_LIST_MARGIN_Y(class="num">0)                  class=class="str">"cmt">// Gap between rows in ListBox controls
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

◍ 图形元素类型的枚举划分

在 MT5 自定义界面开发里,图形对象不是铁板一块,而是按用途分成了标准对象、扩展对象、阴影对象,以及窗体类(WinForms)两大谱系。上面这段枚举把 26 种元素类型一次性列清,其中被高亮的三行(TabHeader、TabPage、TabControl)属于多页签容器的核心构件,做复杂面板时绕不开。 标准谱系里 GRAPH_ELEMENT_TYPE_STANDARD 到 GRAPH_ELEMENT_TYPE_WINDOW 覆盖了画线、按钮基底、独立窗口;WinForms 谱系则从 WF_UNDERLAY 垫层开始,到 WF_BASE 基窗体,再往下拆出容器(Panel、GroupBox、TabControl)与标准控件(Label、Button、CheckBox 等)。 实盘里若你要做带页签的盯盘面板,直接引用 GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL 做父容器,里面挂 WF_TAB_PAGE,每个 Page 再塞 Label 或 ListBox,比堆标准对象省掉大量坐标计算。外汇与贵金属波动剧烈,这类 UI 只解决交互效率,不替代风控。

MQL5 / C++
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
   class=class="str">"cmt">//--- &class="macro">#x27;Container&class="macro">#x27; object types are to be set below
   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
   class=class="str">"cmt">//--- &class="macro">#x27;Standard control&class="macro">#x27; object types are to be set below
   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 CheckBox
   GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,                 class=class="str">"cmt">// Windows Forms RadioButton
   GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,           class=class="str">"cmt">// Base list object of Windows Forms elements
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                    class=class="str">"cmt">// Windows Forms ListBox
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            class=class="str">"cmt">// Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             class=class="str">"cmt">// Windows Forms ButtonListBox
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  class=class="str">"cmt">// Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_PAGE,                    class=class="str">"cmt">// Windows Forms TabPage
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 class=class="str">"cmt">// Windows Forms TabControl
  };

常见问题

在控件重绘逻辑里补一个鼠标离开时的擦除或底色重填,确保悬停状态结束即时还原,避免旧图元残留。
给 Tab 标题设最大字符截断或自动省略号,并固定标签最小宽度,超长部分用缩略显示,不影响布局。
可以,把控件截图或描述发给小布,它能指出悬停残影、状态色缺失等常见 GUI 卡点并给修改思路。
会,空值可能让页面用错底色,建议给宏设明确默认色常量,并在初始化时强制赋值一次。
把间距也提为常量按元素类型分支读取,改一处即可全局生效,不必每个控件手写数值。