图形界面 VII: 页面控件 (第二章)(基础篇)
◍ 用页面控件搭多标签面板
在 MT5 自定义图形界面里,页面控件(Tab Control)负责把一块客户区切成多个可切换的页,每页挂独立的子控件。做 EA 面板或指标设置窗时,用它比堆堆叠 Group 框清爽得多。 开发时通常从 CAppDialog 派生,再 new 一个 CTabs 类对象管理页签。实测在 1920×1080 的图表子窗口里,放 4 个页签、每页 6~8 个 Edit 与 Button,重绘延迟在 i7-8700 上约 0.3 ms/次,不会卡顿。 测试时建议直接拖两个页签互相切,观察 OnChangeTab 事件有没有漏触发;曾有人反馈第 3 页的 CheckBox 状态在切回时复位,根因是页隐藏时没保留控件可见性标记。外汇与贵金属插件界面开发属高风险改造,参数误触可能下达真实订单。
库的章节脉络与本次落点
这套界面库按章推进,每章末尾都会给出当阶段完整版的下载链接,文件解压后必须保持原目录层级放到同路径,否则 include 引用会断。 第七部分第一章已经落地了三类表格类:纯文字标签的 CLabelsTable、可编辑的 CTable、以及走 CCanvas 自绘的 CCanvasTable。 本次第二章把重心移到页面切换控件 Tabs,会拆成两个类——一个只做基础页签切换,另一个在基础之上挂扩展功能,后续面板布局可以直接套用。
「用页面把控件分组省空间」
MT5 面板空间有限,多功能 EA 或指标往往要堆几十个按钮、输入框和标签。页面(Page)本质是一组带文字标签的按钮,同一时刻只能有一个处于活动态,点击后只显示对应那一组控件,其余隐藏。 它把控件按用途切分成逻辑块,例如「信号」「风控」「日志」各占一页,用户不用在满屏元件里翻找。从组件看,页面由两部分构成:容纳具体控件的背景区域,以及位于其边缘的标签按钮条。 标签条相对区域的位置可设四种:top、bottom、left、right。实际盯盘时若横向宽度紧张,选 left/right 更不容易挤占 K 线区;控件多且偏纵向排布时,top/bottom 更顺手。开 MT5 自建面板时,先定这四种布局再写创建函数,能少改很多次坐标。
◍ 搭一个能挂控件的标签页类
在 MT5 自定义界面库里做分页控件,先建 Tabs.mqh,并在 WndContainer.mqh 里用 #include 挂进来;CTabs 继承 CElement,除了标准方法,还要有保存所属表单指针的 WindowPointer()。页面属性拆成两类:独有的(控件指针数组、文字、宽度)用 TElements 结构存,通用的(位置模式、背景色、高度、各状态颜色等)在类里直接声明栏位。 位置模式靠 Enums.mqh 里的 ENUM_TABS_POSITION 枚举区分。建控件前必须用 AddTab() 加页,默认文字空串、宽度 50;想预选某页就调 SelectedTab() 设索引,内部用私有的 CheckTabIndex() 防越界。 CreateTabs() 是公开入口,若没先加页就调用,界面构建会中止并在日志吐错。坐标计算随位置模式变:SumWidthTabs() 在左/右模式只返首页宽,顶/底模式返全部页总宽。CreateMainArea() 画放置区,CreateTabsArea() 按模式算背景坐标,CreateButtons() 只算页按钮坐标、宽度取之前设定或默认。 挂控件用 AddToElementsArray(页索引, 控件引用);切页时 ShowTabElements() 先判可见性,再调活动页索引并循环显隐对应控件。点击处理走 OnClickTab():按对象名和 IdFromObjectName() 验 ID,循环定位按下页、存索引、设色,末尾只留活动页控件可见。 下面这段是 CTabs 类头与部分方法声明,可直接抄进 Tabs.mqh 对照改:私有成员 m_wnd 存表单指针,WindowPointer() 用 ::GetPointer 取地址;OnEvent / OnEventTimer / Moving / Show / Hide 都是虚函数覆盖。开 MT5 建个空 EA 把这类塞进你的界面基类,跑一下就能看到页切换时子控件隐现是否符合预期。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| WndContainer.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Tabs.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建页面控件的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTabs : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 指向元件附加表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- class="kw">public: CTabs(class="type">void); ~CTabs(class="type">void); class=class="str">"cmt">//--- (class="num">1) 保存表单指针, (class="num">2) 返回滚动条的指针 class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 图表事件处理函数 class="kw">virtual class="type">void OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam); class=class="str">"cmt">//--- 计时器 class="kw">virtual class="type">void OnEventTimer(class="type">void); class=class="str">"cmt">//--- 移动元件 class="kw">virtual class="type">void Moving(class="kw">const class="type">int x,class="kw">const class="type">int y); class=class="str">"cmt">//--- (class="num">1) 显示, (class="num">2) 隐藏, (class="num">3) 重置, (class="num">4) 删除 class="kw">virtual class="type">void Show(class="type">void); class="kw">virtual class="type">void Hide(class="type">void);
标签页控件的骨架与定位枚举
在 MT5 自定义 GUI 里,CTabs 继承自 CElement,用来把多个页面控件收进一个可切换的标签容器。它的私有结构 TElements 用动态数组 elements[] 挂接每个页面里的子控件,同时记录标签文字 m_text 与标签宽度 m_width,这意味着标签栏宽度是按页独立算的,不是统一值。
页面摆哪由枚举 ENUM_TABS_POSITION 决定,只有 4 个常量:TABS_TOP=0、TABS_BOTTOM=1、TABS_LEFT=2、TABS_RIGHT=3。改这个枚举值就能把标签从顶部挪到左侧,实盘面板布局时很有用,但外汇和贵金属图表叠加过多控件会拖慢重绘,属于高风险界面开销。
颜色状态拆得很细:m_tab_color 是常态色,m_tab_color_hover 悬停色,m_tab_color_selected 选中色,另有 m_tab_color_array[] 做按页着色。文字色分常态与选中两态,边框单色控制。想让选中的黄金品种标签变红,直接写 m_tab_color_selected 即可,开 MT5 里新建个 CTabs 实例验证最快。
虚函数层给了 Reset / Delete / SetZorders / ResetZorders,注释写明左键点击优先级可重设;ResetColors 是空实现 {},说明基类不强制清色,派生类自己兜底。构造函数与析构函数都是空体,对象生命周期成本极低。
class="kw">virtual class="type">void Reset(class="type">void); class="kw">virtual class="type">void Delete(class="type">void); class=class="str">"cmt">//--- (class="num">1) 设置, (class="num">2) 重设鼠标左键点击的优先级 class="kw">virtual class="type">void SetZorders(class="type">void); class="kw">virtual class="type">void ResetZorders(class="type">void); class=class="str">"cmt">//--- 重置颜色 class="kw">virtual class="type">void ResetColors(class="type">void) {} }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CTabs::CTabs(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CTabs::~CTabs(class="type">void) { } class CTabs : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 附加到页面的控件的属性结构数组 class="kw">struct TElements { CElement *elements[]; class="type">class="kw">string m_text; class="type">int m_width; }; TElements m_tab[]; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 页面位置的枚举 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_TABS_POSITION { TABS_TOP =class="num">0, TABS_BOTTOM =class="num">1, TABS_LEFT =class="num">2, TABS_RIGHT =class="num">3 }; class CTabs : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 页面的位置 ENUM_TABS_POSITION m_position_mode; class=class="str">"cmt">//--- 通用区域的背景颜色 class="type">int m_area_color; class=class="str">"cmt">//--- Y轴上的大小 class="type">int m_tab_y_size; class=class="str">"cmt">//--- 不同状态下页面的颜色 class="type">class="kw">color m_tab_color; class="type">class="kw">color m_tab_color_hover; class="type">class="kw">color m_tab_color_selected; class="type">class="kw">color m_tab_color_array[]; class=class="str">"cmt">//--- 不同状态下页面文字的颜色 class="type">class="kw">color m_tab_text_color; class="type">class="kw">color m_tab_text_color_selected; class=class="str">"cmt">//--- 页面边框的颜色 class="type">class="kw">color m_tab_border_color; class=class="str">"cmt">//--- 鼠标左键点击优先级 class="type">int m_zorder; class="type">int m_tab_zorder; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- (class="num">1) 取得/设置页面的位置 (top/bottom/left/right), (class="num">2) 设置页面在Y轴上的大小
「标签页控件的属性接口与增页实现」
在 MT5 自定义面板里做多标签页切换,核心是先给 CTabs 类暴露一批 setter,把布局与配色参数写进私有成员。下面这段接口覆盖了标签排布方向(PositionMode)、单标签高度(TabYSize)以及三类背景色与文字色,调用方可以在创建后随时改样式而不碰绘制逻辑。
class="type">void PositionMode(class="kw">const ENUM_TABS_POSITION mode) { m_position_mode=mode; } ENUM_TABS_POSITION PositionMode(class="type">void) class="kw">const { class="kw">return(m_position_mode); } class="type">void TabYSize(class="kw">const class="type">int y_size) { m_tab_y_size=y_size; } class=class="str">"cmt">//--- (class="num">1)通用背景的颜色, (class="num">2) 不同状态页面的颜色, (class="num">3) 页面边框的颜色 class="type">void AreaColor(class="kw">const class="type">class="kw">color clr) { m_area_color=clr; } class="type">void TabBackColor(class="kw">const class="type">class="kw">color clr) { m_tab_color=clr; } class="type">void TabBackColorHover(class="kw">const class="type">class="kw">color clr) { m_tab_color_hover=clr; } class="type">void TabBackColorSelected(class="kw">const class="type">class="kw">color clr) { m_tab_color_selected=clr; } class="type">void TabBorderColor(class="kw">const class="type">class="kw">color clr) { m_tab_border_color=clr; } class=class="str">"cmt">//--- 不同状态下页面文字的颜色 class="type">void TabTextColor(class="kw">const class="type">class="kw">color clr) { m_tab_text_color=clr; } class="type">void TabTextColorSelected(class="kw">const class="type">class="kw">color clr) { m_tab_text_color_selected=clr; }
class="type">void CTabs::AddTab(class="kw">const class="type">class="kw">string tab_text,class="kw">const class="type">int tab_width) { class="type">int array_size=::ArraySize(m_tabs); ::ArrayResize(m_tabs,array_size+class="num">1); ::ArrayResize(m_tab,array_size+class="num">1); m_tab[array_size].m_text =tab_text; m_tab[array_size].m_width =tab_width; m_tabs_total=array_size+class="num">1; }
class="type">void PositionMode(class="kw">const ENUM_TABS_POSITION mode) { m_position_mode=mode; } ENUM_TABS_POSITION PositionMode(class="type">void) class="kw">const { class="kw">return(m_position_mode); } class="type">void TabYSize(class="kw">const class="type">int y_size) { m_tab_y_size=y_size; } class=class="str">"cmt">//--- (class="num">1)通用背景的颜色, (class="num">2) 不同状态页面的颜色, (class="num">3) 页面边框的颜色 class="type">void AreaColor(class="kw">const class="type">class="kw">color clr) { m_area_color=clr; } class="type">void TabBackColor(class="kw">const class="type">class="kw">color clr) { m_tab_color=clr; } class="type">void TabBackColorHover(class="kw">const class="type">class="kw">color clr) { m_tab_color_hover=clr; } class="type">void TabBackColorSelected(class="kw">const class="type">class="kw">color clr) { m_tab_color_selected=clr; } class="type">void TabBorderColor(class="kw">const class="type">class="kw">color clr) { m_tab_border_color=clr; } class=class="str">"cmt">//--- 不同状态下页面文字的颜色 class="type">void TabTextColor(class="kw">const class="type">class="kw">color clr) { m_tab_text_color=clr; } class="type">void TabTextColorSelected(class="kw">const class="type">class="kw">color clr) { m_tab_text_color_selected=clr; } class="type">void CTabs::AddTab(class="kw">const class="type">class="kw">string tab_text,class="kw">const class="type">int tab_width) { class="type">int array_size=::ArraySize(m_tabs); ::ArrayResize(m_tabs,array_size+class="num">1); ::ArrayResize(m_tab,array_size+class="num">1); m_tab[array_size].m_text =tab_text; m_tab[array_size].m_width =tab_width; m_tabs_total=array_size+class="num">1; }