DoEasy. 控件 (第 24 部分): 提示(Hint)辅助 WinForms 对象(基础篇)
📘

DoEasy. 控件 (第 24 部分): 提示(Hint)辅助 WinForms 对象(基础篇)

第 1/3 篇

「给库控件加上悬浮提示层」

在 DoEasy 库的第 24 部分里,我们给控件系统补上了 Hint 辅助对象,用来在鼠标悬停时弹出 WinForms 风格的提示框,而不必每次手动写气泡逻辑。 该版本示例发布于 2023 年 2 月 10 日 11:29,原文页面记录浏览量 1082、评论 0,说明这套接口偏底层、适合二次封装而非直接拿来用。 Hint 对象及其衍生类都继承自库内的辅助基类,意味着你可以挂到任意已有控件上,只改提示文本和延迟参数就能跑。 想在 MT5 里验证,直接建个空 EA 把库里 Hint 类挂到按钮上,设 500 毫秒延迟看弹出节奏,外汇与贵金属品种上测试需注意点差跳变带来的高频重绘风险。

图形元素的父子指针与提示对象设计

函数库里大量图形元素彼此挂钩:每个控件都持有一个指向基准对象的指针,并挂在整个对象层级的主父对象之下。旧实现先把对象建出来,再回头补父对象指针,经常出现控件“丢失”父对象的缺陷,而且出错后很难定位。我把父对象指针直接塞进所有图形元素的构造函数,创建时一并指定;独立控件则传 NULL,省掉后续逐个绑定的麻烦,也降低以后写新控件和调试时的排错成本。 MQL5 本身不支持改鼠标光标外观,但用户得知道控件区域能不能交互。我的做法是:在可交互位置叠一个辅助图形对象,用它的样子暗示可执行的操作,这类元素统称为 hint(提示)对象。本次先写基准提示类,再派生出指示隔板可上、下、左、右移动的四个子类——只要光标停在 SplitContainer 的隔板上,对应提示就贴在光标旁,说明能横或竖拖。 后续新控件都会自带这类提示对象,比如将来标出“可改图形尺寸”的区域。对 MT5 使用者而言,开编辑器看构造函数的指针参数变化,就能验证这次层级逻辑的返工点。外汇与贵金属图表上叠加自绘对象属高风险操作,参数误用可能导致界面异常。

◍ 把父对象指针焊进构造函数

做图形控件库重构时,最烦的就是先 new 一个对象再单独 setParent。这次直接把主对象(main)和基准对象(base)的指针塞进类构造函数,创建瞬间对象就认得自己挂在谁下面,独立对象传 NULL 即可,省掉后置绑定的多余调用。 原 IsBase() 命名有歧义:m_element_base 为 NULL 只说明没挂到别的对象,不代表它不能当别人的基准。改成 IsDepend() 后逻辑更干净——返回 m_element_base != NULL 的标志,判断“是否依附”比判断“是否基础”更贴近实际层级关系。 涉及的文件不少:GBaseObj.mqh 里补了返回元素类型字符串的方法,GCnvElement.mqh 删了设置父对象的方法只留获取指针,ShadowObj / Form / WinFormBase 等所有构造函数都加了双指针形参。Button.mqh、ListBox.mqh 等 10 个文件仅改构造,ButtonListBox.mqh 等 6 个还顺手改了新建图形对象时传指针的逻辑。 提示控件(Hint)的颜色常量已经定义好:背景 C'0xFF,0xFF,0xE1'、边框 C'0x76,0x76,0x76'、文字 C'0x5A,0x5A,0x5A'。开 MT5 进 Include\DoEasy\Defines.mqh 搜 CLR_DEF_CONTROL_HINT 就能看到这三行,改色直接调十六进制值。 外汇与贵金属图表上跑自定义控件属高风险操作,改完库类先开模拟盘验渲染,再上实盘。

