利用MQL5创建您自己的图形面板·进阶篇
🖥️

利用MQL5创建您自己的图形面板·进阶篇

(2/3)· 还在用Print塞日志看状态?标准库自带按钮、列表、复选框,子窗口面板半天就能跑起来

含代码示例 第 2/3 篇
很多人写EA只在图表上画线或狂刷Print,真要调参数还得翻代码改输入变量。把交互留在终端外,等于每次微调都重编译。用错控件基类的,面板事件根本收不到鼠标消息。

「用向导起一个带对话框的空指标壳」

在 MT5 里做带 UI 的自定义指标,最快的路子不是手敲框架,而是走 MQL5 向导。新建时选 Custom Indicator,连续填三个对话框:第一个给指标命名,第二个务必勾 OnChartEvent 和 OnTimer,第三个勾 Separate Window Indicator(独立窗口),点 Finish 就能拿到一套编译即可用的空模板。 向导生成的代码里,#property indicator_separate_window 已经写好,意味着指标会强制落在图表下方的独立子窗口。OnInit 返回 0、OnCalculate 直接 return(rates_total),此时拖到图上只会看到一个空对话框,没有任何画线或计算逻辑。 别小看这个空壳,它已经自带三个实用特性:子窗口高度在创建期被自动调成对话框高度,对话框宽度始终等于图表宽度,而且该子窗口能被用户最小化和最大化。外汇和贵金属波动剧烈、杠杆高风险大,先在独立窗口把面板跑稳,再往里塞信号才不容易乱。 下面这段是向导直出的骨架,注意 OnTimer 和 OnChartEvent 函数体都是空的,后面要自己补对话框创建与事件转发:

MQL5 / C++
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=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 自定义指标初始化函数                                          |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 指标缓冲映射

class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
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">//---

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">//---

  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| ChartEvent函数                                                |
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">//---

  }

◍ 用对话框类给指标挂独立窗口面板

想在 MT5 指标里塞一个可交互的控制面板,而不是只画几条线?标准做法是把界面逻辑拆进一个对话框类,指标主体只负责创建和销毁。 下面这段源码把指标声明成独立子窗口(indicator_separate_window),但 plots 和 buffers 都设为 0——它根本不画曲线,只靠 CPanelDialog 撑起一个 130 像素宽的悬浮框。 初始化时先 ExtDialog.Create(0,"Panel Indicator",0,0,0,0,130) 拉出对话框,失败直接返回 -1;接着 Run() 启动消息循环,再 EventSetTimer(1) 挂 1 秒定时器驱动刷新。去初始化里 Destroy() 清面板、EventKillTimer() 撤定时器,避免残留句柄。 外汇与贵金属市场波动剧烈、杠杆风险高,这类面板仅作界面容器,不含任何交易信号;实盘前请在策略测试器里先跑通创建与销毁流程。

MQL5 / C++
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">//--- 开始应用
   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">int OnDeinit()
  {
class=class="str">"cmt">//--- 销毁对话框
   ExtDialog.Destroy();
class=class="str">"cmt">//--- 杀掉定时器
   EventKillTimer();
  }

指标骨架里的三个事件入口

写 MT5 自定义指标时,OnCalculate 是最先被终端调用的主循环函数。上面这段骨架里它直接 return(rates_total),等于告诉系统「本次处理的 K 线总数就是下次重算的基准」,prev_calculated 形同虚设,适合做纯静态面板类指标,不适合需要增量计算的逻辑。 OnTimer 和 OnChartEvent 在这里都是空壳。前者靠 EventSetTimer 触发,没设定时器时永远不会进;后者把图表交互(点击、拖拽、键盘)转交给 ExtDialog.ChartEvent,说明这个指标挂了一个 GUI 对话框对象,事件不自己处理而是下沉给类成员。 开 MT5 新建指标把这段代码贴进 .mq5,编译后拖到 EURUSD 图表,大概率看不到任何重绘——因为计算函数没写 Buffer 赋值。外汇与贵金属杠杆高,这类半成品只验证结构用,实盘前必须补完数据写入。

MQL5 / C++
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">//--- 返回 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">//---
  }
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">//+------------------------------------------------------------------

「把三通道色阶搬进面板控件」

