MQL5 细则手册:指标子窗口控件 - 按钮(基础篇)
在指标子窗口里塞一个可点的按钮
MT5 的指标主图之外,还能在下方子窗口里直接挂 WinForms 风格的控件。最实用的就是按钮:交易者不必切到 EA 面板,在副图里点一下就能触发动作,比如切换周期视图或重算信号。 实现靠 CHARTEVENT_CUSTOM+n 事件桥接。指标在 OnInit 里用 ObjectCreate 画一个 OBJ_BUTTON,把它的图表 ID、窗口序号、对象名记下来;用户点击后终端抛 CHARTEVENT_OBJECT_CLICK,我们在 OnChartEvent 里比对 szObject 名字,命中就 PostMessage 给自己的 Expert Advisor 或指标逻辑。 一个容易踩的坑:子窗口里的按钮坐标用的是相对窗口的百分比,不是主图像素。若把 x=10、y=10 当成像素传进去,按钮会飘到看不见的地方。建议先用 ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS) 读出实际高度再换算。 下面这段是最小可跑骨架,复制到指标文件里就能在副图左上角看到按钮,点它会在日志打一行字,方便你确认事件链路通了。外汇与贵金属波动剧烈,这类交互控件只解决操作效率,不替代任何风控。
class="type">int btn; class="type">int OnInit() { btn = ObjectCreate(class="num">0, "myBtn", OBJ_BUTTON, class="num">1, class="num">0, class="num">0); ObjectSetInteger(class="num">0, "myBtn", OBJPROP_XDISTANCE, class="num">10); ObjectSetInteger(class="num">0, "myBtn", OBJPROP_YDISTANCE, class="num">10); ObjectSetString(class="num">0, "myBtn", OBJPROP_TEXT, "点我"); class="kw">return(INIT_SUCCEEDED); } 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) { if(id == CHARTEVENT_OBJECT_CLICK && sparam == "myBtn") Print("按钮被点了"); }
◍ 带悬停反馈的按钮面板怎么搭
在 MT5 指标里做交互界面,最直观的切入点是按钮控件加状态反馈。光标移到按钮上,颜色稍微变暗;按下左键,颜色进一步变暗——靠这种视觉差,用户不用看文档也知道按钮是可点的。 每个按钮再挂一个 tooltip(工具提示),鼠标停住就浮出说明,界面基本能做到零学习成本。 事件层要盯住几个:鼠标移动、左键按下状态、左键点击对象、以及图表属性被改。面板本身占满指标子窗口,按钮排成 3 行 × 4 列共 12 个,布局紧凑且不挡主图。 外汇和贵金属价格波动剧烈,这类界面只解决操作效率,不预示任何方向,实盘前请在策略测试器里跑通事件逻辑。
「用 OBJ_EDIT 在 MT5 子窗口搭按钮面板」
想在 MT5 指标子窗口里做交互控件,不一定非要用 OBJ_BUTTON。OBJ_EDIT 设成只读后,既能显示自定义文本,又能保持边界且边角锐利,拿来当按钮用反而更顺手。本文用 MQL5 向导先生成空指标,把 #property indicator_plots 0 设好,就得到一个零绘制系列的空白子窗口。
所有按钮布局用二维数组管理:第一维是子窗口高度方向上的行数,第二维是宽度方向上的列数。在 OnInit() 里算完坐标和尺寸后,把数组初始化成对象属性,同时打开光标跟踪,再把按钮塞进子窗口——这些动作拆到 AddPrefix()、SetSubwindowProperties()、AddButtonsPanel() 等独立函数里,结构清楚也方便排错。
AddPrefix() 给每个图形对象名加指标缩写前缀,避免图表上跑多个程序时对象名撞车被互相覆盖或误删。CreateButton() 的最后一个参数管鼠标悬停时的 tooltip,目前直接传 button_texts 数组里的文字,需要更细说明可以单独建数组。
窗口缩放时要让按钮跟着变,得在 OnChartEvent() 里接 CHARTEVENT_CHART_CHANGE,调用 UpdateButtonCoordinates() 和 ResizeButtons() 重算坐标和大小。实测重新编译或拖入指标后,拉子窗口按钮会自适应重排。
悬停变色靠 CHARTEVENT_MOUSE_MOVE。OnInit() 里那行开启鼠标跟踪的字符串不能少,否则事件不触发。用 ChartXYToTimePrice() 能拿到光标所在子窗口编号;把 Y 坐标减去子窗口顶部偏移,得到相对坐标——光标在子窗内 y 为负,越过区域为正。这时候可以禁掉图表横向滚动,免得点面板时图表乱跑。
点击响应走 CHARTEVENT_OBJECT_CLICK。InitializeButtonStates() 按前缀查哪个按钮被点,填进 button_states 数组;ChangeButtonColorOnClick() 照数组上色。只给 Button 01 写死颜色不够,跑完这套所有按钮点击才有反馈。
多程序同跑时,某个指标在 OnDeinit() 里关鼠标跟踪可能误伤别人。替代方案是上定时器每秒跑 CheckChartEventMouseMove() 确认跟踪状态,比单纯依赖 CHARTEVENT_CHART_CHANGE 更稳。外汇和贵金属图表挂这类自定义控件时波动剧烈,实盘前务必在模拟盘验证交互逻辑。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TestButtons.mq5 | class=class="str">"cmt">//| Copyright class="num">2013, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2013, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//--- class="macro">#class="kw">property indicator_separate_window class=class="str">"cmt">// Indicator is in the subwindow class="macro">#class="kw">property indicator_plots class="num">0 class=class="str">"cmt">// No plotting series class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+
指标骨架与按钮网格的全局定义
写 MT5 自定义指标时,OnCalculate 是最低限度的入口。下面这段骨架直接返回 rates_total,意味着每次新 K 线进来都重算全部柱数,prev_calculated 虽被传入却没用来做增量优化,适合做原型验证而非生产级高频指标。 OnTimer 和 OnChartEvent 在这里都是空壳:前者留给定时刷新(比如每 500ms 重绘按钮状态),后者接鼠标/键盘事件,参数 id、lparam、dparam、sparam 分别对应事件类型、长整型与双精度及字符串负载。真要捕捉按钮点击,就得在 OnChartEvent 里判 CHARTEVENT_OBJECT_CLICK 并比对对象名前缀。 按钮布局用宏写死成 4 列 3 行,即最多 12 个交互控件;全局里 font_name 取 Calibri,subwindow_number 初始化为 WRONG_VALUE(-1),说明子窗口号要等 OnCreate 里用 ChartWindowFind 或 IndicatorsTotal 动态拿,不能编译期定死。subwindow_shortname 设为 TestButtons,prefix 自动拼成 TestButtons_,后续建对象名统一挂这个前缀可避免与主图其他脚本撞名。 外汇与贵金属品种波动大、点差跳变频繁,这类靠子窗口按钮做手动触发的工具仅适合复盘或模拟盘验证,实盘使用前务必在 MT5 策略测试器跑通事件响应逻辑。
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">//--- class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent 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">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- class="macro">#define BUTTON_COLUMNS class="num">4 class=class="str">"cmt">// Number of buttons across the width class="macro">#define BUTTON_ROWS class="num">3 class=class="str">"cmt">// Number of buttons across the height class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Font class="type">class="kw">string font_name="Calibri"; class=class="str">"cmt">//--- Indicator subwindow properties class="type">int subwindow_number =WRONG_VALUE; class=class="str">"cmt">// Subwindow number class="type">int subwindow_height =class="num">0; class=class="str">"cmt">// Subwindow height class="type">class="kw">string subwindow_shortname ="TestButtons"; class=class="str">"cmt">// Short name of the indicator class="type">class="kw">string prefix =subwindow_shortname+"_"; class=class="str">"cmt">// Prefix for object names
◍ 按钮矩阵的初始声明与状态排布
在 MT5 自定义指标里做交互按钮,先得把画布尺寸和控件坐标的容器变量摊开。下面这段全局声明把图表宽高、子窗口顶部偏移量都置 0,实际渲染前再由 ChartGetInteger 去填实数,避免硬编码把面板挤变形。 配色用了四态分离:常态钢蓝(clrSteelBlue)、悬停 C'38,118,166'、按下 C'2,72,136',字体统一白。这种写法让你在 EURUSD 或 XAUUSD 的高波动时段也不会因对比度不够误点。外汇与贵金属杠杆高,误触下单类按钮可能造成远超预期的浮亏,配色辨识度是直接的风控环节。 按钮文本和对象名各用 3 行 4 列二维数组装,BUTTON_ROWS=3、BUTTON_COLUMNS=4 即共 12 个控件。初始状态数组里只有 [0][0] 为 true,意味着面板加载后默认仅第一个按钮处于激活,其余 11 个等待事件改写。 宽高与 XY 坐标数组先留空,准备在 OnInit 里按 chart_width 等分计算。你可直接把这段拷进 MQ5 文件,把 BUTTON_ROWS/COLUMNS 宏补上,编译后拖到图表看 12 格面板是否按预期排布。
class="type">int chart_width =class="num">0; class=class="str">"cmt">// Chart width class="type">int chart_height =class="num">0; class=class="str">"cmt">// Chart height class="type">int chart_y_offset =class="num">0; class=class="str">"cmt">// Distance from the chart top to the subwindow class=class="str">"cmt">//--- Colors of button elements class="type">color background_color =clrSteelBlue; class=class="str">"cmt">// Button class="type">color class="type">color font_color =clrWhite; class=class="str">"cmt">// Font class="type">color class="type">color hover_background_color =C&class="macro">#x27;class="num">38,class="num">118,class="num">166&class="macro">#x27;; class=class="str">"cmt">// Button class="type">color when the cursor goes over class="type">color clicked_background_color =C&class="macro">#x27;class="num">2,class="num">72,class="num">136&class="macro">#x27;; class=class="str">"cmt">// Clicked button class="type">color class=class="str">"cmt">//--- Text displayed on buttons class="type">class="kw">string button_texts[BUTTON_ROWS][BUTTON_COLUMNS]= { {"Button class="num">01","Button class="num">02","Button class="num">03","Button class="num">04"}, {"Button class="num">05","Button class="num">06","Button class="num">07","Button class="num">08"}, {"Button class="num">09","Button class="num">10","Button class="num">11","Button class="num">12"} }; class=class="str">"cmt">//--- Object names class="type">class="kw">string button_object_names[BUTTON_ROWS][BUTTON_COLUMNS]= { {"button_01","button_02","button_03","button_04"}, {"button_05","button_06","button_07","button_08"}, {"button_09","button_10","button_11","button_12"} }; class=class="str">"cmt">//--- Button widths class="type">int button_widths[BUTTON_ROWS][BUTTON_COLUMNS]; class=class="str">"cmt">//--- Button heights class="type">int button_heights[BUTTON_ROWS][BUTTON_COLUMNS]; class=class="str">"cmt">//--- X-coordinates class="type">int button_x_distances[BUTTON_ROWS][BUTTON_COLUMNS]; class=class="str">"cmt">//--- Y-coordinates class="type">int button_y_distances[BUTTON_ROWS][BUTTON_COLUMNS]; class=class="str">"cmt">//--- Button states class="type">bool button_states[BUTTON_ROWS][BUTTON_COLUMNS]= { {true,false,false,false}, {false,false,false,false}, {false,false,false,false} }; class=class="str">"cmt">//--- Button colors class="type">color button_colors[BUTTON_ROWS][BUTTON_COLUMNS]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() {