利用 MQL5 的交互式 GUI 改进您的交易图表(第一部分):可移动 GUI(I)·综合运用
🖱️

利用 MQL5 的交互式 GUI 改进您的交易图表(第一部分):可移动 GUI(I)·综合运用

(3/3)·从图表事件到多元素拖拽,一篇收束前两部分碎片知识,直接造出能用的面板

案例拆解 第 3/3 篇
多数交易者写了 EA 却从没碰过 OnChartEvent,图表还是死板的静态画线。把鼠标拖拽、按键、对象点击接进逻辑,面板才会真正听你指挥。本篇把前两部分拆开的概念拼成完整可移动 GUI。

用鼠标拖拽图表标签的底层实现

在 MT5 里想让矩形标签跟着鼠标走,核心是在 OnChartEvent 里捕获按下与移动的差值,再写回 OBJPROP_XDISTANCE / OBJPROP_YDISTANCE。下面这段逻辑里,X - mlbDownX 和 Y - mlbDownY 就是鼠标相对初次左键按下点的位移量,注释里虽把 Y 行误写成 Mouse X,但实际代表的是纵向偏移。 当 MouseState 回归 0(左键松开),代码把 movingState 置 false 并恢复 CHART_MOUSE_SCROLL 为 true,意味着拖拽结束、图表滚轮缩放重新可用。若不做这步,用户松手后图表可能卡在不可滚动状态。 OnInit 中先以 ObjectCreate 建一个名为 TestRectangle 的 OBJ_RECTANGLE_LABEL,锚点传 (0,0);随后把 XDISTANCE、YDISTANCE 都设 100,XSIZE、YSIZE 设 200,等于在窗口左上角偏移 100px 处画了 200×200 的方块。同时开 CHART_EVENT_MOUSE_MOVE 才能让后续移动事件进回调。 全局变量 mlbDownX / mlbDownY 记录按下瞬间坐标,mlbDownXDistance / mlbDownYDistance 记录对象初始距离,movingState 标记拖拽中。外汇与贵金属图表高频跳动,这类 GUI 对象只影响显示层,不改交易逻辑,但误写坐标可能造成视觉错位,实盘前务必在策略测试器外的真实图表手动拖一遍验证。

MQL5 / C++
ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
ChartRedraw(class="num">0);
}
X - mlbDownX class=class="str">"cmt">// Change in Mouse X Position form the initial click
and
Y - mlbDownY class=class="str">"cmt">// Change in Mouse X Position form the initial click
if(MouseState == class="num">0)
  {
  movingState = class="kw">false;
  ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
  class=class="str">"cmt">//Set the name of the rectangle as "TestRectangle"
  class="type">class="kw">string name = "TestRectangle";
  class=class="str">"cmt">//Create a Rectangle Label Object at(time1, price1)=(class="num">0,class="num">0)
  ObjectCreate(class="num">0, name, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0);
  class=class="str">"cmt">//Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
  ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, class="num">100);
  class=class="str">"cmt">//Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
  ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, class="num">100);
  class=class="str">"cmt">//Set XSize to 200px i.e. Width of Rectangle Label
  ObjectSetInteger(class="num">0, name, OBJPROP_XSIZE, class="num">200);
  class=class="str">"cmt">//Set YSize to 200px i.e. Height of Rectangle Label
  ObjectSetInteger(class="num">0, name, OBJPROP_YSIZE, class="num">200);
class=class="str">"cmt">//Set Chart class="kw">property CHART_EVENT_MOUSE_DOWN to true
  ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, true);
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
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">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//Declare some global variable that will be used in the OnChartEvent() function
class="type">int previousMouseState = class="num">0;
class="type">int mlbDownX = class="num">0;
class="type">int mlbDownY = class="num">0;
class="type">int mlbDownXDistance = class="num">0;
class="type">int mlbDownYDistance = class="num">0;
class="type">bool movingState = class="kw">false;
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用鼠标拖拽面板的坐标捕获逻辑

在 MT5 里做可拖拽的盯盘面板,核心不是画图,而是先拦住鼠标移动事件。OnChartEvent 里只认 CHARTEVENT_MOUSE_MOVE,其余图表交互一律不响应,否则你的面板会被缩放和滚动干扰。 下面这段代码把矩形面板的初始位置读出来:XDistance、YDistance 在 OnInit 里设过 100,XSize、YSize 设过 200,所以首次移动时这四个值应当分别是 100、100、200、200,开 MT5 按 F4 编译后下断点能看到。 判定拖拽起点靠鼠标状态跳变:previousMouseState 为 0 且 MouseState 为 1,说明左键刚按下。此时记录 mlbDownX/Y 与对应的 Distance 初值,再判断光标是否落在 XDistance~XDistance+XSize、YDistance~YDistance+YSize 的矩形内,命中才把 movingState 置真。 拖拽过程中每帧用 mlbDownXDistance + (X - mlbDownX) 重算 XDistance,Y 方向同理,同时关掉 CHART_MOUSE_SCROLL 防止图表跟着跑。外汇与贵金属波动剧烈,这类自定义面板仅作辅助,实盘仍属高风险。

