图形界面 V: 垂直与水平滚动条 (第一章)(基础篇)
「MT5 图形控件的滚动条基类结构」
在 MT5 自定义图形界面里,垂直与水平滚动条并非孤立控件,而是从统一基类派生的子类。理解这套继承关系,能让你在写面板时直接复用滚动逻辑,而不是每次手搓坐标换算。 官方示例文章发布于 2016 年 6 月 2 日,截至统计累计阅读 6 187 次、评论 3 条,说明这套界面框架在老交易员群体里确有长期参考价值。 基类负责定义滚动范围、滑块尺寸与位置映射;派生类只补充分支方向(纵向/横向)和消息路由。想在 MT5 里验证,可新建一个空的 indicator 面板,把 CScrollBar 相关类拖进去编译,看滚动事件是否进到 OnChartEvent 的 OBJ_EVENT 分支。外汇与贵金属图表加载此类界面属高风险环境,建议先在模拟盘终端跑通再上实盘。
◍ 系列界面库已落地哪些控件
上一阶段在 MetaTrader 环境里做图形界面库,核心模块还没完工,但已经能把几个基础元件跑起来:主菜单、上下文菜单、状态栏、按钮、按钮组,还有工具提示。这些是直接能拖进 EA 面板用的底层件。 本篇第五部分把重心放到滚动条和列表视图。先写水平、垂直滚动条的类,再拼列表视图——它本质是个组合件,内部直接挂滚动条。 想验证进度,开 MT5 建个空 EA,把已发布的按钮类 include 进来,编译看资源占用,就能确认库目前只覆盖到上述六种元件,滚动条尚未合入主干。
用五个图形对象拼出滚动条
列表视图或表格塞不下全部数据时,就得在控件里嵌滚动条。它的交互靠两类部件:边缘的步进按钮负责按固定步长挪动数据,中间的滑块拖拽则做快速跳转,鼠标左键按住滑块移动即可。 MQL5 里我们通常用五个图形对象堆出一个滚动条:主背景、滑块轨道背景、上下(或左右)两个步进按钮、以及可拖动的滑块本体。垂直与水平两种方向建议拆成独立派生类,共用逻辑放基类。 具体拆法是 CScroll 作基类装公共字段和方法,CScrollV 管垂直、CScrollH 管水平,三者同写在 Scrolls.mqh。这样后续列表控件直接挂派生类,不必重写滚动逻辑。
「滚动条基类怎么搭才不绕弯」
滚动条在 MT5 图形库里不是独立控件,而是依附于需要滚动数据的元件(比如列表视图)。Scrolls.mqh 不必直接塞进 WndContainer.mqh,只要被其他元件类文件间接包含,滚动条类就能在库里生效。 CScroll 作为基类,只放标准方法和可自定义外观的字段:宽度、背景边框色、滑块区域边框色、不同状态底色、以及滚动时按钮图标。垂直条宽度由 X 轴尺寸决定,水平条由 Y 轴决定,默认都是 15 像素;按钮图标默认 13×13 像素,贴着通用背景内侧 1 像素,不盖边框。改了宽度就得同步换图标,否则会穿帮。 滑轨长度和内部区域宽度按背景宽度自动算,具体步数依赖列表项数量和 Y 轴尺寸,主方法末尾两个参数就是传列表视图大小及其可见部分大小。CScroll::CreateBg() 里对象名拼法取决于滚动条类型和是否指定索引——同类型多滚动条时才需要索引。 CScroll::CalculateThumbSize() 先查滑轨区域是否小于最小长度,小于就返回 false 不建滑轨;通过后才算步长,步长小于 1 则置 1,再算可移动工作区,区小于全区域则算长度,否则给默认 15 像素最小值,末尾转 int 并兜底最小值。CreateThumb() 建对象前必须调它。 基类不存类名称,所以公有主方法里加了类型检查:用 CScroll 直接建会终止并日志报错,必须用派生类 CScrollV / CScrollH。用户只需填最少属性,其余自动初始化。鼠标状态用 ENUM_THUMB_MOUSE_STATE 枚举标识,CheckMouseButtonState() 判左键区域,ZeroThumbVariables() 在松开时清变量;滑轨移动模式下非下拉表单要屏蔽,禁用时解除。ChangeObjectsColor() 按光标和左键状态切颜色,分空闲和滑轨移动两态。 派生类须在同文件建,构造函数按所用类起对应名字。下面这段是 CScroll 基类头部骨架,能直接抄进 MT5 看结构。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Scrolls.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Element.mqh" class="macro">#include "Window.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建滚动条的基类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CScroll : class="kw">public CElement { class="kw">protected: class=class="str">"cmt">//--- 指向元件附加表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 保存表单指针 class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 图表事件处理函数 class="kw">virtual class="type">void OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam); class=class="str">"cmt">//--- 计时器 class="kw">virtual class="type">void OnEventTimer(class="type">void) {} class=class="str">"cmt">//--- 移动元件 class="kw">virtual class="type">void Moving(class="kw">const class="type">int x,class="kw">const class="type">int y);
◍ 滚动条基类的虚接口与垂直水平派生
在自定义面板里做滚动条,先得有一个基类把显隐、重置、删除这些行为框死。下面这段基类 CScroll 用 virtual 虚函数留出四个基础动作:Show 显示、Hide 隐藏、Reset 重置、Delete 删除,外加 SetZorders / ResetZorders 管鼠标左键点击的层级优先级,ResetColors 留了空实现方便后面按需覆写。
class=class="str">"cmt">//--- (class="num">1) 显示, (class="num">2) 隐藏, (class="num">3) 重置, (class="num">4) 删除 class="kw">virtual class="type">void Show(class="type">void); class="kw">virtual class="type">void Hide(class="type">void); class="kw">virtual class="type">void Reset(class="type">void); class="kw">virtual class="type">void Delete(class="type">void); class=class="str">"cmt">//--- (class="num">1) 设置, (class="num">2) 重设鼠标左键点击优先级 class="kw">virtual class="type">void SetZorders(class="type">void); class="kw">virtual class="type">void ResetZorders(class="type">void); class=class="str">"cmt">//--- 重置颜色 class="kw">virtual class="type">void ResetColors(class="type">void) {} }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScroll::CScroll(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScroll::~CScroll(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于管理垂直滚动条的类 class=class="str">"cmt">//+------------------------------------------------------------------+ class CScrollV : class="kw">public CScroll { class="kw">public: CScrollV(class="type">void); ~CScrollV(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollV::CScrollV(class="type">void) { class=class="str">"cmt">//--- 在基类中保存元件类的名称 CElement::ClassName(CLASS_NAME); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollV::~CScrollV(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于管理水平滚动条的类 class=class="str">"cmt">//+------------------------------------------------------------------+ class CScrollH : class="kw">public CScroll { class="kw">public: CScrollH(class="type">void); ~CScrollH(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollH::CScrollH(class="type">void) { class=class="str">"cmt">//--- 在基类中保存元件类的名称 CElement::ClassName(CLASS_NAME); } class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- (class="num">1) 显示, (class="num">2) 隐藏, (class="num">3) 重置, (class="num">4) 删除 class="kw">virtual class="type">void Show(class="type">void); class="kw">virtual class="type">void Hide(class="type">void); class="kw">virtual class="type">void Reset(class="type">void); class="kw">virtual class="type">void Delete(class="type">void); class=class="str">"cmt">//--- (class="num">1) 设置, (class="num">2) 重设鼠标左键点击优先级 class="kw">virtual class="type">void SetZorders(class="type">void); class="kw">virtual class="type">void ResetZorders(class="type">void); class=class="str">"cmt">//--- 重置颜色 class="kw">virtual class="type">void ResetColors(class="type">void) {} }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScroll::CScroll(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScroll::~CScroll(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于管理垂直滚动条的类 class=class="str">"cmt">//+------------------------------------------------------------------+ class CScrollV : class="kw">public CScroll { class="kw">public: CScrollV(class="type">void); ~CScrollV(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollV::CScrollV(class="type">void) { class=class="str">"cmt">//--- 在基类中保存元件类的名称 CElement::ClassName(CLASS_NAME); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollV::~CScrollV(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于管理水平滚动条的类 class=class="str">"cmt">//+------------------------------------------------------------------+ class CScrollH : class="kw">public CScroll { class="kw">public: CScrollH(class="type">void); ~CScrollH(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollH::CScrollH(class="type">void) { class=class="str">"cmt">//--- 在基类中保存元件类的名称 CElement::ClassName(CLASS_NAME); } class=class="str">"cmt">//+------------------------------------------------------------------+
滚动条基类的私有状态与对外接口
把水平滚动条析构函数留空,只是给 CScrollH 一个干净退出点,真正重头戏在继承自 CElement 的 CScroll 类里。它把滚动条拆成四块区域:通用区域、滑轨背景、两端箭头图标、可拖动的滑块(thumb),各自带颜色与层级(zorder)属性。 从 protected 段能数出 23 个成员变量,例如 m_thumb_min_length 限制滑块最小像素长度,m_thumb_steps_total 决定整条轨被均分成多少步。这些量直接决定你在 MT5 自定义控件里拖滑块时,一步跳多少、最小能拖多窄。 public 段已经暴露了读写接口:ScrollWidth(const int) 写宽度,ScrollWidth(void) 读宽度,AreaColor(const color) 改背景填充色。你在 EA 面板里想动态改滚动条粗细,调这两个函数即可,不用碰内部变量。 外汇与贵金属图表挂这类自绘控件时,注意高杠杆环境下图表重绘频繁,滑块步长设太小可能让点击响应显得粘滞,建议先在策略测试器里用不同 m_thumb_step_size 验证手感。
class=class="str">"cmt">//| 析构函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollH::~CScrollH(class="type">void) { } class CScroll : class="kw">public CElement { class="kw">protected: class=class="str">"cmt">//--- 滚动条通用区域的属性 class="type">int m_area_width; class="type">int m_area_length; class="type">class="kw">color m_area_color; class="type">class="kw">color m_area_border_color; class=class="str">"cmt">//--- 滑轨背景的属性 class="type">int m_bg_length; class="type">class="kw">color m_bg_border_color; class=class="str">"cmt">//--- 按钮图标 class="type">class="kw">string m_inc_file_on; class="type">class="kw">string m_inc_file_off; class="type">class="kw">string m_dec_file_on; class="type">class="kw">string m_dec_file_off; class=class="str">"cmt">//--- 滑轨不同状态的颜色 class="type">class="kw">color m_thumb_color; class="type">class="kw">color m_thumb_color_hover; class="type">class="kw">color m_thumb_color_pressed; class="type">class="kw">color m_thumb_border_color; class="type">class="kw">color m_thumb_border_color_hover; class="type">class="kw">color m_thumb_border_color_pressed; class=class="str">"cmt">//--- (class="num">1) 滑轨的宽度, (class="num">2) 滑轨的长度 (class="num">3) 以及它的最小长度 class="type">int m_thumb_width; class="type">int m_thumb_length; class="type">int m_thumb_min_length; class=class="str">"cmt">//--- (class="num">1) 滑轨的步长 以及(class="num">2)步数 class="type">class="kw">double m_thumb_step_size; class="type">class="kw">double m_thumb_steps_total; class=class="str">"cmt">//--- 鼠标左键点击优先级 class="type">int m_area_zorder; class="type">int m_bg_zorder; class="type">int m_arrow_zorder; class="type">int m_thumb_zorder; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 滑轨的宽度 class="type">void ScrollWidth(class="kw">const class="type">int width) { m_area_width=width; } class="type">int ScrollWidth(class="type">void) class="kw">const { class="kw">return(m_area_width); } class=class="str">"cmt">//--- (class="num">1) 背景的颜色, (class="num">2) 背景边框的颜色 以及 (class="num">3) 背景内部框的颜色 class="type">void AreaColor(class="kw">const class="type">class="kw">color clr) { m_area_color=clr; }