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

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

(3/3)· 当图表要挂三个可移动仪表板,复制全局变量会让代码瞬间失控,本篇给出精简解法

含代码示例偏理论 第 3/3 篇

很多交易者写 EA 时图省事,直接在 .mq5 里硬抄可移动面板代码,每加一个仪表板就多六个全局变量。等挂到第三个,改一处逻辑要翻遍文件,编译错了都难定位。类的意义就是把这种混乱收进可复用的壳里。

「把仪表板逻辑塞进 .mqh 类里」

在 MT5 里做多仪表板 EA,最忌主文件越写越肿。把创建和拖拽逻辑收进 .mqh 头文件,主 .mq5 只留两个实例声明,后续维护会轻很多。 先动类内部可见性:原来公开暴露的 string Name 在主文件里根本用不到,只服务于头文件内的对象创建,因此把它从 public 改成 private。这一步只是换了个修饰符位置,不影响任何运行时行为。 接着在类里加一个公开方法 CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize),把原先散落在主文件的 ObjectCreate 代码整段搬进全局空间的函数定义。主文件侧只需 #include "GUI_Movable.mqh",然后声明 GUI_Movable Dashboard1、Dashboard2 两个实例。 OnInit() 里分别调 Dashboard1.CreateDashboard(...) 与 Dashboard2.CreateDashboard(...),传入名称与像素坐标、尺寸,返回 INIT_SUCCEEDED 即初始化完成;OnChartEvent() 里把 id、lparam、dparam、sparam 原样甩给两个实例的 OnEvent(),各自按类内逻辑独立响应鼠标。 实测同一图表跑两个可移动矩形标签,先创建的 Dashboard1 在图层层级上低于后创建的 Dashboard2——点选遮挡时这个顺序会直接决定哪个先被命中。外汇与贵金属图表挂 EA 属高风险操作,多对象事件处理前建议在策略测试器先跑一遍。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class GUI_Movable                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class GUI_Movable
  {
class="kw">private:
   class="type">int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
   class="type">bool              movingState;
class="kw">public:
GUI_Movable() : previousMouseState(class="num">0), mlbDownX(class="num">0), mlbDownY(class="num">0), mlbDownXDistance(class="num">0), mlbDownYDistance(class="num">0), movingState(class="kw">false), Name(NULL) {};
   class="type">class="kw">string            Name;
   class="type">void              OnEvent(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class GUI_Movable                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class GUI_Movable
  {
class="kw">private:
   class="type">int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
   class="type">bool              movingState;
   class="type">class="kw">string            Name;
class="kw">public:
GUI_Movable() : previousMouseState(class="num">0), mlbDownX(class="num">0), mlbDownY(class="num">0), mlbDownXDistance(class="num">0), mlbDownYDistance(class="num">0), movingState(class="kw">false), Name(NULL) {};
   class="type">void              OnEvent(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam);
  };
class="type">class="kw">string            Name;
class="kw">public:
   class="type">void              OnEvent(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam);
   class="type">void              CreateDashboard(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GUI_Movable::CreateDashboard(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize) {
   class=class="str">"cmt">//Create a Rectangle Label Object at(time1, price1)=(class="num">0,class="num">0)

用矩形标签搭可拖拽面板

想在 MT5 图表上放一块能跟鼠标跑的监控面板,核心是先建 OBJ_RECTANGLE_LABEL 再喂坐标和尺寸。下面这段初始化把两个面板分别钉在距左缘 100px、距上缘 100px 和 350px 的位置,宽高都设 200px,相当于在图表上画出两块 200×200 的画布。 开启 CHART_EVENT_MOUSE_MOVE 是关键一步,不打开就收不到鼠标移动事件,面板永远动不了。类里用 previousMouseState、mlbDownX/Y 等私有变量记录按下左键那一刻的鼠标点和标签距离,以此算拖拽偏移。 把代码丢进 EA 的 OnInit 和 OnChartEvent,编译挂到 EURUSD 或 XAUUSD 图表上,就能看到两个方块跟随鼠标拖动。外汇和贵金属波动剧烈、杠杆风险高,这类面板只做信息呈现,别当成下单信号。

MQL5 / C++
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, xDis);
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, yDis);
class=class="str">"cmt">//Set XSize to 200px i.e. Width of Rectangle Label
ObjectSetInteger(class="num">0, name, OBJPROP_XSIZE, xSize);
class=class="str">"cmt">//Set YSize to 200px i.e. Height of Rectangle Label
ObjectSetInteger(class="num">0, name, OBJPROP_YSIZE, ySize);
class=class="str">"cmt">//Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, true);
class=class="str">"cmt">//Give dashboard&class="macro">#x27;s name to the class instance
Name = name;
class=class="str">"cmt">//Redraw Chart
ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "GUI_Movable.mqh"
GUI_Movable Dashboard1;
GUI_Movable Dashboard2;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   Dashboard1.CreateDashboard("Dashboard1", class="num">100, class="num">100, class="num">200, class="num">200);
   Dashboard2.CreateDashboard("Dashboard2", class="num">100, class="num">350, class="num">200, class="num">200);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
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)
  {
   Dashboard1.OnEvent(id, lparam, dparam, sparam);
   Dashboard2.OnEvent(id, lparam, dparam, sparam);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class GUI_Movable                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class GUI_Movable
  {
class="kw">private:
   class="type">int            previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
   class="type">bool           movingState;
   class="type">class="kw">string         Name;
class="kw">public:
GUI_Movable() : previousMouseState(class="num">0), mlbDownX(class="num">0), mlbDownY(class="num">0), mlbDownXDistance(class="num">0), mlbDownYDistance(class="num">0), movingState(class="kw">false), Name(NULL) {};
   class="type">void           OnEvent(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam);

◍ 用鼠标拖拽接管面板坐标

在 MT5 里做可拖拽盯盘面板,核心是把图表鼠标事件接进自定义类的 OnEvent。只处理 CHARTEVENT_MOUSE_MOVE,其他事件直接放过,否则会拖慢图表响应。 左键落点的捕获逻辑很关键:previousMouseState 为 0 且 MouseState 为 1,代表这是鼠标左键首次按下。此时把当前 X、Y 以及面板初始的 XDistance、YDistance 存进 mlbDown 系列变量,作为拖拽基准。 命中判定用边界框:X 在 [XDistance, XDistance+XSize]、Y 在 [YDistance, YDistance+YSize] 内才把 movingState 置真。初始化时 XDistance/YDistance 通常为 100,XSize/YSize 为 200,所以面板默认占住图表左上 100,100 到 300,300 的区域。 拖拽中先 ChartSetInteger(0, CHART_MOUSE_SCROLL, false) 锁掉图表自身滚动,再用 ObjectSetInteger 把 XDistance 改成 mlbDownXDistance + X - mlbDownX。外汇与贵金属波动剧烈,这类自绘面板仅作辅助,实盘前请在模拟盘验证交互是否跟手。

MQL5 / C++
class="type">void GUI_Movable::OnEvent(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam)
  {
   class=class="str">"cmt">//Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
   if(id == CHARTEVENT_MOUSE_MOVE)
     {
      class=class="str">"cmt">//define X, Y, XDistance, YDistance, XSize, YSize
      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 = Name;
      class="type">int XDistance = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_XDISTANCE); class=class="str">"cmt">//Should be class="num">100 initially as we set it in OnInit()
      class="type">int YDistance = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_YDISTANCE); class=class="str">"cmt">//Should be class="num">100 initially as we set it in OnInit()
      class="type">int XSize = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_XSIZE); class=class="str">"cmt">//Should be class="num">200 initially as we set it in OnInit()
      class="type">int YSize = (class="type">int)ObjectGetInteger(class="num">0, name, OBJPROP_YSIZE); class=class="str">"cmt">//Should be class="num">200 initially as we set it in OnInit()
      if(previousMouseState == class="num">0 && MouseState == class="num">1) class=class="str">"cmt">//Check if this was the MLB first click
        {
         mlbDownX = X; class=class="str">"cmt">//Set mlbDownX(Variable that stores the initial MLB X location) equal to the current X
         mlbDownY = Y; class=class="str">"cmt">//Set mlbDownY(Variable that stores the initial MLB Y location) equal to the current Y
         mlbDownXDistance = XDistance; class=class="str">"cmt">//Set mlbDownXDistance(Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
         mlbDownYDistance = YDistance; class=class="str">"cmt">//Set mlbDownYDistance(Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
         if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) class=class="str">"cmt">//Check if the click was on the dashboard
           {
            movingState = true; class=class="str">"cmt">//If yes the set movingState to True
           }
        }
      if(movingState)class=class="str">"cmt">//if movingState is true, Update the Dashboard position
        {
         ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, class="kw">false);class=class="str">"cmt">//Restrict Chart to be moved by Mouse
         ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);class=class="str">"cmt">//Update XDistance to: mlbDownXDistance + (X - mlbDownX)

