图形界面III:用三个类搭出可复用按钮控件库(基础篇)
🔘

图形界面III:用三个类搭出可复用按钮控件库(基础篇)

(1/3)· 从简单按钮到分割按钮,MetaTrader 5 界面开发最常被低估的基底件怎么写才不返工

含代码示例实战向 第 1/3 篇

很多交易者把 MT5 自定义面板当成一次性脚本,按钮状态、分组、解锁逻辑每次都重写。控件库前两部分啃完菜单系统,这一篇回到最朴素的按钮,把复用骨架先立住,后面才不会越叠越乱。

「给 MT5 界面塞几个能用的按钮类」

在 MT5 自定义指标或 EA 的图形界面里,原生控件并不够顺手。作者 Anatoli Kazharski 在 2016-04-20 发布的系列第一章,提供了三类按钮的封装思路:简单按钮、图标按钮、分割按钮,原文示例被查看 5773 次、收藏 32 次,说明这套封装在社群里确实有人反复翻出来用。 简单按钮负责最基础的点按触发;图标按钮把位图资源压进控件,节省界面文字空间;分割按钮则在一个控件里同时给出主操作和下拉扩展项,适合参数切换密集的面板。 外汇与贵金属交易界面自动化属于高风险操作环境,任何界面封装只解决交互效率,不替代策略逻辑。开 MT5 用 MetaEditor 新建 include 文件,把这几类按钮类各自独立成文件,是后续章节拼装界面的前提。

◍ 从菜单控件切到按钮:本篇只做轻量扩展

前两部分已经把图形界面库的架构和菜单类啃完了,主菜单和上下文菜单属于最复杂的控件,这一篇调头做最简单的互操作元件——按钮。 本篇会新建六个类:CSimpleButton、CIconButton、CSplitButton 三个独立按钮,以及 CButtonsGroup、CIconButtonsGroup、CRadioButtons 三个关联按钮组。相比之前第五章在 MT4 里测整套界面库的复杂度,这次只补控件不重造轮子。 顺带给上下文菜单加一种模式,并给 CWindow 表单类多塞一个方法参数,用来标记「当前是哪个控件在激活时锁住了表单」——这样表单只能被当初封锁它的那个控件解锁,避免别的控件误触解窗。 前面文章已经完整讲过的基础方法不再逐条复述,相关声明只保留在类代码里,读者开 MT5 看源码即可对应。

自己写一个 MT5 简单按钮类

MT5 标准库 Controls 文件夹里已有 CButton,基类是 CChartObjectButton,对应 OBJ_BUTTON 图形对象。它天然带「按下 / 抬起」两态,且按下时颜色比未按下略深;也可从菜单「插入→对象→图形对象→按钮」手动挂到图表上改参数。 我们要在 Controls 下新建 SimpleButton.mqh,写 CSimpleButton 类。可设属性包括:按钮尺寸、随鼠标位置切换的背景色、两态边框色、文字色。按钮行为分两种模式——一种是松手自动弹起,另一种是像开关一样保持按下,再次点击才抬起,用 IsPressed() 判当前状态。 有时条件不满足需禁用按钮,条件满足再解锁,这种封锁 / 解锁逻辑要在类里实现。自定义事件标识符 ON_CLICK_BUTTON 加到 Defines.mqh,专门给按钮用;CSimpleButton::OnClickButton() 先核对对象名与当前状态,不符就退出,通过则恢复初始色或切到另一组色,末尾发带标识符、元件 ID、索引和名称的消息。 实测时复制前文测试 EA,只留主菜单和上下文菜单项,在 WndContainer.mqh 包含本类。CProgram 里声明三个按钮:两个自动弹起,第三个固定两态。下方代码高亮处就是启用两态模式的开关。 还差一步:让按钮在鼠标悬停按下时变颜色。在 CHARTEVENT_MOUSE_MOVE 里查左键是否按住,CSimpleButton::OnEvent() 中加判断——隐藏、表单禁用、左键未按、按钮禁用,任一成立就退出;否则按焦点和状态设色。编译后挂图表,Simple Button 3 按下时 Simple Button 1 被禁用,松开恢复可用。外汇与贵金属 MT5 开发属高风险环境,实盘前务必在策略测试器验证。

MQL5 / C++
class="macro">#include "Element.mqh"
class="macro">#include "Window.mqh"
class CSimpleButton : class="kw">public CElement
  {
class="kw">private:
   CWindow            *m_wnd;          class=class="str">"cmt">// 指向附加元件表单的指针
class="kw">public:
                     CSimpleButton(class="type">void);
                    ~CSimpleButton(class="type">void);
   class="type">void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }  class=class="str">"cmt">// 保存表单指针
class="kw">public:
   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=class="str">"cmt">//--- 移动元件

「封装简单按钮类的接口与状态字段」

