利用MQL5创建您自己的图形面板·综合运用
(3/3)· 前面铺垫了类库与基础控件,这一篇把面板拼装、参数改写和事件响应串成可运行的完整链路
◍ 用具名常量接管对话框布局
做 MT5 自定义对话框时,控件摆位和留白若直接写死数值,后期改版会非常痛苦。把缩进、间距、控件尺寸和颜色范围提为 #define 常量,是性价比最高的起点。 具名常量的实际好处有两点:一是不用记住四边缩进到底是 11 还是 12,敲名字时编辑器自动列表就能补全;二是哪天想把整体边距从 11 调到 14,只改常量定义一处即可,不必全局替换散落的数值。 下面这组常量可直接拷进 EA 或脚本的头部。左、顶、右、底缩进统一设为 11(含边框宽度),X/Y 轴控件间距为 10,标签宽 50,编辑框宽 50、高 20,颜色元件范围 0–255。 别把正态当圣经 常量值不是金科玉律。11 和 10 只是某套界面基准,高 DPI 屏上可能显得拥挤,开 MT5 后按自己分辨率改 INDENT 与 GAP 两组的数值验证手感。外汇与贵金属插件开发同样涉及实盘高风险,界面只是辅助,别本末倒置。
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 属高风险操作,参数没对齐可能界面错位。
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 字段再继续。
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 方法挂上去,就等于把随机源换成了可人工拨动的旋钮。外汇与贵金属图表加载此类自定义指标存在脚本异常风险,建议先在模拟环境验证。
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 刷新逻辑在实盘可能拖累主线程,建议先在模拟盘验证。
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 里就能验证这条链路。
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 时传进去,你就能在任意窗格挂面板。外汇与贵金属波动剧烈,这类自定义面板只降低盯盘成本,不预示任何涨跌。
class="macro">#class="kw">property indicator_separate_window