「拖完面板记得把图表滚动还回去」

鼠标左键松开后,代码里把 movingState 重置为 false,同时用 ChartSetInteger(0, CHART_MOUSE_SCROLL, true) 把图表滚动权限交还 MT5。若不恢复这项,用户拖完自定义面板会发现滚轮缩放和拖拽平移全部失效,体验直接崩掉。 前面的拖拽逻辑里,ObjectSetInteger 动态调整 OBJPROP_YDISTANCE,新值为 mlbDownYDistance + (Y - mlbDownY),即按下时的纵向偏移加上鼠标实时位移差,再调 ChartRedraw(0) 重绘。这套计算只改标签对象坐标,不动价格坐标,外汇或贵金属图表上叠这类 UI 有滑点误触可能,实盘前先在模拟盘验证。 CreateDashboard 负责初始化矩形标签:ObjectCreate 建 OBJ_RECTANGLE_LABEL,XY 距离和尺寸由入参 xDis/yDis/xSize/ySize 控制,例如传 100/100/200/200 就是距左上角各 100 像素、宽高各 200 像素的面板。CHART_EVENT_MOUSE_MOVE 必须置 true,否则收不到鼠标移动事件,拖动根本不会触发。

MQL5 / C++
      ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);class=class="str">"cmt">//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
      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">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GUI_Movable::CreateDashboard(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize) {
  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, xDis);
  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, yDis);
  class=class="str">"cmt">//Set XSize to 200px i.e. Width of Rectangle Label
  ObjectSetInteger(class="num">0, name, OBJPROP_XSIZE, xSize);
  class=class="str">"cmt">//Set YSize to 200px i.e. Height of Rectangle Label
  ObjectSetInteger(class="num">0, name, OBJPROP_YSIZE, ySize);
  class=class="str">"cmt">//Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
  ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, true);
  class=class="str">"cmt">//Give dashboard&class="macro">#x27;s name to the class instance
  Name = name;
  class=class="str">"cmt">//Redraw Chart
  ChartRedraw();
}

