如何创建任意复杂度的图形面板·进阶篇
🧩

如何创建任意复杂度的图形面板·进阶篇

(2/3)· 从对象继承到 #undef 重定义,把面板底层逻辑一次讲透

偏理论 第 2/3 篇
很多开发者直接抄一段 CAppDialog 代码就跑,却没注意销毁步骤,切品种时控件层层叠加。面板不销毁,EA 重载一次就多一层幽灵界面。理解结构前先避开这个坑,后面才看得懂继承链。

◍ 拆解 CAppDialog 的图层堆叠顺序

在空白图表挂上 LearnCAppDialog.mq5 后,按 Ctrl+B 再点“全部”,对象列表里能直接看到面板拉起的全部图形元素。标准库面板与对话框这套对象,创建次序是固定的:先生成 Border 边框,再往里铺 Back 背景层,背景之上才是 ClientBack 客户区,子控件全加在客户区里,标题和那两个控制按钮压在顶部。 Border 本体是 OBJ_RECTANGLE_LABEL,默认套了白色边框。它纯粹是装饰——白色边线露在外头,主体很快被 Back 层盖住,所以别指望 Border 承载任何交互逻辑,它只负责让面板边缘有那圈白线。 实测里这套顺序乱不得:若先建 Back 再补 Border,边框会被背景吞掉,Ctrl+B 里对象还在,但视觉上白边消失。开 MT5 拖一遍就能确认图层覆盖关系。

「面板类的继承与创建分支」

标准库里面板相关的类看似多,继承链其实很浅。只要搞清 CAppDialog 装了什么、怎么建,其余类基本一通百通。 以 LearnCAppDialog.mq5 里的 AppWindow 面板为例,它挂了 6 个对象,各自只干一件具体活。基于 CAppDialog 的面板既能生在 EA 里,也能生在指标里,但创建路径被程序类型和窗口号卡死了。 若运行程序是 PROGRAM_EXPERT,面板只允许落在主窗口(索引 0),且只能走 CAppDialog::CreateExpert。若是 PROGRAM_INDICATOR,则先看窗口号:0 号主窗口用 CreateIndicator,子窗口反而要用 CreateExpert。 CreateIndicator 有个硬差异——建面板时会自动把宽度对齐窗口、并按需拉高窗口容纳面板。指标示例 [data folder]\MQL5\Indicators\Examples\Panels\SimplePanel\SimplePanel.mq5 建完还能最小化,就是这套逻辑。唯一的例外是指标也可强占主窗口建面板,此时仍调 CreateIndicator。外汇与贵金属脚本挂载面板属高风险操作,建窗失败可能拖慢盯盘响应。

用 #undef 改写面板控件的默认常数

