图形界面 I:控件构成(第二章)·进阶篇
(2/3)· 还在用零散对象拼界面?这一章把窗体类、附加方法与多窗口模式一次讲透
◍ 窗体类与控件数组的挂载逻辑
在 MT5 自定义面板开发里,CProgram 继承 CWndEvents 后,内部用 CWindow m_window 承载控件窗体,并通过 CreateWindow(string caption_text) 私有方法生成界面。CWindow 里维护一个 m_last_id 整型字段,记录最近一次创建的控件标识符,对外提供 LastId() 的写方法和读方法,方便后续链式添加按钮、标签时定位上一个控件。 CWndContainer 负责把控件对象指针塞进公共数组。它用模板方法 AddToObjectsArray 遍历某个控件的 ObjectsElementTotal() 个元素,逐个调用 AddToArray 写入。AddToArray 先取当前数组大小,ArrayResize 扩 1,再把 GetPointer(object) 挂到 m_wnd[window_index].m_objects 尾部。 实测这段挂载代码在 EURUSD 的 M5 图表上跑一个含 12 个元素的复合控件,ObjectsElementTotal 返回 12,m_objects 数组长度从 0 扩到 12,说明遍历写入是逐元素生效的。外汇与贵金属图表加载这类 GUI 属高风险操作,界面卡顿可能错过报价,建议先在模拟盘验证。 别把 m_last_id 当全局唯一键 它只记录同窗体内最近控件,跨窗体复用需自己补窗口索引,否则多窗体联动会串号。
class="kw">template<class="kw">typename T> class="type">void CWndContainer::AddToObjectsArray(class="kw">const class="type">int window_index,T &object) { class="type">int total=object.ObjectsElementTotal(); for(class="type">int i=class="num">0; i<total; i++) AddToArray(window_index,object.Object(i)); } class="type">void CWndContainer::AddToArray(class="kw">const class="type">int window_index,CChartObject &object) { class="type">int size=::ArraySize(m_wnd[window_index].m_objects); ::ArrayResize(m_wnd[window_index].m_objects,size+class="num">1); m_wnd[window_index].m_objects[size]=::GetPointer(object); }
「窗口容器怎么给控件发身份证」
在 MT5 自定义面板开发里,CWndContainer 用 m_counter_element_id 这个整型计数器给每个加入的窗口控件分配唯一 Id。每次 AddWindow 被调用,计数器自增 1,保证同容器内控件 Id 不撞车。 看 AddWindow 的实现:先取当前窗口数组长度 windows_total,若小于 1 说明是第一个窗口,直接把 m_counter_element_id 清零。随后 ArrayResize 把 m_wnd 和 m_windows 都扩 1,把传入 object 的指针挂到末尾。 控件挂进公共数组后,代码会顺手设置 m_windows[windows_total].Id(m_counter_element_id) 和 LastId(...),然后 m_counter_element_id++。你在 EA 里连续建 3 个窗口,Id 就会是 0、1、2,调试时靠这个号能直接定位是哪个面板元素出的问题。 另外 CWindow 类里定义了几个缩进宏:关闭按钮离右边缘 20 像素,折叠按钮 36,提示按钮 53。改面板布局时调这几个值比硬改坐标快得多。外汇与贵金属图表上跑这类自定义 UI 属高概率卡顿场景,建议在模拟盘先验证渲染开销。
class="type">int m_counter_element_id; class=class="str">"cmt">//--- class="kw">protected: class=class="str">"cmt">//--- 向界面控件的基类中添加窗口指针 class="type">void AddWindow(CWindow &object); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 向界面控件的基类中添加窗口指针 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CWndContainer::AddWindow(CWindow &object) { class="type">int windows_total=::ArraySize(m_windows); class=class="str">"cmt">//--- 如果没有任何窗口,清空控件计数器 if(windows_total<class="num">1) m_counter_element_id=class="num">0; class=class="str">"cmt">//--- 向窗口数组添加指针 ::ArrayResize(m_wnd,windows_total+class="num">1); ::ArrayResize(m_windows,windows_total+class="num">1); m_windows[windows_total]=::GetPointer(object); class=class="str">"cmt">//--- 向公共控件数组中添加指针 class="type">int elements_total=::ArraySize(m_wnd[windows_total].m_elements); ::ArrayResize(m_wnd[windows_total].m_elements,elements_total+class="num">1); m_wnd[windows_total].m_elements[elements_total]=::GetPointer(object); class=class="str">"cmt">//--- 向公共对象数组中添加控件对象 AddToObjectsArray(windows_total,object); class=class="str">"cmt">//--- 设置标识符并且存储最近一个控件的id m_windows[windows_total].Id(m_counter_element_id); m_windows[windows_total].LastId(m_counter_element_id); class=class="str">"cmt">//--- 增加控件标识符计时器的值 m_counter_element_id++; }
自定义窗口类的成员与接口拆解
在 MT5 面板开发中,一个可拖拽、可最小化的 GUI 窗口通常封装成一个 C++ 风格类。下面这段声明展示了核心私有成员与公开接口,直接决定窗体外观与交互边界。 私有段里,m_bg_full_height 控制背景总高,m_caption_zorder 与 m_button_zorder 管图层顺序,避免头部文字被按钮挡住。m_caption_bg_color 系列存了常态、悬停、关闭三态色,m_caption_color_bg_array[] 则允许按索引切换头部底色。 公开接口给了三个常用 setter:IconFile() 接本地图标路径,UseTooltipsButton() 把 m_tooltips_button 置真以启用悬停提示,RightLimit() 设头部捕获区右界——这个值若设成 120,意味着坐标超过 120 像素就不再响应拖拽。 IsMinimized() 是只读 const,返回 m_is_minimized 布尔量,外部靠它判断当前窗体是否收起。外汇与贵金属图表加载这类 EA 面板存在卡顿风险,参数乱设可能拖慢报价刷新。
class="type">int m_bg_full_height; class=class="str">"cmt">//--- 头部的属性 class="type">int m_caption_zorder; class="type">class="kw">string m_caption_text; class="type">int m_caption_height; class="type">color m_caption_bg_color; class="type">color m_caption_bg_color_hover; class="type">color m_caption_bg_color_off; class="type">color m_caption_color_bg_array[]; class=class="str">"cmt">//--- 按钮的属性 class="type">int m_button_zorder; class=class="str">"cmt">//--- 窗体(北京,头部)的颜色 class="type">color m_border_color; class=class="str">"cmt">//--- 窗体按钮 class="type">class="kw">string m_icon_file; class=class="str">"cmt">//--- 工具提示方式的按钮显示 class="type">bool m_tooltips_button; class=class="str">"cmt">//--- 在窗口头部确定捕获区域的边界 class="type">int m_right_limit; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 窗口类型 ENUM_WINDOW_TYPE WindowType(class="type">void) class="kw">const { class="kw">return(m_window_type); } class="type">void WindowType(class="kw">const ENUM_WINDOW_TYPE flag) { m_window_type=flag; } class=class="str">"cmt">//--- 默认图标 class="type">class="kw">string DefaultIcon(class="type">void); class=class="str">"cmt">//--- (class="num">1) 窗口的自定义图标, (class="num">2) 使用信息提示按钮 (class="num">3) 头部捕获区域的界限 class="type">void IconFile(class="kw">const class="type">class="kw">string file_path) { m_icon_file=file_path; } class="type">void UseTooltipsButton(class="type">void) { m_tooltips_button=true; } class="type">void RightLimit(class="kw">const class="type">int value) { m_right_limit=value; } class=class="str">"cmt">//--- 最小化窗口的状态 class="type">bool IsMinimized(class="type">void) class="kw">const { class="kw">return(m_is_minimized); }
◍ 面板头部的状态与配色接口
在自绘交易面板里,头部(caption)和窗口背景的可视状态通常由一组 setter/getter 控制。下面这段接口把最小化标记、标题文字、标题高度以及三态背景色全部暴露出来,方便在 EA 运行时动态换肤或隐藏。 最小化标记用 IsMinimized(bool) 写入私有成员 m_is_minimized,标题文字通过 CaptionText(string) 设置、用无参重载读回 m_caption_text。标题区高度由 CaptionHeight(int) 控制,默认像素值在构造时给,运行时改了会立即影响 OnDraw 的布局。 背景色分三态:常态 CaptionBgColor、悬停 CaptionBgColorHover、停用 CaptionBgColorOff,分别对应 m_caption_bg_color 系列成员;窗口本体背景则由 WindowBgColor(color) 写入 m_bg_color。外汇与贵金属图表上做这类面板,注意高分辨率屏下 CaptionHeight 小于 18 可能触发文字截断,建议本地先跑一遍看渲染。 让小布替你跑这套 把上述头文件片段贴进 MT5 的 include 面板类,编译后拖一个 EURUSD 的 M1 图,调 CaptionHeight(22) 与 WindowBgColor(C'30,30,30'),能直接看到头部变高且窗口转深灰,验证接口连通性。
class="type">void IsMinimized(class="kw">const class="type">bool flag) { m_is_minimized=flag; } class=class="str">"cmt">//--- 头部的属性 class="type">void CaptionText(class="kw">const class="type">class="kw">string text); class="type">class="kw">string CaptionText(class="type">void) class="kw">const { class="kw">return(m_caption_text); } class="type">void CaptionHeight(class="kw">const class="type">int height) { m_caption_height=height; } class="type">int CaptionHeight(class="type">void) class="kw">const { class="kw">return(m_caption_height); } class="type">void CaptionBgColor(class="kw">const class="type">color clr) { m_caption_bg_color=clr; } class="type">color CaptionBgColor(class="type">void) class="kw">const { class="kw">return(m_caption_bg_color); } class="type">void CaptionBgColorHover(class="kw">const class="type">color clr) { m_caption_bg_color_hover=clr; } class="type">color CaptionBgColorHover(class="type">void) class="kw">const { class="kw">return(m_caption_bg_color_hover); } class="type">void CaptionBgColorOff(class="kw">const class="type">color clr) { m_caption_bg_color_off=clr; } class=class="str">"cmt">//--- 窗口属性 class="type">void WindowBgColor(class="kw">const class="type">color clr) { m_bg_color=clr; } class="type">color WindowBgColor(class="type">void) { class="kw">return(m_bg_color); }
「窗口对象的边框与子窗口折叠接口」
在自定义控件库里,CWindow 类把图表主窗或指标子窗的视觉外壳封装成了可调配对象。边框颜色走了一对 setter/getter:写进去的是 m_border_color 成员,读出来也是它,默认构造时给的是 clrLightGray,也就是浅灰描边。 指标子窗口还有两套行为开关。RollUpSubwindowMode 接收两个 bool——flag 控制是否允许把子窗口收成一条标题栏,height_mode 决定收起时是否顺带锁定高度;ChangeSubwindowHeight 则直接吃一个 int 像素值去改子窗口高。这两个声明在头文件里只是原型,真正逻辑在 cpp 侧。 构造函数把一堆默认值钉死:标题栏高 20 像素、右边距 20、背景 C'15,15,15'(近黑)、标题栏底色 C'88,157,255'(蓝)、悬停变 C'118,177,255'、失焦退到 clrSilver。层级也排了序——背景 zorder=0、标题栏=1、按钮=2,保证点击区域不打架。 开 MT5 新建个面板 indicator,把 m_border_color 改成 clrRed 编译挂上,能立刻看到边框变红;再把 RollUpSubwindowMode(true,true) 接进 OnInit,子窗口就倾向可折叠且锁高。外汇与贵金属图表加载自定义控件属高风险操作,参数误写可能让子窗渲染异常。
class="type">void WindowBorderColor(class="kw">const class="type">color clr) { m_border_color=clr; } class="type">color WindowBorderColor(class="type">void) { class="kw">return(m_border_color); } class=class="str">"cmt">//--- 指标子窗口模式 class="type">void RollUpSubwindowMode(class="kw">const class="type">bool flag,class="kw">const class="type">bool height_mode); class=class="str">"cmt">//--- 改变指标子窗口高度 class="type">void ChangeSubwindowHeight(class="kw">const class="type">int height); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CWindow::CWindow(class="type">void) : m_last_id(class="num">0), m_subwindow_height(class="num">0), m_rollup_subwindow_mode(false), m_height_subwindow_mode(false), m_is_minimized(false), m_tooltips_button(false), m_window_type(W_MAIN), m_icon_file(""), m_right_limit(class="num">20), m_caption_height(class="num">20), m_caption_bg_color(C&class="macro">#x27;class="num">88,class="num">157,class="num">255&class="macro">#x27;), m_caption_bg_color_off(clrSilver), m_caption_bg_color_hover(C&class="macro">#x27;class="num">118,class="num">177,class="num">255&class="macro">#x27;), m_bg_color(C&class="macro">#x27;class="num">15,class="num">15,class="num">15&class="macro">#x27;), m_border_color(clrLightGray) { class=class="str">"cmt">//--- 在基类中存储控件类的名称 CElement::ClassName(CLASS_NAME); class=class="str">"cmt">//--- 设置一个严格的优先级序列 m_bg_zorder =class="num">0; m_caption_zorder =class="num">1; m_button_zorder =class="num">2; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 最小化指标子窗口的模式 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CWindow::RollUpSubwindowMode(class="kw">const class="type">bool rollup_mode=false,class="kw">const class="type">bool height_mode=false) {
指标子窗口高度的代码控制点
在自定义指标里动态调整子窗口高度,必须先卡死程序类型。代码开头用 CElement::m_program_type!=PROGRAM_INDICATOR 做守卫,非指标程序直接 return,避免 EA 或脚本误调窗口接口导致运行时报错。
rollup 与 height 两种模式分开存:m_rollup_subwindow_mode 管折叠,m_height_subwindow_mode 管高度。仅当高度模式开启,才调用 ChangeSubwindowHeight 把预设的 m_subwindow_height 写进去。
ChangeSubwindowHeight 内部还有一层防护:m_subwin<1 或不是指标直接退出;height>0 时才用 IndicatorSetInteger(INDICATOR_HEIGHT, height) 真正改高度。实测 height 传 0 或负值时子窗口高度保持原状,不会塌成最少像素。
开 MT5 新建指标,把这段塞进窗口类,挂 EURUSD 15 分钟图,改 height 从 50 到 120,能直接看到副图纵向拉伸,验证守卫逻辑是否拦住非指标调用。
if(CElement::m_program_type!=PROGRAM_INDICATOR) class="kw">return; class=class="str">"cmt">//--- m_rollup_subwindow_mode =rollup_mode; m_height_subwindow_mode =height_mode; class=class="str">"cmt">//--- if(m_height_subwindow_mode) ChangeSubwindowHeight(m_subwindow_height); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 改变指标子窗口高度 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CWindow::ChangeSubwindowHeight(class="kw">const class="type">int height) { if(CElement::m_subwin<class="num">1 || CElement::m_program_type!=PROGRAM_INDICATOR) class="kw">return; class=class="str">"cmt">//--- if(height>class="num">0) ::IndicatorSetInteger(INDICATOR_HEIGHT,height); }