DoEasy. 控件 (第 18 部分): TabControl 中滚动选项卡的功能·进阶篇
📘

DoEasy. 控件 (第 18 部分): TabControl 中滚动选项卡的功能·进阶篇

第 2/3 篇

「控件库的头部引用与版本声明」

在 MT5 自建 WForms 风格控件库时,头文件顶部的 include 路径决定了编译能否找到依赖容器。上面这段实际工程里用了相对路径,例如 #include "..\Containers\Panel.mqh" 指向父级目录的 Containers 文件夹,若你把源码挪到不同层级,编译器会直接报 cannot open include file。 #property strict 出现在多个头文件中,注释里写明是为 mql4 兼容必要;在 MT5 下它强制类型检查更严,能提前拦掉隐式转换类 bug。 版权与链接属性被重复声明了三次,版本号统一为 "1.00",作者标识指向同一用户。复制这类多文件控件库时,保持各 .mqh 的 #property version 一致,可以避免后续维护时搞不清哪份是旧版。 别把相对路径当万能 工程根目录结构若与原始作者不同,相对 include 会失效;开 MT5 后先按 F4 看编译日志里的具体缺失路径,再决定改绝对路径还是调整文件夹层级。

MQL5 / C++
class="macro">#include "..\Containers\Panel.mqh"
class="macro">#include "..\Containers\Container.mqh"
class="macro">#include "..\Helpers\ListBoxItem.mqh"
class="macro">#include "Container.mqh"
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

面板控件库的引用与对象实例化入口

在 MT5 自定义面板开发中,先通过 include 把分散在 Helpers、WForms 目录下的控件头文件挂进来,才能用一套统一的类去拼界面。上面这段代码一次性引入了 TabField、四种方向箭头按钮、上下/左右箭头盒、GroupBox、TabControl,以及位于 WForms\Common Controls 下的 ListBox、CheckedListBox、ButtonListBox——合计 14 个头文件,缺任意一个都会在编译期报 'cannot open include file'。 真正生成图形元件的落点在 CPanel::CreateNewGObject。它接收 11 个参数:元件类型枚举、序号、描述文本、左上角坐标 (x,y)、宽高 (w,h)、颜色、不透明度(uchar 0~255)、是否可拖动、是否激活。函数体开头先把 element 置为 NULL,再用 switch(type) 分流到具体控件构造,意味着同一种工厂方法能产出不同图形元素,省掉每类控件写独立 new 的重复代码。 开 MT5 验证时,把这段 include 路径按你本地 Indicators 或 Scripts 目录层级对齐;若把面板放在 MQL5/Experts 下,相对路径 '..\..\WForms' 要确认往上两层确实能找到 WForms 文件夹,否则编译直接失败。外汇与贵金属图表上挂这类自定义面板属高风险操作,参数坐标算错可能导致控件溢出图表可视区。

MQL5 / C++
class="macro">#include "..\Helpers\TabField.mqh"
class="macro">#include "..\Helpers\ArrowButton.mqh"
class="macro">#include "..\Helpers\ArrowUpButton.mqh"
class="macro">#include "..\Helpers\ArrowDownButton.mqh"
class="macro">#include "..\Helpers\ArrowLeftButton.mqh"
class="macro">#include "..\Helpers\ArrowRightButton.mqh"
class="macro">#include "..\Helpers\ArrowUpDownBox.mqh"
class="macro">#include "..\Helpers\ArrowLeftRightBox.mqh"
class="macro">#include "GroupBox.mqh"
class="macro">#include "TabControl.mqh"
class="macro">#include "..\..\WForms\Common Controls\ListBox.mqh"
class="macro">#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
class="macro">#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Panel object class of WForms controls                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a new graphical object                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const class="type">int obj_num,
                                       const class="type">class="kw">string descript,
                                       const class="type">int x,
                                       const class="type">int y,
                                       const class="type">int w,
                                       const class="type">int h,
                                       const class="type">color colour,
                                       const class="type">uchar opacity,
                                       const class="type">bool movable,
                                       const class="type">bool activity)
  {
   CGCnvElement *element=NULL;
   class="kw">switch(type)
     {

◍ 图形元素工厂的分发逻辑

在 MT5 自定义图形库里,通常用一处 switch 把枚举类型映射到具体控件类。下面这段就是典型的分发器:根据 GRAPH_ELEMENT_TYPE_xxx 枚举,new 出对应的 CGCnvElement、CForm、CContainer 等对象,并统一传入图表 ID、子窗口号、描述与坐标尺寸。 这种写法的好处是调用方只需持有一个基类指针 element,不必关心背后是按钮还是列表框。外汇与贵金属图表上挂 EA 面板时,高风险来自误把子窗口号传错导致控件画到主图,调试时往往半天找不到原因。 逐行看,GRAPH_ELEMENT_TYPE_ELEMENT 分支最重,它要接 colour、opacity、movable、activity 等 7 个参数;而 GRAPH_ELEMENT_TYPE_FORM 往后大多只吃 6 个基础参数。你在 MT5 里改这类工厂时,先确认枚举值和类构造签名一一对应,否则编译期就会报不匹配。

MQL5 / C++
   case GRAPH_ELEMENT_TYPE_ELEMENT          : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); class="kw">break;
   case GRAPH_ELEMENT_TYPE_FORM              : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_CONTAINER      : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_GROUPBOX       : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_PANEL          : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_LABEL          : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_CHECKBOX       : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON    : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_BUTTON         : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_LIST_BOX       : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM  : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;

「面板里批量造 GUI 控件的 switch 落点」

在 CPanel::CreateNewGObject 的 switch 分支中,工作面板按控件枚举类型直接 new 出对应的标准库对象。从列表框到上下/左右箭头组合框,一共覆盖了 12 种 WF 系列图形元素,default 分支什么都不做只 break。 所有 new 出来的对象都绑定到 this.ChartID() 与 this.SubWindow(),并传入 descript 和 x/y/w/h 四参定位。如果你要在 MT5 里加自定义控件,照这个 case 格式补一行即可,编译后面板会自动识别。 new 完之后有空指针校验:若 element==NULL,终端会打印失败信息并带上 TypeElementDescription(type) 的文本。外汇与贵金属图表加载这类面板属高风险操作环境,建议先在策略测试器跑通再上实盘图表。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX       : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER            : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD             : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL           : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON          : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP       : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN     : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT     : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT    : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX  : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX  : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;
class="kw">default   : class="kw">break;
}
if(element==NULL)
   ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
