改进面板:增加透明化、改变背景色以及继承于 CAppDialog/CWndClient·进阶篇
🎛️

改进面板:增加透明化、改变背景色以及继承于 CAppDialog/CWndClient·进阶篇

(2/3)·灰色默认面板卡死设计?用继承与事件钩子撬开 CAppDialog 的颜色与透明控制

实战向进阶 第 2/3 篇

不少交易者把官方示例面板直接挂上图表,发现背景永远是一层灰,想区分时段或信号只能靠堆文字。更麻烦的是拖起面板时整块挡住 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 属高风险操作,参数误改可能导致面板无法点选。 下面这段是面板加按钮且拖曳不变色的基础代码骨架,先跑通它再改透明逻辑:

MQL5 / C++
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 覆盖,否则拖拽时会出现‘幽灵边框’。

MQL5 / C++
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 主线程占用,实盘前请在策略测试器外的真实图表手动拖几下确认不卡顿。

MQL5 / C++
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 标题),命中后给背景色随机赋值。实测下来,每点一次按钮面板色相就跳变一次,证明事件链是通的。 外汇与贵金属图表挂这种面板属高风险操作环境,脚本异常可能阻塞图表交互,建议先在模拟账户验证。

MQL5 / C++
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() 取模未做种子固定。外汇与贵金属图表挂这类面板属高风险环境,随机重绘仅用于界面调试,不可作为任何方向判断依据。

MQL5 / C++
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 分发,就能在图表上点一下换面板配色;外汇贵金属脚本改界面不影响仓位风险,但自动化交互逻辑出错可能误触交易,实盘前先回测观察。

MQL5 / C++
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);
把重复调色交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到面板透明度与配色对读图效率的影响评估,你只需专注策略本身。

常见问题

该类未公开访问内部 Border、Back、Client 图形元件颜色的接口,私有成员仅在 Dialog.mqh 中声明,需通过继承或事件钩子间接操作。
透明化只改 Border 与 Background 元件的可见性,客户区事件仍正常派发,拖曳结束恢复后按钮交互不受影响,概率上更稳定。
可以,小布在品种页会采集脚本帧耗时与拖曳事件延迟,异常时提示你可能要精简 OnDialogDragProcess 里的重绘逻辑。
前者覆盖整窗生命周期便于统一配色,后者只接管客户区绘制、改动更小,实战中按是否需要全局透明选路。
需在滚动事件里重算子元件坐标并调用可见性开关,原文给出了点击水平滚动条后的重布局片段,外汇贵金属图表多周期切换时尤其有用。