MQL5 细则手册:指标子窗口控件 - 滚动条·进阶篇
📜

MQL5 细则手册:指标子窗口控件 - 滚动条·进阶篇

(2/3)· 接上篇按钮控件,本文用 CCanvas 与 OBJ_RECTANGLE_LABEL 在指标子窗口实现垂直滚动条,避开大量标签对象拖累性能

含代码示例偏理论 第 2/3 篇
在指标子窗口堆几十个文本标签做长列表,事件响应和重绘都会变慢。改用单个位图标签配合画布渲染,再用矩形标签做滚动框,是更轻量的做法。

用位图标签承载品种属性面板

在 MT5 指标子窗口里画一块自定义画布,比用几十个 Text 对象省事得多。下面这段把画布的增、改、删三件事拆成独立函数,初始化时再挂上鼠标跟踪和字体。 AddCanvas 先查画布在不在:ObjectFind 返回小于 0 表示没找到,就用 CreateBitmapLabel 在指定子窗口 (0,0) 位置按 chart_width × subwindow_height 建一块位图标签。ResizeCanvas 则判断画布是否已归属于该子窗口(返回值等于 subwindow_number),是就直接 Resize,否则补建。DeleteCanvas 仅在 ObjectFind 大于 0 时调用 Destroy 清掉对象。 OnInit 里顺手开了 CHART_EVENT_MOUSE_MOVE,后面悬停读属性才抓得到坐标;ArrayResize 给 symbol_property_colors 和 symbol_property_values 都开了 LIST_SIZE 长度,TEXT 高度用 TextHeight("A")-1 存下来做排版基线。外汇与贵金属杠杆高,面板只是辅助,信号错了止损照样被打。

MQL5 / C++
class="type">void AddCanvas()
  {
class=class="str">"cmt">//--- If there is no canvas, add it
   if(ObjectFind(class="num">0,canvas_name)<class="num">0)
      canvas.CreateBitmapLabel(class="num">0,subwindow_number,canvas_name,class="num">0,class="num">0,chart_width,subwindow_height,color_format);
  }
class="type">void ResizeCanvas()
  {
class=class="str">"cmt">//--- If the canvas has already been added to the indicator subwindow, set the new size
   if(ObjectFind(class="num">0,canvas_name)==subwindow_number)
      canvas.Resize(chart_width,subwindow_height);
class=class="str">"cmt">//--- If there is no canvas, add it
   else
      canvas.CreateBitmapLabel(class="num">0,subwindow_number,canvas_name,class="num">0,class="num">0,chart_width,subwindow_height,color_format);
  }
class="type">void DeleteCanvas()
  {
   if(ObjectFind(class="num">0,canvas_name)>class="num">0)
      canvas.Destroy();
  }
