MQL5 图形界面组合框:从零搭建可复用下拉控件类(基础篇)
🧩

MQL5 图形界面组合框:从零搭建可复用下拉控件类(基础篇)

(1/3)· 多数自定义面板卡在选项堆叠,这篇把复合控件的基类拆给你看

案例拆解 第 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 界面开发高风险,参数没对齐可能整屏对象颜色错乱。

MQL5 / C++
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 面板里很常见,避免退出时顺序错乱。

MQL5 / C++
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 扩展本身不影响成交,但控件遮挡报价区会增加误操作概率,属高风险环境下的体验坑。

MQL5 / C++
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; }
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到控件状态与交互日志,你专注决策而非底层绘制。

常见问题

组合框是复合控件,核心由按钮加列表视图组成,按下按钮才呼出列表,选中后文字回显并隐藏列表。
CElement 提供控件基类属性与方法,CWindow 是控件挂载的表单容器,组合框需借二者完成坐标与事件归属。
小布盯盘内置的 AIGC 辅助可依据你的参数项描述产出控件骨架,但仍需你在 MT5 里校验事件绑定。
在 CComboBox 构造函数中以默认值初始化私有字段,暴露 setter 方法让用户传入自己的配色方案。
这类品种波动高风险大,界面只做参数切换不代决策,下拉项过多时仍需配合滚动条以免遮挡。