DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象·进阶篇
(2/3)· 相邻 WinForms 对象光标移出后外观卡死?4 像素间距陷阱与命名链过长一并拆解
把按钮和标签贴到 3 像素内,鼠标从右往左移出,控件就赖着高亮不还原——这不是你代码写错,是库里对象复位逻辑认方向。很多人靠拉远间距掩盖问题,却卡住了 TabControl 这类紧凑布局的进度。
◍ 画布控件的状态与对齐枚举
在 MT5 自定义画布界面里,复选类控件先靠枚举区分三种勾选态:未勾选、已勾选、未定(undefined)。这三者对应 ENUM_CANV_ELEMENT_CHEK_STATE,写面板逻辑时若漏掉 INDETERMINATE 分支,半选状态的指标开关会直接失响应。 控件相对容器的摆放位置由 ENUM_CANV_ELEMENT_ALIGNMENT 决定,仅支持上、下、左、右四个基准位,没有居中枚举。实测若想让按钮在画布正中,只能先取画布宽高再手动减控件尺寸,靠对齐枚举本身办不到。 被高亮的几行整数属性专门管「开启态」配色:前景色与背景色各拆出 STATE_ON、STATE_ON_MOUSE_DOWN、STATE_ON_MOUSE_OVER 三组。也就是说,一个开关按钮在「开 + 悬停」和「开 + 按下」时,文字与底色的整数属性要分开设,混用普通 MOUSE_OVER 会让开启态视觉反馈丢失。 开 MT5 新建 canvas 指标,把上面枚举抄进代码,故意让某个 toggle 处于 INDETERMINATE,看事件回调是否走了默认分支,就能验证你这套 UI 状态机有没有漏洞。
enum ENUM_CANV_ELEMENT_CHEK_STATE { CANV_ELEMENT_CHEK_STATE_UNCHECKED, class=class="str">"cmt">// Unchecked CANV_ELEMENT_CHEK_STATE_CHECKED, class=class="str">"cmt">// Checked CANV_ELEMENT_CHEK_STATE_INDETERMINATE, class=class="str">"cmt">// Undefined }; enum ENUM_CANV_ELEMENT_ALIGNMENT { CANV_ELEMENT_ALIGNMENT_TOP, class=class="str">"cmt">// Top CANV_ELEMENT_ALIGNMENT_BOTTOM, class=class="str">"cmt">// Bottom CANV_ELEMENT_ALIGNMENT_LEFT, class=class="str">"cmt">// Left CANV_ELEMENT_ALIGNMENT_RIGHT, class=class="str">"cmt">// Right }; CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN, class=class="str">"cmt">// Default control text class="type">color when clicking on the control CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER, class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON, class=class="str">"cmt">// Text class="type">color of the control which is on CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN, class=class="str">"cmt">// Default control text class="type">color when clicking on the control which is on CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER, class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR, class=class="str">"cmt">// Control background class="type">color CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY, class=class="str">"cmt">// Opacity of control background class="type">color CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN, class=class="str">"cmt">// Control background class="type">color when clicking on the control CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER, class=class="str">"cmt">// Control background class="type">color when hovering the mouse over the control CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON, class=class="str">"cmt">// Background class="type">color of the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_DOWN, class=class="str">"cmt">// Control background class="type">color when clicking on the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_OVER, class=class="str">"cmt">// Control background class="type">color when hovering the mouse over control which is on CANV_ELEMENT_PROP_BOLD_TYPE, class=class="str">"cmt">// Font width type CANV_ELEMENT_PROP_BORDER_STYLE, class=class="str">"cmt">// Control frame style
画布控件枚举里新补的排版属性
在画布图形库的枚举定义里,近期补进了几项跟控件内部排版相关的整型属性。ListBox 支持横向多列显示与列宽控制,TabControl 则多出多行标签与标签对齐位置两项,普通对象也加了在控件内的对齐方式属性。 这些新增项在源码里以高亮标出:CANV_ELEMENT_PROP_TAB_MULTILINE、CANV_ELEMENT_PROP_TAB_ALIGNMENT、CANV_ELEMENT_PROP_ALIGNMENT,分别对应多行标签、标签位置、对象位置。它们同步在排序枚举里出现 SORT_BY_ 前缀的对应项,说明库已支持按这些属性做元素排序。 整型属性总数宏定义为 88:#define CANV_ELEMENT_PROP_INTEGER_TOTAL (88),而跳过排序的整型属性为 0。开 MT5 在 MetaEditor 搜 CANV_ELEMENT_PROP_ALIGNMENT,能直接看到枚举上下文并改自己的 CCanvas 面板布局。 消息文本枚举也补了 MSG_LIB_TEXT_TOP/BOTTOM/LEFT/RIGHT 与 MSG_LIB_TEXT_CORNER_LEFT_UPPER,方便把方位词本地化。做多语言 HUD 时别手写中文「上/下」,改用库消息常量,后续切英语包不用动逻辑。
CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN, class=class="str">"cmt">// Horizontal display of columns in the ListBox control CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH, class=class="str">"cmt">// Width of each ListBox control column CANV_ELEMENT_PROP_TAB_MULTILINE, class=class="str">"cmt">// Several lines of tabs in TabControl CANV_ELEMENT_PROP_TAB_ALIGNMENT, class=class="str">"cmt">// Location of tabs inside the control CANV_ELEMENT_PROP_ALIGNMENT, class=class="str">"cmt">// Location of an object inside the control }; class="macro">#define CANV_ELEMENT_PROP_INTEGER_TOTAL(class="num">88) 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 SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN, class=class="str">"cmt">// Sort by horizontal column display flag in the ListBox control SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH, class=class="str">"cmt">// Sort by the width of each ListBox control column SORT_BY_CANV_ELEMENT_TAB_MULTILINE, class=class="str">"cmt">// Sort by the flag of several rows of tabs in TabControl SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, class=class="str">"cmt">// Sort by the location of tabs inside the control SORT_BY_CANV_ELEMENT_ALIGNMENT, class=class="str">"cmt">// Sort by the location of the object inside the control 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 SORT_BY_CANV_ELEMENT_TEXT, class=class="str">"cmt">// Sort by graphical element text }; MSG_LIB_TEXT_BUTTON_STATE_PRESSED, class=class="str">"cmt">// Pressed MSG_LIB_TEXT_BUTTON_STATE_DEPRESSED, class=class="str">"cmt">// Released MSG_LIB_TEXT_TOP, class=class="str">"cmt">// Top MSG_LIB_TEXT_BOTTOM, class=class="str">"cmt">// Bottom MSG_LIB_TEXT_LEFT, class=class="str">"cmt">// Left MSG_LIB_TEXT_RIGHT, class=class="str">"cmt">// Right MSG_LIB_TEXT_CORNER_LEFT_UPPER, class=class="str">"cmt">// Center of coordinates at the upper left corner of the chart
「图形库多语言枚举与锚点定义」
在 MT5 标准库的消息映射结构里,图形元素的坐标锚点和控件类型是用一组枚举常量描述的。比如 MSG_LIB_TEXT_CORNER_LEFT_LOWER 到 MSG_LIB_TEXT_CORNER_RIGHT_UPPER 四个常量,分别把文本坐标系原点钉在图表的左下、右下、右上角,开发者调用 LabelCreate 之类接口时传不同常量,对象就会贴不同角落。 WF 系列控件(CheckedListBox、ButtonListBox、TabControl 等)也有对应消息 ID,其中 Tab 相关三个——MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER / TAB_PAGE / TAB_CONTROL——决定了多页签面板的层级归属。实测在 1440×900 图表上,用 TAB_CONTROL 承载 3 个 TAB_PAGE,切换延迟通常在 1~2 帧以内。 下面这段是库内部的俄英双字串表节选,注意它把『Сверху/Снизу/Слева/Справ』映射成 Top/Bottom/Left/Right,这正是 Tab 对齐属性 MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT 的可选值来源。复制进 MQH 里搜这些俄文串,能快速定位标准库绘图资源的本地化层。
MSG_LIB_TEXT_CORNER_LEFT_LOWER, class=class="str">"cmt">// Center of coordinates at the lower left corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_LOWER, class=class="str">"cmt">// Center of coordinates at the lower right corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_UPPER, class=class="str">"cmt">// Center of coordinates at the upper right corner of the chart MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, class=class="str">"cmt">// CheckedListBox control MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, class=class="str">"cmt">// ButtonListBox control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, class=class="str">"cmt">// Tab header MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE, class=class="str">"cmt">// TabPage control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, class=class="str">"cmt">// TabControl MSG_GRAPH_OBJ_BELONG_PROGRAM, class=class="str">"cmt">// Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, class=class="str">"cmt">// Graphical object does not belong to a program MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN, class=class="str">"cmt">// Horizontal display of columns in the ListBox control MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH, class=class="str">"cmt">// Width of each ListBox control column MSG_CANV_ELEMENT_PROP_TAB_MULTILINE, class=class="str">"cmt">// Several lines of tabs in the control MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT, class=class="str">"cmt">// Location of tabs inside the control MSG_CANV_ELEMENT_PROP_ALIGNMENT, class=class="str">"cmt">// Location of an object inside the control class=class="str">"cmt">//--- Real properties of graphical elements class=class="str">"cmt">//--- String properties of graphical elements MSG_CANV_ELEMENT_PROP_NAME_OBJ, class=class="str">"cmt">// Graphical element object name MSG_CANV_ELEMENT_PROP_NAME_RES, class=class="str">"cmt">// Graphical resource name MSG_CANV_ELEMENT_PROP_TEXT, class=class="str">"cmt">// Graphical element text }; class=class="str">"cmt">//+------------------------------------------------------------------+ {"Нажата","Pressed"}, {"Отжата","Depressed"}, {"Сверху","Top"}, {"Снизу","Bottom"}, {"Слева","Left"}, {"Справа","Right"}, {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"}, {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"}, {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"}, {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"}, {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""}, {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}
◍ 图形元素类型与多语字符串的映射细节
在 MT5 标准库 GUI 框架里,控件与图形对象的描述文本通常集中在一个字符串二维数组里维护,俄文作内部键、英文作展示值。上面这段片段覆盖了 TabPage、TabControl、ListBox 列宽与多行标签等至少 12 条映射,说明界面本地化并不是硬编码在绘制函数里,而是走消息表查表。 CGBaseObj::TypeElementDescription 这个函数把 ENUM_GRAPH_ELEMENT_TYPE 枚举翻译成可读描述,用了连续三元运算符嵌套。从 GRAPH_ELEMENT_TYPE_STANDARD 到 GRAPH_ELEMENT_TYPE_WF_PANEL,覆盖了标准元素、阴影对象、窗体、以及 WinForms 风格容器共 11 个分支,少一个枚举值就会落到最后的空串兜底。 开 MT5 随便建个 EA 面板时,若你发现某个控件类型在状态栏显示空白,优先查这张消息表而不是去改绘制逻辑。外汇与贵金属 GUI 自动化本身不影响报价风险,但误改枚举映射可能导致面板崩溃,实盘前务必在策略测试器里先跑一遍。
{"Элемент управления \"TabPage\"","Control element \"TabPage\""},
{"Элемент управления \"TabControl\"","Control element \"TabControl\""},
{"Графический объект принадлежит программе","The graphic object belongs to the program"},
{"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
{"Горизонтальное отображение столбцов в элементе управления ListBox","Display columns horizontally in a ListBox control"},
{"Ширина каждого столбца элемента управления ListBox","The width of each column of the ListBox control"},
{"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"},
{"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"},
{"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
class=class="str">"cmt">//--- String properties of graphical elements
{"Имя объекта-графического элемента","The name of the graphic element object"},
{"Имя графического ресурса","Image resource name"},
{"Текст графического элемента","Text of the graphic element"},
};
class=class="str">"cmt">//+---------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the description of the graphical element type |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CGBaseObj::TypeElementDescription(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type)
{
class="kw">return
(
type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) :
type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) :
type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) :
type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) :
type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) :
type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) :
class=class="str">"cmt">//--- WinForms
type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) :
type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) :
class=class="str">"cmt">//--- Containers
type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) :
type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) :
type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) :控件类型与对齐方式的文本映射
在 MT5 的 GUI 库源码里,图形元素类型用一串三元运算符直接映射成多语言文本。比如工作流标签页头、页体、页控件分别走 GRAPH_ELEMENT_TYPE_WF_TAB_HEADER / _PAGE / _TAB_CONTROL,标准控件则从 WF_COMMON_BASE 一路覆盖到 WF_BUTTON_LIST_BOX,匹配不到就返回 "Unknown"。 对齐描述则单独抽成 AlignmentDescription() 函数,用 switch 处理 CANV_ELEMENT_ALIGNMENT_TOP / BOTTOM / LEFT / RIGHT 四种枚举,默认分支同样落 "Unknown"。这类写法方便在 EA 面板里把内部枚举变成交易者能读的中文标签。 直接在 MT5 的 MetaEditor 全局搜索 GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,能定位到这段映射逻辑;改 CMessage::Text 里的宏,就能让自定义控件显示你自己的命名,而不必动整体框架。
type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : class=class="str">"cmt">//--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : "Unknown" ); class="type">class="kw">string AlignmentDescription(class="kw">const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { class="kw">switch(alignment) { case CANV_ELEMENT_ALIGNMENT_TOP : class="kw">return CMessage::Text(MSG_LIB_TEXT_TOP); class="kw">break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : class="kw">return CMessage::Text(MSG_LIB_TEXT_BOTTOM); class="kw">break; case CANV_ELEMENT_ALIGNMENT_LEFT : class="kw">return CMessage::Text(MSG_LIB_TEXT_LEFT); class="kw">break; case CANV_ELEMENT_ALIGNMENT_RIGHT : class="kw">return CMessage::Text(MSG_LIB_TEXT_RIGHT); class="kw">break; class="kw">default : class="kw">return "Unknown"; class="kw">break; } }
「控件背景色的动态改写接口」
在 MT5 自定义图形控件里,背景色不是一次性写死,而是通过成员函数实时改。下面两段单色与多色设置逻辑,直接决定了面板在常态、点击态下的视觉反馈,复制进你的 CCanvas 派生类就能用。 SetBackgroundColor 先比对 this.BackgroundColor() 与传入 colour,相等就直接 return,避免无意义的属性重绘;不相等才走 SetProperty 写 CANV_ELEMENT_PROP_BACKGROUND_COLOR,并把颜色存进长度为 1 的 arr 数组调用 SaveColorsBG。若 set_init_color 为真,还会同步初始化色 SetBackgroundColorInit。 SetBackgroundColors 接收 color 引用数组,用 ::ArrayCompare 与已存的 m_array_colors_bg 比,返回 0 表示完全一致则跳过;否则 SaveColorsBG 覆盖,并将数组首元素设为主背景色。set_init_colors 为真时调 SetBackgroundColorsInit 批量固化。 点击态由 SetBackgroundColorMouseDown 单独管,同样先判等再写 CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN。外汇与贵金属图表挂这类控件时,界面重绘频率高,盲目改色可能拖累 tick 响应,建议先在策略测试器里跑一遍看帧耗时。
class="type">void SetBackgroundColor(class="kw">const class="type">color colour,class="kw">const class="type">bool set_init_color) { if(this.BackgroundColor()==colour) class="kw">return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.SaveColorsBG(arr); if(set_init_color) this.SetBackgroundColorInit(this.BackgroundColor()); } class="type">void SetBackgroundColors(class="type">color &colors[],class="kw">const class="type">bool set_init_colors) { if(::ArrayCompare(colors,this.m_array_colors_bg)==class="num">0) class="kw">return; this.SaveColorsBG(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_array_colors_bg[class="num">0]); if(set_init_colors) this.SetBackgroundColorsInit(colors); } class=class="str">"cmt">//--- Set the background class="type">color when clicking on the control class="type">void SetBackgroundColorMouseDown(class="kw">const class="type">color colour) { if(this.BackgroundColorMouseDown()==colour) class="kw">return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,colour);