MQL5 / C++
class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long& lparam, class="kw">const class="type">class="kw">double& dparam, class="kw">const class="type">class="kw">string& sparam)
  {
  if(id == CHARTEVENT_MOUSE_MOVE)
    {
      class="type">int X = (class="type">int)lparam;
      class="type">int Y = (class="type">int)dparam;
      class="type">int MouseState = (class="type">int)sparam;
      class="type">class="kw">string name = "TestRectangle";
      class="type">int XDistance = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_XDISTANCE);
      class="type">int YDistance = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_YDISTANCE);
      class="type">int XSize = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_XSIZE);
      class="type">int YSize = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_YSIZE);
      if(previousMouseState == class="num">0 && MouseState == class="num">1)
        {
        mlbDownX = X;
        mlbDownY = Y;
        mlbDownXDistance = XDistance;
        mlbDownYDistance = YDistance;
        if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
          {
          movingState = true;
          }
        }
      if(movingState)
        {
        ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, class="kw">false);
        ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
        ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
        }
    }
  }

「用鼠标拖拽对象时锁死图表滚动」

在 MT5 自定义指标或 EA 里做图形交互,常遇到一个问题:按住鼠标左键拖对象时,图表也跟着平移或缩放,体验很糟。上面这段逻辑就是专门处理这个事的——当检测到鼠标左键按下(MouseState 非 0),立刻把 CHART_MOUSE_SCROLL 设为 false,锁住图表滚动;松开后(MouseState == 0)再恢复成 true。 核心就三行动作:ChartRedraw(0) 强制重绘让状态立刻生效;movingState 在松开时归位 false;ChartSetInteger(0, CHART_MOUSE_SCROLL, ...) 控制图表是否响应滚轮与拖拽平移。previousMouseState 放在末尾更新,下一帧才能和新的 MouseState 比出边沿触发。 你在 OnInit 里挂好 ChartEvent 的鼠标消息,把这段塞进 OnChartEvent 的 CHARTEVENT_MOUSE_MOVE 分支,编译挂到 EURUSD 15 分钟图拖一根趋势线就能验证:按住线的时候滚轮不会动图表,松手后恢复正常。外汇与贵金属品种波动大,这类交互锁只在本地图表生效,不改服务器订单逻辑,风险限于操作误触。

MQL5 / C++
      ChartRedraw(class="num">0); class=class="str">"cmt">//Redraw Chart
      }
      if(MouseState == class="num">0)class=class="str">"cmt">//Check if MLB is not pressed
      {
      movingState = class="kw">false;class=class="str">"cmt">//set movingState again to class="kw">false
      ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, true);class=class="str">"cmt">//allow the cahrt to be moved again
      }
      previousMouseState = MouseState;class=class="str">"cmt">//update the previousMouseState at the end so that we can use it next time and copare it with new value
      }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+

把这条线请下神坛

这一篇里搭出的可移动仪表盘只是 MQL5 GUI 的入门骨架,真正能用的多窗口管理、元素自定义和拖拽联动都还没落地。作者放出的 Movable_Dashboard_MQL5.mq5 只有 5.27 KB,逻辑极简,直接编译跑一遍就能看清坐标捕获和 OBJ_RECTANGLE 绑定的底子。 下一部分预告会在数天内补上单图表多 GUI 和流水线式搭建指南,到时候开 MT5 把示例文件连同新模块对照读,比反复看文字省事。外汇和贵金属行情波动剧烈,这类自绘面板只是辅助观察,下单决策仍要自己扛风险。 现在先把这份 5.27 KB 的代码吃透,等续篇一到就能直接接上改,不用从头绕。

让小布替你跑这套
这些诊断与面板原型思路,小布盯盘的 AIGC 已内置,打开对应品种页即可看到交互式辅助层,你只需专注策略本身。

常见问题

前者在对象被拖动过程中持续触发,后者只是鼠标在对象上单击的瞬间事件;做可移动 GUI 主要依赖 DRAG 来实时更新坐标。
目前小布提供的是交互式辅助视图与诊断,不直接导出 EA 源码,但你可以参照它的面板布局反推自己的 GUI 结构。
EA 的初始化与事件循环对多数人更直观,指标事件写法相同但容易混淆生命周期,入门先用 EA 降低理解成本。
给每个对象独立的前缀命名,并在 OBJECT_DRAG 里只处理命中对象的 ID,层级用 ChartSetInteger 的 Z 轴顺序分开。