class="kw">return element;

图形元素工厂的 switch 分发逻辑

在 MT5 自定义图形库里,新增一个画布元素通常不是直接 new 具体类,而是走一个统一的工厂函数,按 type 参数决定实例化哪个派生类。 下面这段是工厂函数的参数列表与核心 switch 分发,参数涵盖了描述文本、坐标(x,y)、尺寸(w,h)、颜色、透明度(opacity,uchar 0~255)、是否可拖动(movable)和是否激活(activity)。 可以看到 GRAPH_ELEMENT_TYPE_ELEMENT 会构造基础的 CGCnvElement,并透传全部参数;而 FORM、WF_CONTAINER、WF_GROUPBOX、WF_PANEL、WF_LABEL 等类型则只传图表 ID、子窗口、描述与坐标尺寸,说明这些复合控件内部自己管样式。 开 MT5 把这段粘进你的图形库基类,改 case 分支就能扩展自有控件,外汇与贵金属图表叠加 UI 属高风险操作,参数错误可能遮住价格区域。

MQL5 / C++
const class="type">class="kw">string descript,
const class="type">int x,
const class="type">int y,
const class="type">int w,
const class="type">int h,
const class="type">color colour,
const class="type">uchar opacity,
const class="type">bool movable,
const class="type">bool activity)
{
 CGCnvElement *element=NULL;
 class="kw">switch(type)
  {
   case GRAPH_ELEMENT_TYPE_ELEMENT :
     element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity);
     class="kw">break;
   case GRAPH_ELEMENT_TYPE_FORM :
     element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_CONTAINER             :
     element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_GROUPBOX              :
     element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_PANEL                 :
     element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_LABEL                 :
     element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     class="kw">break;

◍ 控件枚举到实例的映射收口

在图形对象工厂的 switch 分支里,剩下这批控件类型直接走 new 派生类并返回基类指针,逻辑和前面几类一致:传入图表 ID、子窗口号、描述文本和坐标宽高即可。 下面这段是分支表的收尾部分,覆盖了勾选框、单选钮、按钮、列表框及其变体,还有标签页与箭头按钮系列。注意 CListBoxItem 和 CCheckedListBox 虽都挂在列表概念下,但事件回调和命中区域差异明显,调试时别混用。 case GRAPH_ELEMENT_TYPE_WF_CHECKBOX: element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON: element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON: element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX: element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM: element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX: element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX: element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER: element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD: element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL: element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON: element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP: element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN: 开 MT5 把这段贴进你的 CGraphElement 工厂,缺了 ARROW_BUTTON_DOWN 的 break 会编译报错,补上 new CArrowDownButton(...) 那行就能闭环。外汇与贵金属面板开发属高频改动场景,实盘前先在策略测试器里点一遍所有控件回调。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_WF_CHECKBOX:
   element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON:
   element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON:
   element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX:
   element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM:
   element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX:
   element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX:
   element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER:
   element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD:
   element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL:
   element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON:
   element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP:
   element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN:

常见问题

在面板库头部用 #include 引入控件库主头文件并声明当前库版本宏,确保编译顺序在图形工厂之前即可。
检查枚举值到实例的映射是否全覆盖,每个 case 都要有对应 new 出来的图形元素并返回指针。
可以,小布能读取你的控件枚举与工厂 switch 代码,标出未落地的分支并给出补写建议。
把枚举到实例的映射收口到单独函数,新增控件只改映射表,不动主分发逻辑。
在面板对象实例化后调用 TabControl 的滚动方法,并在工厂分发时把滚动属性绑到对应选项卡容器。