利用MQL5创建您自己的图形面板·综合运用
🧩

利用MQL5创建您自己的图形面板·综合运用

(3/3)· 前面铺垫了类库与基础控件,这一篇把面板拼装、参数改写和事件响应串成可运行的完整链路

偏理论进阶 第 3/3 篇
不少交易者把标准库控件当成孤立积木,拼完面板就以为完工,结果参数改不动、点击没反应。图形面板真正的门槛不在画出来,而在事件链有没有接上。

◍ 用具名常量接管对话框布局

做 MT5 自定义对话框时,控件摆位和留白若直接写死数值,后期改版会非常痛苦。把缩进、间距、控件尺寸和颜色范围提为 #define 常量,是性价比最高的起点。 具名常量的实际好处有两点:一是不用记住四边缩进到底是 11 还是 12,敲名字时编辑器自动列表就能补全;二是哪天想把整体边距从 11 调到 14,只改常量定义一处即可,不必全局替换散落的数值。 下面这组常量可直接拷进 EA 或脚本的头部。左、顶、右、底缩进统一设为 11(含边框宽度),X/Y 轴控件间距为 10,标签宽 50,编辑框宽 50、高 20,颜色元件范围 0–255。 别把正态当圣经 常量值不是金科玉律。11 和 10 只是某套界面基准,高 DPI 屏上可能显得拥挤,开 MT5 后按自己分辨率改 INDENT 与 GAP 两组的数值验证手感。外汇与贵金属插件开发同样涉及实盘高风险,界面只是辅助,别本末倒置。

MQL5 / C++
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 INDENT_RIGHT(class="num">11)      class=class="str">"cmt">// 右缩进 (包括边框宽度)
class="macro">#define INDENT_BOTTOM(class="num">11)      class=class="str">"cmt">// 底缩进 (包括边框宽度)
class="macro">#define CONTROLS_GAP_X(class="num">10)      class=class="str">"cmt">// 空格 X-轴
class="macro">#define CONTROLS_GAP_Y(class="num">10)      class=class="str">"cmt">// 空格 Y-轴
class=class="str">"cmt">//--- 标签
class="macro">#define LABEL_WIDTH(class="num">50)      class=class="str">"cmt">// 尺寸 X-轴
class=class="str">"cmt">//--- 编辑
class="macro">#define EDIT_WIDTH(class="num">50)      class=class="str">"cmt">// 尺寸 Y-轴
class="macro">#define EDIT_HEIGHT(class="num">20)      class=class="str">"cmt">// 尺寸 Y-轴
class=class="str">"cmt">//--- 基准颜色 (RGB)
class="macro">#define BASE_COLOR_MIN(class="num">0)       class=class="str">"cmt">// 颜色元件最小值
class="macro">#define BASE_COLOR_MAX(class="num">255)     class=class="str">"cmt">// 颜色元件最大值

把面板控件拆成自包含方法

显示面板类已经搭好骨架,接下来要落地具体功能,第一步就是重写父类的 Create(...) 方法,并在里面挂上附加控件的创建逻辑。 如果图省事把所有控件生成代码全塞进 Create(...) 主体,方法体很快就会膨胀成几百行不可读的“大块头”,后续改一个颜色标签都得在粪坑里找针。 更干净的做法是拆成按职责独立的方法:CreateColor() 管色板,CreateRed() / CreateGreen() / CreateBlue() 各自生成带说明文字的显示元素。Create(...) 只按顺序调它们,任何一步返回 false 就直接中断。 下面这段重写后的 Create 已经把四个子方法接进来了。在 MT5 里你照着补完那四个 bool 方法,编译后面板就会一次性画出色板加三色说明块;外汇与贵金属图表加载此类 EA 属高风险操作,参数没对齐可能界面错位。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 创建                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">bool CPanelDialog::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">//--- 调用父类函数
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))  class="kw">return(false);
class=class="str">"cmt">//--- 附加控件将在此处创建
class=class="str">"cmt">//--- 成功
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 创建                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">bool CPanelDialog::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">//--- 调用父类函数
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) class="kw">return(false);
class=class="str">"cmt">//--- 创建 附加元素
   if(!CreateColor()) class="kw">return(false);
   if(!CreateRed())   class="kw">return(false);
   if(!CreateGreen()) class="kw">return(false);
   if(!CreateBlue())  class="kw">return(false);