class="type">int OnInit()
  {
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 sizes of arrays of symbol properties and their colors
   ArrayResize(symbol_property_colors,LIST_SIZE);
   ArrayResize(symbol_property_values,LIST_SIZE);
class=class="str">"cmt">//--- Set subwindow properties
   SetSubwindowProperties();
class=class="str">"cmt">//--- Set the font for displaying on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
class=class="str">"cmt">//--- Save the text size(height) for calculations
   text_height=canvas.TextHeight("A")-class="num">1;

「画布上滚动显示品种属性的实现细节」

在指标初始化尾部,先按 text_height*LIST_SIZE 算出整张列表高度并写入 list_height,随后 AddCanvas 把画布挂到图表、ShowSymbolInfo 输出内容、ChartRedraw 强制重绘,最后 return(INIT_SUCCEEDED) 结束 OnInit。这一段决定了你打开 MT5 后能否立刻看到面板。 ShowSymbolInfo 的核心是根据滚动条位置 current_thumb_position 跳过已滚出视野的行。for 循环里用 thumb_position 累加 line_size,一旦超过当前滚动位置就 break,此时 line_number 就是可视区起始行号——这意味着 LIST_SIZE 若设 50、line_size 为 0.02,滚动到 0.4 时会从第 20 行起绘。 真正落笔在 canvas.TextOut:属性名固定在 x=655 右对齐,值从 x=665 左对齐,颜色由 symbol_property_colors[i] 控制。y_distance 每行加 text_height,当 list_lines*text_height 超出 subwindow_height 就 break,避免画到子窗口外。 InitializePropertyArrays 从 line_number 开始填 symbol_property_values 和颜色,GetStringSymbolInfoByIndex(i) 按索引抓品种字段。想验证就改 LIST_SIZE 或 line_size,看滚动时跳行是否如算式预期。外汇与贵金属波动剧烈,面板数值仅作参考,实操请自担高风险。

MQL5 / C++
class=class="str">"cmt">//--- Calculate the height of the entire list
  list_height=text_height*LIST_SIZE;
class=class="str">"cmt">//--- Add the canvas to the chart
  AddCanvas();
class=class="str">"cmt">//--- Display the list of symbol properties
  ShowSymbolInfo();
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">//| Displaying current symbol information                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ShowSymbolInfo(class="type">class="kw">double current_thumb_position=class="num">0.0)
  {
  class="type">int    list_lines      =class="num">0;   class=class="str">"cmt">// Counter of strings displayed in the canvas
  class="type">class="kw">double thumb_position  =class="num">0.0; class=class="str">"cmt">// Position of the scroll box expressed as percentage for determining the first displayed class="type">class="kw">string
  class="type">int    y_distance      =class="num">0;   class=class="str">"cmt">// For determining the coordinate of the next class="type">class="kw">string in the list
  class="type">int    line_number     =class="num">0;   class=class="str">"cmt">// Number of the class="type">class="kw">string starting from which the list will be displayed
class=class="str">"cmt">//--- Determine the class="type">class="kw">string starting from which the list will be displayed
  for(class="type">int i=class="num">0; i<LIST_SIZE; i++)
    {
    class=class="str">"cmt">//--- Count strings until you reach the one starting from which the list will be displayed
    if(thumb_position>=current_thumb_position)
      break;
    class=class="str">"cmt">//---
    thumb_position+=line_size;
    line_number++;
    }
class=class="str">"cmt">//--- Initialize list arrays from the specified class="type">class="kw">string
  InitializePropertyArrays(line_number);
class=class="str">"cmt">//--- Clear the canvas
  canvas.Erase(canvas_background_color);
class=class="str">"cmt">//--- Show the list on the canvas
  for(class="type">int i=line_number; i<LIST_SIZE; i++)
    {
    class=class="str">"cmt">//--- Property name
    canvas.TextOut(class="num">655,y_distance,symbol_propety_names[i]+" :",ColorToARGB(clrWhite),TA_RIGHT|TA_TOP);
    class=class="str">"cmt">//--- Property value
    canvas.TextOut(class="num">665,y_distance,symbol_property_values[i],ColorToARGB(symbol_property_colors[i]),TA_LEFT|TA_TOP);
    class=class="str">"cmt">//--- Calculate the coordinate for the next class="type">class="kw">string
    y_distance+=text_height;
    class=class="str">"cmt">//--- Count the number of displayed strings
    list_lines++;
    class=class="str">"cmt">//--- If you go beyond the subwindow boundaries, terminate the loop
    if(list_lines*text_height>subwindow_height)
      break;
    }
class=class="str">"cmt">//--- Refresh the canvas
  canvas.Update();
  }
class=class="str">"cmt">//+-----------------------------------------------------------------------+
class=class="str">"cmt">//| Initializing arrays of values and their colors for the current symbol |
class=class="str">"cmt">//+-----------------------------------------------------------------------+
class="type">void InitializePropertyArrays(class="type">int line_number)
  {
  class="type">int lines_count=class="num">0;
class=class="str">"cmt">//---
  for(class="type">int i=line_number; i<LIST_SIZE; i++)
    {
    class=class="str">"cmt">//--- Determine the value and class="type">class="kw">color of the symbol class="kw">property
    symbol_property_values[i]=GetStringSymbolInfoByIndex(i);

◍ 按索引取品种属性字符串的写法

在 MT5 指标里做自定义信息面板时,常需要把品种属性按索引取出来再拼成字符串显示。下面这段 GetStringSymbolInfoByIndex 用 switch 按 index 分流,分别读 session 成交、买卖挂单、成交量等整数属性。 当对应整数返回 0 时,函数给字符串填 "-",否则用 IntegerToString 转成文本。这样面板里不会出现一堆 0,视觉上更干净,也方便一眼扫到非零活跃字段。 调用方在循环里累加 lines_count,并用 lines_count*text_height 跟 subwindow_height 比较,一旦超出子窗口高度就 break,避免在窄副图里把文字画出边界。 外汇与贵金属品种波动剧烈、杠杆风险高,这类面板只做行情属性透视,不构成任何方向建议;复制代码后建议先在 EURUSD 的 M1 上验证字符高度与换行是否符合你的屏幕。

MQL5 / C++
class="type">class="kw">string GetStringSymbolInfoByIndex(class="type">int index)
  {
   class="type">class="kw">string str                 ="-";
   class="type">long   l_check_value =class="num">0;
   class="type">class="kw">double d_check_value =class="num">0.0;
   class="type">class="kw">string s_check_value ="";
class=class="str">"cmt">//---
   class="kw">switch(index)
     {
      case class="num">0  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_DEALS);
         str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value);                                            break;
      case class="num">1  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_BUY_ORDERS);
         str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value);                                            break;
      case class="num">2  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_SELL_ORDERS);
         str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value);                                            break;
      case class="num">3  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUME);
         str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value);                                            break;
      case class="num">4  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMEHIGH);
         str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value);                                            break;
      case class="num">5  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMELOW);

用 switch 把品种属性一次性读出来

在 MT5 里逐个调 SymbolInfoInteger 太碎,用 switch-case 把常用字段集中处理,代码可读性立刻上来。下面这段覆盖时间、小数位、点差、挂单簿深度、交易计算模式等字段,case 6 到 case 14 都是直接读 _Symbol 的整型属性再转字符串。 注意 case 6、13、14 都做了 l_check_value==0 判断:时间为 0 代表该品种无此项数据(例如过期时间对现券可能为 0),这时输出 '-' 而不是 1970 年的脏时间。case 10 的 BOOKDEPTH 若为 0 也同理处理。 case 11、12 调用了 TradeCalcModeToString / TradeModeToString 两个自定义(或标准库)转换函数,把枚举值翻成可读文本,省得你对着数字猜含义。直接把下面片段塞进你的信息面板 EA,编译后能在图表角落看到实时品种画像。

MQL5 / C++
str=(l_check_value==class="num">0) ? "-" : IntegerToString(l_check_value);
break;
case class="num">6  :
   l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TIME);
   str=(l_check_value==class="num">0) ? "-" : TimeToString(l_check_value);
   break;
case class="num">7  : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_DIGITS));
   break;
case class="num">8  : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD));
   break;
case class="num">9  : str=(!SymbolInfoInteger(_Symbol,SYMBOL_SPREAD_FLOAT)) ? "false" : "true";
   break;
case class="num">10 :
   l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TICKS_BOOKDEPTH);
   str=(l_check_value==class="num">0) ? "-" : DoubleToString(l_check_value,_Digits);
   break;
case class="num">11 : str=TradeCalcModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_CALC_MODE));
   break;
case class="num">12 : str=TradeModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_MODE));
   break;
case class="num">13 :
   l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_START_TIME);
   str=(l_check_value==class="num">0) ? "-" : TimeToString(l_check_value);
   break;
case class="num">14 :
   l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_TIME);
   str=(l_check_value==class="num">0) ? "-" : TimeToString(l_check_value);
   break;
case class="num">15 :

「止损冻结与报价字段的读取分支」

