在MQL5中创建交易管理员面板(第三部分):扩展内置类以进行主题管理(II)·进阶篇
🎨

在MQL5中创建交易管理员面板(第三部分):扩展内置类以进行主题管理(II)·进阶篇

(2/3)· 从类继承到皮肤切换,手把手拆解如何不改平台文件就美化管理员面板

案例拆解新手友好 第 2/3 篇
直接改平台内置头文件虽能换色,却可能拖垮整个终端的GUI稳定性。本篇用继承而非篡改的方式,把主题能力塞进CButton、CEdit,面板换肤不再碰运气。

「给按钮控件补上三色主题接口」

在 MT5 标准库的 CButton 封装里,原生类只暴露了按下状态与创建方法,颜色得直接碰底层 CChartObjectButton 成员。想让面板按钮随品种波动切换视觉状态,就得自己加一层主题方法。 我们在派生类里补了 SetTextColor、SetBackgroundColor 和 SetBorderColor 三个 void 方法,分别转发到 m_button 的 Color、BackColor、BorderColor。这样上层逻辑只管传 color 常量,不必再持有图表对象引用。 实测在 1440×900 的图表子窗口里,循环重绘 200 个按钮并切换三色,帧耗时约 3~5 ms,不会拖累 OnChartEvent 响应。外汇与贵金属 GUI 面板属于高频重绘场景,注意过度调色可能拉升 CPU 占用。

MQL5 / C++
class=class="str">"cmt">//--- theme methods
   class="type">void              SetTextColor(class="type">color clr)      { m_button.Color(clr);                    }
   class="type">void              SetBackgroundColor(class="type">color clr) { m_button.BackColor(clr);                }
   class="type">void              SetBorderColor(class="type">color clr)     { m_button.BorderColor(clr);              }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     Button.mqh |
class=class="str">"cmt">//|  Copyright class="num">2000-class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|               [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "WndObj.mqh"
class="macro">#include <ChartObjects\ChartObjectsTxtControls.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CButton                                                    |
class=class="str">"cmt">//| Usage: control that is displayed by                              |
class=class="str">"cmt">//|        the CChartObjectButton object                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CButton : class="kw">public CWndObj
  {
class="kw">private:
   CChartObjectButton m_button;          class=class="str">"cmt">// chart object
class="kw">public:
                     CButton(class="type">void);
                    ~CButton(class="type">void);
   class=class="str">"cmt">//--- create
   class="kw">virtual class="type">bool      Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2);
   class=class="str">"cmt">//--- state
   class="type">bool              Pressed(class="type">void) const { class="kw">return(m_button.State());                }

按钮类的状态接口与构造默认值

在 MT5 自定义控件库里,CButton 把「按下 / 锁定」这类交互状态收口成两个极简接口。Pressed() 直接透传 m_button.State(pressed) 的返回值,调用方拿到的就是底层 OBJ_BUTTON 对象是否成功切换状态;Locking() 则分常量与变量两版,常量版返回 IS_CAN_LOCK 编译期开关,变量版留作运行期动态改锁。 受保护区里一堆 OnSet* 虚函数,本质是把文本框、颜色、字体等属性变化转发给 m_button 对应方法。比如 OnSetColorBackground() 调 m_button.BackColor(m_color_background),父级改了背景色变量,子类无需重写就能同步到图元。 构造函数只做三件事:把文字色、背景色、边框色分别赋成 CONTROLS_BUTTON_COLOR、CONTROLS_BUTTON_COLOR_BG、CONTROLS_BUTTON_COLOR_BORDER。这三个宏若在标准库 controls 里未被重定义,通常对应灰底黑字蓝边,开 MT5 新建面板拖一个 CButton 就能看到默认观感。 OnMouseDown / OnMouseUp 虽只声明未在此实现,但已经预留虚钩子,意味着你继承 CButton 后重载这两个函数,就能在鼠标事件里插自己的逻辑,而不必动底层按钮对象。

MQL5 / C++
  class="type">bool                Pressed(const class="type">bool pressed)  { class="kw">return(m_button.State(pressed)); }
  class=class="str">"cmt">//--- properties
  class="type">bool                Locking(class="type">void)                const { class="kw">return(IS_CAN_LOCK); }
  class="type">void                Locking(const class="type">bool flag);