MT5 标准库里面板和控件的基础常数都躺在 [数据文件夹]\MQL5\Include\Controls\Defines.mqh 中,被 CWnd 体系引用。继承链是 CWnd → CWndContainer → CDialog → CAppDialog,我们真正要动的就是那批宏替换。 想改这些宏,得先用 #undef 把已声明的宏撤掉,再用新参数重声明。诀窍是在包含 Dialog.mqh 之前先 include Defines.mqh,紧接着 #undef 掉目标宏,然后把输入参数写进去当新值重声明。这样 EA 编译时用的就是你的配色而不是库默认。 CAppDialog 对象自带从 CWndContainer 继承来的 ControlsTotal 方法,能数出容器里控件数量并逐个遍历。调试器里能看到对象创建后的命名规律:m_background 变成 "29437Back"、m_client_area 变成 "29437Client",前面那串 29437 是面板生命周期内的唯一标识。 别把正态当圣经 改 m_client_area 或 m_background 颜色很直接,但注意若调用 CWndContainer::Delete 把 m_client_area 从组里摘除,之后拖面板时客户区不会跟手,会卡在原位;关闭面板时它才随其他元件一起被删。若改用 CWndContainer::Destroy,则是直接销毁对象——示例代码里面板建好后有 5 秒暂停再干掉客户区。外汇与贵金属脚本改动 UI 不影响报价风险,但 MT5 图形对象误操作可能导致图表状态混乱,实盘前请在模拟盘验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Wnd.mqh |
class=class="str">"cmt">//|            Copyright class="num">2009-class="num">2017, MetaQuotes Software Corp.       |
class=class="str">"cmt">//|                       [MQL5官方文档]                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Rect.mqh"
class="macro">#include "Defines.mqh"
class="macro">#include <Object.mqh>
class CDragWnd;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 绘图风格和颜色                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 通用
class="macro">#define CONTROLS_FONT_NAME                    "Trebuchet MS"
class="macro">#define CONTROLS_FONT_SIZE(class="num">10)
class=class="str">"cmt">//--- 文字
class="macro">#define CONTROLS_COLOR_TEXT                   C&class="macro">#x27;0x3B,0x29,0x28&class="macro">#x27;
class="macro">#define CONTROLS_COLOR_TEXT_SEL               White
class="macro">#define CONTROLS_COLOR_BG                     White
class="macro">#define CONTROLS_COLOR_BG_SEL                 C&class="macro">#x27;0x33,0x99,0xFF&class="macro">#x27;
class=class="str">"cmt">//--- 按钮
class="macro">#define CONTROLS_BUTTON_COLOR                 C&class="macro">#x27;0x3B,0x29,0x28&class="macro">#x27;
class="macro">#define CONTROLS_BUTTON_COLOR_BG              C&class="macro">#x27;0xDD,0xE2,0xEB&class="macro">#x27;
class="macro">#define CONTROLS_BUTTON_COLOR_BORDER          C&class="macro">#x27;0xB2,0xC3,0xCF&class="macro">#x27;
class=class="str">"cmt">//--- 标签
class="macro">#define CONTROLS_LABEL_COLOR                  C&class="macro">#x27;0x3B,0x29,0x28&class="macro">#x27;
class=class="str">"cmt">//--- 编辑框
class="macro">#define CONTROLS_EDIT_COLOR                   C&class="macro">#x27;0x3B,0x29,0x28&class="macro">#x27;

◍ 控件配色宏定义逐个拆

在 MT5 自定义面板开发里,所有标准控件的底色、边框色都靠一组 #define 宏集中管理。改一处宏,整个 EA 面板的美观度就跟着变,不用去翻每个 CEdit、CList 的创建代码。 下面是编辑框、滚动条、列表视图等核心控件的配色宏原文: #define CONTROLS_EDIT_COLOR_BG White #define CONTROLS_EDIT_COLOR_BORDER C'0xB2,0xC3,0xCF' //--- 滚动条 #define CONTROLS_SCROLL_COLOR_BG C'0xEC,0xEC,0xEC' #define CONTROLS_SCROLL_COLOR_BORDER C'0xD3,0xD3,0xD3' //--- 客户区域 #define CONTROLS_CLIENT_COLOR_BG C'0xDE,0xDE,0xDE' #define CONTROLS_CLIENT_COLOR_BORDER C'0x2C,0x2C,0x2C' //--- 列表视图 #define CONTROLS_LISTITEM_COLOR_TEXT C'0x3B,0x29,0x28' #define CONTROLS_LISTITEM_COLOR_TEXT_SEL White #define CONTROLS_LISTITEM_COLOR_BG White #define CONTROLS_LISTITEM_COLOR_BG_SEL C'0x33,0x99,0xFF' #define CONTROLS_LIST_COLOR_BG White #define CONTROLS_LIST_COLOR_BORDER C'0xB2,0xC3,0xCF' //--- 复选框组 #define CONTROLS_CHECKGROUP_COLOR_BG C'0xF7,0xF7,0xF7' #define CONTROLS_CHECKGROUP_COLOR_BORDER C'0xB2,0xC3,0xCF' //--- 单选框组 #define CONTROLS_RADIOGROUP_COLOR_BG C'0xF7,0xF7,0xF7' #define CONTROLS_RADIOGROUP_COLOR_BORDER C'0xB2,0xC3,0xCF' //--- 对话框 #define CONTROLS_DIALOG_COLOR_BORDER_LIGHT White #define CONTROLS_DIALOG_COLOR_BORDER_DARK C'0xB6,0xB6,0xB6' #define CONTROLS_DIALOG_COLOR_BG C'0xF0,0xF0,0xF0' #define CONTROLS_DIALOG_COLOR_CAPTION_TEXT C'0x28,0x29,0x3B' #define CONTROLS_DIALOG_COLOR_CLIENT_BG C'0xF7,0xF7,0xF7' #define CONTROLS_DIALOG_COLOR_CLIENT_BORDER C'0xC8,0xC8,0xC8' 逐行看:CONTROLS_EDIT_COLOR_BG 设为 White,编辑框底色纯白;CONTROLS_EDIT_COLOR_BORDER 用 C'0xB2,0xC3,0xCF' 即浅蓝灰边框。滚动条底色 C'0xEC,0xEC,0xEC' 接近浅灰,边框 C'0xD3,0xD3,0xD3' 略深。 列表视图里选中项底色是 C'0x33,0x99,0xFF'(典型 Windows 蓝),未选中文本用 C'0x3B,0x29,0x28' 深棕,对比度足够。对话框标题文字 C'0x28,0x29,0x3B' 近黑蓝,客户区底色 C'0xF7,0xF7,0xF7' 比主背景 C'0xF0,0xF0,0xF0' 稍亮。 把这组宏直接贴进你的 .mq5,把 CONTROLS_LISTITEM_COLOR_BG_SEL 改成 C'0xFF,0x99,0x00' 之类,就能在 MT5 里直观看到自选品种列表高亮色变了。外汇与贵金属交易界面定制虽不影响报价,但过度依赖面板信号仍属高风险行为。

