TabControl 选项卡滚动:从裁剪盲区到可控平移的基础篇(基础篇)
📑

TabControl 选项卡滚动:从裁剪盲区到可控平移的基础篇(基础篇)

(1/3)·多数自建面板的选项卡一多就被边缘吞掉,却从没想过让标题栏自己让位

偏理论 第 1/3 篇
把一堆标签塞进固定宽度的 TabControl,右侧标题被容器切掉就当没看见,是界面开发里最常见的将就。点选一个被裁掉的标签时面板毫无反应,用户只会觉得程序卡死。这篇先把平移行为的底层逻辑铺开,免得后面写滚动按钮时还在补概念窟窿。

「给 TabControl 加上选项卡滚动」

在 DoEasy 库的控件体系里,TabControl 原本只能平铺选项卡,一旦标签数量超过控件宽度就只能靠缩小字体或将就布局。第 18 部分补上了滚动选项卡的能力:当总标签宽度超出可见区域时,自动出现左右滚动逻辑,用户可逐页或按步长平移选项页。 该改动已随 2023 年 1 月 12 日发布的示例入库,原文示例贴文显示阅读量 839、评论 2 条,说明这是一个偏底层、受众窄但实打实影响面板可用性的补丁。打开 MT5 载入该示例,能在控件演示窗口里直接看到标签溢出时的滚动条行为。 外汇与贵金属图表上的自定义面板常堆十几类信号页,若你用 DoEasy 写 EA 面板,建议直接继承这版 TabControl 基类,避免自己重写裁剪逻辑。这类界面增强不改动交易逻辑,但能降低误点概率,属于高风险交易环境下的操作减负。

◍ 让标签栏像 VS 那样向左裁切平移

在 MT5 的 WinForms 风格 TabControl 里,标题字符串排成一行若超过控件宽度,默认会从右边缘直接截断,选不中后面被切掉的标签。MS Visual Studio 的处理方式是:一旦选中了从右边缘被裁的标题,整条标题栏就向左平移,让所选标题完整露出来,原先最左边的标题则移出左边界。 本篇先不做完整的双向滚动条,只实现「选中右裁标题 → 向左平移」这一侧行为,作为后续齐全方法的原型验证。下一篇再补上利用控件按钮向两侧滚动全部标题的完整逻辑。 按钮摆放位置按标题栏方位定:顶部标题时滚动钮在右上角,底部在右下角;左侧标题时钮在左上角,右侧时在右下角。按钮从选项卡边框缩进 1 像素,标题也由按钮边框(而非容器边框)按 1 像素缩进裁切,排列复刻 Visual Studio 的观感。 打开 MT5 新建一个空 EA,拖一个自绘 TabControl 把标题凑到超出宽度,就能对照验证这种裁切平移是否跟描述一致。外汇与贵金属自动化界面开发同样受平台限制,实盘前请在策略测试器充分验证高风险。

把控件点击改成图表事件驱动

在 DoEasy 的 Data.mqh 里补了新消息索引和对应文本后,核心改动是:控件被单击时不再死守本类处理,而是向控件所在图表发事件,由函数库路由到真正有处理能力的类。这样能避开在计时器里轮询图形元素事件,事件响应更直接。 Defines.mqh 新增了 WinForms 对象事件枚举,目前只含两个:单击控件、选择 TabControl 选项卡。后者用来处理被裁剪选项卡标题的点击,并驱动标题行滚动。 辅助控件原先散在 WinForms 共享目录,现在统一挪到 Objects\Graph\WForms\Helpers\,涉及 ArrowButton、TabHeader、ListBoxItem 等 10 个文件。挪完必须改包含路径,否则编译报找不到文件——这是实打实要开的 MT5 文件树核对动作。 容器类里 CreateNewGObject() 的 switch 分支原先多行展开,现在压成一行。改完 Panel、GroupBox、TabField 等文件后,方法可读性一致,也暴露出各容器逻辑雷同,后续可抽公共方法。 点击被裁剪的选项卡标题时,需要让标题行平移使其完整露出。但偏移量在 TabControl 类,标题对象并不知道。解法是用事件带出标题索引和对象名(主对象名与 TabControl 名用分号拼进一个字符串参数),函数库按分隔符拆出两者,精准定位。 函数库现按名构建唯一对象,但已有按名查找方法还要图表 ID。其实本库对象名全局唯一,图表 ID 多余,于是 WinFormBase 加了纯按名返回指针的方法:先补程序名前缀,再查绑定图形元素列表,命中唯一则返回,空则 NULL。