MQL5 / C++
class="macro">#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;)  class=class="str">"cmt">// SplitContainer control background class="type">color
class="macro">#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_DOWN(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;)  class=class="str">"cmt">// Color of SplitContainer control background when clicking on the control
class="macro">#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_OVER(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;)  class=class="str">"cmt">// Color of SplitContainer control background when hovering the mouse over the control
class="macro">#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR(C&class="macro">#x27;0x65,0x65,0x65&class="macro">#x27;)  class=class="str">"cmt">// SplitContainer control frame class="type">color
class="macro">#define CLR_DEF_CONTROL_HINT_BACK_COLOR(C&class="macro">#x27;0xFF,0xFF,0xE1&class="macro">#x27;)  class=class="str">"cmt">// Hint control background class="type">color
class="macro">#define CLR_DEF_CONTROL_HINT_BORDER_COLOR(C&class="macro">#x27;0x76,0x76,0x76&class="macro">#x27;)  class=class="str">"cmt">// Hint control frame class="type">color
class="macro">#define CLR_DEF_CONTROL_HINT_FORE_COLOR(C&class="macro">#x27;0x5A,0x5A,0x5A&class="macro">#x27;)  class=class="str">"cmt">// Hint control text class="type">color
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

「GUI 控件的底层常量与元素枚举」

在 MT5 里手搓自定义面板,第一件事是把尺寸与颜色常量钉死。下面这组宏定义了字体、边框、控制点等基础参数:默认字体 Calibri、字号 8、外框留白 16 像素、边框宽 3,提示图标边长 11。控制点半径 5、颜色 clrDodgerBlue、管理表单尺寸 40,这些数直接决定拖拽体验,改小了容易点不中,改大了挡视野。 图形对象 ID 上限设成 10000,意味着单个 EA 或指标能挂的私有对象数在这个量级内不会撞车。超出后只能自己写映射表,否则 ObjectFind 会返回乱序句柄。 元素类型用枚举区分:从 STANDARD 标准对象,到 STANDARD_EXTENDED 扩展标准对象,再到 WF_CHECKED_LIST_BOX、WF_BUTTON_LIST_BOX 这类仿 WinForms 控件。开 MT5 把这段枚举抄进头文件,就能在调试时一眼看出当前控件属于哪一层。

MQL5 / C++
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 DEF_ARROW_BUTTON_SIZE(class="num">15)                        class=class="str">"cmt">// Default arrow button 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="macro">#define DEF_HINT_ICON_SIZE(class="num">11)                        class=class="str">"cmt">// Hint object side size
class=class="str">"cmt">//--- Graphical object parameters
class="macro">#define PROGRAM_OBJ_MAX_ID(class="num">10000)                     class=class="str">"cmt">// Maximum value of an ID of a graphical object belonging to a program
class="macro">#define CTRL_POINT_RADIUS(class="num">5)                         class=class="str">"cmt">// Radius of the control point on the form for managing graphical object pivot points
class="macro">#define CTRL_POINT_COLOR(clrDodgerBlue)             class=class="str">"cmt">// Radius of the control point on the form for managing graphical object pivot points
class="macro">#define CTRL_FORM_SIZE(class="num">40)                        class=class="str">"cmt">// Size of the control point form for managing graphical object pivot points
class=class="str">"cmt">//+------------------------------------------------------------------+
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
class=class="str">"cmt">//---...
class=class="str">"cmt">//---...
   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
   class=class="str">"cmt">//--- Auxiliary elements of WinForms objects

GUI 枚举里的 Windows Forms 控件映射

在 MQL5 的图形元素类型枚举收尾段,能看到一批 GRAPH_ELEMENT_TYPE_WF_* 常量,它们把 MT5 自定义图形界面组件直接对接到 Windows Forms 的控件语义上。 比如 GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM 对应 ListBox 子项,GRAPH_ELEMENT_TYPE_WF_TAB_HEADER / TAB_FIELD 管标签页头与字段区,而 GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP/DOWN/LEFT/RIGHT 则拆出了四个方向的箭头按钮。 被高亮的 WF_HINT_BASE 与四个 HINT_MOVE_* 常量值得留意:它们定义了拖拽提示基类及上/下/左/右四个移动方向的提示控件,是做可拖拽面板时必须挂接的枚举值。 对应的 MSG_GRAPH_ELEMENT_TYPE_WF_* 消息枚举里,HINT_MOVE_RIGHT/UP/DOWN 的注释原文均写成了 HintMoveLeft,这是标准头文件里的笔误,实际映射应分别为右/上/下提示,开 MT5 调图形对象时别被注释误导。 枚举尾部还出现了俄英双语的字符串对,例如 {"Элемент управления \"UpDownArrowBox\"","Control element \"UpDownArrowBox\""},说明底层资源表同时维护了俄语与英语的界面文案。