MQL5 / C++
class="macro">#define CONTROLS_EDIT_COLOR_BG                White
class="macro">#define CONTROLS_EDIT_COLOR_BORDER            C&class="macro">#x27;0xB2,0xC3,0xCF&class="macro">#x27;
class=class="str">"cmt">//--- 滚动条
class="macro">#define CONTROLS_SCROLL_COLOR_BG              C&class="macro">#x27;0xEC,0xEC,0xEC&class="macro">#x27;
class="macro">#define CONTROLS_SCROLL_COLOR_BORDER          C&class="macro">#x27;0xD3,0xD3,0xD3&class="macro">#x27;
class=class="str">"cmt">//--- 客户区域
class="macro">#define CONTROLS_CLIENT_COLOR_BG              C&class="macro">#x27;0xDE,0xDE,0xDE&class="macro">#x27;
class="macro">#define CONTROLS_CLIENT_COLOR_BORDER          C&class="macro">#x27;0x2C,0x2C,0x2C&class="macro">#x27;
class=class="str">"cmt">//--- 列表视图
class="macro">#define CONTROLS_LISTITEM_COLOR_TEXT          C&class="macro">#x27;0x3B,0x29,0x28&class="macro">#x27;
class="macro">#define CONTROLS_LISTITEM_COLOR_TEXT_SEL      White
class="macro">#define CONTROLS_LISTITEM_COLOR_BG            White
class="macro">#define CONTROLS_LISTITEM_COLOR_BG_SEL        C&class="macro">#x27;0x33,0x99,0xFF&class="macro">#x27;
class="macro">#define CONTROLS_LIST_COLOR_BG                White
class="macro">#define CONTROLS_LIST_COLOR_BORDER            C&class="macro">#x27;0xB2,0xC3,0xCF&class="macro">#x27;
class=class="str">"cmt">//--- 复选框组
class="macro">#define CONTROLS_CHECKGROUP_COLOR_BG          C&class="macro">#x27;0xF7,0xF7,0xF7&class="macro">#x27;
class="macro">#define CONTROLS_CHECKGROUP_COLOR_BORDER      C&class="macro">#x27;0xB2,0xC3,0xCF&class="macro">#x27;
class=class="str">"cmt">//--- 单选框组
class="macro">#define CONTROLS_RADIOGROUP_COLOR_BG          C&class="macro">#x27;0xF7,0xF7,0xF7&class="macro">#x27;
class="macro">#define CONTROLS_RADIOGROUP_COLOR_BORDER      C&class="macro">#x27;0xB2,0xC3,0xCF&class="macro">#x27;
class=class="str">"cmt">//--- 对话框
class="macro">#define CONTROLS_DIALOG_COLOR_BORDER_LIGHT    White
class="macro">#define CONTROLS_DIALOG_COLOR_BORDER_DARK     C&class="macro">#x27;0xB6,0xB6,0xB6&class="macro">#x27;
class="macro">#define CONTROLS_DIALOG_COLOR_BG              C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;
class="macro">#define CONTROLS_DIALOG_COLOR_CAPTION_TEXT    C&class="macro">#x27;0x28,0x29,0x3B&class="macro">#x27;
class="macro">#define CONTROLS_DIALOG_COLOR_CLIENT_BG       C&class="macro">#x27;0xF7,0xF7,0xF7&class="macro">#x27;
class="macro">#define CONTROLS_DIALOG_COLOR_CLIENT_BORDER   C&class="macro">#x27;0xC8,0xC8,0xC8&class="macro">#x27;