「标签栏平移与按名检索的图形元素改造」

要让整行标题向左移,第一步是在左侧找到第一个可见标题,读出它的宽度。这个宽度直接作为整行标题左移的偏移量;平移之后,原本按标题位置画的字段边框就错位了,必须重绘选中标题的边框。目前这套逻辑只支持水平向左,多方向(上下左右)的移动要等下一篇再补。 事件处理里,先调图形对象类的子窗口 Y 偏移修正——因为父类方法被覆盖,漏掉这步画布上元素坐标会乱。随后再跑上面说的标签栏平移方法。 在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 里给集合类加了一个按名称返回元素的方法。由于画布上所有图形元素名称都已带程序名前缀且唯一,搜索时不必再传图表 ID;方法内部会检查传入名,若缺程序名就自动补到开头,确保和真实对象名对得上。 WinForms 对象现在能把事件发回控制程序图表,函数库截获后需重定向到对应类。目前集合类事件处理只认一个事件码:TabControl 里选了某个选项卡。用 StringSplit() 把 sparam 按 ';' 拆成两个名——面板对象 main 和 TabControl 的 base,再借 lparam、dparam 传入选中标题的行列索引,拿到指针后顺次调选项卡标题栏的 offset 方法。 代码段列出了 CDataPropObj 与 CGraphElementsCollection 相关的报错枚举及双语文本,其中 MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES(Failed to get object names)是本次新增检索逻辑配套的失败提示。

MQL5 / C++
class=class="str">"cmt">//--- CDataPropObj
  MSG_DATA_PROP_OBJ_OUT_OF_PROP_RANGE,           class=class="str">"cmt">// Passed class="kw">property is out of object class="kw">property range
  MSG_GRAPH_OBJ_FAILED_CREATE_NEW_HIST_OBJ,      class=class="str">"cmt">// Failed to create an object of the graphical object change history
  MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_HIST_LIST,     class=class="str">"cmt">// Failed to add the change history object to the list
  MSG_GRAPH_OBJ_FAILED_GET_HIST_OBJ,             class=class="str">"cmt">// Failed to receive the change history object
  MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE,           class=class="str">"cmt">// Failed to increase the array size
class=class="str">"cmt">//--- CGraphElementsCollection
  MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,       class=class="str">"cmt">// Failed to get the list of newly added objects
  MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES,         class=class="str">"cmt">// Failed to get object names
  MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,     class=class="str">"cmt">// Failed to remove a graphical object from the list
  MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_LIST,     class=class="str">"cmt">// Failed to remove a graphical object from the list
  MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART,    class=class="str">"cmt">// Failed to remove a graphical object from the chart
  MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST,      class=class="str">"cmt">// Failed to set a graphical object to the list of removed objects
  MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST,      class=class="str">"cmt">// Failed to set a graphical object to the list of renamed objects
class=class="str">"cmt">//--- CDataPropObj
  {"Переданное свойство находится за пределами диапазона свойств объекта","The passed class="kw">property is outside the range of the object&class="macro">#x27;s properties"},
  {"Не удалось создать объект истории изменений графического объекта","Failed to create a graphical object change history object"},
  {"Не удалось добавить объект истории изменений в список","Failed to add change history object to the list"},
  {"Не удалось получить объект истории изменений","Failed to get change history object"},
  {"Не удалось увеличить размер массива","Failed to increase array size"},

