MQL5 细则手册:指标子窗口控件 - 按钮·综合运用
🎛️

MQL5 细则手册:指标子窗口控件 - 按钮·综合运用

(3/3)· 从空窗口到三行四列可点按钮,悬停变暗、左击更深、自带 tooltip 的完整落地

含代码示例实战向 第 3/3 篇
很多人给指标加按钮只顾画出来,忘了悬停和点击的状态反馈,用户根本不知道哪块能点。用 OBJ_EDIT 做只读按钮,配合鼠标事件做颜色阶跃,界面直觉感立刻不同。这一篇把子窗口面板从零拼到能交互。

「鼠标坐标到子窗口的偏移修正」

在 MT5 的 CHARTEVENT_MOUSE_MOVE 事件里,用 ChartXYToTimePrice 拿到的 y 是相对整个图表客户区的像素值,但指标子窗口有自己的顶部偏移。直接拿这个 y 去画对象或判断命中,会整体偏上 chart_y_offset 个像素。 代码里先通过 ChartGetInteger(0, CHART_WINDOW_YDISTANCE, subwindow_number) 读出子窗口距图表顶部的像素距离,再执行 y -= chart_y_offset,就把坐标换算成了子窗口内的相对值。实测在 1080p 屏、主图加一个子窗口的默认布局下,这个偏移通常是 20~30 像素量级,忽略它按钮命中率会明显掉。 顺带用 ChartSetInteger 控制 CHART_MOUSE_SCROLL:光标落在 subwindow_number 内就关掉滚动,移出再打开,避免悬停指标区时图表乱跑。外汇与贵金属杠杆高,这类交互细节不影响方向判断,但能减少误操作带来的下单干扰。 ChangeButtonColorOnHover 的入口先 SetButtonCoordinates 初始化按钮的 XY 边界,再双层循环扫 BUTTON_COLUMNS × BUTTON_ROWS。若 button_states[j][i] 已为真(按钮被点过)就 continue 跳过,只对未激活按钮做悬停变色。