这段 switch 分支专门处理交易约束类与实时报价类的合约属性。case 15 和 16 用 SymbolInfoInteger 取 SYMBOL_TRADE_STOPS_LEVEL 与 SYMBOL_TRADE_FREEZE_LEVEL,若返回 0 表示经纪商未设最小止损距或冻结距,代码将其转成字符串 "false",否则用 IntegerToString 保留具体点数。 case 17~21 则把执行模式、掉期计算方式、周三展期日、到期模式、成交填充模式等枚举值,通过自定义转换函数(如 TradeExeModeToString、SwapModeToString)映射成可读文本,方便面板直接显示。 case 22~24 转向 double 型报价:用 SymbolInfoDouble 抓 SYMBOL_BID、SYMBOL_BIDHIGH、SYMBOL_BIDLOW,等于 0 时打 "-",否则按 _Digits 精度格式化。开 MT5 把 _Symbol 换成 XAUUSD 跑一遍,能直接看到黄金当前买价与当日买价高低,外汇和贵金属波动大、杠杆高,这些字段读数偏差可能让挂单被拒。

MQL5 / C++
l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
str=(l_check_value==class="num">0) ? "false" : IntegerToString(l_check_value);
break;
case class="num">16 :
   l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   str=(l_check_value==class="num">0) ? "false" : IntegerToString(l_check_value);
   break;
case class="num">17 : str=TradeExeModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE));      break;
case class="num">18 : str=SwapModeToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_MODE));              break;
case class="num">19 : str=WeekdayToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_ROLLOVER3DAYS));     break;
case class="num">20 : str=ExpirationModeToString();                                                  break;
case class="num">21 : str=FillingModeToString();                                                     break;
class=class="str">"cmt">//---
case class="num">22 :
   d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);                 break;
case class="num">23 :
   d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDHIGH);
   str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);                 break;
case class="num">24 :
   d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDLOW);

◍ 报价字段的逐档取值写法

这段 switch 分支把 MT5 里 25~32 号报价属性一次性铺开,核心都是用 SymbolInfoDouble 抓当前品种 _Symbol 的双精度值,再按 _Digits 精度转成字符串。 case 25 取 SYMBOL_ASK 实时卖价,case 26/27 取 SYMBOL_ASKHIGH / SYMBOL_ASKLOW 当日卖价极值;case 28~30 对应 SYMBOL_LAST 及其高低,适用于有最后成交价的品种,外汇现货常返回 0 故用 '-' 占位。 case 31 直接取 SYMBOL_POINT 点值,case 32 取 SYMBOL_TRADE_TICK_VALUE_PROFIT 并按 2 位小数显示每跳盈亏金额——这是算仓位风险时最实在的一栏。 开 MT5 按 F4 把这段塞进你的信息面板 EA,切到 XAUUSD 这类点值波动大的品种,盯 case 32 输出,能直观看到每跳盈亏随合约规模变化的差异。外汇与贵金属杠杆高,tick 价值随时变,数值仅作当下参考。

MQL5 / C++
      str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);
      break;
      case class="num">25 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);
         break;
      case class="num">26 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKHIGH);
         str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);
         break;
      case class="num">27 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKLOW);
         str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);
         break;
      case class="num">28 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LAST);
         str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);
         break;
      case class="num">29 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTHIGH);
         str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);
         break;
      case class="num">30 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTLOW);
         str=(d_check_value==class="num">0) ? "-" : DoubleToString(d_check_value,_Digits);
         break;
      case class="num">31 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_POINT),_Digits);
         break;
      case class="num">32 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT),class="num">2);
         break;
把控件状态检查交给小布
滚动框悬停与选中的颜色切换逻辑,小布盯盘的 AIGC 已内置到品种页的控件诊断里,打开即可对照看自己写的事件处理有没有漏掉 MOUSEMOVE。

常见问题

画布只需一个位图标签就能绘制整列文本,避免大量图形对象占用资源,重绘效率更高,事件处理也更集中。
通常在 OnInit 初始化以及图表尺寸变化触发 OnChartEvent 的图表事件时调用,使画布始终贴合指标子窗口大小。
本文示例省略箭头仅留背景与滚动框,拖动即可滚动列表;若需逐行微调可后续自行补箭头按钮逻辑。
目前小布的品种页提供控件状态与事件诊断,不直接输出完整 EA 源码,但可对照它提示的事件遗漏点反查自己的 OnChartEvent 实现。
每次重绘前应先用 Erase 清画布再 TextOut 写新内容,否则旧文本会残留在位图上造成重叠显示。