「给控制面板换一套自定义配色」

想在 MT5 里做带位图按钮的交易面板,第一步往往是改掉标准控件库的默认灰白配色。标准 Controls 库在 Defines.mqh 里用宏写死了字体、按钮和对话框颜色,直接 include 会覆盖不了你的 input 参数。 正确做法是在 include 之前先用 #undef 把 12 个相关宏全部撤掉,再用 #define 把它们重映射到脚本顶部声明的 input 变量上。下面这段就是典型的预处理重定义块,覆盖了字体名、字号、按钮前景/背景/边框、对话框边框明暗与背景等。 注意字体默认设成了 Trebuchet MS、字号 10,按钮文字色用了 C'0x3B,0x29,0x28'(深棕),背景是 C'0xDD,0xE2,0xEB'(浅蓝灰)。这些数值你都能在 MT5 里直接改,然后看面板实时变色。外汇与贵金属行情波动剧烈,这类面板只做交互辅助,不涉及任何方向判断。 预处理做完再 include Dialog.mqh 和 BmpButton.mqh,CBmpButton 类就能用你这套配色实例化。开 MT5 新建 EA 把下面代码贴进头部,编译后拖到图表即可验证面板颜色是否生效。

MQL5 / C++
class="macro">#class="kw">property description "控制面板与对话框. 演示 CBmpButton 类"
class="macro">#include <Controls\Defines.mqh>
class="macro">#undef CONTROLS_FONT_NAME
class="macro">#undef CONTROLS_FONT_SIZE
class="macro">#undef CONTROLS_BUTTON_COLOR
class="macro">#undef CONTROLS_BUTTON_COLOR_BG
class="macro">#undef CONTROLS_BUTTON_COLOR_BORDER
class="macro">#undef CONTROLS_DIALOG_COLOR_BORDER_LIGHT
class="macro">#undef CONTROLS_DIALOG_COLOR_BORDER_DARK
class="macro">#undef CONTROLS_DIALOG_COLOR_BG
class="macro">#undef CONTROLS_DIALOG_COLOR_CAPTION_TEXT
class="macro">#undef CONTROLS_DIALOG_COLOR_CLIENT_BG
class="macro">#undef CONTROLS_DIALOG_COLOR_CLIENT_BORDER
input class="type">class="kw">string  font_name                   = "Trebuchet MS";
input class="type">int     font_size                   = class="num">10;
input class="type">color   button_color                = C&class="macro">#x27;0x3B,0x29,0x28&class="macro">#x27;;
input class="type">color   button_color_bg             = C&class="macro">#x27;0xDD,0xE2,0xEB&class="macro">#x27;;
input class="type">color   button_color_border         = C&class="macro">#x27;0xB2,0xC3,0xCF&class="macro">#x27;;
input class="type">color   dialog_color_border_light   = White;
input class="type">color   dialog_color_border_dark    = C&class="macro">#x27;0xB6,0xB6,0xB6&class="macro">#x27;;
input class="type">color   dialog_color_bg             = C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;;
input class="type">color   dialog_color_caption_text   = C&class="macro">#x27;0x28,0x29,0x3B&class="macro">#x27;;
input class="type">color   dialog_color_client_bg      = C&class="macro">#x27;0xF7,0xF7,0xF7&class="macro">#x27;;
input class="type">color   dialog_color_client_border  = C&class="macro">#x27;0xC8,0xC8,0xC8&class="macro">#x27;;
class="macro">#define CONTROLS_FONT_NAME                font_name
class="macro">#define CONTROLS_FONT_SIZE                font_size
class="macro">#define CONTROLS_BUTTON_COLOR             button_color
class="macro">#define CONTROLS_BUTTON_COLOR_BG          button_color_bg
class="macro">#define CONTROLS_BUTTON_COLOR_BORDER      button_color_border
class="macro">#define CONTROLS_DIALOG_COLOR_BORDER_LIGHT dialog_color_border_light
class="macro">#define CONTROLS_DIALOG_COLOR_BORDER_DARK  dialog_color_border_dark
class="macro">#define CONTROLS_DIALOG_COLOR_BG          dialog_color_bg
class="macro">#define CONTROLS_DIALOG_COLOR_CAPTION_TEXT dialog_color_caption_text
class="macro">#define CONTROLS_DIALOG_COLOR_CLIENT_BG   dialog_color_client_bg
class="macro">#define CONTROLS_DIALOG_COLOR_CLIENT_BORDER dialog_color_client_border
class="macro">#include <Controls\Dialog.mqh>
class="macro">#include <Controls\BmpButton.mqh>

