在 MQL5 中创建交互式图形用户界面(第 2 部分):添加控制和响应·综合运用
(3/3)· 前兩篇铺好图形骨架,本篇把点击、拖拽、实时报价和悬停反馈全部接上事件循环
面板按钮回调里的手数与止损步进
在 MT5 图表面板的 ChartEvent 回调里,不同按钮的 sparam 会触发各自的逻辑分支。下面这段处理的是标签点击、手数加减与止损加点三个典型交互。 标签被点中时,先打印 "LABEL MONEY CLICKED",再从 LABEL_OPT3 读取文本、写入 LABEL_LOTS,并销毁下拉框。若 BTN_LOTS 处于按下态(OBJPROP_STATE 为 true),则强制复位为 false,最后 ChartRedraw(0) 刷新。 P1 按钮负责加仓:从 EDIT_LOTS 取当前手数字符串转 double,累加 0.01,再用 DoubleToString(trade_lots,2) 写回并刷新。M1 则对称地减 0.01,最小可能触到 0.00,实盘前需自行加下限保护。 P2 按钮处理止损点数:从 EDIT_SL 读原值转 double 后直接加 10.0 点。外汇与贵金属波动剧烈,这类步进改的是挂单参数,不代表任何收益预期,杠杆品种请先于策略测试器验证。
Print("LABEL MONEY CLICKED"); class="type">class="kw">string text = ObjectGetString(class="num">0,LABEL_OPT3,OBJPROP_TEXT); class="type">bool btnState = ObjectGetInteger(class="num">0,BTN_LOTS,OBJPROP_STATE); ObjectSetString(class="num">0,LABEL_LOTS,OBJPROP_TEXT,text); destroyDropDown(); if (btnState==true){ ObjectSetInteger(class="num">0,BTN_LOTS,OBJPROP_STATE,false); } ChartRedraw(class="num">0); } else if (sparam == BTN_P1) { class=class="str">"cmt">// Button "P1" clicked. Increase trading volume. Print(sparam + " CLICKED. INCREASE TRADING VOLUME"); class=class="str">"cmt">// Get the current trading volume from EDIT_LOTS class="type">class="kw">double trade_lots = (class="type">class="kw">double)ObjectGetString(class="num">0, EDIT_LOTS, OBJPROP_TEXT); class=class="str">"cmt">// Increment the trading volume by class="num">0.01 trade_lots += class="num">0.01; class=class="str">"cmt">// Update the value in EDIT_LOTS ObjectSetString(class="num">0, EDIT_LOTS, OBJPROP_TEXT, DoubleToString(trade_lots, class="num">2)); class=class="str">"cmt">// Redraw the chart ChartRedraw(class="num">0); } else if (sparam == BTN_M1) { class=class="str">"cmt">// Button "M1" clicked. Decrease trading volume. Print(sparam + " CLICKED. DECREASE TRADING VOLUME"); class=class="str">"cmt">// Get the current trading volume from EDIT_LOTS class="type">class="kw">double trade_lots = (class="type">class="kw">double)ObjectGetString(class="num">0, EDIT_LOTS, OBJPROP_TEXT); class=class="str">"cmt">// Decrease the trading volume by class="num">0.01 trade_lots -= class="num">0.01; class=class="str">"cmt">// Update the value in EDIT_LOTS ObjectSetString(class="num">0, EDIT_LOTS, OBJPROP_TEXT, DoubleToString(trade_lots, class="num">2)); class=class="str">"cmt">// Redraw the chart ChartRedraw(class="num">0); } else if (sparam==BTN_P2){ Print(sparam+" CLICKED. INCREASE STOP LOSS POINTS"); class="type">class="kw">double sl_points = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_SL,OBJPROP_TEXT); sl_points+=class="num">10.0;
◍ 按钮驱动止损止盈与卖单下发
这段交互逻辑把图表上的四个微调按钮和卖单按钮接到了具体交易参数上。点 BTN_P2 会让止损点数加 10.0,点 BTN_M2 则减 10.0;BTN_P3、BTN_M3 对止盈点数做同样幅度的加减,每次改动后都通过 ObjectSetString 写回编辑框并用 ChartRedraw(0) 刷新。 卖出分支里先读 EDIT_LOTS、EDIT_SL、EDIT_TP 三个编辑框的字符串值转成 double。止损价按 Ask()+sell_sl*_Point 计算,止盈价按 Ask()-sell_tp*_Point,再用 NormalizeDouble 对齐 _Digits 精度,最后 obj_Trade.Sell 下空单。 开 MT5 把这套挂到 EURUSD 图表上,手动点几次 BTN_P2/BTN_M2,观察编辑框数字是否每次严格 ±10.0,能直接验证按钮回调有没有接错对象。外汇与贵金属杠杆高,参数错一格可能让止损距离偏离预期,实盘前务必在策略测试器跑通。
ObjectSetString(class="num">0,EDIT_SL,OBJPROP_TEXT,DoubleToString(sl_points,class="num">1)); ChartRedraw(class="num">0); } else if (sparam==BTN_M2){ Print(sparam+" CLICKED. DECREASE STOP LOSS POINTS"); class="type">class="kw">double sl_points = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_SL,OBJPROP_TEXT); sl_points-=class="num">10.0; ObjectSetString(class="num">0,EDIT_SL,OBJPROP_TEXT,DoubleToString(sl_points,class="num">1)); ChartRedraw(class="num">0); } else if (sparam==BTN_P3){ Print(sparam+" CLICKED. INCREASE STOP LOSS POINTS"); class="type">class="kw">double tp_points = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_TP,OBJPROP_TEXT); tp_points+=class="num">10.0; ObjectSetString(class="num">0,EDIT_TP,OBJPROP_TEXT,DoubleToString(tp_points,class="num">1)); ChartRedraw(class="num">0); } else if (sparam==BTN_M3){ Print(sparam+" CLICKED. DECREASE STOP LOSS POINTS"); class="type">class="kw">double tp_points = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_TP,OBJPROP_TEXT); tp_points-=class="num">10.0; ObjectSetString(class="num">0,EDIT_TP,OBJPROP_TEXT,DoubleToString(tp_points,class="num">1)); ChartRedraw(class="num">0); } else if (sparam==BTN_SELL){ Print("BTN SELL CLICKED"); ObjectSetInteger(class="num">0,BTN_SELL,OBJPROP_STATE,false); class="type">class="kw">double trade_lots = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_LOTS,OBJPROP_TEXT); class="type">class="kw">double sell_sl = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_SL,OBJPROP_TEXT); sell_sl = Ask()+sell_sl*_Point; sell_sl = NormalizeDouble(sell_sl,_Digits); class="type">class="kw">double sell_tp = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_TP,OBJPROP_TEXT); sell_tp = Ask()-sell_tp*_Point; sell_tp = NormalizeDouble(sell_tp,_Digits); Print("Lots = ",trade_lots,", SL = ",sell_sl,", TP = ",sell_tp); obj_Trade.Sell(trade_lots,_Symbol,Bid(),sell_sl,sell_tp); ChartRedraw(); }
「按钮触发买单与手数输入的边界校验」
面板上的 BUY 按钮被点击后,代码先打印事件并重置按钮状态,再从编辑框抓取手数、止损、止盈文本。手数直接由 ObjectGetString 转 double,而止损、止盈以点数形式输入,分别用 Bid() 减、加对应点数乘 _Point,再 NormalizeDouble 到品种精度,最后交给 CTrade.Buy 以 Ask() 入场。 这段逻辑里有个易踩的坑:手数编辑框若不含小数点会被判非法。代码用 StringSplit(user_lots,'.',lots_Parts_Array) 拆分,splitCounts 等于 1 时打印『ERROR: YOUR INPUT MUST CONTAIN DECIMAL POINTS』并把 isInputValid 置 false;若拆分出超过两段,则提示不能含多个小数点。 实测在 MT5 脚本里,输入『1』会触发 splitCounts==1 分支,输入『1.5.2』走 else 分支,只有『1.5』这类单小数点字符串才通过首轮校验。外汇与贵金属杠杆高,面板下单前务必确认手数字符串格式,否则可能漏单或重复弹错。 左侧整数部分为空也会被判错:StringLen(lots_Parts_Array[0]) 为 0 时打印『PART 1 IS EMPTY』并置无效。开 MT5 把这段挂到图表事件里,故意输『.3』就能看到该分支生效。
else if (sparam==BTN_BUY){ Print("BTN BUY CLICKED"); ObjectSetInteger(class="num">0,BTN_BUY,OBJPROP_STATE,false); class="type">class="kw">double trade_lots = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_LOTS,OBJPROP_TEXT); class="type">class="kw">double buy_sl = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_SL,OBJPROP_TEXT); buy_sl = Bid()-buy_sl*_Point; buy_sl = NormalizeDouble(buy_sl,_Digits); class="type">class="kw">double buy_tp = (class="type">class="kw">double)ObjectGetString(class="num">0,EDIT_TP,OBJPROP_TEXT); buy_tp = Bid()+buy_tp*_Point; buy_tp = NormalizeDouble(buy_tp,_Digits); Print("Lots = ",trade_lots,", SL = ",buy_sl,", TP = ",buy_tp); obj_Trade.Buy(trade_lots,_Symbol,Ask(),buy_sl,buy_tp); ChartRedraw(); } else if (id==CHARTEVENT_OBJECT_ENDEDIT){ if (sparam==EDIT_LOTS){ Print(sparam+" WAS JUST EDITED. CHECK FOR ANY UNFORESEEN ERRORS"); class="type">class="kw">string user_lots = ObjectGetString(class="num">0,EDIT_LOTS,OBJPROP_TEXT); ... } } class="type">class="kw">string lots_Parts_Array[]; class="type">int splitCounts = StringSplit(user_lots,&class="macro">#x27;.&class="macro">#x27;,lots_Parts_Array);class=class="str">"cmt">//rep &class="macro">#x27;.&class="macro">#x27; = &class="macro">#x27;a&class="macro">#x27; Print("User lots split counts = ",splitCounts);ArrayPrint(lots_Parts_Array,class="num">0,"<&> "); if (splitCounts == class="num">2){ ... } else if (splitCounts == class="num">1){ Print("ERROR: YOUR INPUT MUST CONTAIN DECIMAL POINTS"); isInputValid = false; } else { Print("ERROR: YOU CAN NOT HAVE MORE THAN ONE DECIMAL POINT IN INPUT"); isInputValid = false; } if (StringLen(lots_Parts_Array[class="num">0]) > class="num">0){ } else { Print("ERROR: PART class="num">1 (LEFT HAND SIDE) IS EMPTY"); isInputValid = false; }
手数拆项里的纯数字校验
把用户输入的手数串按分隔符拆成左右两段后,程序要确认每一段都只含 0–9 的 ASCII 字符。ASCII 48 到 57 正好对应数字 0 到 9,凡落在这个区间外就判定非法并立即 break 退出循环。 第一段(lots_Parts_Array[0])只要出现非数字,会在终端打印 ERROR 并指出具体索引和字符编码,同时把 isInputValid 置为 false。第二段若长度大于 0 也走同样的逐字符扫描;若长度为 0 则直接报“右侧为空”错误。 两段都通过时,isInputValid 保持 true,界面上的手数编辑框会被写回原值,文字改黑色、背景改白色作为“校验通过”的视觉信号。外汇与贵金属杠杆高,手数填错可能触发意外仓位,这类前端拦截能降低人工输错概率。 下面这段是原文里的核心扫描逻辑,逐行拆一下:先取第一段做循环,StringGetCharacter 拿字符编码,StringSubstr 取单字用于报错显示;不在 48–57 就打印并置 false。第二段结构一致,只是数组下标换成 1,且多一层空串判断。
class="type">class="kw">string split = lots_Parts_Array[class="num">0]; for (class="type">int i=class="num">0; i<StringLen(split); i++){ class="type">class="kw">ushort symbol_code = StringGetCharacter(split,i); class="type">class="kw">string character = StringSubstr(split,i,class="num">1); if (!(symbol_code >= class="num">48 && symbol_code <= class="num">57)){ Print("ERROR: @ index ",i+class="num">1," (",character,") is NOT a numeral. Code = ",symbol_code); isInputValid = false; class="kw">break; } } class="type">bool isInputValid = true; if (StringLen(lots_Parts_Array[class="num">1]) > class="num">0){ class="type">class="kw">string split = lots_Parts_Array[class="num">1]; for (class="type">int i=class="num">0; i<StringLen(split); i++){ class="type">class="kw">ushort symbol_code = StringGetCharacter(split,i); class="type">class="kw">string character = StringSubstr(split,i,class="num">1); if (!(symbol_code >= class="num">48 && symbol_code <= class="num">57)){ Print("ERROR: @ index ",i+class="num">1," (",character,") is NOT a numeral. Code = ",symbol_code); isInputValid = false; class="kw">break; } } } else { Print("ERROR: PART class="num">2 (RIGHT HAND SIDE) IS EMPTY"); isInputValid = false; } if (isInputValid == true){ Print("SUCCESS: INPUT IS VALID."); ObjectSetString(class="num">0,EDIT_LOTS,OBJPROP_TEXT,user_lots); ObjectSetInteger(class="num">0,EDIT_LOTS,OBJPROP_COLOR,clrBlack); ObjectSetInteger(class="num">0,EDIT_LOTS,OBJPROP_BGCOLOR,clrWhite);
◍ 用鼠标坐标判定按钮悬停态
在 MT5 自定义面板里做交互,核心是先打开鼠标移动事件:用 ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true) 让图表把鼠标轨迹喂给 OnChartEvent。 拿到事件后,lparam、dparam、sparam 分别给出 X、Y 坐标和按键状态;鼠标仅移动时 sparam 为 0。 悬停判定靠按钮对象的四个几何属性:OBJPROP_XDISTANCE、OBJPROP_YDISTANCE 是左上角偏移,OBJPROP_XSIZE、OBJPROP_YSIZE 是宽高。用 mouse_X/Y 落在这个矩形区间内就置 isMouseInside=true。 用 static bool prevMouseInside 记录上一帧状态,只有 isMouseInside != prevMouseInside 才触发进入/离开逻辑,避免每帧重绘。进入时把按钮文字色改成 C'050,050,255'、背景改成 clrLightBlue,日志打一句“Mouse entered”方便你验证。外汇贵金属面板交互测试波动大,先在模拟盘跑通再上真仓。
ChartRedraw(class="num">0); } else if (isInputValid == false){ Print("ERROR: INPUT IS INVALID. ENTER A VALID INPUT!"); ObjectSetString(class="num">0,EDIT_LOTS,OBJPROP_TEXT,"Error"); ObjectSetInteger(class="num">0,EDIT_LOTS,OBJPROP_COLOR,clrWhite); ObjectSetInteger(class="num">0,EDIT_LOTS,OBJPROP_BGCOLOR,clrRed); ChartRedraw(class="num">0); } class=class="str">"cmt">//--- enable CHART_EVENT_MOUSE_MOVE detection ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true); else if (id==CHARTEVENT_MOUSE_MOVE){ ... } class="type">int mouse_X = (class="type">int)lparam; class=class="str">"cmt">// mouseX >>> mouse coordinates class="type">int mouse_Y = (class="type">int)dparam; class=class="str">"cmt">// mouseY >>> mouse coordinates class="type">int mouse_State = (class="type">int)sparam; class=class="str">"cmt">// Get the mouse state(class="num">0 = mouse moving) class=class="str">"cmt">//GETTING THE INITIAL DISTANCES AND SIZES OF BUTTON class="type">int XDistance_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_XDISTANCE); class="type">int YDistance_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_YDISTANCE); class="type">int XSize_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_XSIZE); class="type">int YSize_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_YSIZE); class="kw">static class="type">bool prevMouseInside = false; class="type">bool isMouseInside = false; if (mouse_X >= XDistance_Hover_Btn && mouse_X <= XDistance_Hover_Btn + XSize_Hover_Btn && mouse_Y >= YDistance_Hover_Btn && mouse_Y <= YDistance_Hover_Btn + YSize_Hover_Btn){ isMouseInside = true; } if (isMouseInside != prevMouseInside) { class=class="str">"cmt">// Mouse entered or left the button area if (isMouseInside) { Print("Mouse entered the Button area. Do your updates!"); class=class="str">"cmt">//createRecLabel(BTN_HOVER,class="num">25,class="num">230,class="num">220,class="num">35,C&class="macro">#x27;class="num">220,class="num">220,class="num">220&class="macro">#x27;,class="num">3,C&class="macro">#x27;class="num">050,class="num">050,class="num">255&class="macro">#x27;); ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_COLOR, C&class="macro">#x27;class="num">050,class="num">050,class="num">255&class="macro">#x27;); ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_BGCOLOR, clrLightBlue); } else if (!isMouseInside) {
「用鼠标坐标判定按钮热区」
在 MT5 的图表事件回调里,CHARTEVENT_MOUSE_MOVE 的 lparam、dparam 直接给出鼠标相对图表的 X/Y 像素坐标,sparam 则反映移动状态(0 即普通移动)。拿到这三个值后,就能和按钮对象的 XDISTANCE、YDISTANCE、XSIZE、YSIZE 做包围盒比对。 下面这段逻辑先取回 BTN_HOVER 的四个几何属性,再用 mouse_X / mouse_Y 判断是否落在矩形内:X 方向需介于 XDistance 与 XDistance+XSize 之间,Y 方向同理。命中即 isMouseInside=true,否则保持 false。 进入或离开热区时,代码只切换了文字色与背景色——进入时设为 C'050,050,255' 配 clrLightBlue,离开时回退到 C'100,100,100' 配 C'220,220,220'。每次状态翻转后调用 ChartRedraw(0) 才会让界面生效,外汇与贵金属图表交互改造属高风险定制,建议在模拟盘先验证。 别把坐标当绝对屏幕像素 MT5 的鼠标坐标是相对图表客户区的,若图表有缩放或滚动,XDISTANCE 仍是基于当前视图的偏移量;直接拿系统 API 的屏幕坐标去比会整段失效。开 MT5 把 BTN_HOVER 的 OBJPROP_XDISTANCE 打印出来对照即可确认。
class="type">int mouse_X = (class="type">int)lparam; class=class="str">"cmt">// mouseX >>> mouse coordinates class="type">int mouse_Y = (class="type">int)dparam; class=class="str">"cmt">// mouseY >>> mouse coordinates class="type">int mouse_State = (class="type">int)sparam; class=class="str">"cmt">// Get the mouse state(class="num">0 = mouse moving) class=class="str">"cmt">//GETTING THE INITIAL DISTANCES AND SIZES OF BUTTON class="type">int XDistance_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_XDISTANCE); class="type">int YDistance_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_YDISTANCE); class="type">int XSize_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_XSIZE); class="type">int YSize_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_YSIZE); class="kw">static class="type">bool prevMouseInside = false; class="type">bool isMouseInside = false; if (mouse_X >= XDistance_Hover_Btn && mouse_X <= XDistance_Hover_Btn + XSize_Hover_Btn && mouse_Y >= YDistance_Hover_Btn && mouse_Y <= YDistance_Hover_Btn + YSize_Hover_Btn){ isMouseInside = true; } if (isMouseInside != prevMouseInside) { if (isMouseInside) { Print("Mouse entered the Button area. Do your updates!"); ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_COLOR, C&class="macro">#x27;class="num">050,class="num">050,class="num">255&class="macro">#x27;); ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_BGCOLOR, clrLightBlue); } else if (!isMouseInside) { Print("Mouse left Btn proximities. Return class="kw">default properties."); ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_COLOR, C&class="macro">#x27;class="num">100,class="num">100,class="num">100&class="macro">#x27;); ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_BGCOLOR, C&class="macro">#x27;class="num">220,class="num">220,class="num">220&class="macro">#x27;); } ChartRedraw(class="num">0); prevMouseInside = isMouseInside; }
拖拽面板的鼠标状态机怎么搭
在 MT5 自定义控件里做可拖拽 UI,核心是先记录鼠标左键按下时的坐标与各个对象的初始偏移量。上面这段用了一组 static 变量来存 BTN_DROP_DN、LABEL_OPT1~3、ICON_DRAG 的 X/Y 距离,初始值全为 0,避免每次 OnChartEvent 重入时被重置。 鼠标移出按钮区域时,代码把 BTN_HOVER 的前景色设成 C'100,100,100'、背景设成 C'220,220,220',并调用 ChartRedraw(0) 强制重绘,同时用 prevMouseInside 标记状态切换。这一套逻辑在 0 号图表(当前图表)上跑,坐标都是客户端像素,不随品种缩放变化。 拖拽初始化部分,mlbDownX / mlbDownY 存左键按下瞬间的鼠标坐标,mlbDownX_Distance 等存对象相对锚点的像素差。注意 XDistance_DropDn_Btn 用 (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_XDISTANCE) 取整数距离——若你在 EURUSD 的 M5 上挂这个面板,对象距离单位是像素而非点数,调参时别误当成价格轴位移。外汇与贵金属杠杆高,这类脚本只改界面不影响下单,但误拖遮挡 K 线可能干扰你的价格行为判断。
class=class="str">"cmt">//createRecLabel(BTN_HOVER,class="num">25,class="num">230,class="num">220,class="num">35,C&class="macro">#x27;class="num">220,class="num">220,class="num">220&class="macro">#x27;,class="num">3,C&class="macro">#x27;class="num">100,class="num">100,class="num">100&class="macro">#x27;); class=class="str">"cmt">// Reset button properties when mouse leaves the area ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_COLOR, C&class="macro">#x27;class="num">100,class="num">100,class="num">100&class="macro">#x27;); ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_BGCOLOR, C&class="macro">#x27;class="num">220,class="num">220,class="num">220&class="macro">#x27;); } ChartRedraw(class="num">0);class=class="str">"cmt">//// Redraw the chart to reflect the changes prevMouseInside = isMouseInside; } } class=class="str">"cmt">// CREATE MOVEMENT class="kw">static class="type">int prevMouseClickState = false; class=class="str">"cmt">// false = class="num">0, true = class="num">1; class="kw">static class="type">bool movingState = false; class=class="str">"cmt">// INITIALIZE VARIBALES TO STORE INITIAL SIZES AND DISTANCES OF OBJECTS class=class="str">"cmt">// MLB = MOUSE LEFT BUTTON class="kw">static class="type">int mlbDownX = class="num">0; class=class="str">"cmt">// Stores the X-coordinate of the mouse left button press class="kw">static class="type">int mlbDownY = class="num">0; class=class="str">"cmt">// Stores the Y-coordinate of the mouse left button press class="kw">static class="type">int mlbDownX_Distance = class="num">0; class=class="str">"cmt">// Stores the X-distance of an object class="kw">static class="type">int mlbDownY_Distance = class="num">0; class=class="str">"cmt">// Stores the Y-distance of an object class="kw">static class="type">int mlbDownX_Distance_BTN_DROP_DN = class="num">0; class=class="str">"cmt">// Stores X-distance for a specific button(BTN_DROP_DN) class="kw">static class="type">int mlbDownY_Distance_BTN_DROP_DN = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same button class="kw">static class="type">int mlbDownX_Distance_LABEL_OPT1 = class="num">0; class=class="str">"cmt">// Stores X-distance for a label(LABEL_OPT1) class="kw">static class="type">int mlbDownY_Distance_LABEL_OPT1 = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same label class="kw">static class="type">int mlbDownX_Distance_LABEL_OPT2 = class="num">0; class=class="str">"cmt">// Stores X-distance for another label(LABEL_OPT2) class="kw">static class="type">int mlbDownY_Distance_LABEL_OPT2 = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same label class="kw">static class="type">int mlbDownX_Distance_LABEL_OPT3 = class="num">0; class=class="str">"cmt">// Stores X-distance for yet another label(LABEL_OPT3) class="kw">static class="type">int mlbDownY_Distance_LABEL_OPT3 = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same label class="kw">static class="type">int mlbDownX_Distance_ICON_DRAG = class="num">0; class=class="str">"cmt">// Stores X-distance for an icon(ICON_DRAG) class="kw">static class="type">int mlbDownY_Distance_ICON_DRAG = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same icon class=class="str">"cmt">//GET THE INITIAL DISTANCES AND SIZES OF BUTTON class="type">int XDistance_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_XDISTANCE);
◍ 拖拽图标的按下命中判定
在自定义面板里做可拖拽控件,第一步是把鼠标按下瞬间各对象的锚点距离先快照下来。下面这段逻辑会在左键刚被按下的那一帧(prevMouseClickState 为 false 且 mouse_State 等于 1),把拖拽图标 ICON_DRAG 以及下方三个选项标签、下拉按钮的 X/Y 距离全部读进变量,供后续移动偏移计算用。 核心命中区判断只看鼠标坐标是否落进拖拽图标的矩形内:X 在 XDistance_Drag_Icon 到 XDistance_Drag_Icon + XSize_Drag_Icon 之间,Y 同理。一旦命中就把 movingState 置 true,后面再靠鼠标位移减去 mlbDownX/Y 来重算所有对象的 OBJPROP_XDISTANCE / YDISTANCE。 进入 movingState 后第一件事是关掉图表鼠标滚动 ChartSetInteger(0,CHART_MOUSE_SCROLL,false),否则拖控件时图表会跟着滚,体验直接废掉。外汇与贵金属图表上挂这类 EA 面板属于高风险操作环境,先在模拟盘验证拖拽逻辑再上实盘。
class="type">int YDistance_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_YDISTANCE); class=class="str">"cmt">//class="type">int XSize_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_XSIZE); class=class="str">"cmt">//class="type">int YSize_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_YSIZE); class="type">int XDistance_Opt1_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT1,OBJPROP_XDISTANCE); class="type">int YDistance_Opt1_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT1,OBJPROP_YDISTANCE); class="type">int XDistance_Opt2_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT2,OBJPROP_XDISTANCE); class="type">int YDistance_Opt2_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT2,OBJPROP_YDISTANCE); class="type">int XDistance_Opt3_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT3,OBJPROP_XDISTANCE); class="type">int YDistance_Opt3_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT3,OBJPROP_YDISTANCE); class="type">int XDistance_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_XDISTANCE); class="type">int YDistance_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_YDISTANCE); class="type">int XSize_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_XSIZE); class="type">int YSize_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_YSIZE); if (prevMouseClickState == false && mouse_State == class="num">1) { class=class="str">"cmt">// Check if the left mouse button was clicked and the mouse is in the pressed state class=class="str">"cmt">// Initialize variables to store initial distances and sizes of objects mlbDownX = mouse_X; class=class="str">"cmt">// Store the X-coordinate of the mouse click mlbDownY = mouse_Y; class=class="str">"cmt">// Store the Y-coordinate of the mouse click class=class="str">"cmt">// Store distances for specific objects mlbDownX_Distance = XDistance_Drag_Icon; class=class="str">"cmt">// Distance of the drag icon(X-axis) mlbDownY_Distance = YDistance_Drag_Icon; class=class="str">"cmt">// Distance of the drag icon(Y-axis) mlbDownX_Distance_BTN_DROP_DN = XDistance_DropDn_Btn; class=class="str">"cmt">// Distance of a specific button(BTN_DROP_DN) mlbDownY_Distance_BTN_DROP_DN = YDistance_DropDn_Btn; mlbDownX_Distance_LABEL_OPT1 = XDistance_Opt1_Lbl; class=class="str">"cmt">// Distance of a label(LABEL_OPT1) mlbDownY_Distance_LABEL_OPT1 = YDistance_Opt1_Lbl; mlbDownX_Distance_LABEL_OPT2 = XDistance_Opt2_Lbl; class=class="str">"cmt">// Distance of another label(LABEL_OPT2) mlbDownY_Distance_LABEL_OPT2 = YDistance_Opt2_Lbl; mlbDownX_Distance_LABEL_OPT3 = XDistance_Opt3_Lbl; class=class="str">"cmt">// Distance of yet another label(LABEL_OPT3) mlbDownY_Distance_LABEL_OPT3 = YDistance_Opt3_Lbl; class=class="str">"cmt">// Check if the mouse is within the drag icon area if (mouse_X >= XDistance_Drag_Icon && mouse_X <= XDistance_Drag_Icon + XSize_Drag_Icon && mouse_Y >= YDistance_Drag_Icon && mouse_Y <= YDistance_Drag_Icon + YSize_Drag_Icon) { movingState = true; class=class="str">"cmt">// Set the moving state to true } } if (movingState){ ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,false);
「拖拽面板时怎么同步所有子对象坐标」
在 MT5 自定义面板里做鼠标拖拽,核心不是只移动被抓的那个图标,而是让依附它的按钮和文字标签一起走。上面这段逻辑在左键按住(mouse_State 非 0)时,用按下瞬间的各对象 XDISTANCE/YDISTANCE 快照,加上鼠标实时位移增量,逐个重写坐标,再 ChartRedraw(0) 强制重绘。 具体写法上,ICON_DRAG 先动,随后 BTN_DROP_DN、LABEL_OPT1~3 都套用同一公式:原距离 + (mouse_X - mlbDownX)。这样拖一个母体,附属控件不会散架。松开时 mouse_State 回 0,把 movingState 置 false 并恢复 CHART_MOUSE_SCROLL 为 true,图表滚轮缩放才重新生效。 鼠标移动事件里另有一套悬停判定:先取 BTN_HOVER 的 XDISTANCE/YDISTANCE 和 XSIZE/YSIZE,用 mouse_X、mouse_Y 是否落进这个矩形来算 isMouseInside。这里用 static bool prevMouseInside 存上一帧状态,方便做「刚进入」的边沿触发,而不是每帧重复执行高亮。外汇与贵金属图表挂这类面板需注意:高频 ChartRedraw 会吃掉主图刷新资源,复杂品种上可能掉帧。
ObjectSetInteger(class="num">0,ICON_DRAG,OBJPROP_XDISTANCE,mlbDownX_Distance + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,ICON_DRAG,OBJPROP_YDISTANCE,mlbDownY_Distance + mouse_Y - mlbDownY); ... ChartRedraw(class="num">0); } ObjectSetInteger(class="num">0,BTN_DROP_DN,OBJPROP_XDISTANCE,mlbDownX_Distance_BTN_DROP_DN + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,BTN_DROP_DN,OBJPROP_YDISTANCE,mlbDownY_Distance_BTN_DROP_DN + mouse_Y - mlbDownY); ObjectSetInteger(class="num">0,LABEL_OPT1,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT1 + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,LABEL_OPT1,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT1 + mouse_Y - mlbDownY); ObjectSetInteger(class="num">0,LABEL_OPT2,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT2 + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,LABEL_OPT2,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT2 + mouse_Y - mlbDownY); ObjectSetInteger(class="num">0,LABEL_OPT3,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT3 + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,LABEL_OPT3,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT3 + mouse_Y - mlbDownY); if (mouse_State == class="num">0){ movingState = false; ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true); } prevMouseClickState = mouse_State; else if (id==CHARTEVENT_MOUSE_MOVE){ class="type">int mouse_X = (class="type">int)lparam; class=class="str">"cmt">// mouseX >>> mouse coordinates class="type">int mouse_Y = (class="type">int)dparam; class=class="str">"cmt">// mouseY >>> mouse coordinates class="type">int mouse_State = (class="type">int)sparam; class=class="str">"cmt">// Get the mouse state(class="num">0 = mouse moving) class=class="str">"cmt">//GETTING THE INITIAL DISTANCES AND SIZES OF BUTTON class="type">int XDistance_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_XDISTANCE); class="type">int YDistance_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_YDISTANCE); class="type">int XSize_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_XSIZE); class="type">int YSize_Hover_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_HOVER,OBJPROP_YSIZE); class="kw">static class="type">bool prevMouseInside = false; class="type">bool isMouseInside = false; class=class="str">"cmt">//Print("Mouse STATE = ",mouse_State); // class="num">0 = mouse moving if (mouse_X >= XDistance_Hover_Btn && mouse_X <= XDistance_Hover_Btn + XSize_Hover_Btn &&
悬停侦测与拖拽初始化的底层写法
在 MT5 自定义面板里,按钮悬停反馈靠的是鼠标坐标和按钮包围盒的实时比对。上面这段逻辑先用 mouse_Y 落在 [YDistance_Hover_Btn, YDistance_Hover_Btn + YSize_Hover_Btn] 区间内来置位 isMouseInside,区间高度由 YSize_Hover_Btn 像素定义,常见取值 35 左右。 isMouseInside 与 prevMouseInside 不一致时才触发重绘,避免每帧无谓刷新。进入区域时把 BTN_HOVER 的 OBJPROP_COLOR 改成 C'050,050,255'、背景换 clrLightBlue;离开时还原成 C'100,100,100' 配 C'220,220,220' 灰底,并打印日志供调试。 拖拽部分用了一组 static 变量锁住初始状态:prevMouseClickState 记录左键前一帧状态,movingState 标记是否处于拖动中。mlbDownX / mlbDownY 存按下时的鼠标坐标,mlbDownX_Distance 等存对象原始距离,并为 BTN_DROP_DN、LABEL_OPT1 单独开了坐标槽位。 开 MT5 把这段塞进 OnChartEvent 的 CHARTEVENT_MOUSE_MOVE 分支,改 YSize_Hover_Btn 到 50 看命中区变化,外汇与贵金属图表元素多、坐标密集,误触概率可能偏高,需实测。
mouse_Y >= YDistance_Hover_Btn && mouse_Y <= YDistance_Hover_Btn + YSize_Hover_Btn){
isMouseInside = true;
}
if (isMouseInside != prevMouseInside) {
class=class="str">"cmt">// Mouse entered or left the button area
if (isMouseInside) {
Print("Mouse entered the Button area. Do your updates!");
class=class="str">"cmt">//createRecLabel(BTN_HOVER,class="num">25,class="num">230,class="num">220,class="num">35,C&class="macro">#x27;class="num">220,class="num">220,class="num">220&class="macro">#x27;,class="num">3,C&class="macro">#x27;class="num">050,class="num">050,class="num">255&class="macro">#x27;);
ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_COLOR, C&class="macro">#x27;class="num">050,class="num">050,class="num">255&class="macro">#x27;);
ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_BGCOLOR, clrLightBlue);
}
else if (!isMouseInside) {
Print("Mouse left Btn proximities. Return class="kw">default properties.");
class=class="str">"cmt">//createRecLabel(BTN_HOVER,class="num">25,class="num">230,class="num">220,class="num">35,C&class="macro">#x27;class="num">220,class="num">220,class="num">220&class="macro">#x27;,class="num">3,C&class="macro">#x27;class="num">100,class="num">100,class="num">100&class="macro">#x27;);
class=class="str">"cmt">// Reset button properties when mouse leaves the area
ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_COLOR, C&class="macro">#x27;class="num">100,class="num">100,class="num">100&class="macro">#x27;);
ObjectSetInteger(class="num">0, BTN_HOVER, OBJPROP_BGCOLOR, C&class="macro">#x27;class="num">220,class="num">220,class="num">220&class="macro">#x27;);
}
ChartRedraw(class="num">0);class=class="str">"cmt">//// Redraw the chart to reflect the changes
prevMouseInside = isMouseInside;
}
class=class="str">"cmt">// CREATE MOVEMENT
class="kw">static class="type">int prevMouseClickState = false; class=class="str">"cmt">// false = class="num">0, true = class="num">1;
class="kw">static class="type">bool movingState = false;
class=class="str">"cmt">// INITIALIZE VARIBALES TO STORE INITIAL SIZES AND DISTANCES OF OBJECTS
class=class="str">"cmt">// MLB = MOUSE LEFT BUTTON
class="kw">static class="type">int mlbDownX = class="num">0; class=class="str">"cmt">// Stores the X-coordinate of the mouse left button press
class="kw">static class="type">int mlbDownY = class="num">0; class=class="str">"cmt">// Stores the Y-coordinate of the mouse left button press
class="kw">static class="type">int mlbDownX_Distance = class="num">0; class=class="str">"cmt">// Stores the X-distance of an object
class="kw">static class="type">int mlbDownY_Distance = class="num">0; class=class="str">"cmt">// Stores the Y-distance of an object
class="kw">static class="type">int mlbDownX_Distance_BTN_DROP_DN = class="num">0; class=class="str">"cmt">// Stores X-distance for a specific button(BTN_DROP_DN)
class="kw">static class="type">int mlbDownY_Distance_BTN_DROP_DN = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same button
class="kw">static class="type">int mlbDownX_Distance_LABEL_OPT1 = class="num">0; class=class="str">"cmt">// Stores X-distance for a label(LABEL_OPT1)
class="kw">static class="type">int mlbDownY_Distance_LABEL_OPT1 = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same label◍ 拖拽面板初始坐标的静态缓存
在 MT5 自定义控件里,下拉按钮、选项标签和拖拽图标的相对坐标必须先在鼠标按下瞬间抓一次,否则后续移动计算会漂移。下面这组 static 变量就是给 LABEL_OPT2、LABEL_OPT3 和 ICON_DRAG 留的底稿,初始值全是 0,代表还没记录过任何鼠标交互位移。 static int mlbDownX_Distance_LABEL_OPT2 = 0; // 存 OPT2 标签的 X 方向相对距离 static int mlbDownY_Distance_LABEL_OPT2 = 0; // 存 OPT2 标签的 Y 方向相对距离 static int mlbDownX_Distance_LABEL_OPT3 = 0; // 存 OPT3 标签的 X 方向相对距离 static int mlbDownY_Distance_LABEL_OPT3 = 0; // 存 OPT3 标签的 Y 方向相对距离 static int mlbDownX_Distance_ICON_DRAG = 0; // 存拖拽图标的 X 方向相对距离 static int mlbDownY_Distance_ICON_DRAG = 0; // 存拖拽图标的 Y 方向相对距离 真正取数靠 ObjectGetInteger 带 OBJPROP_XDISTANCE / OBJPROP_YDISTANCE。例如下拉按钮的坐标读取:XDistance_DropDn_Btn 拿到的是图表左上角到对象的水平像素偏移,YDistance_DropDn_Btn 是垂直偏移,强制转 int 丢小数。 int XDistance_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_XDISTANCE); int YDistance_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_YDISTANCE); ICON_DRAG 还额外取了 XSIZE / YSIZE,说明图标拖动时要按自身尺寸做边界钳制。触发记录的门槛是 prevMouseClickState==false 且 mouse_State==1,即「上一帧没按、这一帧左键按下」的边沿信号——外汇与贵金属图表上做这类 UI 高风险,坐标算错会让面板飞出可视区。 开 MT5 把这段塞进 OnChartEvent 的 case CHARTEVENT_MOUSE_MOVE,断点看第一次按下时六个 static 是否从 0 变成实际像素值,就能验证缓存逻辑通没通。
class="kw">static class="type">int mlbDownX_Distance_LABEL_OPT2 = class="num">0; class=class="str">"cmt">// Stores X-distance for another label(LABEL_OPT2) class="kw">static class="type">int mlbDownY_Distance_LABEL_OPT2 = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same label class="kw">static class="type">int mlbDownX_Distance_LABEL_OPT3 = class="num">0; class=class="str">"cmt">// Stores X-distance for yet another label(LABEL_OPT3) class="kw">static class="type">int mlbDownY_Distance_LABEL_OPT3 = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same label class="kw">static class="type">int mlbDownX_Distance_ICON_DRAG = class="num">0; class=class="str">"cmt">// Stores X-distance for an icon(ICON_DRAG) class="kw">static class="type">int mlbDownY_Distance_ICON_DRAG = class="num">0; class=class="str">"cmt">// Stores Y-distance for the same icon class=class="str">"cmt">//GET THE INITIAL DISTANCES AND SIZES OF BUTTON class="type">int XDistance_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_XDISTANCE); class="type">int YDistance_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_YDISTANCE); class=class="str">"cmt">//class="type">int XSize_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_XSIZE); class=class="str">"cmt">//class="type">int YSize_DropDn_Btn = (class="type">int)ObjectGetInteger(class="num">0,BTN_DROP_DN,OBJPROP_YSIZE); class="type">int XDistance_Opt1_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT1,OBJPROP_XDISTANCE); class="type">int YDistance_Opt1_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT1,OBJPROP_YDISTANCE); class="type">int XDistance_Opt2_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT2,OBJPROP_XDISTANCE); class="type">int YDistance_Opt2_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT2,OBJPROP_YDISTANCE); class="type">int XDistance_Opt3_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT3,OBJPROP_XDISTANCE); class="type">int YDistance_Opt3_Lbl = (class="type">int)ObjectGetInteger(class="num">0,LABEL_OPT3,OBJPROP_YDISTANCE); class="type">int XDistance_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_XDISTANCE); class="type">int YDistance_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_YDISTANCE); class="type">int XSize_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_XSIZE); class="type">int YSize_Drag_Icon = (class="type">int)ObjectGetInteger(class="num">0,ICON_DRAG,OBJPROP_YSIZE); if (prevMouseClickState == false && mouse_State == class="num">1) { class=class="str">"cmt">// Check if the left mouse button was clicked and the mouse is in the pressed state class=class="str">"cmt">// Initialize variables to store initial distances and sizes of objects
「拖拽面板的鼠标命中与跟随逻辑」
在自定义 HUD 面板里实现鼠标拖拽,第一步是把左键按下瞬间的坐标和各个子对象的初始距离全部存下来。下面这段代码在 CHARTEVENT_MOUSE_MOVE 且左键按下时记录 mouse_X / mouse_Y,并把拖拽图标、下拉按钮、三个选项标签的 XDISTANCE / YDISTANCE 快照到对应变量,供后续偏移计算复用。 判定拖拽起点是否落在图标热区内,用的是四边界包含判断:mouse_X 在 [XDistance_Drag_Icon, XDistance_Drag_Icon + XSize_Drag_Icon] 区间内,且 mouse_Y 同理落在 Y 区间,才把 movingState 置 true。实测中若 XSize_Drag_Icon 设成 20 像素、YSize 设成 20 像素,热区就是 400 像素²,过小会导致黄金时段手抖点不中。 进入 movingState 后先关掉 CHART_MOUSE_SCROLL,避免图表随拖拽滚动。之后对每个对象调用 ObjectSetInteger 改写 OBJPROP_XDISTANCE / OBJPROP_YDISTANCE,新距离 = 按下时快照距离 + (当前 mouse_X - 按下时 mouse_X),Y 轴同构,最后 ChartRedraw 强制重绘。开 MT5 把这套变量名替进自己的面板对象,拖一下就能验证跟随是否线性无残影。 外汇与贵金属图表上挂这类可拖控件,需注意鼠标事件在高刷新率屏上可能一秒触发上百次,过度重绘会吃掉 CPU,建议对 mouse_X 差值做阈值过滤再 ChartRedraw。
mlbDownX = mouse_X; class=class="str">"cmt">// Store the X-coordinate of the mouse click mlbDownY = mouse_Y; class=class="str">"cmt">// Store the Y-coordinate of the mouse click class=class="str">"cmt">// Store distances for specific objects mlbDownX_Distance = XDistance_Drag_Icon; class=class="str">"cmt">// Distance of the drag icon(X-axis) mlbDownY_Distance = YDistance_Drag_Icon; class=class="str">"cmt">// Distance of the drag icon(Y-axis) mlbDownX_Distance_BTN_DROP_DN = XDistance_DropDn_Btn; class=class="str">"cmt">// Distance of BTN_DROP_DN mlbDownY_Distance_BTN_DROP_DN = YDistance_DropDn_Btn; mlbDownX_Distance_LABEL_OPT1 = XDistance_Opt1_Lbl; class=class="str">"cmt">// Distance of LABEL_OPT1 mlbDownY_Distance_LABEL_OPT1 = YDistance_Opt1_Lbl; mlbDownX_Distance_LABEL_OPT2 = XDistance_Opt2_Lbl; class=class="str">"cmt">// Distance of LABEL_OPT2 mlbDownY_Distance_LABEL_OPT2 = YDistance_Opt2_Lbl; mlbDownX_Distance_LABEL_OPT3 = XDistance_Opt3_Lbl; class=class="str">"cmt">// Distance of LABEL_OPT3 mlbDownY_Distance_LABEL_OPT3 = YDistance_Opt3_Lbl; class=class="str">"cmt">// Check if the mouse is within the drag icon area if (mouse_X >= XDistance_Drag_Icon && mouse_X <= XDistance_Drag_Icon + XSize_Drag_Icon && mouse_Y >= YDistance_Drag_Icon && mouse_Y <= YDistance_Drag_Icon + YSize_Drag_Icon) { movingState = true; class=class="str">"cmt">// Set the moving state to true } } if (movingState){ ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,false); ObjectSetInteger(class="num">0,ICON_DRAG,OBJPROP_XDISTANCE,mlbDownX_Distance + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,ICON_DRAG,OBJPROP_YDISTANCE,mlbDownY_Distance + mouse_Y - mlbDownY); ObjectSetInteger(class="num">0,BTN_DROP_DN,OBJPROP_XDISTANCE,mlbDownX_Distance_BTN_DROP_DN + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,BTN_DROP_DN,OBJPROP_YDISTANCE,mlbDownY_Distance_BTN_DROP_DN + mouse_Y - mlbDownY); ObjectSetInteger(class="num">0,LABEL_OPT1,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT1 + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,LABEL_OPT1,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT1 + mouse_Y - mlbDownY); ObjectSetInteger(class="num">0,LABEL_OPT2,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT2 + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,LABEL_OPT2,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT2 + mouse_Y - mlbDownY); ObjectSetInteger(class="num">0,LABEL_OPT3,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT3 + mouse_X - mlbDownX); ObjectSetInteger(class="num">0,LABEL_OPT3,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT3 + mouse_Y - mlbDownY); ChartRedraw(class="num">0); }
鼠标空闲时交还图表滚轮控制权
当检测到鼠标状态变量 mouse_State 归零,说明用户当前没有在图表上做拖拽或按下操作,此时应把滚动控制权还给 MT5 原生环境。 具体做法是先把 movingState 置为 false,停止自定义移动逻辑,再调用 ChartSetInteger(0, CHART_MOUSE_SCROLL, true) 恢复图表鼠标滚轮缩放与平移。 最后用 prevMouseClickState = mouse_State 记录这一帧的鼠标状态,供下一帧做边沿判断,避免重复触发。外汇与贵金属行情跳动快,这类交互细节没处理好,手动分析时容易误触缩放。
if (mouse_State == class="num">0){ movingState = false; ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,true); } prevMouseClickState = mouse_State; }
◍ 把这条线请下神坛
这套面板把按钮悬停、买卖价实时刷新、可点击下单与一键平仓都塞进了 MQL5 GUI,再配上可拖动子面板和下拉列表,本质上只是把交易动作从手动点终端搬到了自定义界面里。附件 DASHBOARD_PART_2.mq5 有 43.17 KB、编译后 ex5 为 64.13 KB,直接在 MT5 里加载就能看到交互效果。 它确实让操作路径变短了,但外汇和贵金属本身高波动、高杠杆,界面再顺手也改不了品种风险。真要验证价值,自己改两行 OnClick 里的下单手数逻辑,跑一周模拟盘看响应是否跟得上你的节奏就行。