DoEasy. 控件(第 二十九 部分):滚动条(ScrollBar)辅助控件·进阶篇
(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 面板验证。
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">//| &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 参构造的解析顺序。
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 参数,就能在指定子窗口看到滚动条占位,验证属性通路是否如预期放开。
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,说明滑块计算逻辑在这一版并未真正实现,接手改控件时别指望它返回有效拖拽区长度。
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 这类实时品种上直接抛野指针。外汇与贵金属杠杆高,面板崩了可能漏掉一键平仓,实盘前务必在策略测试器跑一遍类型覆盖。
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: