利用 MQL5 的交互式 GUI 改进您的交易图表(第 III 部分):简易可移动交易 GUI·进阶篇
(2/3)· 还在把按钮坐标写死在 EA 里?本篇用 .mqh 类把七个元素拆成可复用模块,让仪表板真正能拖着跑
用类封装MT5图表标签的生灭
在 MT5 里做自定义面板,第一步往往是把图表上的文字标签管起来。下面这段代码用 C++ 风格的类把标签的创建和销毁收进同一个对象,避免在 OnInit / OnDeinit 里散落一堆 ObjectCreate 调用。 RectangleLabel TitleBar 与 MainDashboardBody 是两个实例,前者尺寸 200×20 定位在 (100,100),后者 200×100 定位在 (100,119),两者都设了 BORDER_FLAT 平边。初始化末尾调 ChartRedraw(0) 强制重绘,EA 卸载时 OnDeinit 里先 Destroy 主体再 Destroy 标题栏,顺序反过来也可能泄漏对象。 Label 类只留了私有名和几个内联方法:Create 负责建对象,Destroy 直接 ObjectDelete(0,_name) 删掉,SetTextColor / SetText 分别改颜色和文字。注意这里 _name 是私有成员,Create 传进来的 name 必须写进 _name 才能真正删对,原片段没展示赋值,复制时容易漏。 外汇与贵金属波动剧烈,这类 GUI 对象不参与报价,但占用终端资源;EA 频繁重载时若 Destroy 不全,图表上可能残留幽灵标签,建议开 MT5 用脚本跑一遍 OnInit→OnDeinit 看对象树。
RectangleLabel TitleBar; class=class="str">"cmt">// Declaration of a TitleBar object RectangleLabel MainDashboardBody; class=class="str">"cmt">// Declaration of a MainDashboardBody object class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { TitleBar.Create("TitleBar", class="num">100, class="num">100, class="num">200, class="num">20); class=class="str">"cmt">// Creating the TitleBar with specified dimensions TitleBar.SetBorderType(BORDER_FLAT); class=class="str">"cmt">// Setting the border type of TitleBar to be flat MainDashboardBody.Create("MainDashboardBody", class="num">100, class="num">119, class="num">200, class="num">100); class=class="str">"cmt">// Creating the MainDashboardBody with specified dimensions MainDashboardBody.SetBorderType(BORDER_FLAT); class=class="str">"cmt">// Setting the border type of MainDashboardBody to be flat ChartRedraw(class="num">0); class=class="str">"cmt">// Redrawing the chart to reflect changes class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">// Indicating successful initialization } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { MainDashboardBody.Destroy(); class=class="str">"cmt">// Destroying the MainDashboardBody object TitleBar.Destroy(); class=class="str">"cmt">// Destroying the TitleBar object } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Label class definition | class=class="str">"cmt">//+------------------------------------------------------------------+ class Label { class="kw">private: class="type">class="kw">string _name; class=class="str">"cmt">// Name of the label class="kw">public: Label(class="type">void); class=class="str">"cmt">// Constructor ~Label(class="type">void); class=class="str">"cmt">// Destructor class="type">void Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis); class=class="str">"cmt">// Method to create a label class="type">void Destroy() {ObjectDelete(class="num">0, _name);} class=class="str">"cmt">// Method to destroy a label class="type">void SetTextColor(class="type">class="kw">color col) {ObjectSetInteger(class="num">0, _name, OBJPROP_COLOR, col);} class=class="str">"cmt">// Method to set the text class="type">class="kw">color class="type">void SetText(class="type">class="kw">string text) {ObjectSetString(class="num">0, _name, OBJPROP_TEXT, text);} class=class="str">"cmt">// Method to set the text content
◍ 给图表挂上可复用的文字标签类
在 MT5 里做自定义面板,最轻量的起点就是 OBJ_LABEL 对象。下面这段把标签的取文本、字号、字体封装成类方法,调用层不用再反复写 ObjectGetString / ObjectSetInteger。 GetText() 用 ObjectGetString(0, _name, OBJPROP_TEXT) 回读标签内容;SetFontSize(int) 与 SetFont(string) 分别改 OBJPROP_FONTSIZE 和 OBJPROP_FONT。注意构造和析构函数体都是空的,资源释放依赖图表对象自动回收,若脚本重复运行要先 ObjectDelete 同名对象避免堆叠。 Create 方法里 ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0) 的第三参时间轴、第四五参坐标都填 0,因为标签走的是 XDISTANCE / YDISTANCE 像素偏移而非价格坐标——实测在 1920 宽屏上 xDis=100 大约距左缘 5.2% 屏宽。 OnInit 中 TitleBar 与 MainDashboardBody 的 y 坐标差 19(100 与 119),正好留出 20 像素高的标题条下边界;外汇与贵金属图表叠加这类自绘元素时,需留意其可能遮挡右侧报价列,属高风险界面的视觉干扰。
class="type">class="kw">string GetText() {class="kw">return ObjectGetString(class="num">0, _name, OBJPROP_TEXT);} class=class="str">"cmt">// Method to retrieve the text content class="type">void SetFontSize(class="type">int fontSize) {ObjectSetInteger(class="num">0, _name, OBJPROP_FONTSIZE, fontSize);} class=class="str">"cmt">// Method to set the font size class="type">void SetFont(class="type">class="kw">string fontName) {ObjectSetString(class="num">0, _name, OBJPROP_FONT, fontName);} class=class="str">"cmt">// Method to set the font name class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ Label::Label(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ Label::~Label(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method to create a label object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Label::Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis) { class=class="str">"cmt">// Code to create label object, set its position, and assign its name ObjectCreate(class="num">0, name, OBJ_LABEL, class="num">0, class="num">0, class="num">0); ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, xDis); ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, yDis); _name = name; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "RectangleLabel.mqh" class=class="str">"cmt">// Including the RectangleLabel class definition RectangleLabel TitleBar; class=class="str">"cmt">// Declaration of a TitleBar object RectangleLabel MainDashboardBody; class=class="str">"cmt">// Declaration of a MainDashboardBody object class="macro">#include "Label.mqh" class=class="str">"cmt">// Including the Label class definition Label TitleText; class=class="str">"cmt">// Declaration of a Label object class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { TitleBar.Create("TitleBar", class="num">100, class="num">100, class="num">200, class="num">20); class=class="str">"cmt">// Creating the TitleBar with specified dimensions TitleBar.SetBorderType(BORDER_FLAT); class=class="str">"cmt">// Setting the border type of TitleBar to be flat MainDashboardBody.Create("MainDashboardBody", class="num">100, class="num">119, class="num">200, class="num">100); class=class="str">"cmt">// Creating the MainDashboardBody with specified dimensions
「EA面板对象的创建与销毁节奏」
在 MT5 的 EA 初始化函数里,先通过 RectangleLabel 和 Label 的 Create 方法把面板控件实例化,坐标和尺寸直接写死即可。上面这段把 TitleBar 放在 (100,100) 宽200高20,MainDashboardBody 放在 (100,119) 宽200高100,两个都设了 BORDER_FLAT,视觉上就是平边矩形不凸出。 TitleText 在 (110,101) 写「Simple Trading EA V1.0」,字号10、黑色;LotSizeText 在 (110,140) 写「Lot Size:」,字号12、黑色。注意 LotSizeText 的坐标 y=140 落进了 MainDashboardBody 的区域内(119~219),说明文字层是叠在底板之上的,实际跑起来不会遮挡。 初始化末尾调 ChartRedraw(0) 强制重绘,返回 INIT_SUCCEEDED 才算挂 EA 成功。OnDeinit 里三个 Destroy 必须一一对应,漏掉任何一个都可能让图表残留旧控件。 开 MT5 把这段塞进 OnInit / OnDeinit 验证:若改了 TitleBar 的 y 从100到90,标题栏会和主面板重叠 29 像素,界面会显脏,调坐标时按 100/119 这种 19 像素落差留白更稳。
MainDashboardBody.SetBorderType(BORDER_FLAT); class=class="str">"cmt">// 设置主面板边框为平边 TitleText.Create("TitleText", class="num">110, class="num">101); class=class="str">"cmt">// 在(class="num">110,class="num">101)创建标题文字 TitleText.SetText("Simple Trading EA V1.class="num">0"); class=class="str">"cmt">// 文字内容 TitleText.SetFontSize(class="num">10); class=class="str">"cmt">// 字号10 TitleText.SetTextColor(clrBlack); class=class="str">"cmt">// 黑色 ChartRedraw(class="num">0); class=class="str">"cmt">// 重绘图表 class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">// 初始化成功 class="type">void OnDeinit(const class="type">int reason) { MainDashboardBody.Destroy(); class=class="str">"cmt">// 销毁主面板 TitleBar.Destroy(); class=class="str">"cmt">// 销毁标题栏 TitleText.Destroy(); class=class="str">"cmt">// 销毁标题文字 } class="macro">#include "RectangleLabel.mqh" RectangleLabel TitleBar; RectangleLabel MainDashboardBody; class="macro">#include "Label.mqh" Label TitleText; Label LotSizeText; class="type">int OnInit() { TitleBar.Create("TitleBar", class="num">100, class="num">100, class="num">200, class="num">20); class=class="str">"cmt">// 标题栏x100 y100 宽200高20 TitleBar.SetBorderType(BORDER_FLAT); MainDashboardBody.Create("MainDashboardBody", class="num">100, class="num">119, class="num">200, class="num">100); class=class="str">"cmt">// 主面板x100 y119 宽200高100 MainDashboardBody.SetBorderType(BORDER_FLAT); TitleText.Create("TitleText", class="num">110, class="num">101); TitleText.SetText("Simple Trading EA V1.class="num">0"); TitleText.SetFontSize(class="num">10); TitleText.SetTextColor(clrBlack); LotSizeText.Create("LotSizeText", class="num">110, class="num">140); class=class="str">"cmt">// 手数字段x110 y140 LotSizeText.SetText("Lot Size:"); LotSizeText.SetFontSize(class="num">12); LotSizeText.SetTextColor(clrBlack);
EA 退出时如何干净销毁图形对象
在 MT5 的 EA 生命周期里,OnDeinit 是释放资源的最后一道关口。上面这段代码在退出时依次调用 MainDashboardBody、TitleBar、TitleText、LotSizeText 的 Destroy 方法,把创建过的 OBJ_LABEL 类对象逐个从图表上删掉,避免重加载时残留旧标签。 ChartRedraw(0) 放在初始化收尾,是为了让新建的面板立即重绘出来;return(INIT_SUCCEEDED) 告诉终端初始化没问题。若跳过 OnDeinit 的销毁,切换周期时图表上可能叠出两套同名对象。 下面定义的 Edit 类把输入框封装成了可复用的控件:Create 按 xDis/yDis/xSize/ySize 定位,Destroy 直接 ObjectDelete(0,_name) 删对象。SetBorderColor、SetBGColor、SetTextColor 分别改边框、背景、文字色,全部走 ObjectSetInteger;SetText 与 GetText 用 ObjectSetString / ObjectGetString 读写 OBJPROP_TEXT。 实盘加载这类面板 EA 前,先在策略测试器用 0.1 手回测一遍 EURUSD 的 M15,确认 OnDeinit 被调用后对象数归零,外汇保证金交易杠杆高,误删或残留都可能干扰下单逻辑。
ChartRedraw(class="num">0); class=class="str">"cmt">// Redrawing the chart to reflect changes class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">// Indicating successful initialization } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { MainDashboardBody.Destroy(); class=class="str">"cmt">// Destroying the MainDashboardBody object TitleBar.Destroy(); class=class="str">"cmt">// Destroying the TitleBar object TitleText.Destroy(); class=class="str">"cmt">// Destroying the TitleText object LotSizeText.Destroy(); class=class="str">"cmt">// Destroying the LotSizeText object } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Edit class definition | class=class="str">"cmt">//+------------------------------------------------------------------+ class Edit { class="kw">private: class="type">class="kw">string _name; class=class="str">"cmt">// Name of the edit control class="kw">public: Edit(class="type">void); class=class="str">"cmt">// Constructor ~Edit(class="type">void); class=class="str">"cmt">// Destructor class="type">void Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize); class=class="str">"cmt">// Method to create an edit control class="type">void Destroy() {ObjectDelete(class="num">0, _name);} class=class="str">"cmt">// Method to destroy an edit control class="type">void SetBorderColor(class="type">class="kw">color col) {ObjectSetInteger(class="num">0, _name, OBJPROP_BORDER_COLOR, col);} class=class="str">"cmt">// Method to set the border class="type">class="kw">color class="type">void SetBGColor(class="type">class="kw">color col) {ObjectSetInteger(class="num">0, _name, OBJPROP_BGCOLOR, col);} class=class="str">"cmt">// Method to set the background class="type">class="kw">color class="type">void SetTextColor(class="type">class="kw">color col) {ObjectSetInteger(class="num">0, _name, OBJPROP_COLOR, col);} class=class="str">"cmt">// Method to set the text class="type">class="kw">color class="type">void SetText(class="type">class="kw">string text) {ObjectSetString(class="num">0, _name, OBJPROP_TEXT, text);} class=class="str">"cmt">// Method to set the text content class="type">class="kw">string GetText() {class="kw">return ObjectGetString(class="num">0, _name, OBJPROP_TEXT);} class=class="str">"cmt">// Method to retrieve the text content }; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 在 MT5 图表上搭一个输入框控件
写一个可复用的编辑框类,第一步是把构造和析构留空,真正初始化交给 Create 方法。下面这段 MQL5 代码演示了 Edit 类如何用 ObjectCreate 在图表上生成一个 OBJ_EDIT 控件,并设定相对坐标与尺寸。 空构造与析构只是占位,避免对象生成时做多余动作;Create 里先以图表子窗口 0、时间 0、价格 0 创建对象,再用 ObjectSetInteger 把 XDISTANCE/YDISTANCE 和 XSIZE/YSIZE 逐个写入,最后把 name 存进成员变量 _name 供后续调用。 实际面板里,LotSize 编辑框往往和 RectangleLabel 做的标题栏、Label 做的文字并排。OnInit 中 TitleBar 放在 (100,100) 尺寸 200×20,MainDashboardBody 紧随其后在 (100,119) 尺寸 200×100,两者都用 BORDER_FLAT;标题文字字号设 10、内容写「Simple Trading EA V1.0」。打开 MT5 按这套坐标跑一遍,能直观看到控件层叠关系。 外汇与贵金属图表上叠加自绘 UI 属于高风险操作环境,控件坐标若和 K 线区域重叠会挡视线,参数需按自己屏幕 DPI 微调。
class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ Edit::Edit(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ Edit::~Edit(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method to create an edit control object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Edit::Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize) { class=class="str">"cmt">// Code to create edit control object, set its position, size, and assign its name ObjectCreate(class="num">0, name, OBJ_EDIT, class="num">0, class="num">0, class="num">0); ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, xDis); ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, yDis); ObjectSetInteger(class="num">0, name, OBJPROP_XSIZE, xSize); ObjectSetInteger(class="num">0, name, OBJPROP_YSIZE, ySize); _name = name; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "RectangleLabel.mqh" class=class="str">"cmt">// Including the RectangleLabel class definition RectangleLabel TitleBar; class=class="str">"cmt">// Declaration of a TitleBar object RectangleLabel MainDashboardBody; class=class="str">"cmt">// Declaration of a MainDashboardBody object class="macro">#include "Label.mqh" class=class="str">"cmt">// Including the Label class definition Label TitleText; class=class="str">"cmt">// Declaration of a Label object Label LotSizeText; class=class="str">"cmt">// Declaration of a LotSizeText object class="macro">#include "Edit.mqh" class=class="str">"cmt">// Including the Edit class definition Edit LotSize; class=class="str">"cmt">// Declaration of a LotSize object class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { TitleBar.Create("TitleBar", class="num">100, class="num">100, class="num">200, class="num">20); class=class="str">"cmt">// Creating the TitleBar with specified dimensions TitleBar.SetBorderType(BORDER_FLAT); class=class="str">"cmt">// Setting the border type of TitleBar to be flat MainDashboardBody.Create("MainDashboardBody", class="num">100, class="num">119, class="num">200, class="num">100); class=class="str">"cmt">// Creating the MainDashboardBody with specified dimensions MainDashboardBody.SetBorderType(BORDER_FLAT); class=class="str">"cmt">// Setting the border type of MainDashboardBody to be flat TitleText.Create("TitleText", class="num">110, class="num">101); class=class="str">"cmt">// Creating the TitleText at(class="num">110,class="num">101) TitleText.SetText("Simple Trading EA V1.class="num">0"); class=class="str">"cmt">// Setting its text to "Simple Trading EA V1.class="num">0" TitleText.SetFontSize(class="num">10); class=class="str">"cmt">// Setting its font size to class="num">10
「面板对象的销毁与按钮类收尾」
初始化收尾阶段把标题文字、手数标签和输入框都摆到了固定坐标:LotSizeText 在 (110,140),LotSize 编辑框在 (220,140) 且宽 50 高 20,默认填 0.01、底色 clrWhiteSmoke。最后调 ChartRedraw(0) 强制重绘,返回 INIT_SUCCEEDED 才算 EA 加载成功。 退出时 OnDeinit 里挨个 Destroy 掉 MainDashboardBody、TitleBar、TitleText、LotSizeText、LotSize 五个对象,不清理的话切换周期可能留下残影控件。 后面跟了一个极简 Button 类,私有成员只有 _name 字符串,公有 Create 接收 name、xDis、yDis、xSize、ySize 五个参数去建对象;SetBorderColor 直接用 ObjectSetInteger 改 OBJPROP_BORDER_COLOR。这类封装让你在 MT5 里批量生成按钮时少写重复代码,复制去改坐标就能跑。
TitleText.SetTextColor(clrBlack); class=class="str">"cmt">// Setting its text class="type">class="kw">color to clrBlack LotSizeText.Create("LotSizeText", class="num">110, class="num">140); class=class="str">"cmt">// Creating the LotSizeText at(class="num">110,class="num">140) LotSizeText.SetText("Lot Size:"); class=class="str">"cmt">// Setting its text to "Lot Size:" LotSizeText.SetFontSize(class="num">12); class=class="str">"cmt">// Setting its font size to class="num">12 LotSizeText.SetTextColor(clrBlack); class=class="str">"cmt">// Setting its text class="type">class="kw">color to clrBlack LotSize.Create("LotSize", class="num">220, class="num">140, class="num">50, class="num">20); class=class="str">"cmt">// Creating the LotSize with specified dimensions LotSize.SetBorderColor(clrBlack); class=class="str">"cmt">// Setting its Border Color to clrBlack LotSize.SetBGColor(clrWhiteSmoke); class=class="str">"cmt">// Setting its BG Color to clrWhiteSmoke LotSize.SetText("class="num">0.01"); class=class="str">"cmt">// Setting its text to class="num">0.01 LotSize.SetTextColor(clrBlack); class=class="str">"cmt">// Setting its text class="type">class="kw">color to clrBlack ChartRedraw(class="num">0); class=class="str">"cmt">// Redrawing the chart to reflect changes class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">// Indicating successful initialization } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { MainDashboardBody.Destroy(); class=class="str">"cmt">// Destroying the MainDashboardBody object TitleBar.Destroy(); class=class="str">"cmt">// Destroying the TitleBar object TitleText.Destroy(); class=class="str">"cmt">// Destroying the TitleText object LotSizeText.Destroy(); class=class="str">"cmt">// Destroying the LotSizeText object LotSize.Destroy(); class=class="str">"cmt">// Destroying the LotSize object } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Button class definition | class=class="str">"cmt">//+------------------------------------------------------------------+ class Button { class="kw">private: class="type">class="kw">string _name; class=class="str">"cmt">// Name of the button control class="kw">public: Button(class="type">void); class=class="str">"cmt">// Constructor ~Button(class="type">void); class=class="str">"cmt">// Destructor class="type">void Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize); class=class="str">"cmt">// Method to create a button control class="type">void SetBorderColor(class="type">class="kw">color col) {ObjectSetInteger(class="num">0, _name, OBJPROP_BORDER_COLOR, col);} class=class="str">"cmt">// Method to set the border class="type">class="kw">color