DoEasy. 控件(第 二十九 部分):滚动条(ScrollBar)辅助控件·进阶篇
📜

DoEasy. 控件(第 二十九 部分):滚动条(ScrollBar)辅助控件·进阶篇

(2/3)· 多数自定义面板卡在内容溢出却无滚动条,本篇先把静态滚动条骨架搭对

偏理论 第 2/3 篇

接上篇 TrackBar 的铺垫,这一篇真正落地的是它依赖的底层对象——滚动条。很多自己写面板的交易者把滑块当独立控件硬凑,结果鼠标一抓就错位,根源是没分清基准类和派生类的职责边界。

工作流控件类型的字符串映射

在 MT5 的 GUI 框架里,图形元素类型常量需要转成可读文本,方便调试和日志输出。上面这段三元表达式链就是把 GRAPH_ELEMENT_TYPE_WF_* 系列枚举逐一映射到 CMessage::Text 对应的多语言资源ID。 注意末尾三个被高亮的类型:WF_SCROLL_BAR、WF_SCROLL_BAR_VERTICAL、WF_SCROLL_BAR_HORISONTAL(拼写是 HORISONTAL 而非 HORIZONTAL),它们负责滚动条及其垂直/水平实例的识别。若你自定义面板时漏掉这三个分支,运行时遇到滚动条对象会落到 "Unknown" 分支。 直接在 MT5 的 MetaEditor 里搜 GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR 就能定位官方头文件中的定义,复制这段映射进自己的 CGraphElement::TypeName() 类似函数可做类型自检。外汇与贵金属 GUI 面板开发同样面临平台稳定性风险,建议先在策略测试器外的普通 EA 面板验证。

MQL5 / C++
   type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR)        :
   class=class="str">"cmt">//--- Auxiliary control objects
   type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)            :
   type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)             :
   type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
   type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
   type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
   type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
   type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
   type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
   type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
   type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
   type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
   type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE)             :
   type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT)        :
   type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT)       :
   type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP)          :
   type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN)        :
   type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR)      :
   type==GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ)             :
   type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR)            :
   type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL)   :
   type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL) :
   "Unknown"
   );

◍ 滚动条辅助控件的基类

基准滚动条对象将从所有 WinForms 库对象的基类继承。 它将是某种抽象的滚动条对象,而特定对象的必要参数需在其后代中指定。 在函数库辅助对象 \MQL5\Include\DoEasy\Objects\Graph\WForms\ Helpers\ 的文件夹中,在 ScrollBar.mqh 中创建新的类 CScrollBar。 该类 应派生自 CWinFormBase 类 ,而 这个类和其它类的文件都应包含在所创建对象的文件中 : 在类的私密部分中,声明创建箭头按钮的方法、创建新图形对象的标准方法、计算捕获区域(滑块)大小的方法、以及初始化对象参数的方法: 计算不同子对象中滑块大小的方法将生成不同大小的捕获区域,因为它的位置取决于滚动条的方向 — 垂直或水平。 创建按钮对象的方法还将在不同的继承对象中创建不同的按钮:对于垂直滚动条,我们需要向上/向下箭头的按钮;对于水平滚动条,我们需要向左/向右箭头的按钮。 所有这些方法都是虚拟的,以便可以在派生类中重写它们。 在类的受保护部分中,声明创建捕获区域的虚拟方法(将在方法中创建按钮和滑块),和受保护的构造函数(函数库图形元素的标准)。 在公开部分中,编写返回对象属性维护标志、参数构造函数、和虚拟对象计时器事件处理程序的方法: 每次创建新的图形元素时,我们都会考虑类似的方法 — 它们都是雷同的,并且对于函数库中的每个图形对象具有相同的用意。 我们来研究一下所声明方法的实现。 受保护的构造函数: 构造函数 接收所创建对象的类型和其它标准参数 。 在初始化清单中,把欲创建对象的类型传递给父类构造函数 。 在构造函数主体中, 设置图形元素的类型 ,以及 函数库图形对象的类型 ,并 调用参数初始化方法 。 参数型构造函数: 此处的所有内容都与受保护的构造函数雷同, 但对象类型在初始化清单中硬编码为 ScrollBar 。 元素属性初始化方法: 在此,将对象的每一侧边框大小设置为一个像素,将边框类型设置为“简单”,并添加元素背景、边框和文本颜色。 创建捕获区域对象的方法: 在创建箭头按钮对象和滑块的方法中,调用虚拟方法 CreateArrowButtons(),其应在后代对象中重新定义。 计算捕获区域大小的方法: 此处,该方法返回零,而其应在派生类中重写,因为对于每个派生对象,滑块的大小和位置都不同。 该方法创建新的图形对象: 该方法是函数库图形对象的标准方法。 它允许您创建五个控件:向上/向下箭头的按钮、向右/向左箭头,和滑块所基于的按钮对象。 计时器事件处理程序: 目前,该方法为空,不执行任何操作。 我将在后续文章中应对它。 该对象是一个抽象滚动条。 它不执行任何操作,并只是作为创建另外两个对象的基础 — 垂直和水平滚动条。 [CODE] <span class="comment">//+------------------------------------------------------------------+</span> <span class="comment">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs

「滚动条类构造与捕获区计算」

在 MT5 自定义图形库里,CScrollBar 的公开构造接收 8 个常量参数:x/y/w/h 定义控件在画布上的矩形位置与尺寸,colour 和 opacity 控制外观(opacity 为 uchar,范围 0~255),movable 与 activity 决定用户能否拖动及是否响应事件。 类内部用 CalculateThumbAreaSize(void) 虚函数计算滑块(thumb)捕获区的像素大小,Initialize(void) 负责把元素属性归位。protected 区的 CreateThumbArea(void) 才是真正生成捕获区对象的环节,外部不可直接调用。 另一个受保护构造函数把 type、main_obj、base_obj 和两个坐标参数一并传入,说明滚动条依赖两个已有画布元素作父子锚点。外汇与贵金属图表上挂这类自绘控件,属于高风险环境下的 UI 扩展,参数填错可能让 EA 面板在实时行情中失联。 开 MT5 新建一套 CGCnvElement 派生类,把下面这段声明原样贴进头文件,就能确认编译器对 8 参构造的解析顺序。

MQL5 / C++
const class="type">int x,
const class="type">int y,
const class="type">int w,
const class="type">int h,
const class="type">class="kw">color colour,
const class="type">uchar opacity,
const class="type">bool movable,
const class="type">bool activity);
class=class="str">"cmt">//--- Calculate the capture area size
  class="kw">virtual class="type">int       CalculateThumbAreaSize(class="type">void);
class=class="str">"cmt">//--- Initialize the element properties
  class="type">void              Initialize(class="type">void);
class="kw">protected:
class="kw">protected:
class=class="str">"cmt">//--- Create the capture area object
  class="kw">virtual class="type">void      CreateThumbArea(class="type">void);
class=class="str">"cmt">//--- Protected constructor with object type, chart ID and subwindow
           CScrollBar(const ENUM_GRAPH_ELEMENT_TYPE type,
              CGCnvElement *main_obj,CGCnvElement *base_obj,
              const class="type">long chart_id,
              const class="type">int subwindow,
              const class="type">class="kw">string descript,
              const class="type">int x,
              const class="type">int y,
              const class="type">int w,
              const class="type">int h);
class="kw">public:
class=class="str">"cmt">//--- Supported object properties(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string ones

滚动条类的属性支持与构造落地

在自定义图形控件里,CScrollBar 通过三个重载的 SupportProperty 虚函数,把整型、双精度、字符串三类画布属性全部放行,返回 true 意味着该滚动条对象不限制任何标准属性读写。 实际构造时,受保护构造函数接收 ENUM_GRAPH_ELEMENT_TYPE 类型的元素类别,并把它同时传给基类 CWinFormBase 与内部 SetTypeElement,保证控件在图表子窗口中的归属关系一致。 注意 m_type 被显式赋值为 OBJECT_DE_TYPE_GWF_HELPER,这个标记让库内部把它识别为窗体辅助元素,而非独立绘图对象;外汇与贵金属图表上挂这类自绘控件时,需意识到过度重绘可能拖慢 Tick 响应,属高风险调试项。 开 MT5 把这段构造函数塞进你的 include 链,改一下 chart_id 与 subwindow 参数,就能在指定子窗口看到滚动条占位,验证属性通路是否如预期放开。

MQL5 / C++
class="kw">virtual class="type">bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property) { class="kw">return true; }
class="kw">virtual class="type">bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property)  { class="kw">return true; }
class="kw">virtual class="type">bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property)  { class="kw">return true; }

class=class="str">"cmt">//--- Constructor
                  CScrollBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                              const class="type">long chart_id,
                              const class="type">int subwindow,
                              const class="type">class="kw">string descript,
                              const class="type">int x,
                              const class="type">int y,
                              const class="type">int w,
                              const class="type">int h);
class=class="str">"cmt">//--- Timer
 class="kw">virtual class="type">void      OnTimer(class="type">void);
      };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------+
class=class="str">"cmt">//| Protected constructor with an object type,   |
class=class="str">"cmt">//| chart ID and subwindow                       |
class=class="str">"cmt">//+---------------------------------------------+
CScrollBar::CScrollBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                     const class="type">long chart_id,
                     const class="type">int subwindow,
                     const class="type">class="kw">string descript,
                     const class="type">int x,
                     const class="type">int y,
                     const class="type">int w,
                     const class="type">int h) : CWinFormBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
class=class="str">"cmt">//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;

◍ 滚动条控件的构造与初始化拆解

