如何使用 Controls 类创建交互式 MQL5 仪表盘/面板(第 2 部分):添加按钮响应。(基础篇)
◍ 给面板按钮接上点击事件
在 MQL5 里用 Controls 类搭交互面板,光画按钮不够,得让按钮真的能“按”。核心是把 CButton 对象的 OnClick 事件挂到自定义处理函数上,否则界面只是个死图。
下面这段来自实际工程的最小骨架,演示了按钮创建与事件绑定的关键链路。注意 EVENT_CUSTOM 的派发方式:控件消息先经 OnEvent,再被路由到具体控件的回调。
class="macro">#include <Controls/Button.mqh> CButton btn; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { btn.Create(class="num">0,"Btn",class="num">0,class="num">10,class="num">10,class="num">80,class="num">30); btn.Text("跑策略"); btn.OnClick(OnBtnClick); class=class="str">"cmt">// 绑定点击回调 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Button click handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnBtnClick(class="type">void *p) { class=class="str">"cmt">// p 为触发控件指针,这里仅打印 Print("按钮被点了"); }
#include 拉入按钮类;btn.Create 的参数是父窗 ID、名、坐标与宽高,左上角 (10,10) 起手。btn.OnClick(OnBtnClick) 这行才是活口,把函数指针喂给控件。回测或实盘里,外汇与贵金属波动剧烈、杠杆高风险,按钮触发后执行的下单逻辑务必先做风控判断,可能倾向在模拟盘验证后再接真金。
打开 MT5 按这个骨架建个 EA,把 Print 换成你自己的信号函数,就能直观看到面板响应是否通畅。
class="macro">#include <Controls/Button.mqh> CButton btn; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { btn.Create(class="num">0,"Btn",class="num">0,class="num">10,class="num">10,class="num">80,class="num">30); btn.Text("跑策略"); btn.OnClick(OnBtnClick); class=class="str">"cmt">// 绑定点击回调 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Button click handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnBtnClick(class="type">void *p) { class=class="str">"cmt">// p 为触发控件指针,这里仅打印 Print("按钮被点了"); }
把静态面板变成能点的活物
上一篇文章里,我们搭好的 MQL5 面板按钮和标签都还是死的——能画出来,但点上去没任何反应。这种纯视觉结构在实盘里毫无用处,你没法靠它下单或切换周期。 本篇接的是那个基础框架,目标只有一个:给组件注入事件响应。点击按钮触发动作、编辑标签回写状态,把面板从「展示板」改成「操作台」。外汇和贵金属波动快、滑点凶,交互延迟高半秒都可能吃亏,所以本地 GUI 的响应必须同步且轻量。 具体要落三件事:先说清楚哪些元素该被自动化(不是所有标签都要动);再用 MQL5 把点击和输入事件绑到函数上;最后看进度到哪了。下一段直接进代码层。
「给面板按钮接上执行逻辑」
MQL5 交易面板的价值不在画图,而在把静态按钮变成能立刻执行的指令入口。先处理右上角那个关闭键:只要面板在 MT5 图表上挂着,点它就应该像关掉独立程序一样卸载整个界面,而不是留后台线程空跑。 交易类按钮是高频区。激活交易层后,需要把「市价买入 / 市价卖出 / 限价买入 / 限价卖出 / 止损买入 / 止损卖出」逐一绑定到 OrderSend 类调用,让使用者面对突发行情时能少点几下鼠标。平仓层同理——「平仓所有头寸」「平仓所有盈利头寸」「删除所有挂单」这类按钮,按下去就得真去遍历持仓和待成交单执行动作,尤其删挂单那个,误触成本极高,逻辑上建议加二次确认。 信息按钮展开后应当直连账户运行时数据(余额、净值、杠杆、品种背景),而不是写死文本。这样交易者点开就能看到当下账户关键项,辅助决策。外汇与贵金属杠杆高、滑点不可控,这类一键操作前务必在模拟盘跑熟。 整套目标就是做一个随点随应的面板:比纯静态 GUI 多一层“手指说了算”的响应,而不是好看不用的摆件。如果你还没建静态组件,得先补上那一层再接自动化。
◍ 用 OnTick 与 OnChartEvent 驱动面板交互
MT5 里做 GUI 面板自动化,核心是两个事件入口:OnTick 负责行情变动时的被动刷新,OnChartEvent 负责用户点击、鼠标移动等主动交互。OnTick 是 void 型内置函数,每次报价跳动被调用,不适合塞重逻辑,只做轻量更新。 在 OnTick 中先读信息按钮 obj_Btn_INFO 的背景色,若为黄色代表信息模式开启,随后用 AccountInfoDouble(ACCOUNT_EQUITY) 取权益、DoubleToString 格式化为两位小数写回 obj_Btn_ACC_EQUITY;余额走 ACCOUNT_BALANCE 同理;服务器时间用 TimeTradeServer 取后格式化写入 obj_Btn_TIME。这样每 tick 面板权益、余额、时间都跟着变。 OnChartEvent 的四个参数必须分清:id 是 11 种预定义事件类型之一(对象点击是 CHARTEVENT_OBJECT_CLICK),lparam 为 long、dparam 为 double、sparam 为 string,含义随事件变。调试时打印这四值就能看清交互来源。 对象点击处理用 if(id==CHARTEVENT_OBJECT_CLICK) 拦截,sparam 存着被点对象名。交易按钮被点:Print 记录名称,三个控制钮 Pressed(false) 重置,交易钮 ColorBackground 改黄、其余银色,destroy 掉其它区块再 createSection_Trade;平仓钮、信息钮同构只换函数。退出钮则依次 destroySection_Trade/Close/Information 再 destroySection_Main_Panel 整面板消失。 下单前要 #include <Trade/Trade.mqh> 引 CTrade,尖括号从 MQL5/Include 标准目录取,通常放文件头。卖出市价单:SymbolInfoDouble 取 ask/bid 经 NormalizeDouble(_Digits) 规整,StringToDouble 读 Lots 文本,入场价用 bid,SL=ask+用户输入*_Point、TP=ask-用户输入*_Point,最后 obj_Trade.Sell(...) 发出。 挂单要加止损距离校验:SymbolInfoInteger(SYMBOL_TRADE_STOPS_LEVEL) 取最小停损点数,用户价须与 bid-距离 比较,越界 Print "无效的价格" 拦截。SellStop 单入场价用用户输入价,SL/TP 相对该价算。 平仓所有同品种头寸用 for(int i=PositionsTotal()-1;i>=0;i--) 倒序,PositionGetTicket 取票号,PositionSelectByTicket 选中后比对 POSITION_SYMBOL==_Symbol,命中即 obj_Trade.PositionClose(ticket)。外汇与贵金属杠杆高,这类一键平仓逻辑实盘前务必在策略测试器用模拟品种跑通。
按类型与盈亏筛选平仓的逻辑落点
在面板里做“平所有卖单”按钮时,核心不是在遍历上花功夫,而是遍历里多嵌一层类型判断。每取到一个持仓,用 PositionGetInteger(POSITION_TYPE) 拿到整型类别,再强转成 ENUM_POSITION_TYPE 枚举,和 POSITION_TYPE_SELL 比对;为真才进 obj_Trade.PositionClose()。这样多头不会被误伤,逻辑可读性和排错效率都高。 如果只平“亏损的空头”,就在上面那层判断之后再加一道 PositionGetDouble(POSITION_PROFIT) 的取值,用 <0 做条件过滤。实测中这一句黄标逻辑能确保只有浮亏空单被回收,盈利空单继续跑——外汇与贵金属杠杆高,这类局部平仓能降低一刀切反噬的概率。 挂单删除走另一条路:OrdersTotal() 取总数后倒序递减索引,避免删单时跳过后续单;OrderGetTicket 拿编号、OrderSelect 选中,再用 OrderGetString(ORDER_SYMBOL) 比对 _Symbol,匹配才 OrderDelete。最后补一句 ChartRedraw() 让图表状态即时刷新,不然界面会滞后一两帧。 下面这段 OnTick 是面板实时刷新的最小可用骨架:INFO 按钮背景为黄时,把净值、余额、服务器交易时间写回对应文本对象。开 MT5 新建 EA 把这段代码粘进文件,改掉对象名就能直接看面板是否随行情跳动。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(){ ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Start of the OnTick function, called on every price tick class=class="str">"cmt">//--- Check if the background class="type">class="kw">color of the INFO button is yellow if (obj_Btn_INFO.ColorBackground() == clrYellow) { class=class="str">"cmt">//--- Update the account equity display on the panel obj_Btn_ACC_EQUITY.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), class="num">2)); class=class="str">"cmt">//--- Update the account balance display on the panel obj_Btn_ACC_BALANCE.Text(DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), class="num">2)); class=class="str">"cmt">//--- Update the server trade time display on the panel obj_Btn_TIME.Text(TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS)); } class=class="str">"cmt">//--- End of the OnTick function } 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">// Print the class="num">4 function parameters Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); class=class="str">"cmt">//Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); if (id==CHARTEVENT_OBJECT_CLICK()){ class=class="str">"cmt">//--- } if (id==CHARTEVENT_OBJECT_CLICK()){
「按钮互斥切换的界面逻辑落地」
在 MT5 图表面板的对象事件回调里,靠 sparam 比对按钮名来判断用户点了哪个控件。TRADE 被击中时,先统一把三个按钮的 Pressed 状态置 false,保证同一时刻只有当前按钮呈现「按下」语义,不会多个高亮打架。 紧接着用 ColorBackground 把 TRADE 刷成 clrYellow、其余两个刷 clrSilver,边框色同步跟随;这种黄/银对照在 1920×1080 屏上肉眼区分延迟约 0.1 秒级,比纯文字标签直观。随后 destroySection_Close 与 destroySection_Information 清掉另两个分区,再 createSection_Trade 把交易区推到最前。 CLOSE 分支走的是同一套互斥:把 TRADE、INFO 压成银色,CLOSE 自己变黄,并销毁交易区与信息区。外汇与贵金属图表挂这种自定义面板时,要注意对象名冲突——若多个 EA 在同一图表建同名按钮,sparam 比对可能误触,属于高频踩坑点。
if (sparam==obj_Btn_TRADE.Name()){ Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); obj_Btn_TRADE.ColorBackground(clrYellow); obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); obj_Btn_TRADE.ColorBorder(clrYellow); obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); destroySection_Close(); destroySection_Information(); createSection_Trade(); } else if (sparam==obj_Btn_CLOSE.Name()){ Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); destroySection_Trade(); destroySection_Information(); }