图形界面 V: 垂直与水平滚动条 (第一章)(基础篇)
📘

图形界面 V: 垂直与水平滚动条 (第一章)(基础篇)

第 1/3 篇

「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 看结构。

MQL5 / C++
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 留了空实现方便后面按需覆写。

MQL5 / C++
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">//+------------------------------------------------------------------+
逐行看:第1到4行注释点出四个核心生命周期动作,对应的 virtual void 声明让子类能各自重写;SetZorders 和 ResetZorders 专门处理左键点击的 Z 序,做叠层界面时容易忽略这点导致点了滚动条却触发底层图表。ResetColors 后面直接写空函数体,说明颜色重置不是基类职责,交给你自己的派生类填。 CScrollV 和 CScrollH 都公开继承 CScroll,构造函数里只做了一件事——调用 CElement::ClassName(CLASS_NAME) 把类名牌挂到基类上。这意味着你在 MT5 里 new 一个 CScrollV 或 CScrollH 后,基类的元素识别名就已经就位,后续用 CElement 接口做统一管理时不会认不出对象类型。外汇和贵金属图表挂这类自定义控件属高风险操作,加载前建议在策略测试器先跑一遍空面板验证继承链不报错。

MQL5 / C++
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 验证手感。

MQL5 / C++
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;              }

常见问题

多数情况是没把滚动条拆成背景、滑块、上下按钮、轨道五个对象分别定位。先按五个图形对象拼装,再统一用基类管坐标,就不会偏。
按钮、标签、编辑框等基础控件已落地,滚动条在本章作为基类给出垂直与水平派生结构,可直接继承使用。
可以。小布能读取你的控件结构描述,自动生成滚动条基类派生代码并给出对齐校验,省去手动拼五个对象的步骤。
滑块位置、可见范围必须私有防止错改;设置最大值、当前偏移量、重绘通知要留虚接口给垂直水平派生类调用。
不会。基类只管状态和虚接口,方向相关命中判断交给派生类,实测对象事件分发无明显延迟。