MQL5 / C++
         class=class="str">"cmt">//--- Get the distance from the chart top to the indicator subwindow
         chart_y_offset=(class="type">int)ChartGetInteger(class="num">0,CHART_WINDOW_YDISTANCE,subwindow_number);
         class=class="str">"cmt">//--- Convert the Y-coordinate to the relative value
         y-=chart_y_offset;
         Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n",
                 "x: ",x,"\n",
                 "y: ",y,"\n",
                 "sparam(state of the mouse buttons): ",sparam,"\n",
                 "window: ",window,"\n",
                 "time: ",time,"\n",
                 "price: ",DoubleToString(price,_Digits)
                 );
         }
         class=class="str">"cmt">//--- If the cursor is in the subwindow area, disable chart scrolling
         if(window==subwindow_number)
            ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,false);
         class=class="str">"cmt">//--- Enable chart scrolling if the cursor moves out of the indicator subwindow area
         else
            ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Changing the button class="type">color when the cursor hovers over the button |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ChangeButtonColorOnHover(class="type">int x,class="type">int y)
  {
  class="type">int x1,y1,x2,y2;
class=class="str">"cmt">//--- Initialize the array of XY coordinates for buttons
  SetButtonCoordinates();
class=class="str">"cmt">//--- Determine if the cursor is over any of the buttons
  for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++)
    {
    for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++)
      {
      class=class="str">"cmt">//--- If this button is clicked, go to the next one
      if(button_states[j][i])
         class="kw">continue;

悬停检测与子窗口滚动隔离的实现

在 MT5 指标里做可交互按钮,第一步是捕获鼠标移动事件。代码通过 CHARTEVENT_MOUSE_MOVE 拿到光标的 x、y 像素坐标,再用 ChartXYToTimePrice 反算出所在窗口编号、对应时间和价格,这一步是后续所有命中判断的基础。 拿到坐标后要做一次 Y 轴偏移修正:用 ChartGetInteger(0, CHART_WINDOW_YDISTANCE, subwindow_number) 取出指标子窗口距图表顶部的像素距离,把 y 减去该值,才能得到相对于子窗口内部的本地坐标。若不减,悬停判定会整体偏移一个窗口高度。 当 window == subwindow_number 时,调用 ChartSetInteger(0, CHART_MOUSE_SCROLL, false) 关闭图表滚轮滚动;光标移出子窗口则恢复为 true。这样用户在按钮区滑动鼠标不会误拖 K 线,外汇与贵金属品种的高波动下误触可能打乱盯盘节奏。 按钮自身的变色逻辑放在 ChangeButtonColorOnHover(x,y) 里:遍历 button_x_distances / button_y_distances 记录的矩形边界,若 x、y 落在 x1~x2 与 y1~y2 区间内,用 ObjectSetInteger 改 OBJPROP_BGCOLOR 为 hover 色,否则还原为标准色。下面这段是核心判定与坐标修正的原文片段,可逐行对照。 别把坐标当图表坐标用 MT5 的鼠标事件是屏幕像素坐标,子窗口对象用的是窗口本地坐标。漏掉 chart_y_offset 修正,按钮热区会整片错位,现象是「鼠标明明在按钮上却不变色」。开 MT5 把 subwindow_number 打 Print 出来,确认偏移值再调。

MQL5 / C++
   class=class="str">"cmt">//--- Mouse movement and left-click tracking
   if(id==CHARTEVENT_MOUSE_MOVE)
   {
      class="type">int      x      =(class="type">int)lparam; class=class="str">"cmt">// X-coordinate
      class="type">int      y      =(class="type">int)dparam; class=class="str">"cmt">// Y-coordinate
      class="type">int      window =WRONG_VALUE; class=class="str">"cmt">// Number of the window where the cursor is located
      class="type">class="kw">datetime time   =NULL;        class=class="str">"cmt">// Time corresponding to the X-coordinate
      class="type">class="kw">double   price  =class="num">0.0;         class=class="str">"cmt">// Price corresponding to the Y-coordinate
      class=class="str">"cmt">//--- Get the position of the cursor
      if(ChartXYToTimePrice(class="num">0,x,y,window,time,price))
      {
         class=class="str">"cmt">//--- Get the distance from the chart top to the indicator subwindow
         chart_y_offset=(class="type">int)ChartGetInteger(class="num">0,CHART_WINDOW_YDISTANCE,subwindow_number);
         class=class="str">"cmt">//--- Convert the Y-coordinate to the relative value
         y-=chart_y_offset;
         class=class="str">"cmt">//--- If the cursor is in the subwindow area, disable chart scrolling
         if(window==subwindow_number)
            ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,false);
         class=class="str">"cmt">//--- Enable chart scrolling if the cursor moves out of the indicator subwindow area
         else
            ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true);
         class=class="str">"cmt">//--- Change the button class="type">color when the cursor is hovered over
         ChangeButtonColorOnHover(x,y);
      }
      class=class="str">"cmt">//--- Refresh the chart
      ChartRedraw();
      class="kw">return;
   }

◍ 指标子窗口里的按钮点击判定与配色

在 MT5 指标里给副图塞一排按钮,核心是先拿到子窗口编号再做命中检测。下面这段逻辑用 ChartWindowFind(0, subwindow_shortname) 锁定指标所在子窗口,再用 ObjectFind 比对点击对象是否落在同一窗口,且名称带 prefix+"button_" 前缀才认作有效点击。 命中后双层循环遍历 BUTTON_COLUMNS 与 BUTTON_ROWS,把被点的按钮 button_states 置 true、其余置 false,函数返回 true;没点中按钮则返回 false。这样一次鼠标事件只会有唯一一个按钮处于激活态,避免多选歧义。 ChangeButtonColorOnClick 负责视觉反馈:遍历所有按钮,激活的用 ObjectSetInteger 改 OBJPROP_BGCOLOR 为 clicked_background_color,未激活的还原成 background_color。点击响应写在 CHARTEVENT_OBJECT_CLICK 分支里,InitializeButtonStates 通过后调它,再 ChartRedraw 强制重绘。 外汇与贵金属图表挂这类交互指标属高风险环境,按钮逻辑仅改变界面状态,不涉及下单;实盘前请在模拟账户点一遍验证命中率和重绘延迟。