class=class="str">"cmt">//--- CGraphElementsCollection
  {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
  {"Не удалось получить имена объектов","Failed to get object names"},
  {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
  {"Не удалось удалить графический объект из списка","Failed to class="kw">delete graphic object from the list"},
  {"Не удалось удалить графический объект с графика","Failed to class="kw">delete graphic object from the chart"},
  {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"},
  {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"}

◍ WinForms 控件事件与自适应尺寸枚举定义

在 MT5 自建图形界面时,先把控件事件码和尺寸模式固化成枚举,后续回调才好分流处理。下面这段把「无事件 / 点击 / 选项卡切换」三态列进 ENUM_WF_CONTROL_EVENT,并以 GRAPH_OBJ_EVENTS_NEXT_CODE 为基准顺延,保证不撞系统对象事件码。 WF_CONTROL_EVENT_TAB_SELECT 之后用宏 WF_CONTROL_EVENTS_NEXT_CODE 定为 (WF_CONTROL_EVENT_TAB_SELECT+1),也就是 3。如果你要加新事件,只需在枚举里续写,宏会自动让出下一个整数槽位。 尺寸自适应分两种:CANV_ELEMENT_AUTO_SIZE_MODE_GROW 只增不减,适合日志框;CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK 可增可缩,适合随窗口拉伸的面板。实盘 EA 界面用错模式,可能在隐藏子控件时把布局挤变形。 代码里还引了 ..\Common Controls\Button.mqh,说明 ArrowButton 依赖基础按钮类。开 MT5 新建 WinForms 类库时,先确认这个相对路径存在,否则编译直接报 cannot open include file。

MQL5 / C++
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_TAB_SELECT,                          class=class="str">"cmt">// "TabControl tab selection" event
   };
class="macro">#define WF_CONTROL_EVENTS_NEXT_CODE(WF_CONTROL_EVENT_TAB_SELECT+class="num">1)   class=class="str">"cmt">// The code of the next event after the last graphical element event code
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Mode of automatic interface element resizing                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE
  {
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW,         class=class="str">"cmt">// Increase only
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,  class=class="str">"cmt">// Increase and decrease
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                    ListBoxItem.mqh |
class=class="str">"cmt">//|                                     Copyright class="num">2022, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                        [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict    class=class="str">"cmt">// Necessary for mql4
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\Common Controls\Button.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Label object class of WForms controls                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                    ArrowButton.mqh |
class=class="str">"cmt">//|                                     Copyright class="num">2022, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                        [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+

把箭头按钮拼进自定义面板

想给 MT5 图表做带左右、上下箭头的操作控件,不必从零写绘图逻辑,直接复用现成的 WForms 风格封装类即可。核心是把按钮基类和容器类分别 include 进你的 EA 或指标工程。 关键引用只有两行,路径相对当前源码目录: #include "..\Common Controls\Button.mqh" #include "..\Containers\Panel.mqh" 前者提供 ArrowLeftRightBox 与 ArrowUpDownBox 两个箭头按钮类,后者提供 Panel 容器用来承载它们。实际部署时,把按钮挂到 Panel 上再整体贴到图表坐标,就能做出步进调参的小控件。 外汇与贵金属波动剧烈,这类自定义面板只解决交互效率,不替代任何方向判断;参数误触可能在毫秒级行情里放大滑点风险。

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict    class=class="str">"cmt">// Necessary for mql4
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\Common Controls\Button.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Arrow Button object class of WForms controls                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                            ArrowLeftRightBox.mqh |
class=class="str">"cmt">//|                            Copyright class="num">2022, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict    class=class="str">"cmt">// Necessary for mql4
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\Containers\Panel.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ArrowLeftRightBox object class of WForms controls              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                              ArrowUpDownBox.mqh |
class=class="str">"cmt">//|                            Copyright class="num">2022, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict    class=class="str">"cmt">// Necessary for mql4
class=class="str">"cmt">//+------------------------------------------------------------------+
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到控件裁剪与事件响应的异常提示,你专注决策就好。

常见问题

当单行标题总宽超过控件宽度且未实现滚动逻辑时,超出部分会被容器边缘硬裁剪,不会自动平移或出按钮。
部分对象的鼠标处理程序不提供所需功能,需向图表发事件再由库路由到真正能处理该事件的类,避免计时器轮询图形元素。
可以,小布盯盘的 AIGC 模块内置了常见图形控件裁剪与事件丢失的诊断,省去你手动逐帧排查。
标题在顶部或底部时按钮分别居右上或右下,在左侧居左上、右侧居右下,且从选项卡边框缩进一个像素,仿 MS Visual Studio 排布。
不是,本篇仅做初步测试:选中右裁标题时向左平移,齐全的双向滚动按钮留在系列后续篇实现。