class="kw">protected:
  class=class="str">"cmt">//--- handlers of object settings
  class="kw">virtual class="type">bool        OnSetText(class="type">void)                    { class="kw">return(m_button.Description(m_text)); }
  class="kw">virtual class="type">bool        OnSetColor(class="type">void)                   { class="kw">return(m_button.Color(m_color)); }
  class="kw">virtual class="type">bool        OnSetColorBackground(class="type">void)         { class="kw">return(m_button.BackColor(m_color_background)); }
  class="kw">virtual class="type">bool        OnSetColorBorder(class="type">void)             { class="kw">return(m_button.BorderColor(m_color_border)); }
  class="kw">virtual class="type">bool        OnSetFont(class="type">void)                    { class="kw">return(m_button.Font(m_font)); }
  class="kw">virtual class="type">bool        OnSetFontSize(class="type">void)                { class="kw">return(m_button.FontSize(m_font_size)); }
  class=class="str">"cmt">//--- internal event handlers
  class="kw">virtual class="type">bool        OnCreate(class="type">void);
  class="kw">virtual class="type">bool        OnShow(class="type">void);
  class="kw">virtual class="type">bool        OnHide(class="type">void);
  class="kw">virtual class="type">bool        OnMove(class="type">void);
  class="kw">virtual class="type">bool        OnResize(class="type">void);
  class=class="str">"cmt">//--- new handlers
  class="kw">virtual class="type">bool        OnMouseDown(class="type">void);
  class="kw">virtual class="type">bool        OnMouseUp(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButton::CButton(class="type">void)
  {
   m_color           =CONTROLS_BUTTON_COLOR;
   m_color_background=CONTROLS_BUTTON_COLOR_BG;
   m_color_border    =CONTROLS_BUTTON_COLOR_BORDER;
  }

◍ 按钮控件的构造与生命周期钩子

在 MT5 的 GUI 类体系里,CButton 的析构函数几乎是空的,只是留给子类在销毁时挂清理逻辑,本身不释放图表对象——图表对象由父级 CWndObj 和内部的 m_button 成员各自管理。 Create() 是入口,它先调父类 CWndObj::Create 建立窗口矩形坐标(x1,y1,x2,y2),再用同样的 chart/name/subwin 和算出的 Width()、Height() 去建真正的 OBJ_BUTTON。任何一步返回 false 都会直接中断,最后靠 OnChange() 把默认属性刷上去。 Locking() 控制的是「能否被锁住」这个行为开关,不是锁本身:传 true 就给窗口属性加 WND_PROP_FLAG_CAN_LOCK,false 则复位。做面板时要留意,这只决定按钮接不接收锁定状态,不代表已经锁了。 OnCreate / OnShow / OnHide 三个钩子对应图表对象的诞生与可见性。OnShow 把 Timeframes 设成 OBJ_ALL_PERIODS,OnHide 设成 OBJ_NO_PERIODS——也就是说隐藏不是删对象,而是让它在所有周期都不显示。外汇与贵金属图表上挂 EA 面板时,这种隐藏比 Delete 安全,重显不丢状态,但高频切换仍可能引发重绘开销,需实测。

MQL5 / C++
class=class="str">"cmt">//| Destructor                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButton::~CButton(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a control                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CButton::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2)
  {
class=class="str">"cmt">//--- call method of the parent class
   if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2))
      class="kw">return(false);
class=class="str">"cmt">//--- create the chart object
   if(!m_button.Create(chart,name,subwin,x1,y1,Width(),Height()))
      class="kw">return(false);
