MQL5 图形界面组合框:从零搭建可复用下拉控件类(基础篇)
(1/3)· 多数自定义面板卡在选项堆叠,这篇把复合控件的基类拆给你看
MT5 图形库里的组合框是怎么来的
MetaTrader 5 标准图形库从 2016 年 6 月 27 日发布的系列教程起,开始系统补充组合框(ComboBox)控件,原示例帖文显示当时已有 5.1k 次查看、20 条讨论,说明这类下拉选择控件在自定义面板里需求不低。 组合框本质上把一个可编辑输入框和一条下拉列表绑在一起,用户在 EA 或指标面板上点开就能选品种、周期或参数档位,比一堆按钮省空间。要自己造一个,得先写一个专门管理该控件的类,把绘制、鼠标命中、展开收起这些动作封装进去。 类写好之后,还要接进库的总引擎:让它在主循环里能收到鼠标和重绘事件,不然控件点不开也只是摆设。外汇与贵金属自定义界面开发波动大、环境多变,建议先在历史回放里把组合框展开收起测几十次再上实盘。
「这套 GUI 库从哪接、怎么摆」
本系列第一篇《图形界面 I:库结构准备》已经把整套库的目标讲清,每章末尾都附有当前阶段完整库的下载链接,文件解压后必须按压缩包内的目录层级原样放置,否则 MT5 加载时会找不到依赖。 在第五部分前两章里,我们写好了滚动条和列表视图的类,并演示了当数据超出设计区域时如何把滚动条挂到元件上。本章要做的组合框(combobox)类,本质上就是把前面那几个元件再包一层,属于复合控件。 如果你手头已有第五部分的库,直接对照本章新增的 CComboBox 类文件,放进相同 include 路径,编译一个空窗口挂上组合框就能验证结构是否连通。外汇与贵金属界面自动化高风险,脚本异常可能造成误下单。
◍ 用组合框收拢多选项界面
组合框(combobox)本质是按钮加列表视图的复合体:点按钮弹出下拉列表,选中某项后文字回显到按钮上,列表随即收起。当 EA 或指标要暴露多个离散参数(比如周期档位、品种分组)时,用它比堆一排 RadioButton 更省空间。 拆解其可视结构,至少包含四块:元件背景、作为描述用的文字标签、可点击的按钮,以及下拉列表视图的标记。理解这四块,是后面写自定义类封装控件的地基。 在 MT5 里新建一个带 5~6 个枚举参数的面板,若全用独立勾选元件,子窗口高度很容易破 300 像素;换成组合框能把同一组选项压进 20 像素行高。外汇与贵金属图表插件属高风险开发,先在策略测试器的资源窗口拖一个标准 ComboBox 验证交互,再考虑自绘。
从零拼一个 MT5 组合框类
想在 MT5 里自己写一个可复用的组合框(ComboBox),第一步是建一个 ComboBox.mqh,把三个基类拉进来:CElement 管控件底层、CWindow 是承载表单、CListView 负责那个可收起的列表。缺任何一个,后面编译都过不去。 用户要能改配色和文字,所以类里得先留好一组私有字段:背景色、标签文字与边距、按钮尺寸与三态颜色、箭头图标及偏移等。构造函数里全部给默认值,这样不传参也能先画出来。 组合框主体靠五个私有方法在公有 CreateComboBox() 里依次调用。其中 CreateList() 最特殊——列表视图永远是别控件的一部分,所以 CListView 要加一个指向所属组合框的指针字段;创建时若没拿到指针,直接终止并报日志。 下拉逻辑的核心在 ChangeComboboxListState():先查可用性,被挡就退出;可见则隐藏并复位表单阻挡,隐藏则显示并阻挡表单。列表和组合框必须共用同一个 ID,创建完列表立刻隐藏,这是易漏的一点。 颜色随鼠标悬停切换,靠 OnEventTimer() 里调 ChangeObjectsColor() 实现。若组合框本身是下拉件且列表收起,或表单与控件可用,才改色——这段条件判断直接决定界面会不会闪。外汇贵金属 EA 界面开发高风险,参数没对齐可能整屏对象颜色错乱。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ComboBox.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Element.mqh" class="macro">#include "Window.mqh" class="macro">#include "ListView.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建组合框的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CComboBox : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 元件附加的表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- class="kw">public: CComboBox(class="type">void); ~CComboBox(class="type">void); class=class="str">"cmt">//--- 保存表单指针 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">//--- 移动元件
「下拉框类的虚函数与私有属性布局」
在 MT5 自定义 GUI 里,CComboBox 继承自 CElement,把下拉框的位移、显隐、层级和配色全部抽象成虚函数。Moving(x,y) 负责控件重定位,Show/Hide/Reset/Delete 分别对应显示、隐藏、状态重置与资源删除四种生命周期操作,注释里标得很直白。 SetZorders 与 ResetZorders 管的是鼠标左键点击优先级——也就是多个控件重叠时谁吃事件;ResetColors 则在主题切换或状态回滚时把颜色刷回默认。这些都是 virtual 声明,意味着子类可以重写而不动基类调用链。 私有段里能看出控件拆成了两块:标签(m_label_text 加 x/y gap 和四态颜色)和按钮(m_button_text 加宽高与 off/hover/pressed 四态色)。m_label_color_array[] 与 m_button_color_array[] 是状态色板容器,实际渲染时按索引取色,比写死分支更省事。 构造函数只做了一件事:CElement::ClassName(CLASS_NAME) 把类标识塞进基类,析构函数为空——资源释放交给 Reset/Delete 而非析构,这种写法在 EA 面板里很常见,避免退出时顺序错乱。
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); 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">//--- Reset the class="type">class="kw">color class="kw">virtual class="type">void ResetColors(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CComboBox::CComboBox(class="type">void) { class=class="str">"cmt">//--- 在基类中保存类的名称 CElement::ClassName(CLASS_NAME); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CComboBox::~CComboBox(class="type">void) { } class CComboBox : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 组合框属性: class=class="str">"cmt">// 一般背景颜色 class="type">class="kw">color m_area_color; class=class="str">"cmt">//--- 文字标签的文字和边缘距离 class="type">class="kw">string m_label_text; class="type">int m_label_x_gap; class="type">int m_label_y_gap; class=class="str">"cmt">//--- 不同状态下文本标签的颜色 class="type">class="kw">color m_label_color; class="type">class="kw">color m_label_color_off; class="type">class="kw">color m_label_color_hover; class="type">class="kw">color m_label_color_array[]; class=class="str">"cmt">//--- (class="num">1) 按钮的文字 和 (class="num">2)它的大小 class="type">class="kw">string m_button_text; class="type">int m_button_x_size; class="type">int m_button_y_size; class=class="str">"cmt">//--- 不同状态下按钮的颜色 class="type">class="kw">color m_button_color; class="type">class="kw">color m_button_color_off; class="type">class="kw">color m_button_color_hover; class="type">class="kw">color m_button_color_pressed; class="type">class="kw">color m_button_color_array[]; class=class="str">"cmt">//--- 不同状态下按钮 class="type">class="kw">color m_button_border_color; class="type">class="kw">color m_button_border_color_off; class=class="str">"cmt">//--- 不同状态下按钮文字的颜色
◍ 下拉按钮类的私有字段与读写接口
在 MT5 自定义控件里,带下拉菜单的按钮通常用一组私有成员保存外观与层级状态。下面这段声明覆盖了文字色、箭头图位偏移以及鼠标点击的 z-order,三者共同决定控件在图表上的命中优先级。 color m_button_text_color; color m_button_text_color_off; // 标签的边缘距离 int m_drop_arrow_x_gap; int m_drop_arrow_y_gap; // 活动与阻止状态下带有下拉菜单的按钮的标签 string m_drop_arrow_file_on; string m_drop_arrow_file_off; // 鼠标左键点击的优先级 int m_area_zorder; int m_button_zorder; int m_zorder; 公开接口则负责把外部参数写进这些字段。比如 AreaColor() 直接赋值 m_area_color,LabelText() 改写 m_label_text,而同名的 LabelText(void) 以 const 方法返回当前标签内容——这种「设值 / 取值」成对出现是 MQL5 面板类的常见写法。 void AreaColor(const color clr) { m_area_color=clr; } void LabelText(const string label_text) { m_label_text=label_text; } string LabelText(void) const { return(m_label_text); } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } string ButtonText(void) const { return(m_button_text); } void ButtonXSize(const int x_size) { m_button_x_size=x_size; } 实盘加载这类面板时,若发现下拉箭头点不中,优先核对 m_area_zorder 与 m_button_zorder 的数值关系——z 序小的区域会被覆盖,点击事件可能落到背景而非按钮上。外汇与贵金属图表挂 EA 做 UI 扩展本身不影响成交,但控件遮挡报价区会增加误操作概率,属高风险环境下的体验坑。
class="type">class="kw">color m_button_text_color; class="type">class="kw">color m_button_text_color_off; class=class="str">"cmt">//--- 标签的边缘距离 class="type">int m_drop_arrow_x_gap; class="type">int m_drop_arrow_y_gap; class=class="str">"cmt">//--- 活动与阻止状态下带有下拉菜单的按钮的标签 class="type">class="kw">string m_drop_arrow_file_on; class="type">class="kw">string m_drop_arrow_file_off; class=class="str">"cmt">//--- 鼠标左键点击的优先级 class="type">int m_area_zorder; class="type">int m_button_zorder; class="type">int m_zorder; class=class="str">"cmt">//--- class="kw">public: 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 LabelText(class="kw">const class="type">class="kw">string label_text) { m_label_text=label_text; } class="type">class="kw">string LabelText(class="type">void) class="kw">const { class="kw">return(m_label_text); } class=class="str">"cmt">//--- 文本标签的边缘距离 class="type">void LabelXGap(class="kw">const class="type">int x_gap) { m_label_x_gap=x_gap; } class="type">void LabelYGap(class="kw">const class="type">int y_gap) { m_label_y_gap=y_gap; } class=class="str">"cmt">//--- (class="num">1) 返回按钮文字, (class="num">2) 设置按钮大小 class="type">class="kw">string ButtonText(class="type">void) class="kw">const { class="kw">return(m_button_text); } class="type">void ButtonXSize(class="kw">const class="type">int x_size) { m_button_x_size=x_size; }