MQL5 细则手册:指标子窗口控件 - 按钮(基础篇)
📘

MQL5 细则手册:指标子窗口控件 - 按钮(基础篇)

第 1/3 篇

在指标子窗口里塞一个可点的按钮

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) 读出实际高度再换算。 下面这段是最小可跑骨架,复制到指标文件里就能在副图左上角看到按钮,点它会在日志打一行字,方便你确认事件链路通了。外汇与贵金属波动剧烈,这类交互控件只解决操作效率,不替代任何风控。

MQL5 / C++
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 更稳。外汇和贵金属图表挂这类自定义控件时波动剧烈,实盘前务必在模拟盘验证交互逻辑。

MQL5 / C++
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 策略测试器跑通事件响应逻辑。

MQL5 / C++
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 格面板是否按预期排布。

MQL5 / C++
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()
  {

常见问题

用 OBJ_BUTTON 对象配合指标子窗口坐标创建,绑定 ChartEvent 的鼠标点击 ID 即可响应,注意把对象锚点设到对应子窗口索引。
在 OnChartEvent 里捕获 CHARTEVENT_MOUSE_MOVE,比对光标坐标与按钮矩形区域,动态改 OBJPROP_BGCOLOR 和 OBJPROP_COLOR。
可以,小布能读取你的指标脚本结构并标出未处理的点击事件与坐标越界风险,打开对应品种页就能看到诊断提示。
用二维数组存按钮的横向纵向序号与启用状态,初始化时按网格循环创建对象,避免逐个手写坐标。
多半是子窗口对象没设对窗口编号,或 OnChartEvent 没过滤子窗口的点击消息,需显式判断窗口 ID 再处理。