在 MT5 自定义面板开发里,把按钮逻辑封成一个 CSimpleButton 类能省掉大量重复代码。它继承自 CElement,基类先声明了一组虚函数:Moving 负责按 x、y 偏移移动控件,Show/Hide/Reset/Delete 分别对应显示、隐藏、重置和删除四种生命周期操作,注释里标得很直白。 SetZorders 和 ResetZorders 管的是鼠标左键点击优先级,也就是多个控件叠在一起时谁先响应点击,这在密集面板里很容易踩坑。 CSimpleButton 的私有段暴露了按钮的全部可调配属性:m_button_text 存文字,m_button_x_size / m_button_y_size 定尺寸;背景色就给了五套——常规、禁用、悬停、按下以及一套颜色数组 m_back_color_array,边框和文字也各有 off / pressed 状态色。 两个 bool 字段值得单独看:m_two_state 控制按钮是否双态(按一次锁、再按弹),m_button_state 标记当前可用还是禁用。对外只开放了 CreateSimpleButton(传 chart_id、subwin、文字和坐标)和内部 CreateButton,其余状态设置方法留在 public 段待补。 开 MT5 新建 EA 把这段类骨架贴进 include,先只填 CreateSimpleButton 的参数,就能在副图看到按钮占位,验证继承结构是否跑通。

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 CSimpleButton : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 用于创建按钮的对象
  CButton           m_button;
  class=class="str">"cmt">//--- 按钮属性:
  class=class="str">"cmt">//    (class="num">1) Text, (class="num">2) size
  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">color             m_back_color;
  class="type">color             m_back_color_off;
  class="type">color             m_back_color_hover;
  class="type">color             m_back_color_pressed;
  class="type">color             m_back_color_array[];
  class=class="str">"cmt">//--- 边框颜色
  class="type">color             m_border_color;
  class="type">color             m_border_color_off;
  class=class="str">"cmt">//--- 文字颜色
  class="type">color             m_text_color;
  class="type">color             m_text_color_off;
  class="type">color             m_text_color_pressed;
  class=class="str">"cmt">//--- 鼠标左键点击的优先级
  class="type">int               m_button_zorder;
  class=class="str">"cmt">//--- 两种按钮状态的模式
  class="type">bool              m_two_state;
  class=class="str">"cmt">//--- 可用/禁用
  class="type">bool              m_button_state;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 用于创建简单按钮的方法
  class="type">bool              CreateSimpleButton(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">class="kw">string button_text,class="kw">const class="type">int x,class="kw">const class="type">int y);
  class=class="str">"cmt">//---
class="kw">private:
  class="type">bool              CreateButton(class="type">void);
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- (class="num">1) 设置按钮模式,
  class=class="str">"cmt">//    (class="num">2) 按钮的一般状态 (可用/封锁禁用)

◍ 把按钮状态与配色拆成可读接口

在 MT5 自定义控件里,把按钮的内部状态和外显属性分开暴露,是后期维护面板的关键一步。下面这段类方法定义,把「是否双态」「当前是否被按下」「按钮文本与配色」全部做成了独立 getter/setter。 TwoState() 接收 bool 写入 m_two_state,控制按钮是点按即弹还是锁定切换;IsPressed() 直接返回 m_button.State() 的真实控件状态,比自己缓存标志更不易出错。ButtonState() 有两个重载,一个读 m_button_state,一个声明写入口,负责把外部事件同步进对象。 配色部分细分得很碎:文字有常规、off、pressed 三色,背景有常规、off、hover、pressed 四色,边框单独一列。实际盯盘面板里,hover 和 pressed 用不同背景色(例如 m_back_color_hover 与 m_back_color_pressed 取异色),能让你在黄金 1 分钟图快速辨认当前触发的是哪一类订单按钮。外汇与贵金属波动剧烈,这类视觉反馈只降低误操作概率,不预示任何方向。 开 MT5 建个空 EA,把下面接口原样贴进你的 CButton 派生类,编译后拖两个按钮到图表,分别设 BackColorHover(C'30,90,30') 与 BackColorPressed(C'90,30,30'),鼠标划过和点击就能看出状态分流是否生效。

MQL5 / C++
  class="type">void                TwoState(class="kw">const class="type">bool flag)                 { m_two_state=flag;                 }
  class="type">bool                IsPressed(class="type">void)                            class="kw">const { class="kw">return(m_button.State());       }
  class="type">bool                ButtonState(class="type">void)                          class="kw">const { class="kw">return(m_button_state);         }
  class="type">void                ButtonState(class="kw">const class="type">bool state);
  class=class="str">"cmt">//--- 按钮大小
  class="type">void                ButtonXSize(class="kw">const class="type">int x_size)              { m_button_x_size=x_size;          }
  class="type">void                ButtonYSize(class="kw">const class="type">int y_size)              { m_button_y_size=y_size;          }
  class=class="str">"cmt">//--- (class="num">1) 返回按钮文字, (class="num">2) 设置按钮文字的颜色
  class="type">class="kw">string              Text(class="type">void)                                 class="kw">const { class="kw">return(m_button.Description()); }
  class="type">void                TextColor(class="kw">const class="type">color clr)                 { m_text_color=clr;                }
  class="type">void                TextColorOff(class="kw">const class="type">color clr)              { m_text_color_off=clr;            }
  class="type">void                TextColorPressed(class="kw">const class="type">color clr)          { m_text_color_pressed=clr;        }
  class=class="str">"cmt">//--- 设置按钮背景的颜色
  class="type">void                BackColor(class="kw">const class="type">color clr)                { m_back_color=clr;                }
  class="type">void                BackColorOff(class="kw">const class="type">color clr)             { m_back_color_off=clr;            }
  class="type">void                BackColorHover(class="kw">const class="type">color clr)           { m_back_color_hover=clr;          }
  class="type">void                BackColorPressed(class="kw">const class="type">color clr)         { m_back_color_pressed=clr;        }
  class=class="str">"cmt">//--- 设置按钮边框的颜色
  class="type">void                BorderColor(class="kw">const class="type">color clr)              { m_border_color=clr;              }

按钮状态切换与点击响应的底层实现

在 MT5 自定义控件里,按钮的视觉状态完全由 ButtonState() 驱动:传入 true 时套用激活态的文字、背景、边框三色,传入 false 则切到 off 系列颜色,这一行赋值直接决定界面反馈。 点击事件走 CHARTEVENT_OBJECT_CLICK 分支,先比对象名再判 m_button_state,若按钮处于禁用态会强制 State(false) 并直接返回,避免无效触发。单状态模式每次点击只刷新颜色不锁状态,双状态模式则根据 m_button.State() 在按下/松开间切换,并写入 m_back_color_pressed 到颜色数组。 宏 ON_CLICK_BUTTON (8) 把点击事件编码成独立 ID,方便在 EA 里与图表其他对象区分。外汇与贵金属图表挂这类按钮属于高风险环境下的界面扩展,实盘前应在策略测试器里把状态切换跑满一遍。 把下面这段代码贴进 CSimpleButton 类定义之后,编译后拖一个按钮到 XAUUSD 的 M5 图,点几下就能看到双色态翻转是否跟手。

MQL5 / C++
class="type">void CSimpleButton::ButtonState(class="kw">const class="type">bool state)
  {
  m_button_state=state;
  m_button.State(class="kw">false);
  m_button.Color((state)? m_text_color : m_text_color_off);
  m_button.BackColor((state)? m_back_color : m_back_color_off);
  m_button.BorderColor((state)? m_border_color : m_border_color_off);
  }
class="macro">#define ON_CLICK_BUTTON(class="num">8)  class=class="str">"cmt">// 按下按钮
class CSimpleButton : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 处理按钮按下事件
  class="type">bool            OnClickButton(class="kw">const class="type">class="kw">string clicked_object);
  };