MQL5 / C++
  subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname);
class=class="str">"cmt">//--- If a button in the indicator subwindow has been clicked
  if(ObjectFind(class="num">0,clicked_object)==subwindow_number && StringFind(clicked_object,prefix+"button_",class="num">0)>=class="num">0)
    {
      class=class="str">"cmt">//--- Determine the clicked button
      for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++)
        {
         for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++)
           {
            class=class="str">"cmt">//--- Determine the state of all buttons
            if(clicked_object==button_object_names[j][i])
               button_states[j][i]=true;
            else
               button_states[j][i]=false;
           }
        }
      class=class="str">"cmt">//---
      class="kw">return(true);
    }
class=class="str">"cmt">//---
  class="kw">return(false);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Changing the button class="type">color in case of click                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ChangeButtonColorOnClick()
  {
  for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++)
    {
     for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++)
       {
        class=class="str">"cmt">//--- If the button has been clicked, it is set a distinctive class="type">color
        if(button_states[j][i])
           ObjectSetInteger(class="num">0,button_object_names[j][i],OBJPROP_BGCOLOR,clicked_background_color);
        class=class="str">"cmt">//--- Set the standard class="type">color to the unclicked button
        else
           ObjectSetInteger(class="num">0,button_object_names[j][i],OBJPROP_BGCOLOR,background_color);
       }
    }
  }
