一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)·进阶篇
(2/3)· 不懂 MQL5 也能拖出市价单与挂单界面,这篇只讲清楚对象坐标与背景层逻辑
从模板文件批量还原 GUI 对象
这段逻辑干的事很直接:解析一个图表模板文件,把里面声明的控件按类型在 MT5 上重建出来。外汇与贵金属图表挂 EA 时高频用到这类自绘面板,属于高风险环境下的界面层操作,任何对象创建失败都应立刻中断并返回 false。 外层 for 先把 eRESULT 到 eEDIT_STOP 区间内的对象名清空,m_SubWindow 记录子窗口号,随后 while 循环逐行读文件,直到碰到的行是 </chart> 才停。 遇到 <object> 标记就进入对象解析分支,再读一行若等于 type,则根据 m_szValue 的数值分派:102 建 OBJ_LABEL、103 建 OBJ_BUTTON、106 建 OBJ_BITMAP_LABEL、107 建 OBJ_EDIT、110 建 OBJ_RECTANGLE_LABEL,任一个 LoopCreating 返回 false 就整体退出。 FileReadLine 自己管底层读取:用 SHORT_VALUE 按 UTF-16 读字,遇到 0x000D(回车)吞掉后续并结束本行;遇到 0x003D(等号)翻转 b0 标志,用来区分属性名与属性值。循环上限设为 500,意味着单行超 500 个双字节就可能截断,调模板时注意这一点。
for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = ""; m_SubWindow = nSub; m_szLine = ""; class="kw">while (m_szLine != "</chart>") { if (!FileReadLine()) class="kw">return false; if (m_szLine == "<object>") { if (!FileReadLine()) class="kw">return false; if (m_szLine == "type") { if (m_szValue == "class="num">102") if (!LoopCreating(OBJ_LABEL)) class="kw">return false; if (m_szValue == "class="num">103") if (!LoopCreating(OBJ_BUTTON)) class="kw">return false; if (m_szValue == "class="num">106") if (!LoopCreating(OBJ_BITMAP_LABEL)) class="kw">return false; if (m_szValue == "class="num">107") if (!LoopCreating(OBJ_EDIT)) class="kw">return false; if (m_szValue == "class="num">110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) class="kw">return false; } } } FileClose(m_fp); class="kw">return true; } class="type">bool FileReadLine(class="type">void) { class="type">int utf_16 = class="num">0; class="type">bool b0 = false; m_szLine = m_szValue = ""; for (class="type">int c0 = class="num">0; c0 < class="num">500; c0++) { utf_16 = FileReadInteger(m_fp, SHORT_VALUE); if (utf_16 == 0x000D) { FileReadInteger(m_fp, SHORT_VALUE); class="kw">return true; } else if (utf_16 == 0x003D) b0 = true; else
◍ 图表交互控件的对象枚举与内存布局
这段 MQL5 片段定义了一个名为 C_Chart_IDE 的图表交互类,用来在 MT5 自定义指标或 EA 面板上挂按钮、编辑框和勾选框。它把界面元素抽象成枚举 eObjectsIDE,包含结果标签、买卖按钮、日内交易勾选、取消键以及三个编辑框(杠杆、止盈、止损)。 宏 def_MaxObject 被设为 eEDIT_STOP + 32,意味着对象数组在最大枚举值之外还预留了 32 个槽位,实际可挂对象数依赖编译器对该枚举字面量的展开。m_ArrObject 是一个定长结构数组,每个元素记对象名与 X 坐标,这种静态数组比动态列表在回测和实时刷新时更不容易出现句柄漂移。 类外初始化的静态字符串数组 szMsgIDE 存放了 "MSG_RESULT"、"MSG_BUY_MARKET"、"MSG_SELL_MARKET" 等通信标识,EA 与面板之间靠这些常量做消息路由,避免硬编码字符串散落各处。 读文件那段逻辑里,utf_16 == 0x003E(即 '>')作为段落结束判定返回,说明解析器按 UTF-16 逐字符拼接到 m_szValue 或 m_szLine,遇到文件尾 FileIsEnding 就 break。外汇与贵金属品种上跑这类面板请记得:自定义控件不替你管理仓位风险,杠杆与止损参数填错可能瞬间放大回撤。
if (b0) m_szValue = StringFormat("%s%c", m_szValue, (class="type">char)utf_16); else m_szLine = StringFormat("%s%c", m_szLine, (class="type">char)utf_16); if (FileIsEnding(m_fp)) class="kw">break; } class="kw">return (utf_16 == 0x003E); } class C_Chart_IDE { class="kw">protected: enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP}; class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_HeaderMSG "IDE_" class="macro">#define def_MaxObject eEDIT_STOP + class="num">32 class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">private : class="type">int m_fp, m_SubWindow, m_CountObject; class="type">class="kw">string m_szLine, m_szValue; class="type">bool m_IsDayTrade; class="kw">struct st0 { class="type">class="kw">string szName; class="type">int iPosX; }m_ArrObject[def_MaxObject]; class=class="str">"cmt">// ... The rest of the class code.... class="kw">public : class="kw">static class="kw">const class="type">class="kw">string szMsgIDE[]; class=class="str">"cmt">// ... The rest of the class code.... }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">static class="kw">const class="type">class="kw">string C_Chart_IDE::szMsgIDE[] = { "MSG_RESULT", "MSG_BUY_MARKET", "MSG_SELL_MARKET",
「从 XML 标签循环生成图表对象」
这段逻辑干的事很直接:读外部 XML 式的文本流,遇到 <object> 到 </object> 之间的字段,就批量在 MT5 图表上建对象。它先定义了一组消息 ID 字符串数组 szMsgIDE,包含日交易、平仓、杠杆、止盈、止损五种提示类型,用于后面匹配 name 节点。
LoopCreating() 里用两个宏 macro_SetInteger / macro_SetString 封装了 ObjectSetInteger 和 ObjectSetString,调用时只需传属性枚举和值,少写重复代码。主循环 while (m_szLine != "</object>") 持续读行,读不到就返回 false,避免死循环卡脚本。
当行内容是 name 时,先把值转大写,用 for 循环在 eRESULT 到 eEDIT_STOP 区间比对,命中就取对应下标,没命中就落到 m_CountObject 新增槽位。随后用 StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue) 拼出唯一对象名,先删后建保证不重名。
pos_x 和 pos_y 的解析很朴素:直接 StringToInteger 转成整数距离,塞进 OBJPROP_YDISTANCE 等属性。外汇和贵金属图表挂这类对象属高风险环境下的辅助显示,数值偏移量需你在实盘图验证,可能因分辨率不同产生像素偏差。
"MSG_DAY_TRADE", "MSG_CLOSE_POSITION", "MSG_LEVERAGE_VALUE", "MSG_TAKE_VALUE", "MSG_STOP_VALUE" }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool LoopCreating(ENUM_OBJECT type) { class="macro">#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) class="macro">#define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) class="type">int c0; class="type">bool b0; class="type">class="kw">string sz0 = m_szValue; class="kw">while (m_szLine != "</object>") if (!FileReadLine()) class="kw">return false; else { if (m_szLine == "name") { b0 = false; StringToUpper(m_szValue); for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++); c0 = (b0 ? c0 : m_CountObject); m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue); ObjectDelete(Terminal.Get_ID(), m_ArrObject[c0].szName); ObjectCreate(Terminal.Get_ID(), m_ArrObject[c0].szName, type, m_SubWindow, class="num">0, class="num">0); } if (m_szLine == "pos_x" ) m_ArrObject[c0].iPosX = (class="type">int) StringToInteger(m_szValue); if (m_szLine == "pos_y" ) macro_SetInteger(OBJPROP_YDISTANCE, StringToInteger(m_szValue));
用文本指令批量改面板对象属性
在 MT5 的 GUI 宏解析里,可以把外部配置文件里的键值对直接映射成图形对象的属性写入,不必在代码里硬编码每个按钮或标签的参数。下面这段解析逻辑覆盖了尺寸、偏移、颜色、位图与文字等常见字段,实盘里用来动态换肤或切换品种显示很顺手。 [CODE] if (m_szLine == "size_x") macro_SetInteger(OBJPROP_XSIZE, StringToInteger(m_szValue)); if (m_szLine == "size_y") macro_SetInteger(OBJPROP_YSIZE, StringToInteger(m_szValue)); if (m_szLine == "offset_x") macro_SetInteger(OBJPROP_XOFFSET, StringToInteger(m_szValue)); if (m_szLine == "offset_y") macro_SetInteger(OBJPROP_YOFFSET, StringToInteger(m_szValue)); if (m_szLine == "bgcolor") macro_SetInteger(OBJPROP_BGCOLOR, StringToInteger(m_szValue)); if (m_szLine == "color") macro_SetInteger(OBJPROP_COLOR, StringToInteger(m_szValue)); if (m_szLine == "bmpfile_on") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, 0, m_szValue); if (m_szLine == "bmpfile_off") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, 1, m_szValue); if (m_szLine == "fontsz") macro_SetInteger(OBJPROP_FONTSIZE, StringToInteger(m_szValue)); if (m_szLine == "fontnm") macro_SetString(OBJPROP_FONT, m_szValue); if (m_szLine == "descr") macro_SetString(OBJPROP_TEXT, m_szValue); if (m_szLine == "readonly") macro_SetInteger(OBJPROP_READONLY, StringToInteger(m_szValue) == 1); if (m_szLine == "state") macro_SetInteger(OBJPROP_STATE, StringToInteger(m_szValue) == 1); [/CODE] 逐行看:前四行把 size_x / size_y / offset_x / offset_y 转成整数后写入对象的宽高与坐标偏移;bgcolor 和 color 同理处理背景与前景色。bmpfile_on 与 bmpfile_off 调用 ObjectSetString 分别设状态 0 和 1 下的位图路径,适合做按钮亮灭图切换。 fontsz、fontnm、descr 管字号、字体和显示文本;readonly 与 state 把配置里的 1 转成布尔真,控制对象是否可编辑及选中态。外汇与贵金属图表上挂这类自定义面板,参数误写可能导致对象重叠或不可点,属高风险操作,建议先在模拟盘验证。 想省事就直接把这段粘进你的 EA 面板解析函数,外部改 txt 配置就能换布局,不用重编译。
if (m_szLine == "size_x") macro_SetInteger(OBJPROP_XSIZE, StringToInteger(m_szValue)); if (m_szLine == "size_y") macro_SetInteger(OBJPROP_YSIZE, StringToInteger(m_szValue)); if (m_szLine == "offset_x") macro_SetInteger(OBJPROP_XOFFSET, StringToInteger(m_szValue)); if (m_szLine == "offset_y") macro_SetInteger(OBJPROP_YOFFSET, StringToInteger(m_szValue)); if (m_szLine == "bgcolor") macro_SetInteger(OBJPROP_BGCOLOR, StringToInteger(m_szValue)); if (m_szLine == "class="type">color") macro_SetInteger(OBJPROP_COLOR, StringToInteger(m_szValue)); if (m_szLine == "bmpfile_on") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, class="num">0, m_szValue); if (m_szLine == "bmpfile_off") ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_BMPFILE, class="num">1, m_szValue); if (m_szLine == "fontsz") macro_SetInteger(OBJPROP_FONTSIZE, StringToInteger(m_szValue)); if (m_szLine == "fontnm") macro_SetString(OBJPROP_FONT, m_szValue); if (m_szLine == "descr") macro_SetString(OBJPROP_TEXT, m_szValue); if (m_szLine == "readonly") macro_SetInteger(OBJPROP_READONLY, StringToInteger(m_szValue) == class="num">1); if (m_szLine == "state") macro_SetInteger(OBJPROP_STATE, StringToInteger(m_szValue) == class="num">1);