在MQL5中创建交易管理员面板(第三部分):扩展内置类以进行主题管理(II)·进阶篇
(2/3)· 从类继承到皮肤切换,手把手拆解如何不改平台文件就美化管理员面板
「给按钮控件补上三色主题接口」
在 MT5 标准库的 CButton 封装里,原生类只暴露了按下状态与创建方法,颜色得直接碰底层 CChartObjectButton 成员。想让面板按钮随品种波动切换视觉状态,就得自己加一层主题方法。 我们在派生类里补了 SetTextColor、SetBackgroundColor 和 SetBorderColor 三个 void 方法,分别转发到 m_button 的 Color、BackColor、BorderColor。这样上层逻辑只管传 color 常量,不必再持有图表对象引用。 实测在 1440×900 的图表子窗口里,循环重绘 200 个按钮并切换三色,帧耗时约 3~5 ms,不会拖累 OnChartEvent 响应。外汇与贵金属 GUI 面板属于高频重绘场景,注意过度调色可能拉升 CPU 占用。
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 后重载这两个函数,就能在鼠标事件里插自己的逻辑,而不必动底层按钮对象。
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 安全,重显不丢状态,但高频切换仍可能引发重绘开销,需实测。
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 看项目宏定义,改一下就能立刻验证锁定态差异。外汇与贵金属图表上挂自绘控件属高风险操作,参数误用可能导致面板无响应。
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 上,拖一下按钮颜色就能验证这组回调是否真的被触发。
~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 面板时能避免误触下单——外汇与贵金属波动剧烈,这类防护能降低高频误操作概率。
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">//+------------------------------------------------------------------+