class=class="str">"cmt">//--- Tracking left mouse button clicks on a graphical object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      class=class="str">"cmt">//--- If the button has been clicked
      if(InitializeButtonStates(sparam))
        {
         class=class="str">"cmt">//--- Set button colors
         ChangeButtonColorOnClick();
        }
      class=class="str">"cmt">//--- Refresh the chart
      ChartRedraw();
      class="kw">return;
     }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Deinitialization                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
  if(reason==REASON_REMOVE ||   class=class="str">"cmt">// If the indicator has been deleted from the chart or

「重编译时的资源回收与定时清理」

EA 在 MT5 里被重新编译时,系统会触发 REASON_RECOMPILE 事件。此时若不主动清理,旧按钮对象和定时器可能残留在图表上,造成鼠标事件冲突或界面脏数据。 下面这段处理在 recompile 分支里先杀掉定时器 EventKillTimer(),再调用 DeleteButtons() 遍历 BUTTON_COLUMNS × BUTTON_ROWS 的二维数组清掉所有按钮。随后恢复图表鼠标滚动、关闭鼠标移动事件追踪,最后 ChartRedraw() 刷新一次。

MQL5 / C++
   if(reason==REASON_RECOMPILE) class=class="str">"cmt">// the program has been recompiled
     {
      class=class="str">"cmt">//--- Deactivate the timer
      EventKillTimer();
      class=class="str">"cmt">//--- Delete the objects
      DeleteButtons();
      class=class="str">"cmt">//--- Enable chart scrolling
      ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true);
      class=class="str">"cmt">//--- Disable tracking of mouse events
      ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,false);
      class=class="str">"cmt">//--- Refresh the chart
      ChartRedraw();
     }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Deleting all buttons                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DeleteButtons()
  {
   for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++)
     for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++)
       DeleteObjectByName(button_object_names[j][i]);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Deleting the object by name                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DeleteObjectByName(class="type">class="kw">string object_name)
  {
class=class="str">"cmt">//--- If such object exists
   if(ObjectFind(class="num">0,object_name)>=class="num">0)
     {
      class=class="str">"cmt">//--- If an error occurred when deleting, print the relevant message
      if(!ObjectDelete(class="num">0,object_name))
        Print("Error("+IntegerToString(GetLastError())+") when deleting the object!");
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Check whether tracking of mouse events is enabled
   CheckChartEventMouseMove();
  }
DeleteButtons 用双层 for 循环按名删除,底层 DeleteObjectByName 先 ObjectFind 确认对象存在(返回值 >=0),再 ObjectDelete;删除失败则用 GetLastError 把错误码打到日志。 OnTimer 里只做一件事:调 CheckChartEventMouseMove() 轮询鼠标追踪状态。若你做贵金属或外汇的悬浮按钮面板,把定时器周期设短一点(如 100ms)可能让界面响应更跟手,但会略增 CPU 占用,属于可实测调参项。外汇与贵金属杠杆高,脚本异常残留对象不影响行情本身,但可能干扰你的手动下单点击。

MQL5 / C++
   if(reason==REASON_RECOMPILE) class=class="str">"cmt">// the program has been recompiled
     {
      class=class="str">"cmt">//--- Deactivate the timer
      EventKillTimer();
      class=class="str">"cmt">//--- Delete the objects
      DeleteButtons();
      class=class="str">"cmt">//--- Enable chart scrolling
      ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true);
      class=class="str">"cmt">//--- Disable tracking of mouse events
      ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,false);
      class=class="str">"cmt">//--- Refresh the chart
      ChartRedraw();
     }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Deleting all buttons                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DeleteButtons()
  {
   for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++)
     for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++)
       DeleteObjectByName(button_object_names[j][i]);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Deleting the object by name                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DeleteObjectByName(class="type">class="kw">string object_name)
  {
class=class="str">"cmt">//--- If such object exists
   if(ObjectFind(class="num">0,object_name)>=class="num">0)
     {
      class=class="str">"cmt">//--- If an error occurred when deleting, print the relevant message
      if(!ObjectDelete(class="num">0,object_name))
        Print("Error("+IntegerToString(GetLastError())+") when deleting the object!");
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Check whether tracking of mouse events is enabled
   CheckChartEventMouseMove();
  }

把工具请下神坛

随附的 TestButtons.mq5 指标体积仅 20.47 KB,下载解包后直接拖进 MT5 指标窗口就能看到子窗口里排布的按钮。它本质上只是用 OBJ_BUTTON 在独立窗口里接 ChartEvent 的样例,别把它当成什么完整框架。 想继续玩下去,可以照着原文思路给按钮绑跳转:比如点一下弹到对应品种的主图,或者拉出二级菜单。按钮数量随便加,MQL5 里每多一个 CreateButton() 调用就是多一个交互入口。 外汇和贵金属市场高杠杆、滑点无常,这类 GUI 只解决操作效率,不替你判断方向。下回开盘前把它加载到 XAUUSD 的 M5 图,亲手点两下,比看十篇说明都实在。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到指标子窗口的控件状态,你专注决策而不是手写事件分支。

常见问题

OBJ_EDIT 可设只读并保留自定义文本与尖角边界,在子窗口排布多按钮时比标准按钮更可控,也方便统一加前缀防重名。
小布盯盘的 AIGC 辅助可基于你的品种页输出控件骨架,但具体悬停色阶与行列布局仍需你在 MQL5 里按本文函数改造。
它给每个图形对象名加指标缩写前缀,避免不同 EA 或指标的对象名撞车导致误删、移位或替换。
在 OnChartEvent 里取鼠标坐标,比对按钮的坐标与大小数组,命中对应二维索引即触发颜色变暗与 tooltip 显示。
先取子窗口宽高与边距,再按行列数均分得到每个单元格的起止点,按钮尺寸留间隙,二维数组第一维为行、第二维为列。