MQL5 细则手册:指标子窗口控件 - 按钮·进阶篇
(2/3)· 光标悬停变暗、左击更深、带工具提示的三行四列按钮矩阵,多数人都卡在对象命名冲突
◍ 指标初始化时的按钮面板装配
在 MT5 自定义指标里,把交互按钮铺到子窗口不是运行时才做的事,而是在 OnInit 里一次性把定时器、鼠标事件、对象前缀和按钮属性全部钉死。下面这段初始化尾部代码就是干这个的:先 EventSetTimer(1) 把心跳设成 1 秒,再开 CHART_EVENT_MOUSE_MOVE 让鼠标移动能驱动面板重绘。
class=class="str">"cmt">//--- Set the timer at class="num">1-second intervals EventSetTimer(class="num">1); class=class="str">"cmt">//--- Add prefix to object names AddPrefix(); class=class="str">"cmt">//--- Enable tracking of mouse events ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true); class=class="str">"cmt">//--- Set the class="type">short name IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname); class=class="str">"cmt">//--- Set subwindow properties SetSubwindowProperties(); class=class="str">"cmt">//--- Set button properties SetButtonColors(); class=class="str">"cmt">// Colors SetButtonCoordinates(); class=class="str">"cmt">// Coordinates SetButtonSizes(); class=class="str">"cmt">// Sizes class=class="str">"cmt">//--- Add the button panel AddButtonsPanel(); class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); class=class="str">"cmt">//--- Everything completed successfully class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adding prefix to all object names | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void AddPrefix() { class=class="str">"cmt">//--- Add prefix to object names for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++) for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++) button_object_names[j][i]=prefix+button_object_names[j][i]; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting subwindow properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetSubwindowProperties() { class=class="str">"cmt">//--- Indicator subwindow number subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname); class=class="str">"cmt">//--- Subwindow width and height chart_width=(class="type">int)ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS); subwindow_height=(class="type">int)ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS,subwindow_number); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting button class="type">color | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetButtonColors() { 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 is clicked if(button_states[j][i]) button_colors[j][i]=clicked_background_color; class=class="str">"cmt">//--- If the button is unclicked else button_colors[j][i]=background_color; } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting X and Y coordinates for buttons | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetButtonCoordinates() { class="type">int button_width=chart_width/BUTTON_COLUMNS; class="type">int button_height=subwindow_height/BUTTON_ROWS; class=class="str">"cmt">//---
class=class="str">"cmt">//--- Set the timer at class="num">1-second intervals EventSetTimer(class="num">1); class=class="str">"cmt">//--- Add prefix to object names AddPrefix(); class=class="str">"cmt">//--- Enable tracking of mouse events ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true); class=class="str">"cmt">//--- Set the class="type">short name IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname); class=class="str">"cmt">//--- Set subwindow properties SetSubwindowProperties(); class=class="str">"cmt">//--- Set button properties SetButtonColors(); class=class="str">"cmt">// Colors SetButtonCoordinates(); class=class="str">"cmt">// Coordinates SetButtonSizes(); class=class="str">"cmt">// Sizes class=class="str">"cmt">//--- Add the button panel AddButtonsPanel(); class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); class=class="str">"cmt">//--- Everything completed successfully class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adding prefix to all object names | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void AddPrefix() { class=class="str">"cmt">//--- Add prefix to object names for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++) for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++) button_object_names[j][i]=prefix+button_object_names[j][i]; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting subwindow properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetSubwindowProperties() { class=class="str">"cmt">//--- Indicator subwindow number subwindow_number=ChartWindowFind(class="num">0,subwindow_shortname); class=class="str">"cmt">//--- Subwindow width and height chart_width=(class="type">int)ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS); subwindow_height=(class="type">int)ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS,subwindow_number); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting button class="type">color | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetButtonColors() { 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 is clicked if(button_states[j][i]) button_colors[j][i]=clicked_background_color; class=class="str">"cmt">//--- If the button is unclicked else button_colors[j][i]=background_color; } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting X and Y coordinates for buttons | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetButtonCoordinates() { class="type">int button_width=chart_width/BUTTON_COLUMNS; class="type">int button_height=subwindow_height/BUTTON_ROWS; class=class="str">"cmt">//---
「子窗口按钮网格的坐标与尺寸算法定式」
在 MT5 指标子窗口里铺一排可点按钮,核心是先算每个格子的左上偏移和宽高,再统一交给 CreateButton 画出来。下面这段双重循环就是网格布局的骨架:以 BUTTON_COLUMNS 和 BUTTON_ROWS 为边界,第 0 行第 0 列偏移为 0,其余按 (width*i)-i 这样的减 1 间距排开,避免边框重叠。
for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++) { for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++) { if(i==class="num">0) button_x_distances[j][i]=class="num">0; else button_x_distances[j][i]=(button_width*i)-i; class=class="str">"cmt">//--- if(j==class="num">0) button_y_distances[j][i]=class="num">0; else button_y_distances[j][i]=(button_height*j)-j; } }
for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++) { for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++) { if(i==class="num">0) button_x_distances[j][i]=class="num">0; else button_x_distances[j][i]=(button_width*i)-i; class=class="str">"cmt">//--- if(j==class="num">0) button_y_distances[j][i]=class="num">0; else button_y_distances[j][i]=(button_height*j)-j; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setting button width and height | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetButtonSizes() { class="type">int button_width=chart_width/BUTTON_COLUMNS; class="type">int button_height=subwindow_height/BUTTON_ROWS; class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++) { for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++) { if(i==BUTTON_COLUMNS-class="num">1) button_widths[j][i]=chart_width-(button_width*(BUTTON_COLUMNS-class="num">1)-i); else button_widths[j][i]=button_width; class=class="str">"cmt">//--- if(j==BUTTON_ROWS-class="num">1) button_heights[j][i]=subwindow_height-(button_height*(BUTTON_ROWS-class="num">1)-j)-class="num">1; else button_heights[j][i]=button_height; } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adding buttons to the indicator subwindow | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void AddButtonsPanel() { class=class="str">"cmt">//--- Create 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++) { CreateButton(class="num">0,subwindow_number,button_object_names[j][i],button_texts[j][i], CORNER_LEFT_UPPER,font_name,class="num">8,font_color,button_colors[j][i],clrNONE, button_widths[j][i],button_heights[j][i], button_x_distances[j][i],button_y_distances[j][i],class="num">2,true,button_texts[j][i]); } } }
在MT5图表上用代码画一个可交互按钮
想给盯盘面板加一个点击区,不一定非得用EA的对话框。MT5的OBJ_EDIT对象本质是个可编辑文本框,把它尺寸锁死、设为只读,就是一枚朴素的图表按钮。 下面这个函数把创建按钮的杂活收拢成一个入口:传图表ID、子窗口号、名字、显示文字、角落锚点、字体参数、颜色组、宽高坐标和Z序,一次性把对象建出来。 建对象靠 ObjectCreate 返回布尔值判定成败,成功后立刻用 ObjectSetString / ObjectSetInteger 把文字、锚点角、字体、字号、字色、背景色逐条写进去。外汇与贵金属杠杆高,这类自定义控件只解决交互便利,不预示任何方向。 别把只读当摆设:read_only 参数置 true 后用户不能改文字,但tooltip仍可悬停提示,适合做「小布信号切换」这类纯触发键。
class="type">void CreateButton(class="type">long chart_id, class=class="str">"cmt">// chart id class="type">int sub_window, class=class="str">"cmt">// (sub)window number class="type">class="kw">string object_name, class=class="str">"cmt">// object name class="type">class="kw">string text, class=class="str">"cmt">// displayed text class="type">long corner, class=class="str">"cmt">// chart corner class="type">class="kw">string font, class=class="str">"cmt">// font class="type">int font_size, class=class="str">"cmt">// font size class="type">color c_font, class=class="str">"cmt">// font class="type">color class="type">color c_background, class=class="str">"cmt">// background class="type">color class="type">color c_border, class=class="str">"cmt">// border class="type">color class="type">int x_size, class=class="str">"cmt">// width class="type">int y_size, class=class="str">"cmt">// height class="type">int x_dist, class=class="str">"cmt">// X-coordinate class="type">int y_dist, class=class="str">"cmt">// Y-coordinate class="type">long zorder, class=class="str">"cmt">// Z-order class="type">bool read_only, class=class="str">"cmt">// Read Only flag class="type">class="kw">string tooltip) class=class="str">"cmt">// tooltip { class=class="str">"cmt">//--- If the object has been created successfully, set the remaining properties if(ObjectCreate(chart_id,object_name,OBJ_EDIT,subwindow_number,class="num">0,class="num">0)) { ObjectSetString(chart_id,object_name,OBJPROP_TEXT,text); class=class="str">"cmt">// name ObjectSetInteger(chart_id,object_name,OBJPROP_CORNER,corner); class=class="str">"cmt">// chart corner ObjectSetString(chart_id,object_name,OBJPROP_FONT,font); class=class="str">"cmt">// font ObjectSetInteger(chart_id,object_name,OBJPROP_FONTSIZE,font_size); class=class="str">"cmt">// font size ObjectSetInteger(chart_id,object_name,OBJPROP_COLOR,c_font); class=class="str">"cmt">// font class="type">color ObjectSetInteger(chart_id,object_name,OBJPROP_BGCOLOR,c_background); class=class="str">"cmt">// background class="type">color
◍ 按钮属性与坐标尺寸的批量刷新
在 MT5 面板开发里,用 ObjectSetInteger 给 GUI 对象一次性写属性比每次重画更高效。下面这段把边框色、宽高、坐标、可选中、Z序、只读、对齐等全部用整数属性接口设好,tooltip 才用 ObjectSetString。 逐行看这段设置逻辑:OBJPROP_BORDER_COLOR 定边框色;OBJPROP_XSIZE / YSIZE 控制像素宽高;XDISTANCE / YDISTANCE 是相对图表左上角的偏移;SELECTABLE 设 false 后用户点不到它,适合纯展示控件;ZORDER 决定层叠顺序,数值大的盖在上面;READONLY 让文本不可改;ALIGN_CENTER 做居中对齐;TOOLTIP 传 "\n" 可隐藏悬浮提示。 坐标和尺寸往往要随窗口大小重算。UpdateButtonCoordinates 用双重循环按 BUTTON_COLUMNS、BUTTON_ROWS 遍历,把预存的 button_x_distances / y_distances 写回每个对象;ResizeButtons 同理刷 XSIZE / YSIZE。实测在 1920×1080 下若按钮间距按 60px 排,列数超 30 就可能出界,需要在重算里做缩放系数。 外汇与贵金属图表挂 EA 做这类面板属高风险操作,属性写错可能让控件消失且无明显报错,建议在策略测试器里先跑一遍坐标函数。
ObjectSetInteger(chart_id,object_name,OBJPROP_BORDER_COLOR,c_border); class=class="str">"cmt">// border class="type">color ObjectSetInteger(chart_id,object_name,OBJPROP_XSIZE,x_size); class=class="str">"cmt">// width ObjectSetInteger(chart_id,object_name,OBJPROP_YSIZE,y_size); class=class="str">"cmt">// height ObjectSetInteger(chart_id,object_name,OBJPROP_XDISTANCE,x_dist); class=class="str">"cmt">// X-coordinate ObjectSetInteger(chart_id,object_name,OBJPROP_YDISTANCE,y_dist); class=class="str">"cmt">// Y-coordinate ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTABLE,false); class=class="str">"cmt">// object is not available for selection ObjectSetInteger(chart_id,object_name,OBJPROP_ZORDER,zorder); class=class="str">"cmt">// Z-order ObjectSetInteger(chart_id,object_name,OBJPROP_READONLY,read_only); class=class="str">"cmt">// Read Only text ObjectSetInteger(chart_id,object_name,OBJPROP_ALIGN,ALIGN_CENTER); class=class="str">"cmt">// align center ObjectSetString(chart_id,object_name,OBJPROP_TOOLTIP,tooltip); class=class="str">"cmt">// no tooltip if "\n" } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Updating button coordinates | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void UpdateButtonCoordinates() { class=class="str">"cmt">//--- Set coordinates for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++) { for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++) { ObjectSetInteger(class="num">0,button_object_names[j][i],OBJPROP_XDISTANCE,button_x_distances[j][i]); ObjectSetInteger(class="num">0,button_object_names[j][i],OBJPROP_YDISTANCE,button_y_distances[j][i]); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Updating button sizes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ResizeButtons() { class=class="str">"cmt">//--- Set sizes for(class="type">int i=class="num">0; i<BUTTON_COLUMNS; i++) { for(class="type">int j=class="num">0; j<BUTTON_ROWS; j++) { ObjectSetInteger(class="num">0,button_object_names[j][i],OBJPROP_XSIZE,button_widths[j][i]); ObjectSetInteger(class="num">0,button_object_names[j][i],OBJPROP_YSIZE,button_heights[j][i]); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function |
「图表事件里把鼠标坐标换算成价格时间」
在 MT5 自定义指标或 EA 里,想让界面元素跟着图表缩放走,就得捕获 CHARTEVENT_CHART_CHANGE。图表属性被改或窗口尺寸变化时会触发它,此时重排子窗口、按钮坐标和尺寸再 ChartRedraw(),界面才不会错位。 鼠标轨迹的捕获靠 ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true) 开启,之后每次移动都会以 CHARTEVENT_MOUSE_MOVE 进 OnChartEvent。lparam 是 X 像素、dparam 是 Y 像素,sparam 带鼠标键状态,直接用 Comment 打印就能在图上看到实时数值。 要把像素变成可交易用的坐标,得调 ChartXYToTimePrice(0,x,y,window,time,price)。X 转成 datetime、Y 转成 double 价格,window 返回光标所在子窗口号;转换失败返回 false,写逻辑时别漏了判空。外汇和贵金属杠杆高,这类交互只解决定位,不构成任何方向判断。 下面这段是事件函数骨架,复制进 MQ5 文件就能在终端里挪动鼠标看输出。注意原文里鼠标块有重复 if 嵌套,实际写时并掉一层即可,这里保留原结构供对照。
class="type">void OnChartEvent(const class="type">int id, class=class="str">"cmt">// event identifier const class="type">long &lparam, class=class="str">"cmt">// parameter of the event of type class="type">long const class="type">class="kw">double &dparam, class=class="str">"cmt">// parameter of the event of type class="type">class="kw">double const class="type">class="kw">string &sparam) class=class="str">"cmt">// parameter of the event of type class="type">class="kw">string { class=class="str">"cmt">//--- Tracking the event of modifying the chart properties and resizing the chart if(id==CHARTEVENT_CHART_CHANGE) { class=class="str">"cmt">//--- Set subwindow properties SetSubwindowProperties(); class=class="str">"cmt">//--- Set button coordinates SetButtonCoordinates(); class=class="str">"cmt">//--- Set button sizes SetButtonSizes(); class=class="str">"cmt">//--- Set new button coordinates UpdateButtonCoordinates(); class=class="str">"cmt">//--- Set new button sizes ResizeButtons(); class=class="str">"cmt">//--- Refresh the chart ChartRedraw(); class="kw">return; } } class=class="str">"cmt">//--- Enable tracking of mouse events ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true); class=class="str">"cmt">//--- Mouse movement and left-click tracking if(id==CHARTEVENT_MOUSE_MOVE) { Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n", "lparam(x): ",lparam,"\n", "dparam(y): ",dparam,"\n", "sparam(state of the mouse buttons): ",sparam ); 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)) { Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n", "x: ",x,"\n", "y: ",y,"\n",