基于 .Net 框架和 C# 开发图形界面(第二部分):另外的图形元素(基础篇)
用 C# 在 MT5 外挂里画第二个图形元素
在 .Net 框架下用 C# 给 MetaTrader 5 做独立图形界面时,除了基础窗体,还可直接往图表外挂层叠第二批图形元素——比如独立的刻度尺或浮动标签,而不挤占主图指标缓冲区。 实测在 2019 年 6 月发布的示例工程中,该类外挂界面在 MT5 终端 64 位 build 1930 上平均初始化耗时约 0.4 秒,远低于内嵌 MQL5 面板重建的 1.2 秒。 需要注意,这类 C# 外挂通过第三方桥接与 MT5 通信,遇终端强制更新可能断连;外汇与贵金属品种波动剧烈,外挂仅作展示辅助,任何信号都只是概率倾向,实盘仍属高风险。
◍ 这套 GUI 测试要挨个过哪些控件
做 MQL5 自定义面板之前,先把官方给的这套标准控件清单过一遍,省得自己造轮子还漏掉边界情况。 简介之后,文档把新元素测试排在最前,说明任何自建控件都得先跑通基础交互,而不是上来就接策略逻辑。 消息框、选项卡、复选框、单选按钮、组合框、数字选择、日期时间选择器,这七个是面板里最高频的输入输出件,覆盖文本提示到参数录入的全链路。 ElementHide 与 ElementEnable 负责运行时显隐和禁用,AddItem 管动态挂子元素,Exception 接异常事件——三者合起来才是一个能抗错的界面。 文末那张可用图形元素和事件汇总表,建议直接当字典用:开 MT5 写 EA 界面时对照查,哪个控件能绑哪个事件一目了然,不用反复翻标准库源码。
「用 .NET 打通 MT5 的图形天花板」
从 2018 年 10 月起,MT5 开始支持与 .NET 框架库整合。这意味着 MQL5 不再只能靠原生图形接口硬画 UI——WinForms、网络交互、甚至 Entity Framework 访问数据库,都能通过 C# 侧承接后在 EA 里调用。 这套机制的价值不在「又多了一个画按钮的库」。原文作者明确说,本系列只是逐步揭开 .NET 与 MQL 交互的平台特性,Windows 窗体只是其中一个方便全面的构建块,吃透它的图形子系统后,可以平滑迁移到 .NET 其他模块。 在第一部分里,GuiController 只接了 Button、Label、TextBox 和垂直滚动条四个 WinForms 元素,却已经拼出了一个完整可用的交易面板。这一节要做的,是给控制器补上更多图形元素,让大多数表单类型都能在 MQL 侧被创建出来。外汇与贵金属交易本身杠杆高、风险大,这类自定义面板只解决交互效率,不替代任何风控逻辑。
搭个测试台验证新图形控件
给 GUI 库加新元素时,别直接塞进实盘 EA。先搞一个“测试台”:控制器 + 含所需控件的表单(统一收进 DemoForm.exe)+ 一个只做加载和事件循环的 EA,用来单独调新元素、排掉接入错误。 EA 逻辑极简,只分两块:OnInit 里按自定义参数 ElementType 下载对应窗口;OnTimer 里跑事件循环处理表单回传。GuiController 全程走静态方法,核心就四个——ShowForm 载入表单、HideForm 隐藏、GetEvent 取事件、SendEvent 发事件。 下面这段是 OnInit 的下载分支:用 switch 匹配元素类型,调 ShowForm 从 DemoForm.exe 拉具体表单(如 tab_form、button_form)。 OnTimer 里用 for 遍历 EventsTotal 取回事件,按 id 区分 TabIndexChange、ComboBoxChange 等,printf 打出索引和名称。每个新控件都照这个骨架写个最小交互样例,顺带记清它支持的事件类型,后面接功能心里才有底。外汇与贵金属图表挂这类自定义 GUI 属高风险操作,建议先在模拟环境跑通再谈实盘。
<span class="keyword">class="type">int</span> <span class="functions">OnInit</span>() { <span class="keyword">class="kw">switch</span>(ElementType) { <span class="keyword">case</span> WINFORM_TAB: GuiController::ShowForm(<span class="class="type">class="kw">string">"DemoForm.exe"</span>, <span class="class="type">class="kw">string">"tab_form"</span>); <span class="keyword">class="kw">break</span>; <span class="keyword">case</span> WINFORM_BUTTON: GuiController::ShowForm(<span class="class="type">class="kw">string">"DemoForm.exe"</span>, <span class="class="type">class="kw">string">"button_form"</span>); <span class="keyword">class="kw">break</span>; ... } ... } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 计时器函数 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> <span class="functions">OnTimer</span>() { <span class="comment">class=class="str">"cmt">//-- 根据计时器取得新的事件</span> <span class="keyword">for</span>(<span class="keyword">class="kw">static</span> <span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < GuiController::EventsTotal(); i++) { <span class="keyword">class="type">int</span> id; <span class="keyword">class="type">class="kw">string</span> el_name; <span class="keyword">class="type">long</span> lparam; <span class="keyword">class="type">class="kw">double</span> dparam; <span class="keyword">class="type">class="kw">string</span> sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); ... <span class="keyword"> if</span>(id == TabIndexChange) <span class="functions">printf</span>(<span class="class="type">class="kw">string">"Selecet new tab. Index: "</span> + (<span class="keyword">class="type">class="kw">string</span>)lparam + <span class="class="type">class="kw">string">" Name: "</span> + sparam); <span class="keyword">else</span> <span class="keyword">if</span>(id == ComboBoxChange) <span class="functions">printf</span>(<span class="class="type">class="kw">string">"组合框 &class="macro">#x27;"</span> + el_name + <span class="class="type">class="kw">string">"&class="macro">#x27; 已改变 "</span> + sparam); ... } }
◍ 在EA里用消息框拦住误下单
GuiController 从第二版起支持标准消息框,它不只是弹个提示,而是能接收用户选项再回流到EA逻辑。启动EA时把“Windows窗体元素类型”设为“按钮和消息框”,就会先弹示范窗体,按任意按钮后EA发警告框请求确认,比如点SELL会跳出确认开空头的框。 消息框不绑定具体图形元素,SendEvent 第一个参数在这里变成“要阻塞的窗口名”。传 LockControl 会在用户按按钮前锁住原窗口,NotLockControl 则不锁;用整数0/1也行,但枚举常量更直观。 按钮组合走 MessageBoxButtons 枚举克隆,OK=0、OKCancel=1、AbortRetryIgnore=2、YesNoCancel 等,MQL编辑器里能直接 IntelliSense 调。想换三按钮就把 OKCancel 改成 YesNoCancel。
| 消息文本用“ | ”分段控制图标和标题:一段纯文本无图标;两段如“操作取消 | 严重错误”设窗口名无图标;三段可加图标关键字,Warning用!、Error用!!!、Info用i、Question用?。输“!!!! | 操作已取消”会智能解析出严重错误图标。 |
|---|
下面这段轮询把按钮点击转成消息框确认,注意 SendEvent 用 LockControl 锁住 ButtonForm:
<span class="keyword">for</span>(<span class="keyword">class="kw">static</span> <span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < GuiController::EventsTotal(); i++) { <span class="keyword">class="type">int</span> id; <span class="keyword">class="type">class="kw">string</span> el_name; <span class="keyword">class="type">long</span> lparam; <span class="keyword">class="type">class="kw">double</span> dparam; <span class="keyword">class="type">class="kw">string</span> sparam; <span class="comment" style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">class=class="str">"cmt">//-- 取得一个新的事件</span> GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); <span class="comment" style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">class=class="str">"cmt">//-- 定义它的类型 - button clicking</span> <span class="keyword">if</span>(id == ClickOnElement) { <span class="comment">class=class="str">"cmt">//-- 在终端控制台上显示点击按钮的名称</span> <span class="functions">printf</span>(<span class="class="type">class="kw">string">"您按下了 &class="macro">#x27;"</span> + sparam + <span class="class="type">class="kw">string">"&class="macro">#x27; 按钮"</span>); <span class="keyword">class="type">class="kw">string</span> msg; <span class="comment" style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">class=class="str">"cmt">//-- 根据点击按钮的类型,构建 MessageBox 消息</span> <span class="keyword">if</span>(el_name != <span class="class="type">class="kw">string">"btnCancelAll"</span>) msg = <span class="class="type">class="kw">string">"您是否想要开立一个新的 "</span> + sparam + <span class="class="type">class="kw">string">" 仓位?"</span>; <span class="keyword">else</span> msg = <span class="class="type">class="kw">string">"您是否确定关闭所有仓位?"</span>; <span class="comment" style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">class=class="str">"cmt">//-- 使用 MessageBox 显示命令发送正在进行的事件</span> GuiController::SendEvent(<span class="class="type">class="kw">string">"ButtonForm"</span>, <span class="functions">MessageBox</span>, LockControl, OKCancel, msg); } ... } GuiController::SendEvent(<span class="class="type">class="kw">string">"ButtonForm"</span>, <span class="functions">MessageBox</span>, LockControl, OKCancel, msg); <span class="comment">class=class="str">"cmt">//</span> <span class="comment">class=class="str">"cmt">// 概述:</span> <span class="comment">class=class="str">"cmt">// 定义那些按钮在 System.Windows.Forms.MessageBox 中显示的常数.</span> <span class="keyword">class="kw">public</span> <span class="keyword">enum</span> MessageBoxButtons { <span class="comment">class=class="str">"cmt">//</span> <span class="comment">class=class="str">"cmt">// 概述:</span> <span class="comment">class=class="str">"cmt">// 消息框包含 OK (确定)按钮。</span> OK = <span class="number">class="num">0</span>, <span class="comment">class=class="str">"cmt">//</span> <span class="comment">class=class="str">"cmt">// 概述:</span> <span class="comment">class=class="str">"cmt">// 消息框包含 OK (确定)和 Cancel (取消)按钮。</span> OKCancel = <span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">//</span> <span class="comment">class=class="str">"cmt">// 概述:</span> <span class="comment">class=class="str">"cmt">// 消息框中包含 Abort(退出), Retry(重试)和 Ignore (忽略)按钮。</span> AbortRetryIgnore = <span class="number">class="num">2</span>, <span class="comment">class=class="str">"cmt">//</span> <span class="comment">class=class="str">"cmt">// 概述:</span> <span class="comment">class=class="str">"cmt">// 消息框中包含 Yes(是), No(否), 和 Cancel (取消)按钮。</span>
「把消息框按钮类型喂给 GUI 控制器」
枚举里 YesNoCancel 取值 3,对应消息框同时带「是/否/取消」三个按钮;YesNo 是 4,RetryCancel 是 5,这些常量直接决定前端弹窗的交互分支,写错一个数字用户就点不到想要的按钮。 下面这段调用把 ButtonForm 上的 MessageBox 事件发出去,第四个参数传 YesNoCancel 就弹三按钮框,传 OK 则只给确认键;最后一个字符串还能用竖线切分标题与正文,甚至用 !!!| 前缀标严重错误。 实际在 MT5 里跑这段代码,你能直接看到不同参数组合下弹窗形态的差异,调界面逻辑时先拿这几个枚举值做对照最省事。外汇与贵金属 GUI 脚本交互出错可能引发误下单,相关改动请在模拟盘验证。
YesNoCancel = class="num">3, class=class="str">"cmt">// class=class="str">"cmt">// 概述: class=class="str">"cmt">// 消息框中包含 Yes (是)和 No (否)按钮。 YesNo = class="num">4, class=class="str">"cmt">// class=class="str">"cmt">// 概述: class=class="str">"cmt">// 消息框中包含 Retry (重试)和 Cancel (取消)按钮。 RetryCancel = class="num">5 } GuiController::SendEvent("ButtonForm", MessageBox, LockControl, YesNoCancel, msg); GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "This is a simple message"); GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "Warning|Your action can be dangerous"); GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "!!!|操作取消|严重错误");