做面板显示前先想清楚三件事:要呈现什么数据、对话框上放哪些控件、这些控件之间怎么联动。视觉美观虽不影响功能,但能降低使用者的认知负担,顺手把标准库控件include进来就行。 本例用 RGB 三个色阶当教学载体,每个分量范围都是 0–255。红色随每次 OnCalculate 事件随机变,绿色随每次定时器事件随机变,蓝色留给手动微调——这种分工能直观看出不同事件驱动机制的差异。 控件布局上,颜色块用 CPanel 画;红绿数值走只读 CEdit,蓝数值用 CSpinEdit 管理,每个字段前都挂一个 CLabel 写说明文字。下面这段类声明直接定义了全部私有成员,复制进 PanelDialog.mqh 就能编译。 别把随机当信号 红绿通道的随机赋值只是演示事件触发,没有任何市场含义;外汇和贵金属波动高风险,拿这类面板练手时别误读成行情特征。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                 PanelDialog.mqh |
class=class="str">"cmt">//|                          Copyright class="num">2011, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                             [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------
class="macro">#include <Controls\Dialog.mqh>
class="macro">#include <Controls\Panel.mqh>
class="macro">#include <Controls\Edit.mqh>
class="macro">#include <Controls\Label.mqh>
class="macro">#include <Controls\SpinEdit.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| CPanelDialog类                                                  |
class=class="str">"cmt">//| 函数:主要应用程序对话框                                         |
class=class="str">"cmt">//+------------------------------------------------------------------
class CPanelDialog : class="kw">public CAppDialog
  {
class="kw">private:
   class=class="str">"cmt">//--- 附加控件
   CPanel            m_color;                 class=class="str">"cmt">// 显示颜色对象
   CLabel            m_label_red;             class=class="str">"cmt">// "红" 级别标题对象
   CEdit             m_field_red;             class=class="str">"cmt">// "红" 数值显示对象
   CLabel            m_label_green;           class=class="str">"cmt">// "绿" 级别标题对象
   CEdit             m_field_green;           class=class="str">"cmt">// "绿"数值显示对象
   CLabel            m_label_blue;            class=class="str">"cmt">// "蓝" 级别标题对象
   CSpinEdit         m_edit_blue;             class=class="str">"cmt">// "蓝" 数值控件
   class=class="str">"cmt">//--- 参数值
   class="type">int               m_red;                   class=class="str">"cmt">// "红" 值
   class="type">int               m_green;                 class=class="str">"cmt">// "绿" 值
   class="type">int               m_blue;                  class=class="str">"cmt">// "蓝" 值
class="kw">public:
                     CPanelDialog(class="type">void);

◍ 面板对话框的空构造与析构

在 MT5 自定义面板开发中,CPanelDialog 作为控制面板的基类,其构造与析构函数常被留空以延迟资源绑定。 下面这段继承自 CAppDialog 的简化定义,给出了最薄骨架:声明虚析构保证派生类释放安全,构造函数不带参数。 实际项目里,你大概率会在 OnCreate 或后续方法里才挂按钮、标签;直接放构造里反而难调试。开 MT5 新建 EA 面板时,先抄这个空壳编译通过,再逐步塞控件最稳。

MQL5 / C++
~CPanelDialog(class="type">void);
};
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| Constructor                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------
CPanelDialog::CPanelDialog(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| Destructor                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------
CPanelDialog::~CPanelDialog(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------

控件联动先定死一条规矩

做 MT5 自定义对话框时,先把交互底线卡住:红、绿、蓝三个色阶参数里任意一个被改,对话框都必须把这次变动实时反映出来,不能只刷某一个控件。 这条原则看着像废话,但落到 MQL5 面板开发里,很多人会把 OnEvent 只绑到某个 Edit 或 ColorButton 上,结果另两个色阶改了界面不跟手。 具体怎么在 CAppDialog 派生类里统一派发变更事件,留到真正建对话框那一步再拆,现在只要记住这条“全参数可见”的约束就行。

把面板状态监控交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到自定义面板里的数值与颜色告警,你只管决策要不要改参数。

常见问题

Panel基于矩形标签做可视分组,不带模态阻塞;Dialog是完整表单,会拦截底层图表交互。纯显示面板用Panel更轻。
事件驱动模型要求把控件挂到图表子窗口并正确转发OnChartEvent,漏掉ChartSetInteger的可见性或事件绑定就会失灵。
目前小布盯盘内置的是通用品种页诊断,自定义面板数值需你导出或对照看;后续若开放本地指标桥接,可自动映射颜色与文本状态。
SpinEdit适合连续步进的枚举值,占用窄;ComboBox适合选项多、空间富余时下拉选,避免面板被撑爆。
纯对象绘制不占计算缓冲,事件回调也轻;但若每帧重绘大图或超量标签,可能拖慢老旧终端的响应。