利用 MQL5 的交互式 GUI 改进您的交易图表(第一部分):可移动 GUI(I)·综合运用
(3/3)·从图表事件到多元素拖拽,一篇收束前两部分碎片知识,直接造出能用的面板
用鼠标拖拽图表标签的底层实现
在 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 对象只影响显示层,不改交易逻辑,但误写坐标可能造成视觉错位,实盘前务必在策略测试器外的真实图表手动拖一遍验证。
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 防止图表跟着跑。外汇与贵金属波动剧烈,这类自定义面板仅作辅助,实盘仍属高风险。
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 分钟图拖一根趋势线就能验证:按住线的时候滚轮不会动图表,松手后恢复正常。外汇与贵金属品种波动大,这类交互锁只在本地图表生效,不改服务器订单逻辑,风险限于操作误触。
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 的代码吃透,等续篇一到就能直接接上改,不用从头绕。