class="type">void CSimpleButton::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)
  {
  if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickButton(sparam))
         class="kw">return;
     }
  }
class="type">bool CSimpleButton::OnClickButton(class="kw">const class="type">class="kw">string clicked_object)
  {
  if(m_button.Name()!=clicked_object)
     class="kw">return(class="kw">false);
  if(!m_button_state)
     {
      m_button.State(class="kw">false);
      class="kw">return(class="kw">false);
     }
  if(!m_two_state)
     {
      m_button.State(class="kw">false);
      m_button.Color(m_text_color);
      m_button.BackColor(m_back_color);
     }
  else
     {
      if(m_button.State())
        {
         m_button.State(true);
         m_button.Color(m_text_color_pressed);
         m_button.BackColor(m_back_color_pressed);
         CElement::InitColorArray(m_back_color_pressed,m_back_color_pressed,m_back_color_array);
        }
      else
        {
         m_button.State(class="kw">false);
把控件样板交给小布盯盘生成
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到;按钮组的重复声明与事件绑定模板,也能让小布替你跑这套,你专注决策逻辑。

常见问题

基类 CButton 已封装开关状态与边框模式,CSimpleButton 在其上补了分组与事件接口,避免每次手动处理对象属性。
倾向用于一个主操作附带几个次级选项的控件,比如周期切换加自定义时长,比并列多个简单按钮省空间。
这样只有触发封锁的控件能解锁表单,避免其他按钮误触导致界面状态错乱,尤其在多控件面板里更稳。
小布盯盘内置的 AIGC 可识别控件结构并生成看板片段,但交易逻辑仍需你在 MT5 端自行编译部署,外汇贵金属波动剧烈,先模拟验证再上实盘。
不是,前者用图标做互斥选择,后者偏文本单选语义,事件回传字段不同,混用会加大维护成本。