class=class="str">"cmt">//--- 成功
   class="kw">return(true);
  }

「相对坐标下挂容器建控件」

做 MT5 面板时,子控件别用绝对坐标硬写,否则换容器就全乱。CPanelDialog::CreateBlue 这套写法把位移锚在容器左上角,创建完必须靠 Add(...) 塞进对话框,否则界面上根本不显示。 看坐标段:y1 从 INDENT_TOP 往下偏移 2*(EDIT_HEIGHT+CONTROLS_GAP_Y),意味着“蓝色”这一行排在前面两个编辑框之下;x2=x1+EDIT_WIDTH 定宽,LABEL_WIDTH+CONTROLS_GAP_X 的二次偏移把输入框推到标题右侧。 代码里 m_label_blue 先建文本“Blue”,m_edit_blue 后建并设了 MinValue(BASE_COLOR_MIN)、MaxValue(BASE_COLOR_MAX) 与当前值 m_blue。外汇与贵金属 EA 面板涉及高风险,参数越界可能让控件输入失效,开 MT5 把这段抄进类里跑一下,确认对话框里出现可改的 Blue 字段再继续。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 创建“蓝色”控件解释性标题                    |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">bool CPanelDialog::CreateBlue(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x1=INDENT_LEFT;
   class="type">int y1=INDENT_TOP+class="num">2*(EDIT_HEIGHT+CONTROLS_GAP_Y);
   class="type">int x2=x1+EDIT_WIDTH;
   class="type">int y2=y1+EDIT_HEIGHT;
class=class="str">"cmt">//--- 创建标题
   if(!m_label_blue.Create(m_chart_id,m_name+"LabelBlue",m_subwin,x1,y1+class="num">1,x2,y2)) class="kw">return(false);
   if(!m_label_blue.Text("Blue")) class="kw">return(false);
   if(!Add(m_label_blue)) class="kw">return(false);
class=class="str">"cmt">//--- 调整坐标
   x1+=LABEL_WIDTH+CONTROLS_GAP_X;
   x2=x1+EDIT_WIDTH;
class=class="str">"cmt">//--- 创建控件
   if(!m_edit_blue.Create(m_chart_id,m_name+"Blue",m_subwin,x1,y1,x2,y2)) class="kw">return(false);
   if(!Add(m_edit_blue)) class="kw">return(false);
   m_edit_blue.MinValue(BASE_COLOR_MIN);
   m_edit_blue.MaxValue(BASE_COLOR_MAX);
   m_edit_blue.Value(m_blue);
class=class="str">"cmt">//--- 成功
   class="kw">return(true);
  }

◍ 从外部接管色阶参数的三个接口