MQL5 / C++
  GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,                    class=class="str">"cmt">// Windows Forms ListBoxItem
  GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                        class=class="str">"cmt">// Windows Forms TabHeader
  GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                         class=class="str">"cmt">// Windows Forms TabField
  GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,             class=class="str">"cmt">// Windows Forms SplitContainerPanel
  GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                      class=class="str">"cmt">// Windows Forms ArrowButton
  GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,                   class=class="str">"cmt">// Windows Forms UpArrowButton
  GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,                 class=class="str">"cmt">// Windows Forms DownArrowButton
  GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,                 class=class="str">"cmt">// Windows Forms LeftArrowButton
  GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,                class=class="str">"cmt">// Windows Forms RightArrowButton
  GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,              class=class="str">"cmt">// Windows Forms UpDownArrowButtonsBox
  GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,              class=class="str">"cmt">// Windows Forms LeftRightArrowButtonsBox
  GRAPH_ELEMENT_TYPE_WF_SPLITTER,                          class=class="str">"cmt">// Windows Forms Splitter
  GRAPH_ELEMENT_TYPE_WF_HINT_BASE,                         class=class="str">"cmt">// Windows Forms HintBase
  GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,                    class=class="str">"cmt">// Windows Forms HintMoveLeft
  GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,                   class=class="str">"cmt">// Windows Forms HintMoveRight
  GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,                      class=class="str">"cmt">// Windows Forms HintMoveUp
  GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,                    class=class="str">"cmt">// Windows Forms HintMoveDown
};
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,    class=class="str">"cmt">// UpDownArrowBox control
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,    class=class="str">"cmt">// LeftRightArrowBox control
  MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE,               class=class="str">"cmt">// HintBase control
  MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,          class=class="str">"cmt">// HintMoveLeft control
  MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,         class=class="str">"cmt">// HintMoveLeft control
  MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,            class=class="str">"cmt">// HintMoveLeft control
  MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,          class=class="str">"cmt">// HintMoveLeft control
  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
  {"Элемент управления "UpDownArrowBox"","Control element "UpDownArrowBox"

◍ 图形元素类型的描述映射逻辑

在自定义图形库里,每种 ENUM_GRAPH_ELEMENT_TYPE 都需要对应一段可读文本,方便面板悬浮提示和调试输出。上面那组本地化元组先登记了 HintBase、HintMoveLeft 等五个方向控件,以及「图形对象是否属于程序」这两种归属状态描述。 真正做类型翻译的是 CGBaseObj::TypeElementDescription()。它用三元运算符把枚举值逐个比对,命中后返回 CMessage::Text() 查到的对应文案;从 GRAPH_ELEMENT_TYPE_STANDARD 到 WF_SPLIT_CONTAINER,覆盖了标准、扩展、影子对象、窗体以及 WinForms 容器系列共 13 个分支。 你在 MT5 里接这套结构时,直接抄这个函数就能把底层枚举转成中文标签。若自己加了新元素类型,记得在三元链末尾补分支,否则会落到默认的空串返回,面板可能显示空白。

MQL5 / C++
class="type">class="kw">string CGBaseObj::TypeElementDescription(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)              :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)        :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)    :

常见问题

把提示层做成独立对象并绑定父控件指针,生命周期由父对象管理,不要每次鼠标移动都新建销毁。
在控件构造函数里直接传入父对象指针并保存为私有成员,后续所有子元素通过它回溯坐标与事件。
可以,小布能读取你的控件结构描述并标出未释放的提示对象,给出父子引用断点位置。
按枚举里的元素类型反查描述映射表,每个图形元素类型都有对应文本,照表填控件类别即可。
延迟参数写在提示辅助对象的基础属性里,改构造时的等待常量就能缩短或拉长触发时间。