在 MT5 自定义图形界面里,CScrollBar 作为滚动条控件继承自 CWinFormBase,构造时通过初始化列表把元素类型写死为 GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR,并传入主对象、基准对象、图表 ID、子窗口及坐标宽高。带参构造函数还会补一句 SetTypeElement 与 m_type=OBJECT_DE_TYPE_GWF_HELPER,把对象在系统内部的派生态标记清楚。 Initialize() 里给滚动条轨道设了明确默认值:边框统一 1 像素、FRAME_STYLE_SIMPLE 样式,背景、边框、前景分别用 CLR_DEF_CONTROL_SCROLL_BAR_TRACK_BACK_COLOR / BORDER_COLOR / FORE_COLOR 三套宏色。你在 MT5 里若看到滚动条太细或颜色发暗,优先改这三个宏或此处 1 像素边框值。 CreateThumbArea() 调用 CreateArrowButtons 时传入的两个参数都是 DEF_ARROW_BUTTON_SIZE,意味着上下(或左右)箭头按钮是正方形,边长等于该宏。CalculateThumbAreaSize() 当前直接 return 0,说明滑块计算逻辑在这一版并未真正实现,接手改控件时别指望它返回有效拖拽区长度。

MQL5 / C++
this.Initialize();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor indicating the main and base objects,                |
class=class="str">"cmt">//| chart ID and subwindow                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
CScrollBar::CScrollBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                       const class="type">long chart_id,
                       const class="type">int subwindow,
                       const class="type">class="kw">string descript,
                       const class="type">int x,
                       const class="type">int y,
                       const class="type">int w,
                       const class="type">int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
  this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR);
  this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
  this.Initialize();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------+
class=class="str">"cmt">//| Initialize the element properties             |
class=class="str">"cmt">//+---------------------------------------------+
class="type">void CScrollBar::Initialize(class="type">void)
  {
  this.SetBorderSizeAll(class="num">1);
  this.SetBorderStyle(FRAME_STYLE_SIMPLE);
  this.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_TRACK_BACK_COLOR,true);
  this.SetBorderColor(CLR_DEF_CONTROL_SCROLL_BAR_TRACK_BORDER_COLOR,true);
  this.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_TRACK_FORE_COLOR,true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------+
class=class="str">"cmt">//| Create the capture area object               |
class=class="str">"cmt">//+---------------------------------------------+
class="type">void CScrollBar::CreateThumbArea(class="type">void)
  {
  this.CreateArrowButtons(DEF_ARROW_BUTTON_SIZE,DEF_ARROW_BUTTON_SIZE);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------+
class=class="str">"cmt">//| Calculate the capture area size              |
class=class="str">"cmt">//+---------------------------------------------+
class="type">int CScrollBar::CalculateThumbAreaSize(class="type">void)
  {
  class="kw">return class="num">0;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------+
class=class="str">"cmt">//| Create a new graphical object                |
class=class="str">"cmt">//+---------------------------------------------+
CGCnvElement *CScrollBar::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                           const class="type">int obj_num,

「工厂方法按类型生成图形控件」

在 MT5 自定义图形库里,用同一个入口函数按枚举类型分流,是最省事的写法。下面这段工厂方法接收类型、描述、坐标、尺寸、颜色、透明度与可移动/可交互标志,根据 type 分支 new 出对应控件实例。 GRAPH_ELEMENT_TYPE_WF_BUTTON 对应普通按钮 CButton,四个箭头方向则分别映射到 CArrowDownButton / CArrowUpButton / CArrowLeftButton / CArrowRightButton,坐标参数 x,y,w,h 直接透传给基类构造器,默认挂在当前图表子窗口。 default 分支留空意味着未识别类型时 element 保持 NULL,调用方必须判空,否则后续对 element 解引用会在 EURUSD 这类实时品种上直接抛野指针。外汇与贵金属杠杆高,面板崩了可能漏掉一键平仓,实盘前务必在策略测试器跑一遍类型覆盖。

MQL5 / C++
const class="type">class="kw">string descript,
const class="type">int x,
const class="type">int y,
const class="type">int w,
const class="type">int h,
const class="type">class="kw">color colour,
const class="type">uchar opacity,
const class="type">bool movable,
const class="type">bool activity)
{
 CGCnvElement *element=NULL;
 class="kw">switch(type)
  {
   case GRAPH_ELEMENT_TYPE_WF_BUTTON           :
     element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN  :
     element=new CArrowDownButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP    :
     element=new CArrowUpButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT  :
     element=new CArrowLeftButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT :
     element=new CArrowRightButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
     break;
   class="kw">default:
把控件外观巡检交给小布
悬停与捕获态的颜色切换这类重复核对,小布盯盘的 AIGC 已内置,打开对应品种页即可看到面板元素的状态差异,你只需专注交互逻辑。

常见问题

基准类定义滚动条共有的底图、滑块捕获区与箭头按钮的通用属性和消息索引,具体方向由垂直和水平派生类扩展。
可以,小布盯盘的图形元素巡检能标出隐藏与可见态切换异常,减少手动逐帧核对面板外观的时间。
本篇只构建对象外观与鼠标交互骨架,内容绑定与窗体滚动控制留到后续篇,避免一次引入过多未验证逻辑。
新消息索引与文本添加在 Data.mqh 中,并在基准图形对象类的 TypeElementDescription 方法里调用描述。