SplitContainer 面板隔板:从零理解可拖拽分区控件的底层构造(基础篇)
(1/3)· 很多 MQL5 界面库用死了静态面板,却从没搞清隔板对象该如何接管鼠标区域与重绘
「给面板加一块可拖拽的隔板」
在 DoEasy 控件库里,SplitContainer 的本质就是一块能上下或左右分隔两个子面板的隔板对象,用户拖动它即可实时改变两侧区域占比。 在 MT5 里这类控件常用于把图表上的信息面板和信号面板拆开,避免相互遮挡。实际验证时,可在库示例工程加载 SplitContainer 演示,拖动隔板看子面板重绘是否卡顿。 原文示例发布于 2023 年 2 月 2 日,公开阅读量约 1187,说明这类基础控件在量化交易者里需求稳定。外汇与贵金属图表加载自定义控件存在平台兼容性风险,建议先在模拟账户验证。
SplitContainer 隔板交互的取舍与实现
当前函数库已能创建 SplitContainer 控件,以两个默认参数生成静态面板对象,内部由隔板分隔两块区域。原生 MS Visual Studio 里隔板可拖动改尺寸,悬停出特征光标、按住出虚线框、移动出阴影跟随、松手重排面板。 MQL5 没有更改光标外观的接口,所以这里不去做“抓取拖动”信号,而是直接在隔板区覆盖阴影来表示可移动。算法比 VS 更短:悬停显阴影,按住阴影移动时面板尺寸立即跟随新隔板位置变化,松手且光标离开后阴影隐藏、尺寸保留。 隔板对象派生自 CWinFormBase 基类,重写了清除与重绘虚拟方法,用虚线填充整块区域。可见性由 SplitContainer 事件处理:悬停控制区显示,离开隐藏。这类区域既能包住隔板本身,也能包住最小化、最大化、关闭等按钮。 外汇与贵金属 MT5 环境属高风险,自定义控件仅用于界面交互,不预示任何交易结果。
◍ 给控件库补上窗体缩放与隔板状态底座
要在 MT5 自建 UI 里支持鼠标拖拽改窗体大小、放可移动隔板,得先给 DoEasy 底层补一组枚举与属性。原库图形元素整数属性总数到本次改动被推到 122 个,新增的隔板方向默认 0 即垂直位,这数字直接决定你后面序列化对象时文件结构是否对齐。 改动集中在 Defines.mqh、Data.mqh 与 Graph 下的几个类。鼠标状态枚举新增了窗口顶部、底部、左、右缩放区的一组 ID,事件列表里把原来的 MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL 位置让给 MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL,末尾仍以 NEXT_CODE 宏收口,保证后续追加不冲突。 Form.mqh 里设置鼠标状态的方法要写一位字位:CursorInsideControlArea() 返回 true 时,把 m_mouse_state_flags 的第 10 位(bit 10)置 1,出区域则清 0。这一位就是你 EA 面板判断「光标是否在控件区」的唯一信源,没它隔板拖动逻辑跑不起来。 GCnvElement 构造函数给所有新属性默认填零,结构读写方法双向映射字段;WinFormBase 重绘前先查显示标志,不显示就跳过。下面这段是缩放区鼠标状态的枚举片段,注意注释行是新增区块起始:
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 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED, class=class="str">"cmt">// The cursor is inside the active area, left mouse button is released class=class="str">"cmt">//--- Within the window scrolling area MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window scrolling area, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area, the mouse buttons are not clicked
「把鼠标状态映射成可捕获的事件枚举」
在 MT5 自定义面板开发里,光知道光标在哪儿不够,还得区分「按没按」「滚没滚」。原文把窗口交互拆成两套枚举:一套描述光标所在区域与按键状态(FORM_STATE),一套把这些状态直接定义为可监听的事件(MOUSE_EVENT)。 枚举 ENUM_MOUSE_EVENT 以 CHART_OBJ_EVENTS_NEXT_CODE 为起点赋值,保证不与图表内置对象事件冲突。它覆盖窗体外、窗体内、标题激活区、滚动区、缩放区五类空间,每类再分 NOT_PRESSED / PRESSED / WHEEL,激活区额外多一个 RELEASED 左键释放态,共 17 个枚举值。 实际写 EA 或指标时,直接在 OnChartEvent 里用 switch 接这些事件码,就能让面板响应拖拽标题、滚轮翻页。外汇与贵金属图表挂这类自定义 UI 属高风险操作环境,事件逻辑出错可能卡死图表,建议先在模拟账户 MT5 里单步验证。
enum ENUM_MOUSE_EVENT { MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, class=class="str">"cmt">// No event class=class="str">"cmt">//--- MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED, class=class="str">"cmt">// The cursor is outside the form, the mouse buttons are not clicked MOUSE_EVENT_OUTSIDE_FORM_PRESSED, class=class="str">"cmt">// The cursor is outside the form, the mouse button(any) is clicked MOUSE_EVENT_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_EVENT_INSIDE_FORM_NOT_PRESSED, class=class="str">"cmt">// The cursor is inside the form, no mouse buttons are clicked MOUSE_EVENT_INSIDE_FORM_PRESSED, class=class="str">"cmt">// The cursor is inside the form, the mouse button(any) is clicked MOUSE_EVENT_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_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is inside the active area, the mouse buttons are not clicked MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED, class=class="str">"cmt">// The cursor is inside the active area, any mouse button is clicked MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL, class=class="str">"cmt">// The cursor is inside the active area, the mouse wheel is being scrolled MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED, class=class="str">"cmt">// The cursor is inside the active area, left mouse button is released class=class="str">"cmt">//--- Within the window scrolling area MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area, the mouse buttons are not clicked MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area, the mouse button(any) is clicked MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window scrolling area, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area MOUSE_EVENT_INSIDE_RESIZE_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area, the mouse buttons are not clicked MOUSE_EVENT_INSIDE_RESIZE_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area, the mouse button(any) is clicked MOUSE_EVENT_INSIDE_RESIZE_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area, the mouse wheel is being scrolled };
拆分区鼠标事件与图形元素类型枚举
在自定义指标或 EA 的 GUI 开发中,先要把鼠标在窗口分隔条(splitter)上的状态拆清楚。MT5 内部用一组枚举值区分光标是否落在分隔区、是否按下、是否滚动滚轮:未按下、按下任意键、滚轮滚动,分别对应三个独立事件码。 为了不让后续事件码冲突,工程里习惯用宏把“最后一个鼠标事件码 +1”定义为 MOUSE_EVENT_NEXT_CODE。例如本段中 MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL 加 1,得到下一个可用事件起点,方便自己扩展触摸或右键事件。 图形元素则归到 ENUM_GRAPH_ELEMENT_TYPE 里。基础层有 STANDARD、SHADOW_OBJ、ELEMENT、FORM、WINDOW;往上叠 WinForms 仿真层,从 WF_BASE 到 WF_CONTAINER、WF_PANEL、WF_TAB_CONTROL、WF_SPLIT_CONTAINER,再到具体控件 WF_LABEL、WF_BUTTON、WF_CHECKBOX、WF_RADIOBUTTON,共 20 个类型常量。 开 MT5 新建一个空 EA,把下面枚举贴进头文件,编译后能在调试器里看到这些常量。做贵金属或外汇面板时,高风险在于误把 WF_SPLIT_CONTAINER 当普通 WINDOW 处理,可能导致拖拽分隔条时事件丢失。
class=class="str">"cmt">//--- Within the window separator area MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area, the mouse buttons are not clicked MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area, the mouse button(any) is clicked MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window separator area, the mouse wheel is being scrolled }; class="macro">#define MOUSE_EVENT_NEXT_CODE(MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL+class="num">1) class=class="str">"cmt">// The code of the next event after the last mouse event code 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 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 GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, class=class="str">"cmt">// Windows Forms TabControl GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER, class=class="str">"cmt">// Windows Forms SplitContainer 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
◍ WinForms 图形元素与控件事件枚举的收尾定义
在自定义面板库的类型体系末尾,补上了 Windows Forms 风格控件的细分元素枚举,从基础列表对象一直列到方向箭头与分隔条。其中 GRAPH_ELEMENT_TYPE_WF_SPLITTER 负责切分容器面板的可拖拽分隔区,是布局类 EA 面板常碰到的元素。 紧随其后的 ENUM_WF_CONTROL_EVENT 把用户能触发的动作码定死了:基础点击、点击取消、Tab 切换自不必说,四个方向的滚动箭头点击也单独成事件。WF_CONTROL_EVENT_SPLITTER_MOVE 专门捕获分隔条被拖动的位置变更,配合前面的 SPLITTER 元素类型,就能在 MT5 里做可拖拽调宽的信息栏。 开 MT5 新建一个 include 把这两段枚举贴进去,编译后挂到图表,用 OnChartEvent 打印事件码,就能验证分隔条拖动是否真的回传了 WF_CONTROL_EVENT_SPLITTER_MOVE。外汇与贵金属图表上跑这类自定义 UI 仍属高风险操作,事件逻辑出错可能卡死图表。
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 class=class="str">"cmt">//--- Auxiliary elements of WinForms objects 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 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| List of possible WinForms control events | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_WF_CONTROL_EVENT { WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,class=class="str">"cmt">// No event WF_CONTROL_EVENT_CLICK, class=class="str">"cmt">// "Click on the control" event WF_CONTROL_EVENT_CLICK_CANCEL, class=class="str">"cmt">// "Canceling the click on the control" event WF_CONTROL_EVENT_TAB_SELECT, class=class="str">"cmt">// "TabControl tab selection" event WF_CONTROL_EVENT_CLICK_SCROLL_LEFT, class=class="str">"cmt">// "Clicking the control left button" event WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT, class=class="str">"cmt">// "Clicking the control right button" event WF_CONTROL_EVENT_CLICK_SCROLL_UP, class=class="str">"cmt">// "Clicking the control up button" event WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, class=class="str">"cmt">// "Clicking the control down button" event WF_CONTROL_EVENT_SPLITTER_MOVE, class=class="str">"cmt">// "Control separator relocation" event };