想在 MT5 指标运行期动态改基色,而不是每次都进代码里硬改,就得在面板类里暴露三个公共方法:SetRed、SetGreen、SetBlue,分别接管红、绿、蓝色阶。调用后不仅内部变量刷新,对应数值框会同步显示新值,色板也立即重绘。 以 SetRed 为例,入参先做边界检查,小于 0 或大于 255 直接 return,避免 RGB 通道越界。合法值写入 m_red 后,用 IntegerToString 推到 m_field_red 的文本框,再调 SetColor 重画面板。 原始指标里红色随每个 Calculate 事件随机跳、绿色随 Timer 事件随机跳,把这套 Set 方法挂上去,就等于把随机源换成了可人工拨动的旋钮。外汇与贵金属图表加载此类自定义指标存在脚本异常风险,建议先在模拟环境验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 设置“红”值                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void CPanelDialog::SetRed(const class="type">int value)
  {
class=class="str">"cmt">//--- 检查
   if(value<class="num">0 || value>class="num">255) class="kw">return;
class=class="str">"cmt">//--- 保存
   m_red=value;
class=class="str">"cmt">//--- 设置
   m_field_red.Text(IntegerToString(value));
class=class="str">"cmt">//--- 设置面板颜色
   SetColor();
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                 PanelIndicator.mq5 |
class=class="str">"cmt">//|                    Copyright class="num">2011, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                             [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------
class="macro">#class="kw">property copyright "Copyright class="num">2011, 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 indicator_separate_window
class="macro">#class="kw">property indicator_plots                class="num">0
class="macro">#class="kw">property indicator_buffers             class="num">0
class="macro">#class="kw">property indicator_minimum             class="num">0.0
class="macro">#class="kw">property indicator_maximum             class="num">0.0
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 包含文件                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------
class="macro">#include "PanelDialog.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 全局变量                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------
CPanelDialog ExtDialog;
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 初始化                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 创建应用对话框
   if(!ExtDialog.Create(class="num">0,"Panel Indicator",class="num">0,class="num">0,class="num">0,class="num">0,class="num">130))
      class="kw">return(-class="num">1);
class=class="str">"cmt">//--- 开始应用

对话框生命周期与事件回调怎么接

这段把自定义对话框的初始化、去初始化和四类事件回调直接铺开,能照抄进自己的指标或 EA 骨架里。Init 里先跑 ExtDialog.Run(),失败直接 return(-2),成功才 EventSetTimer(1) 挂上 1 秒定时器,最后 return(0) 告诉终端启动正常。 去初始化 OnDeinit 只做两件事:ExtDialog.Destroy() 释放对话框资源,EventKillTimer() 把定时器杀掉,避免退出后还空转占线程。 OnCalculate 每次报价进来会调 ExtDialog.SetRed(MathRand()%256) 随机改红色分量,并返回 rates_total 让下一根 K 线继续走;OnTimer 每秒触发一次改绿色分量。图表交互(点击、拖拽)走 OnChartEvent,原样转发给 ExtDialog.ChartEvent 处理。 开 MT5 建个空指标把这段贴进去,编译后能看到面板颜色每秒和每 tick 乱跳,就说明事件链路通了。外汇和贵金属行情跳动快,这类 GUI 刷新逻辑在实盘可能拖累主线程,建议先在模拟盘验证。

MQL5 / C++
  if(!ExtDialog.Run())
        class="kw">return(-class="num">2);
class=class="str">"cmt">//--- 创建定时器
  EventSetTimer(class="num">1);
class=class="str">"cmt">//--- 成功
  class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 去初始化                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- 销毁对话框
  ExtDialog.Destroy();
class=class="str">"cmt">//--- 杀掉定时器
  EventKillTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 迭代                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">int begin,
                const class="type">class="kw">double &price[])
  {
class=class="str">"cmt">//--- 改变对话框属性
  ExtDialog.SetRed(MathRand()%class="num">256);
class=class="str">"cmt">//--- 返回 prev_calculated 数值用于下次调用
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 定时器事件处理函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- 改变对话框属性
  ExtDialog.SetGreen(MathRand()%class="num">256);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 图表事件处理函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- 处理事件
  ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
class=class="str">"cmt">//+------------------------------------------------------------------

「对话框事件只抓尺寸与蓝阶两处」

MT5 自定义面板的控件交互靠事件机制跑,但没必要全接。内部事件里只有对话框尺寸变化值得处理,外部事件更窄,只盯“蓝色”色阶被改这一项。 内部尺寸处理直接重载父类 OnResize()。先调 CAppDialog::OnResize() 保底,再用 ClientAreaWidth() 减去右侧各项固定间距(INDENT_RIGHT+LABEL_WIDTH+CONTROLS_GAP_X+EDIT_WIDTH+CONTROLS_GAP_X+INDENT_LEFT)算出色板新宽,赋给 m_color.Width()。高度不变时逻辑极简,宽度一动才重排色板。 外部事件处理程序要求是空类型无参类方法,比如 OnChangeBlue():把 m_edit_blue.Value() 读进 m_blue,再调 SetColor() 刷新面板。想让面板接外部事件,必须重载 OnEvent(...),但打开 PanelDialog.mqh 你会发现有声明无主体——真相在标准库 <Controls\Defines.mqh> 的宏里。 EVENT_MAP_BEGIN/END 那段伪代码实际只干一件事:m_edit_blue 收到 ON_CHANGE 就调 OnChangeBlue 并返回 true,其余事件甩给父类。开 MT5 载一段带色板的 PanelDialog 改蓝色滑块,断点打在 OnChangeBlue 里就能验证这条链路。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 调整处理函数                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">bool CPanelDialog::OnResize(class="type">void)
  {
class=class="str">"cmt">//--- 调用父类函数
   if(!CAppDialog::OnResize()) class="kw">return(false);
class=class="str">"cmt">//--- 改变颜色面板宽度
   m_color.Width(ClientAreaWidth()-(INDENT_RIGHT+LABEL_WIDTH+CONTROLS_GAP_X+EDIT_WIDTH+CONTROLS_GAP_X+INDENT_LEFT));
class=class="str">"cmt">//--- 成功
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 改变“蓝”水平事件的处理函数                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void CPanelDialog::OnChangeBlue(class="type">void)
  {
class=class="str">"cmt">//--- 保存
   m_blue=m_edit_blue.Value();
class=class="str">"cmt">//--- 设置面板颜色
   SetColor();
  }
class="kw">virtual class="type">bool  OnEvent(const class="type">int id,const class="type">long &lparam,
                       const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam);
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 处理事件                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------
EVENT_MAP_BEGIN(CPanelDialog)
   ON_EVENT(ON_CHANGE,m_edit_blue,OnChangeBlue)
EVENT_MAP_END(CAppDialog)

◍ 画得少,看得清

用标准库类搭显示面板,核心是把控件逻辑和窗口绑定拆开写,而不是指望一个指标文件包揽主图与子图。官方自带更复杂的参考在 Experts\Examples\Controls\ 与 Indicators\Examples\Panels\ 下,比如 ChartPanel 和 SimplePanel,直接开 MT5 终端翻这两目录比空读文档更快。 有交易者实测卡在 #property indicator_separate_window:一旦声明,面板永远掉进子窗口,主图贴不回去。平台限制是一个指标只能驻主窗或子窗,绕法是建两个指标,用 ChartIndicatorAdd 让一个拉起另一个,靠事件或缓冲区传数据。 附带的 paneldialog.mqh(24.01 KB)和 panelindicator.mq5(6.38 KB)是可跑的底子。先抄这两个文件改一处:把子窗口号抽成变量,ObjectCreate 时传进去,你就能在任意窗格挂面板。外汇与贵金属波动剧烈,这类自定义面板只降低盯盘成本,不预示任何涨跌。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
把面板状态监控交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到控件触发的状态变化,你专注策略逻辑而非反复手验。

常见问题

位于 Controls\ComboBox.mqh,它基于编辑、带图像按钮和列表控件组合实现下拉选择。
目前小布内置的是通用诊断视图,自定义面板需你在终端跑起来后对照查看,小布负责把重复劳动交给小布,你专注决策。
显示面板偏向只读或轻交互展示,EA控制面板常需回写参数并触发交易事件,两者都基于嵌入式图表对象但事件订阅范围不同。
增量减量字段只改了内部变量,需在事件处理里显式调用重绘或颜色更新方法,否则视觉不会自动同步。