别急着下结论

把已有 EA 或指标里的仪表板改成可拖拽,其实只差几行代码:包含 GUI_Movable.mqh、建一个类实例、把面板名字绑上去,鼠标就能拉动。原文示例里 clsSample 从私有继承改成 public clsGUI 后,CreatePanel 的私有访问报错立刻消失,这是评论区实测过的修法。 继承方式直接决定子类能不能碰父类方法:public 继承保留 is-a 关系,protected 和 private 会把父类成员降级,写面板子类前先定好限定符,否则编译期就卡住。 外汇与贵金属波动剧烈、杠杆风险高,这类 GUI 改造只解决交互效率,不替代任何交易逻辑。开 MT5 把那两行 class 定义切换一下,体会访问权限的差异,比看十遍说明来得实在。

MQL5 / C++
class clsSample : clsGUI
class clsSample : class="kw">public clsGUI
把面板状态交给小布盯盘看
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多面板占用与刷新延迟,你只管调策略逻辑。

常见问题

单独 .mqh 让类可跨 EA 和指标复用,主文件只留编译入口,管理负担更低,改界面逻辑不必动交易核心。
目前小布盯盘内置的是诊断视图,不支持外接自定义面板;但你可用它核对面板品种与周期是否和实盘一致。
只要每个实例有独立 ID 与坐标边界,事件分发按 ChartEvent 的 id 区分,通常不会冲突,但需注意拖拽阈值重叠。
可以,虚函数适合做基类统一接口,子类重写绘制与交互;不过偏理论层面,初版用普通方法更易排错。