对话框基类的控件装配逻辑

CDialog 是 MT5 标准库中对话框与指标面板的共同基类,它不直接画界面,而是把五个底层控件拼起来:白色边框、背景、标题栏、关闭按钮、客户区。这五个成员在 private 段声明,外部只能透过类方法去驱动。 Create() 是整个装配入口。它先调父类 CWndContainer::Create() 确立图表、名称、子窗口与坐标边界(x1,y1,x2,y2),任一依赖控件构建失败就返回 false,对话框直接起不来。 注意 m_panel_flag 这个开关:只有当它为真时才跳过白色边框的创建,意味着默认每个对话框都带边框。如果你在做极简面板,记得在实例化前把该标志置位,否则多一层 CPanel 会吃掉约 2~3 像素的边距。 下面这段是类定义与 Create 方法的核心骨架,逐行拆完就能在 MT5 里照抄验证。

MQL5 / C++
class CDialog : class="kw">public CWndContainer
  {
class="kw">private:
   CPanel       m_white_border;    class=class="str">"cmt">// "白色边框" 对象
   CPanel       m_background;      class=class="str">"cmt">// 背景对象
   CEdit        m_caption;         class=class="str">"cmt">// 窗口标题对象
   CBmpButton   m_button_close;    class=class="str">"cmt">// "关闭"按钮对象
   CWndClient   m_client_area;     class=class="str">"cmt">// 客户区域对象
class="kw">protected:
class="type">bool CDialog::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)
  {
   if(!CWndContainer::Create(chart,name,subwin,x1,y1,x2,y2))
      class="kw">return(class="kw">false);
   if(!m_panel_flag && !CreateWhiteBorder())
      class="kw">return(class="kw">false);
   if(!CreateBackground())
      class="kw">return(class="kw">false);
   if(!CreateCaption())
      class="kw">return(class="kw">false);
   if(!CreateButtonClose())
      class="kw">return(class="kw">false);
   if(!CreateClientArea())
      class="kw">return(class="kw">false);
把控件层级交给小布梳理
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到面板对象树与事件流向,省去你手动 printf 跟踪的功夫。

常见问题

CAppDialog 继承自 CDialog,后者负责把对话框纳入控件组管理,前者在此之上封装了 AppWindow 级别的面板容器行为,理解这一层才能正确挂载自定义控件。
标准库头文件里很多布局常数已被预设,直接改会被宏覆盖失效;先 #undef 解除原定义,再按你的面板尺寸重定义,才能生效且不影响其他包含文件。
可以,小布盯盘内置的 AIGC 能解析品种页上的控件事件流,帮你快速定位哪个按钮没接到 ChartEvent,不必逐行加调试输出。
会,继承顺序决定了绘制与消息传递路径,不清楚谁覆盖谁的方法,就容易出现按钮不响应或错位,概率上调试时间会翻倍。
全局声明实例、创建 AppWindow、传递 ChartEvent、最后 Destroy 销毁,缺了销毁会在切换周期时堆叠旧控件。