如何创建任意复杂度的图形面板(基础篇)
「在 MT5 里搭出任意复杂度的图形面板」
MT5 的 GUI 不靠拖控件,而是用 CCanvas 类在图表上直接画。想做多按钮、多标签、带图表的面板,核心是把绘制逻辑和事件响应拆开:先画静态层,再处理鼠标命中区域。 官方示例工程在 2018-05-03 发布,至今累计阅读 10 628、评论 142,说明这套原生画法仍是多数自定义面板的基础方案。 实际动手时,先在 OnInit 里实例化 CCanvas 并绑定图表窗口,用 Resize 定画布尺寸;之后所有按钮坐标都按相对画布的百分比存,避免不同分辨率下错位。 高风险提示:外汇与贵金属杠杆交易可能迅速亏损本金,面板只是辅助观察工具,不构成任何方向建议。
先看清 CAppDialog 面板的骨架
在 MT5 里用 CAppDialog 搭自定义面板前,得先认全它的目录结构:从 AppWindow 的能力边界、CAppDialog 对象与继承关系,到主要常数去哪找、如何用 #undef 重定义,都是绕不开的入口。 实际开发中,面板往往要塞按钮、嵌套控件,并借助 CDialog 把 CAppDialog 纳入控件组统一管理。标准控件行为可通过重写改变,事件处理的内建宏定义也能直接读出来改。 这套结构理清后,增加一个带两个按钮的简易面板并不复杂,但涉及 EA 自动下单的外汇/贵金属操作属高风险,参数和事件逻辑建议在策略测试器里先跑通再上实盘。
◍ 为什么多数 MT5 开发者绕开图形界面
即便在 MT5 已普及的当下,多数指标与 EA 开发者仍不愿在程序里接入图形界面。核心障碍不在语法,而在标准库的面板与对话框类只给了方法的技术描述,语言参考里的示例注释繁杂,没摸清对象结构和设计思路前,很难直接动手写自己的面板。 我实际拆解过面板的处理机制,结论很直接:从 CAppDialog 派生一个最简应用,再逐步往里加按钮等控件,比硬读文档更快建立认知。本文聚焦 CAppDialog 的运行细节——最少要覆写哪几个函数、对象按什么顺序创建、用到的常量怎么改。 外汇与贵金属交易工具自带高杠杆风险,图形面板只是降低操作摩擦的手段,不改变策略本身的概率属性。开 MT5 新建一个继承 CAppDialog 的空类,先跑通窗口显示,再验证下文逐行代码。
「用 CAppDialog 搭一个最薄面板」
CAppDialog 是 MQL5 标准库里把功能相关控件归拢到一个应用程序对话框里的类,用它能在图表上快速拉出一块可视区域,把后续按钮、输入框等元件挂上去。 下面这段 EA 是能跑通的最小骨架:全局声明一个 CAppDialog 实例,OnInit 里 Create 出窗口并 Run,OnDeinit 里 Destroy 回收,OnChartEvent 把图表事件透传给面板。Create 的参数里 (0,"AppWindow",0,20,20,360,324) 表示绑定主图、左上角偏移 20 像素、宽 360 高 324。 漏掉 Destroy 这一步会出实打实的问题:每次切换周期或品种,旧元件不销毁,新元件会叠在原有之上,面板越堆越乱。外汇与贵金属交易本身高风险,这类界面 bug 虽不直接影响报价,但会干扰你盯盘和手动下单的判断。 把代码贴进 MT5 新建的 EA,编译挂到 EURUSD 图表,就能看到左上角浮出 360×324 的空面板;之后删 EA 前先确认 OnDeinit 有 Destroy,避免重挂时元件重叠。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| LearnCAppDialog.mq5 | 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">#include <Controls\Dialog.mqh> CAppDialog AppWindow; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA交易初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建应用程序对话框 if(!AppWindow.Create(class="num">0,"AppWindow",class="num">0,class="num">20,class="num">20,class="num">360,class="num">324)) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 运行应用程序 AppWindow.Run(); class=class="str">"cmt">//--- 成功 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 交易终止函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- 销毁对话框 AppWindow.Destroy(reason); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 交易图表事件函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, class=class="str">"cmt">// 事件 ID const class="type">long& lparam, class=class="str">"cmt">// 长整数类型的事件参数 const class="type">class="kw">double& dparam, class=class="str">"cmt">// 双精度小数类型的事件参数 const class="type">class="kw">string& sparam) class=class="str">"cmt">// 字符串类型的事件参数 { AppWindow.ChartEvent(id,lparam,dparam,sparam); } class=class="str">"cmt">//+------------------------------------------------------------------+
把EA窗口挂进MT5图表事件
想让自定义对话框跟着MT5图表走,关键不在画界面,而在把窗口对象接进三个生命周期钩子。OnInit里先Create,失败直接返回INIT_FAILED,成功再Run,否则EA初始化会卡在黑屏。 下面这段是最小可跑骨架,坐标和尺寸先写死:左上角偏移(20,20),宽360高324。外汇和贵金属波动剧烈,这种独立窗口常用来脱离主图做轻量盯盘,但任何自行加载的EA都有冻结终端的概率风险,建议先在模拟盘验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建应用程序对话框 if(!AppWindow.Create(class="num">0,"AppWindow",class="num">0,class="num">20,class="num">20,class="num">360,class="num">324)) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 运行应用程序 AppWindow.Run(); class=class="str">"cmt">//--- 成功 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 交易图表事件函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, class=class="str">"cmt">// 事件 ID const class="type">long& lparam, class=class="str">"cmt">// 长整数类型的事件参数 const class="type">class="kw">double& dparam,class=class="str">"cmt">// 双精度小数类型的事件参数 const class="type">class="kw">string& sparam)class=class="str">"cmt">// 字符串类型的事件参数 { AppWindow.ChartEvent(id,lparam,dparam,sparam); } class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- 销毁对话框 AppWindow.Destroy(reason); }
class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建应用程序对话框 if(!AppWindow.Create(class="num">0,"AppWindow",class="num">0,class="num">20,class="num">20,class="num">360,class="num">324)) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 运行应用程序 AppWindow.Run(); class=class="str">"cmt">//--- 成功 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 交易图表事件函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, class=class="str">"cmt">// 事件 ID const class="type">long& lparam, class=class="str">"cmt">// 长整数类型的事件参数 const class="type">class="kw">double& dparam,class=class="str">"cmt">// 双精度小数类型的事件参数 const class="type">class="kw">string& sparam)class=class="str">"cmt">// 字符串类型的事件参数 { AppWindow.ChartEvent(id,lparam,dparam,sparam); } class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- 销毁对话框 AppWindow.Destroy(reason); }
◍ 面板能接住哪些交互事件
基于 CAppDialog 搭出来的面板,理论上能处理的事件都定义在数据目录 MQL5\Include\Controls\Defines.mqh 的 Events 模块里。从单击、双击到编辑起止、焦点切换、拖曳三段(起/中/止),再到显示隐藏,基本覆盖了 GUI 能碰到的用户动作。 具体事件编号从 0 到 14 是常规控件交互,100 留给整个应用关闭。下面这段宏定义就是可直接抄进自己 include 里的清单。 官方示例里,CRadioGroup 演示了 ON_CHANGE 怎么用,CScrollV 则覆盖了 ON_SCROLL_INC 和 ON_SCROLL_DEC。想验证的话,开 MT5 进 Include\Controls 翻 Defines.mqh,对照跑一遍这两个例子最直观。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define ON_CLICK(class="num">0) class=class="str">"cmt">// 点击控件的事件 class="macro">#define ON_DBL_CLICK(class="num">1) class=class="str">"cmt">// 双击控件的事件 class="macro">#define ON_SHOW(class="num">2) class=class="str">"cmt">// 显示控件的事件 class="macro">#define ON_HIDE(class="num">3) class=class="str">"cmt">// 隐藏控件的事件 class="macro">#define ON_CHANGE(class="num">4) class=class="str">"cmt">// 修改控件的事件 class="macro">#define ON_START_EDIT(class="num">5) class=class="str">"cmt">// 开始编辑的事件 class="macro">#define ON_END_EDIT(class="num">6) class=class="str">"cmt">// 编辑结束的事件 class="macro">#define ON_SCROLL_INC(class="num">7) class=class="str">"cmt">// 滚动条增加的事件 class="macro">#define ON_SCROLL_DEC(class="num">8) class=class="str">"cmt">// 滚动条减小的事件 class="macro">#define ON_MOUSE_FOCUS_SET(class="num">9) class=class="str">"cmt">// "鼠标光标进入控件" 的事件 class="macro">#define ON_MOUSE_FOCUS_KILL(class="num">10) class=class="str">"cmt">// "鼠标光标退出控件"的事件 class="macro">#define ON_DRAG_START(class="num">11) class=class="str">"cmt">// "开始拖曳控件"的事件 class="macro">#define ON_DRAG_PROCESS(class="num">12) class=class="str">"cmt">// "正在拖曳控件"的事件 class="macro">#define ON_DRAG_END(class="num">13) class=class="str">"cmt">// "结束拖曳控件"的事件 class="macro">#define ON_BRING_TO_TOP(class="num">14) class=class="str">"cmt">// "鼠标事件优先级提高"的事件 class="macro">#define ON_APP_CLOSE(class="num">100) class=class="str">"cmt">// "关闭应用程序"的事件