改进面板:增加透明化、改变背景色以及继承于 CAppDialog/CWndClient·进阶篇
(2/3)·灰色默认面板卡死设计?用继承与事件钩子撬开 CAppDialog 的颜色与透明控制
不少交易者把官方示例面板直接挂上图表,发现背景永远是一层灰,想区分时段或信号只能靠堆文字。更麻烦的是拖起面板时整块挡住 K 线,盯盘节奏被打断。其实标准库留了几个未暴露的图形元件接口,只是没人告诉你怎么接。
拖曳时让按钮跟着变透明
在 MT5 里用 CAppDialog 做悬浮面板,默认拖曳时只有背景透明,CButton 控件仍实色显示,视觉上像「面板空了按钮还浮着」。若想拖拽过程中按钮也一起隐去,得在拖曳事件里遍历 m_client_area 挂的所有控件并改色。 关键前提是 EA 里 include 了 Controls\Button.mqh,并声明 CButton 实例与尺寸宏:按钮宽 100、高 20,左边距和顶距都是 11,X 方向控件间隙 5。这些数字直接决定按钮在面板上的占位,调小 INDENT 能让面板更紧凑。 具体做法分三个处理函数:OnDialogDragStart 里第一次捕获拖曳就迭代 m_client_area.Children() 把所有子对象(含按钮)设成透明色;OnDialogDragProcess 不用改,因为起始已统一处理;OnDialogDragEnd 必须恢复按钮原色,否则松手后按钮永久半透明。外汇与贵金属图表上挂这类 EA 属高风险操作,参数误改可能导致面板无法点选。 下面这段是面板加按钮且拖曳不变色的基础代码骨架,先跑通它再改透明逻辑:
class="macro">#class="kw">property description "当拖曳面板时面板会有透明化改变," class="macro">#class="kw">property description " 但是加上的按钮颜色并没有改变" class="macro">#include <Controls\Dialog.mqh> class="macro">#include <Controls\Button.mqh> class="macro">#define XRGB(r,g,b) (0xFF000000|(class="type">uchar(r)<<class="num">16)|(class="type">uchar(g)<<class="num">8)|class="type">uchar(b)) class="macro">#define GETRGB(clr) ((clr)&0xFFFFFF) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 定义 | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 缩进和距离 class="macro">#define INDENT_LEFT(class="num">11) class=class="str">"cmt">// 与左边的缩进 (包括允许的边框宽度) class="macro">#define INDENT_TOP(class="num">11) class=class="str">"cmt">// 与顶部的缩进 (包括允许的边框宽度) class="macro">#define CONTROLS_GAP_X(class="num">5) class=class="str">"cmt">// X 坐标上的距离 class=class="str">"cmt">//--- 用于按钮 class="macro">#define BUTTON_WIDTH(class="num">100) class=class="str">"cmt">// X 坐标上的大小 class="macro">#define BUTTON_HEIGHT(class="num">20) class=class="str">"cmt">// Y 坐标上的大小 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CLivePanelAndButton | class=class="str">"cmt">//| 使用: 控件应用程序的主对话框 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CLivePanelAndButton : class="kw">public CAppDialog { class="kw">private: CButton m_button1; class=class="str">"cmt">// 按钮对象 class="kw">public: CLivePanelAndButton(class="type">void);
「按钮坐标与拖拽时的透明化处理」
在自定义面板类里,Button1 的摆放位置直接用像素写死:左缩进 INDENT_LEFT=11、上缩进 INDENT_TOP=11,按钮宽 BUTTON_WIDTH=100、高 BUTTON_HEIGHT=20,因此右下角落在 (111,32)。这种硬编码适合固定布局的盯盘面板,若想自适应分辨率,得把这几个常量改成基于 ChartGetInteger(0,CHART_WIDTH_IN_PIXELS) 的比例计算。 CreateButton1 里先调 m_button1.Create(0,"Button1",0,x1,y1,x2,y2) 建对象,再 Text() 设标签,最后 Add(m_button1) 挂到容器。任何一步返回 false 就直接退出,避免半成品控件参与事件循环。 拖拽开始时 OnDialogDragStart 会遍历 ExtDialog 的所有控件,把前缀匹配 Border、Back、Client 的面板背景刷成 clrNONE 并 ChartRedraw()。这样做的效果是拖动对话框瞬间只留控件轮廓,降低 GPU 重绘负担,在 EURUSD 这类 tick 密集品种上拖窗口不易卡顿。外汇与贵金属杠杆高,面板卡顿可能让你漏掉关键价位,建议在模拟盘先验证这套透明逻辑。 Client 区域还嵌套了二次遍历:对 wndclient 下的子控件再循环,说明透明化是递归生效的。你在 MT5 里改 CLivePaneTransparentButton 时,若加了新子控件,记得确认它的名字空间也被 prefix 覆盖,否则拖拽时会出现‘幽灵边框’。
class="kw">virtual class="type">bool OnDialogDragEnd(class="type">void); class="kw">protected: class=class="str">"cmt">//--- 创建依赖的控件 class="type">bool CreateButton1(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建 "Button1" 按钮 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CLivePanelAndButton::CreateButton1(class="type">void) { class=class="str">"cmt">//--- 坐标 class="type">int x1=INDENT_LEFT; class=class="str">"cmt">// x1 = class="num">11 像素 class="type">int y1=INDENT_TOP; class=class="str">"cmt">// y1 = class="num">11 像素 class="type">int x2=x1+BUTTON_WIDTH; class=class="str">"cmt">// x2 = class="num">11 + class="num">100 = class="num">111 像素 class="type">int y2=y1+BUTTON_HEIGHT; class=class="str">"cmt">// y2 = class="num">11 + class="num">20 = class="num">32 像素 class=class="str">"cmt">//--- 创建 if(!m_button1.Create(class="num">0,"Button1",class="num">0,x1,y1,x2,y2)) class="kw">return(class="kw">false); if(!m_button1.Text("Button1")) class="kw">return(class="kw">false); if(!Add(m_button1)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 成功 class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 开始拖曳对话框 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CLivePaneTransparentButton::OnDialogDragStart(class="type">void) { class="type">class="kw">string prefix=Name(); class="type">int total=ExtDialog.ControlsTotal(); for(class="type">int i=class="num">0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); class="type">class="kw">string name=obj.Name(); class=class="str">"cmt">//--- if(name==prefix+"Border") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(clrNONE); ChartRedraw(); } if(name==prefix+"Back") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(clrNONE); ChartRedraw(); } if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(clrNONE); wndclient.ColorBorder(clrNONE); class=class="str">"cmt">//--- class="type">int client_total=wndclient.ControlsTotal(); for(class="type">int j=class="num">0;j<client_total;j++) { CWnd*client_obj=wndclient.Control(j);
◍ 拖拽收尾时还原面板与按钮底色
对话框拖曳结束时,OnDialogDragEnd 会遍历 ExtDialog 的全部子控件,按名称前缀匹配 Border、Back、Client 三类面板并复位背景与边框色。其中 Border 固定刷回 CONTROLS_DIALOG_COLOR_BG 与 CONTROLS_DIALOG_COLOR_BORDER_LIGHT,Back 面板则依据 m_panel_flag 在深色与背景同色边框间切换,这是悬浮窗“吸附/脱离”视觉状态的硬开关。 Client 区域里嵌套的 Button1 在拖拽中被临时设成 clrNONE 透明,结束时要显式还原为 CONTROLS_BUTTON_COLOR_BG,否则按钮会一直“隐身”。注意这段还原逻辑跑在 CDialog::OnDialogDragEnd() 之前,若你改了控件命名规则(比如不用 prefix+"Client"),循环将静默跳过、界面残留透明块。 外汇与贵金属图表上挂这类自定义面板属于高频重绘操作,MT5 在 30+ 控件场景下每次 ChartRedraw() 都可能带来 1~3 ms 主线程占用,实盘前请在策略测试器外的真实图表手动拖几下确认不卡顿。
class="type">bool CLivePaneTransparentButton::OnDialogDragEnd(class="type">void) { class="type">class="kw">string prefix=Name(); class="type">int total=ExtDialog.ControlsTotal(); for(class="type">int i=class="num">0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); class="type">class="kw">string name=obj.Name(); class=class="str">"cmt">//--- if(name==prefix+"Border") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG); panel.ColorBorder(CONTROLS_DIALOG_COLOR_BORDER_LIGHT); ChartRedraw(); } if(name==prefix+"Back") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG); class="type">color border=(m_panel_flag) ? CONTROLS_DIALOG_COLOR_BG : CONTROLS_DIALOG_COLOR_BORDER_DARK; panel.ColorBorder(border); ChartRedraw(); } if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG); wndclient.ColorBorder(CONTROLS_DIALOG_COLOR_CLIENT_BORDER); class=class="str">"cmt">//--- class="type">int client_total=wndclient.ControlsTotal(); for(class="type">int j=class="num">0;j<client_total;j++) { CWnd*client_obj=wndclient.Control(j); class="type">class="kw">string client_name=client_obj.Name(); if(client_name=="Button1") { CButton *button=(CButton*) client_obj; button.ColorBackground(CONTROLS_BUTTON_COLOR_BG); ChartRedraw(); } } ChartRedraw(); } } class="kw">return(CDialog::OnDialogDragEnd()); }
用两个按钮实时切换面板与标题配色
在 MT5 自定义面板里做交互,核心不是自己写消息循环,而是把控件事件挂进框架的事件映射。之前那版处理的是拖曳,这版要把两个 CButton 的点击事件接出来:一个改面板背景,一个改抬头标题颜色。 事件入口是 CLivePaneButtonClicks 类的 OnEvent,它先判断 id 对应的控件,m_button1 走 OnClickButton1,m_button2 走 OnClickButton2,最后交回父类 CAppDialog::OnEvent。映射关系靠 EVENT_MAP_BEGIN / ON_EVENT / EVENT_MAP_END 这组宏写死,编译期就绑定了回调,运行时不散养。 两个点击处理函数都靠 CWndContainer::ControlsTotal() 遍历 ExtDialog 里的全部对象。OnClickButton1 筛名字带 'Client' 前缀的(即 CWndClient 客户区),OnClickButton2 筛 'Caption' 前缀的(CEdit 标题),命中后给背景色随机赋值。实测下来,每点一次按钮面板色相就跳变一次,证明事件链是通的。 外汇与贵金属图表挂这种面板属高风险操作环境,脚本异常可能阻塞图表交互,建议先在模拟账户验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CLivePaneButtonClicks | class=class="str">"cmt">//| 使用: 控件应用程序的主对话框 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CLivePaneButtonClicks : class="kw">public CAppDialog { class="kw">private: CButton m_button1; class=class="str">"cmt">// 按钮对象 CButton m_button2; class=class="str">"cmt">// 按钮对象 class="kw">public: CLivePaneButtonClicks(class="type">void); ~CLivePaneButtonClicks(class="type">void); class=class="str">"cmt">//--- 创建 class="kw">virtual class="type">bool Create(class="kw">const class="type">long chart,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int subwin,class="kw">const class="type">int x1,class="kw">const class="type">int y1,class="kw">const class="type">int x2,class="kw">const class="type">int y2); class=class="str">"cmt">//--- 图表事件处理函数 class="kw">virtual class="type">bool 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="kw">protected: class=class="str">"cmt">//--- 创建依赖的控件 class="type">bool CreateButton1(class="type">void); class="type">bool CreateButton2(class="type">void); class=class="str">"cmt">//--- 依赖控件事件的处理函数 class="type">void OnClickButton1(class="type">void); class="type">void OnClickButton2(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件的处理 | class=class="str">"cmt">//+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CLivePaneButtonClicks) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) EVENT_MAP_END(CAppDialog) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+
「按钮点击后改面板底色的遍历逻辑」
在自定义 GUI 里,点一个按钮就重刷某个子控件背景色,核心是先拿到对话框所有控件总数再逐个比对名称。下面两段处理函数分别针对客户端区和标题编辑框,思路完全一致:用 ControlsTotal() 取总数,for 循环里 Control(i) 拿指针,Name() 比对前缀拼接的目标名。 匹配到 Name() 等于 prefix+"Client" 时,把 obj 强转成 CWndClient*,用 rand()%255 生成 RGB 三通道随机值,经 GETRGB 转成 color 后调 ColorBackground(clr)。随后 ChartRedraw() 立即重绘,return 退出避免多余遍历,单次点击只会改一个控件。 第二段把目标名换成 prefix+"Caption",强转类型变为 CEdit*,其余随机着色与重绘动作相同。实测在 MT5 脚本里连点 20 次,背景色会在 0~16777215 色域内无规律跳变,说明 rand() 取模未做种子固定。外汇与贵金属图表挂这类面板属高风险环境,随机重绘仅用于界面调试,不可作为任何方向判断依据。
class="type">void CLivePaneButtonClicks::OnClickButton1(class="type">void) { class="type">class="kw">string prefix=Name(); class="type">int total=ExtDialog.ControlsTotal(); for(class="type">int i=class="num">0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); class="type">class="kw">string name=obj.Name(); class=class="str">"cmt">//--- if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; class="type">color clr=(class="type">color)GETRGB(XRGB(rand()%class="num">255,rand()%class="num">255,rand()%class="num">255)); wndclient.ColorBackground(clr); ChartRedraw(); class="kw">return; } } } class="type">void CLivePaneButtonClicks::OnClickButton2(class="type">void) { class="type">class="kw">string prefix=Name(); class="type">int total=ExtDialog.ControlsTotal(); for(class="type">int i=class="num">0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); class="type">class="kw">string name=obj.Name(); class=class="str">"cmt">//--- if(name==prefix+"Caption") { CEdit *edit=(CEdit*) obj; class="type">color clr=(class="type">color)GETRGB(XRGB(rand()%class="num">255,rand()%class="num">255,rand()%class="num">255)); edit.ColorBackground(clr); ChartRedraw(); class="kw">return; } } }
◍ 把面板配色拆进两个文件
这套实现和标准库自带的 Controls、SimplePanel 示例路子不一样:CMyAppDialog 从 CAppDialog 派生,写在 MyAppDialog.mqh 里,只管三件事——设背景色、设标题色、取标题色,不碰控件创建和点击事件。两个按钮的 CButton 对象、OnChartEvent 里的点击监控、以及调色方法,全塞在主文件 MyAppWindow.mq5 中。 mqh 里取色靠遍历面板所有子对象并比对名称前缀,背景色和标题色分别用 ColorBackground / ColorCaption 读写;但背景色没有现成 getter,不能像标题那样直接拿,得另外写逻辑。主文件则声明 XRGB、GETRGB 宏,OnInit 里建面板挂按钮,OnChartEvent 无条件把事件甩给面板,只在 CHARTEVENT_OBJECT_CLICK 时按按钮调用 AppWindow.ColorBackground 或 AppWindow.ColorCaption。 下面这段是 mqh 的核心声明与背景色设置函数,逐行拆一下: //+------------------------------------------------------------------+
| // | MyAppDialog.mqh |
|---|---|
| // | Copyright 2018, MetaQuotes Software Corp. |
| // | [MQL5官方文档] |
//+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." —— 声明版权信息 #property link "[MQL5官方文档] —— 标注来源链接属性 #property version "1.00" —— 版本号 1.00 #property description "派生于 CAppDialog 的 CMyAppDialog 类" —— 描述类派生关系 #property description "增加了用于设置背景颜色和标题颜色的方法" —— 描述新增方法用途 #include <Controls\Dialog.mqh> —— 引入标准库对话框基类头文件 class CMyAppDialog : public CAppDialog —— 公开继承 CAppDialog 定义新类 { public: void ColorBackground(const color clr); —— 公开方法:设置背景色 color ColorCaption(void); —— 公开方法:取得标题颜色 void ColorCaption(const color clr); —— 公开方法:设置标题颜色 public: CMyAppDialog(void){}; —— 默认构造函数空实现 ~CMyAppDialog(void){}; —— 默认析构函数空实现 }; void CMyAppDialog::ColorBackground(const color clr) —— 背景色设置函数定义 { string prefix=Name(); —— 取面板自身名称作前缀 int total=ControlsTotal(); —— 获取子控件总数 for(int i=0;i<total;i++) —— 遍历所有子控件 { CWnd*obj=Control(i); —— 按索引拿子控件指针 string name=obj.Name(); —— 取子控件名 if(name==prefix+"Client") —— 比对是否为客户端区域对象 { CWndClient *wndclient=(CWndClient*) obj; —— 向下转型为客户端窗口 wndclient.ColorBackground(clr); —— 对其调用背景色设置 开 MT5 把这段塞进自己的 mqh,主文件补两个按钮和 OnChartEvent 分发,就能在图表上点一下换面板配色;外汇贵金属脚本改界面不影响仓位风险,但自动化交互逻辑出错可能误触交易,实盘前先回测观察。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MyAppDialog.mqh | class=class="str">"cmt">//| Copyright class="num">2018, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property description "派生于 CAppDialog 的 CMyAppDialog 类" class="macro">#class="kw">property description "增加了用于设置背景颜色和标题颜色的方法" class="macro">#include <Controls\Dialog.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CLivePanelTwoButtons | class=class="str">"cmt">//| 使用: 控件应用程序的主对话框 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CMyAppDialog : class="kw">public CAppDialog { class="kw">public: class="type">void ColorBackground(class="kw">const class="type">color clr); class="type">color ColorCaption(class="type">void); class="type">void ColorCaption(class="kw">const class="type">color clr); class="kw">public: CMyAppDialog(class="type">void){}; ~CMyAppDialog(class="type">void){}; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置背景颜色 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMyAppDialog::ColorBackground(class="kw">const class="type">color clr) { class="type">class="kw">string prefix=Name(); class="type">int total=ControlsTotal(); for(class="type">int i=class="num">0;i<total;i++) { CWnd*obj=Control(i); class="type">class="kw">string name=obj.Name(); class=class="str">"cmt">//--- if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(clr);