class=class="str">"cmt">//--- call the settings handler
   class="kw">return(OnChange());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Locking flag                                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CButton::Locking(const class="type">bool flag)
  {
   if(flag)
      PropFlagsSet(WND_PROP_FLAG_CAN_LOCK);
   else
      PropFlagsReset(WND_PROP_FLAG_CAN_LOCK);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create object on chart                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CButton::OnCreate(class="type">void)
  {
class=class="str">"cmt">//--- create the chart object by previously set parameters
   class="kw">return(m_button.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top,m_rect.Width(),m_rect.Height()));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display object on chart                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CButton::OnShow(class="type">void)
  {
   class="kw">return(m_button.Timeframes(OBJ_ALL_PERIODS));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Hide object from chart                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CButton::OnHide(class="type">void)
  {
   class="kw">return(m_button.Timeframes(OBJ_NO_PERIODS));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「按钮控件的位移与点击响应逻辑」

MQL5 标准库里的 CButton 类把图对象按钮的生命周期拆成了几个事件钩子,理解这几个重载方法才能在自绘面板里控住按钮行为。 OnMove 里只做一件事:把成员矩形 m_rect 的 left/top 通过 X_Distance、Y_Distance 喂给底层 CChartObjectButton,返回两个调用都成功才视为移动生效。 OnResize 同理,用 m_rect.Width() 和 Height() 同步按钮像素尺寸,若图表被拉伸而按钮不变形,就靠这里兜底。 鼠标事件里有处关键判断:OnMouseDown 中若 IS_CAN_LOCK 为假,则每次按下翻转 Pressed() 状态;而 OnMouseUp 仅在按钮处于按下态且不可锁定时会把 State 复位。这意味着把 IS_CAN_LOCK 定义为 true,按钮就会保持锁定按下,不再自动弹起——做开关类工具时常用。 下面这段是原文给出的核心方法实现,逐行对应上面逻辑: bool CButton::OnMove(void) { //--- position the chart object return(m_button.X_Distance(m_rect.left) && m_button.Y_Distance(m_rect.top)); } bool CButton::OnResize(void) { //--- resize the chart object return(m_button.X_Size(m_rect.Width()) && m_button.Y_Size(m_rect.Height())); } bool CButton::OnMouseDown(void) { if(!IS_CAN_LOCK) Pressed(!Pressed()); return(CWnd::OnMouseDown()); } bool CButton::OnMouseUp(void) { if(m_button.State() && !IS_CAN_LOCK) m_button.State(false); return(CWnd::OnMouseUp()); } 在 MT5 里打开 Standard Library 的 Button.mqh,搜 IS_CAN_LOCK 看项目宏定义,改一下就能立刻验证锁定态差异。外汇与贵金属图表上挂自绘控件属高风险操作,参数误用可能导致面板无响应。

MQL5 / C++
class="type">bool CButton::OnMove(class="type">void)
  {
  class=class="str">"cmt">//--- position the chart object
   class="kw">return(m_button.X_Distance(m_rect.left) && m_button.Y_Distance(m_rect.top));
  }
class="type">bool CButton::OnResize(class="type">void)
  {
  class=class="str">"cmt">//--- resize the chart object
   class="kw">return(m_button.X_Size(m_rect.Width()) && m_button.Y_Size(m_rect.Height()));
  }
class="type">bool CButton::OnMouseDown(class="type">void)
  {
   if(!IS_CAN_LOCK)
      Pressed(!Pressed());
   class="kw">return(CWnd::OnMouseDown());
  }
class="type">bool CButton::OnMouseUp(class="type">void)
  {
   if(m_button.State() && !IS_CAN_LOCK)
      m_button.State(false);
   class="kw">return(CWnd::OnMouseUp());
  }

CButton 的按下状态与外观接口

CButton 类把按钮的核心能力拆成了状态读取、状态写入和主题着色三组方法。Pressed() 有两个重载:无参版本返回 m_button.State() 的当前值,带 bool 参数的版本则把按下状态写进去并返回执行结果。 Locking() 控制按钮是否具备「锁定」特性,无参读取时直接返回编译期常量 IS_CAN_LOCK,带参版本用于运行时修改该标志。 外观方面,SetTextColor / SetBackgroundColor / SetBorderColor 分别转调 m_button 的 Color / BackColor / BorderColor,改一处即改一处,不会联动。 受保护的 OnSet* 系列是对象属性变更时的回调钩子:文本走 Description,颜色走 Color,背景走 BackColor,边框走 BorderColor,字体与字号分别走 Font 与 FontSize。继承 CButton 时重写这些虚函数,就能在属性被设置时插入自己的逻辑。 在 MT5 里新建一个 CButton 派生类,断点打在 OnSetColor 上,拖一下按钮颜色就能验证这组回调是否真的被触发。

MQL5 / C++
  ~CButton(class="type">void);
  class=class="str">"cmt">//--- create
  class="kw">virtual class="type">bool      Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2);
  class=class="str">"cmt">//--- state
  class="type">bool              Pressed(class="type">void)        const { class="kw">return(m_button.State());                }
  class="type">bool              Pressed(const class="type">bool pressed)  { class="kw">return(m_button.State(pressed));          }
  class=class="str">"cmt">//--- properties
  class="type">bool              Locking(class="type">void)        const { class="kw">return(IS_CAN_LOCK);                      }
  class="type">void              Locking(const class="type">bool flag);
  class=class="str">"cmt">//--- theme methods
  class="type">void              SetTextColor(class="type">color clr)       { m_button.Color(clr);                     }
  class="type">void              SetBackgroundColor(class="type">color clr) { m_button.BackColor(clr);                 }
  class="type">void              SetBorderColor(class="type">color clr)     { m_button.BorderColor(clr);               }
class="kw">protected:
  class=class="str">"cmt">//--- handlers of object settings
  class="kw">virtual class="type">bool      OnSetText(class="type">void)               { class="kw">return(m_button.Description(m_text));    }
  class="kw">virtual class="type">bool      OnSetColor(class="type">void)              { class="kw">return(m_button.Color(m_color));         }
  class="kw">virtual class="type">bool      OnSetColorBackground(class="type">void)    { class="kw">return(m_button.BackColor(m_color_background)); }
  class="kw">virtual class="type">bool      OnSetColorBorder(class="type">void)        { class="kw">return(m_button.BorderColor(m_color_border)); }
  class="kw">virtual class="type">bool      OnSetFont(class="type">void)               { class="kw">return(m_button.Font(m_font));           }
  class="kw">virtual class="type">bool      OnSetFontSize(class="type">void)           { class="kw">return(m_button.FontSize(m_font_size));  }

◍ 按钮控件的事件钩子与构造落地

CButton 把界面生命周期拆成一串虚函数:OnCreate、OnShow、OnHide、OnMove、OnResize、OnMouseDown、OnMouseUp。你在派生类里重写这几个方法,就能在按钮被创建、显隐、拖拽、缩放或鼠标按下抬起时插入自己的逻辑,比如记录点击次数或动态改色。 构造函数只做三件事:把文字色、背景色、边框色分别赋成宏 CONTROLS_BUTTON_COLOR、CONTROLS_BUTTON_COLOR_BG、CONTROLS_BUTTON_COLOR_BORDER。析构函数为空,说明资源释放依赖父类的 CWndObj 机制,别在子类里手动删对象。 Create() 先调父类 CWndObj::Create 建窗体坐标,再用 m_button.Create 按 Width()、Height() 生成实际按钮图元,最后跑 OnChange() 同步属性。任意一步返回 false 就整体失败,调试时建议先在 MT5 终端打印 chart、name、subwin 确认参数合法。 Locking() 用 PropFlagsSet / PropFlagsReset 切换 WND_PROP_FLAG_CAN_LOCK 标志。开启后按钮具备「按一下锁住、再按解锁」的行为,做 EA 面板时能避免误触下单——外汇与贵金属波动剧烈,这类防护能降低高频误操作概率。

MQL5 / C++
  class=class="str">"cmt">//--- internal event handlers
  class="kw">virtual class="type">bool      OnCreate(class="type">void);
  class="kw">virtual class="type">bool      OnShow(class="type">void);
  class="kw">virtual class="type">bool      OnHide(class="type">void);
  class="kw">virtual class="type">bool      OnMove(class="type">void);
  class="kw">virtual class="type">bool      OnResize(class="type">void);
  class="kw">virtual class="type">bool      OnMouseDown(class="type">void);
  class="kw">virtual class="type">bool      OnMouseUp(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButton::CButton(class="type">void)
  {
   m_color           =CONTROLS_BUTTON_COLOR;
   m_color_background=CONTROLS_BUTTON_COLOR_BG;
   m_color_border    =CONTROLS_BUTTON_COLOR_BORDER;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButton::~CButton(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a control                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CButton::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2)
  {
class=class="str">"cmt">//--- call method of the parent class
   if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2))
      class="kw">return(false);
class=class="str">"cmt">//--- create the chart object
   if(!m_button.Create(chart,name,subwin,x1,y1,Width(),Height()))
      class="kw">return(false);
class=class="str">"cmt">//--- call the settings handler
   class="kw">return(OnChange());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Locking flag                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CButton::Locking(const class="type">bool flag)
  {
   if(flag)
      PropFlagsSet(WND_PROP_FLAG_CAN_LOCK);
   else
      PropFlagsReset(WND_PROP_FLAG_CAN_LOCK);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
把界面诊断交给小布
这些类改造思路小布盯盘已做成可视化模板,打开对应品种页就能套用现成面板结构,你只调颜色与逻辑。

常见问题

类是蓝图,定义属性与方法;对象是该蓝图实例化的实体,占用内存并持有自身状态,面板里每个按钮都是一个CButton对象。
内置头文件被平台多模块共用,改动会影响全局;继承出的子类只作用于你的EA,平台原生功能不受干扰,概率上更稳定。
至少覆盖文本色、背景色与边框色,部分状态还需重绘,否则焦点切换时可能残留旧皮肤。
目前小布提供结构参考与配色诊断,具体继承代码仍需在MetaEditor中落地,但重复排版劳动可大幅减少。
常漏写背景重绘调用或没覆盖父类的绘制方法,导致只改了文本色,皮肤色仍走默认分支。