图形界面 V: 垂直与水平滚动条 (第一章)·进阶篇
📜

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

(2/3)· 列表视图装不下数据时,滚动条类架构错了就会处处补丁,这篇先把骨架立稳

案例拆解 第 2/3 篇
很多自定义面板写到表格溢出就卡住,临时堆图形对象做滚动,结果按钮和滑块状态全对不上。先厘清基类与派生类的分工,后续列表视图接入能省掉大半返工。

「滚动条配色与图标接口的底层绑定」

自定义滚动条类里,边框和背景色并非直接写死在绘制函数,而是透过一组 setter 把颜色存进成员变量。AreaBorderColor 与 BgBorderColor 分别接管滑轨区域边框、背景边框;改这两个值,MT5 控件重绘时才会换肤。 按钮图标走的是文件路径注入,不是内嵌位图。IncFileOn / IncFileOff / DecFileOn / DecFileOff 四个接口接收 string 路径,分别指向增量键 hover 与非 hover、减量键两套状态图。留空字符串时对应状态不画图标,这是调试界面闪烁时常漏看的点。 Thumb 系列更细:ThumbColor 管滑块底色,ThumbColorHover / ThumbColorPressed 管鼠标悬停与按下的底色;边框同理拆成 BorderColor 与带 Hover、Pressed 后缀的三态。三态若只设了默认态,滑上去颜色不会变,容易误以为控件坏了。 构造函数把 m_area_width 初始化为 15,即滑轨默认宽 15 像素;m_area_length、m_thumb_width 初始为 0,意味着不主动算长度,滑块宽度就是塌的。开盘前若没调过这几个值,滚动条可能不可见。外汇与贵金属图表挂这类自定义控件属高风险改造,参数错乱可能卡 UI,建议在模拟账户先跑。

MQL5 / C++
class="type">void AreaBorderColor(class="kw">const class="type">class="kw">color clr) { m_area_border_color=clr; }
class="type">void BgBorderColor(class="kw">const class="type">class="kw">color clr) { m_bg_border_color=clr; }
class=class="str">"cmt">//--- 设置按钮图标
class="type">void IncFileOn(class="kw">const class="type">class="kw">string file_path) { m_inc_file_on=file_path; }
class="type">void IncFileOff(class="kw">const class="type">class="kw">string file_path){ m_inc_file_off=file_path; }
class="type">void DecFileOn(class="kw">const class="type">class="kw">string file_path) { m_dec_file_on=file_path; }
class="type">void DecFileOff(class="kw">const class="type">class="kw">string file_path){ m_dec_file_off=file_path; }
class=class="str">"cmt">//--- (class="num">1) 滑轨背景的颜色 以及(class="num">2)滑轨背景边框的颜色
class="type">void ThumbColor(class="kw">const class="type">class="kw">color clr) { m_thumb_border_color=clr; }
class="type">void ThumbColorHover(class="kw">const class="type">class="kw">color clr) { m_thumb_border_color_hover=clr; }
class="type">void ThumbColorPressed(class="kw">const class="type">class="kw">color clr) { m_thumb_border_color_pressed=clr; }
class="type">void ThumbBorderColor(class="kw">const class="type">class="kw">color clr) { m_thumb_border_color=clr; }
class="type">void ThumbBorderColorHover(class="kw">const class="type">class="kw">color clr) { m_thumb_border_color_hover=clr; }
class="type">void ThumbBorderColorPressed(class="kw">const class="type">class="kw">color clr){ m_thumb_border_color_pressed=clr; }
};
CScroll::CScroll(class="type">void) : m_area_width(class="num">15),
             m_area_length(class="num">0),
             m_inc_file_on(""),
             m_inc_file_off(""),
             m_dec_file_on(""),
             m_dec_file_off(""),
             m_thumb_width(class="num">0),

◍ 滚动条类的初始化与图层优先级

在 MT5 自定义控件里,CScroll 的构造函数用初始化列表把滑块最小长度锁死在 15 像素,背景与滑块配色全部用 C'R,G,B' 整型色值写死,例如滑块常态 C'190,190,190'、按下态 C'160,160,160'。这样的硬编码适合做统一风格的面板,但若要做深色主题得逐行改。 构造函数体内紧接着给四个图层的 zorder 赋值:滑动区 8、背景 9、箭头 10、滑块 11。数字越大越靠前,意味着鼠标点击判定会优先落在滑块和箭头上,避免背景吞掉事件。 类声明里把 m_area、m_bg、m_inc、m_dec、m_thumb 全部保护起来,对外只暴露 CreateScroll(chart_id, subwin, x, y, items_total, visible_items_total)。后两个参数决定内容总量与可视量,是滑块长度计算的输入,开 MT5 写个测试面板传不同值就能看到滑块比例变化。 CreateBg 内部先拼对象名,依据类名判断是竖滚还是横滚:CScrollV 用 '_scrollv_bg_' 前缀,否则 '_scrollh_bg_'。这一行决定了同图表里多个滚动条不会重名冲突,复制代码时别顺手改了前缀规则。

MQL5 / C++
m_thumb_length(class="num">0),
 m_thumb_min_length(class="num">15),
 m_area_color(C&class="macro">#x27;class="num">210,class="num">210,class="num">210&class="macro">#x27;),
 m_area_border_color(C&class="macro">#x27;class="num">240,class="num">240,class="num">240&class="macro">#x27;),
 m_bg_border_color(C&class="macro">#x27;class="num">210,class="num">210,class="num">210&class="macro">#x27;),
 m_thumb_color(C&class="macro">#x27;class="num">190,class="num">190,class="num">190&class="macro">#x27;),
 m_thumb_color_hover(C&class="macro">#x27;class="num">180,class="num">180,class="num">180&class="macro">#x27;),
 m_thumb_color_pressed(C&class="macro">#x27;class="num">160,class="num">160,class="num">160&class="macro">#x27;),
 m_thumb_border_color(C&class="macro">#x27;class="num">170,class="num">170,class="num">170&class="macro">#x27;),
 m_thumb_border_color_hover(C&class="macro">#x27;class="num">160,class="num">160,class="num">160&class="macro">#x27;),
 m_thumb_border_color_pressed(C&class="macro">#x27;class="num">140,class="num">140,class="num">140&class="macro">#x27;)
 {
class=class="str">"cmt">//--- 设置鼠标左键点击的优先级
 m_area_zorder  =class="num">8;
 m_bg_zorder    =class="num">9;
 m_arrow_zorder =class="num">10;
 m_thumb_zorder =class="num">11;
 }
class CScroll : class="kw">public CElement
 {
class="kw">protected:
 class=class="str">"cmt">//--- 用于创建滚动条的对象
 CRectLabel       m_area;
 CRectLabel       m_bg;
 CBmpLabel        m_inc;
 CBmpLabel        m_dec;
 CRectLabel       m_thumb;
 class=class="str">"cmt">//---
class="kw">public:
 class=class="str">"cmt">//--- 用于创建滚动条的方法
 class="type">bool             CreateScroll(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">int items_total,class="kw">const class="type">int visible_items_total);
 class=class="str">"cmt">//---
class="kw">private:
 class="type">bool             CreateArea(class="type">void);
 class="type">bool             CreateBg(class="type">void);
 class="type">bool             CreateInc(class="type">void);
 class="type">bool             CreateDec(class="type">void);
 class="type">bool             CreateThumb(class="type">void);
 };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建滚动条背景                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CScroll::CreateBg(class="type">void)
 {
class=class="str">"cmt">//--- 构造对象名称
 class="type">class="kw">string name     ="";
 class="type">class="kw">string name_part =(CElement::ClassName()=="CScrollV")? "_scrollv_bg_" : "_scrollh_bg_";
class=class="str">"cmt">//--- 如果没有指定索引

滚动条背景与滑轨长度的实算逻辑

在自定义 GUI 的滚动条类里,背景对象 m_bg 的创建依赖类名分支:竖向滚动条 CScrollV 取 Y 方向可用长度减两倍滑块宽再减 2 作为滑轨区长 m_bg_length,横向则按 X 方向算。这个减 2 是边框占位,改 m_thumb_width 会直接改变 x/y 偏移与背景尺寸,开 MT5 调参能立刻看到滑块贴边变化。 滑轨步长用 (m_bg_length - m_thumb_min_length) / m_thumb_steps_total 算,若结果小于 1 则强制为 1,避免整数坐标下卡死不动。当 work_area 小于背景长时,滑块长 = 背景长 - work_area + 步长,否则退化为最小长度 m_thumb_min_length,这段代码决定了拖拽手感是否跟手。 下面这段是背景创建与滑轨计算的核心片段,注意 CElement::Index() 为 WRONG_VALUE 时用 Id() 命名,否则拼上 Index 和 Id,避免同程序多实例对象名冲突。外汇与贵金属图表上挂这类自定义控件需注意:高频刷新可能触发对象重绘卡顿,属高风险定制用法。

MQL5 / C++
if(CElement::Index()==WRONG_VALUE)
      name=CElement::ProgramName()+name_part+(class="type">class="kw">string)CElement::Id();
class=class="str">"cmt">//--- 如果已经指定了索引
   else
      name=CElement::ProgramName()+name_part+(class="type">class="kw">string)CElement::Index()+"__"+(class="type">class="kw">string)CElement::Id();
class=class="str">"cmt">//--- 坐标
   class="type">int x=class="num">0;
   class="type">int y=class="num">0;
class=class="str">"cmt">//--- 大小
   class="type">int x_size=class="num">0;
   class="type">int y_size=class="num">0;
class=class="str">"cmt">//--- 根据滚动条类型设置属性
   if(CElement::ClassName()=="CScrollV")
     {
      m_bg_length =CElement::YSize()-(m_thumb_width*class="num">2)-class="num">2;
      x           =CElement::X()+class="num">1;
      y           =CElement::Y()+m_thumb_width+class="num">1;
      x_size      =m_thumb_width;
      y_size      =m_bg_length;
     }
   else
     {
      m_bg_length =CElement::XSize()-(m_thumb_width*class="num">2)-class="num">2;
      x           =CElement::X()+m_thumb_width+class="num">1;
      y           =CElement::Y()+class="num">1;
      x_size      =m_bg_length;
      y_size      =m_thumb_width;
     }
class=class="str">"cmt">//--- 创建对象
   if(!m_bg.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      class="kw">return(false);
class=class="str">"cmt">//--- 设置属性
   m_bg.BackColor(m_area_color);
   m_bg.Color(m_bg_border_color);
   m_bg.BorderType(BORDER_FLAT);
   m_bg.Corner(m_corner);
   m_bg.Selectable(false);
   m_bg.Z_Order(m_bg_zorder);
   m_bg.Tooltip("\n");
class=class="str">"cmt">//--- 保存坐标
   m_bg.X(x);
   m_bg.Y(y);
class=class="str">"cmt">//--- 保存边缘
   m_bg.XGap(x-m_wnd.X());
   m_bg.YGap(y-m_wnd.Y());
class=class="str">"cmt">//--- 保存大小
   m_bg.XSize(x_size);
   m_bg.YSize(y_size);
class=class="str">"cmt">//--- 保存对象指针
   CElement::AddToArray(m_bg);
   class="kw">return(true);
   }
class CScroll : class="kw">public CElement
  {
class="kw">protected:
class="kw">public:
   class=class="str">"cmt">//--- 计算滚动条滑轨的长度
   class="type">bool            CalculateThumbSize(class="type">void);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计算滚动条滑轨的长度                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CScroll::CalculateThumbSize(class="type">void)
  {
class=class="str">"cmt">//--- 如果移动滑轨的区域长度小于最小滑轨长度,就不需要计算
   if(m_bg_length<m_thumb_min_length)
      class="kw">return(false);
class=class="str">"cmt">//--- 计算滑轨步长
   m_thumb_step_size=(class="type">class="kw">double)(m_bg_length-m_thumb_min_length)/m_thumb_steps_total;
class=class="str">"cmt">//--- 步长不能小于1
   m_thumb_step_size=(m_thumb_step_size<class="num">1)? class="num">1 : m_thumb_step_size;
class=class="str">"cmt">//--- 计算用于移动滑轨的工作区域的大小
   class="type">class="kw">double work_area=m_thumb_step_size*m_thumb_steps_total;
class=class="str">"cmt">//--- 如果工作区域的大小小于整个区域的大小,取得滑轨的大小,否则设置最小值
   class="type">class="kw">double thumb_size=(work_area<m_bg_length)? m_bg_length-work_area+m_thumb_step_size : m_thumb_min_length;
class=class="str">"cmt">//--- 使用类型转换检查滑轨大小
   m_thumb_length=((class="type">int)thumb_size<m_thumb_min_length)? m_thumb_min_length :(class="type">int)thumb_size;

「滚动条滑块的创建逻辑」

在 MT5 自定义控件里,滚动条滑块(thumb)的实例化由 CScroll::CreateThumb 完成。函数先拼出对象名:垂直滚动条带 _scrollv_thumb_ 后缀,水平滚动条带 _scrollh_thumb_,未指定 Index 时用 ProgramName+后缀+Id,指定了则用 ProgramName+后缀+Index+__+Id,避免同图表多实例重名。 坐标与尺寸按类型分支:垂直态 x 取滑块既有 X 或 m_x+1,y 取既有 Y 或 m_y+m_thumb_width+1,宽高锁定为 m_thumb_width 与 m_thumb_length;水平态则交换长宽角色,x 基准变成 m_x+m_thumb_width+1。 CalculateThumbSize 若返回 false 会直接 return(true) 跳过创建——这是个容易踩的坑,意味着外部必须先保证滑块尺寸算通。随后 m_thumb.Create 失败才返回 false,成功则写死 BORDER_FLAT、不可选中、Z 序与边距,并把指针存入数组。 外层 CScroll::CreateScroll 接收 chart_id、subwin、x、y、items_total、visible_items_total 六个参数;若 m_wnd 指针无效(POINTER_INVALID)会直接 Print 报错并中断,所以传参前务必用 CElement 基类绑好父窗口指针。

MQL5 / C++
class="type">bool CScroll::CreateThumb(class="type">void)
  {
class=class="str">"cmt">//--- 构造对象名称
   class="type">class="kw">string name     ="";
   class="type">class="kw">string name_part =(CElement::ClassName()=="CScrollV")? "_scrollv_thumb_" : "_scrollh_thumb_";
class=class="str">"cmt">//--- 如果没有指定索引
   if(CElement::Index()==WRONG_VALUE)
      name=CElement::ProgramName()+name_part+(class="type">class="kw">string)CElement::Id();
class=class="str">"cmt">//--- 如果已经指定了索引
   else
      name=CElement::ProgramName()+name_part+(class="type">class="kw">string)CElement::Index()+"__"+(class="type">class="kw">string)CElement::Id();
class=class="str">"cmt">//--- 坐标
   class="type">int x=class="num">0;
   class="type">int y=class="num">0;
class=class="str">"cmt">//--- 大小
   class="type">int x_size=class="num">0;
   class="type">int y_size=class="num">0;
class=class="str">"cmt">//--- 计算滚动条的大小
   if(!CalculateThumbSize())
      class="kw">return(true);
class=class="str">"cmt">//--- 根据滚动条类型设置属性
   if(CElement::ClassName()=="CScrollV")
     {
      x      =(m_thumb.X()>class="num">0) ? m_thumb.X() : m_x+class="num">1;
      y      =(m_thumb.Y()>class="num">0) ? m_thumb.Y() : m_y+m_thumb_width+class="num">1;
      x_size =m_thumb_width;
      y_size =m_thumb_length;
     }
   else
     {
      x      =(m_thumb.X()>class="num">0) ? m_thumb.X() : m_x+m_thumb_width+class="num">1;
      y      =(m_thumb.Y()>class="num">0) ? m_thumb.Y() : m_y+class="num">1;
      x_size =m_thumb_length;
      y_size =m_thumb_width;
     }
class=class="str">"cmt">//--- 创建对象
   if(!m_thumb.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      class="kw">return(false);
class=class="str">"cmt">//--- 设置属性
   m_thumb.BackColor(m_thumb_color);
   m_thumb.Color(m_thumb_border_color);
   m_thumb.BorderType(BORDER_FLAT);
   m_thumb.Corner(m_corner);
   m_thumb.Selectable(false);
   m_thumb.Z_Order(m_thumb_zorder);
   m_thumb.Tooltip("\n");
class=class="str">"cmt">//--- 保存坐标
   m_thumb.X(x);
   m_thumb.Y(y);
class=class="str">"cmt">//--- 保存边缘
   m_thumb.XGap(x-m_wnd.X());
   m_thumb.YGap(y-m_wnd.Y());
class=class="str">"cmt">//--- 保存大小
   m_thumb.XSize(x_size);
   m_thumb.YSize(y_size);
class=class="str">"cmt">//--- 保存对象指针
   CElement::AddToArray(m_thumb);
   class="kw">return(true);
  }

class="type">bool CScroll::CreateScroll(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">int items_total,class="kw">const class="type">int visible_items_total)
  {
class=class="str">"cmt">//--- 如果没有表单指针
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
    {
     ::Print(__FUNCTION__," > 在创建滚动条之前, 类必须传入"

◍ 滚动条基类的初始化与鼠标状态枚举

CScroll 的初始化函数里先做了两道防护:表单指针取不到时直接返回 false;若基类 CElement 的 ClassName() 为空,说明有人想直接实例化基类,也立刻退出并提示必须用派生类 CScrollV 或 CScrollH。 变量初始化时按方向区分滑轨尺寸:竖向滚动条(CScrollV)用 XSize() 作宽度、YSize() 作长度,横向则反过来;滑块宽度固定为区域宽减 2,总步数 = 条目总数 - 可见条目数 + 1。例如 100 条数据、可见 10 条,m_thumb_steps_total 就是 91。 创建流程依次调 CreateArea / CreateBg / CreateInc / CreateDec / CreateThumb,任一步失败即终止。若所属窗口是对话框或已最小化,整体 Hide()。 鼠标交互侧定义了 ENUM_THUMB_MOUSE_STATE:0=未按下、1=在滑块外按下、2=在滑块内按下。CScroll 类暴露 CheckMouseButtonState(bool) 来记录按键区域,并用 ZeroThumbVariables() 在拖动结束后清零 m_thumb_size_fixing、m_thumb_point_fixing 等定位变量。 别拿基类当实例用 直接 new CScroll 会在运行时报错退出,MT5 终端里看到『使用滚动条的派生类』这句打印,就该把代码改成 CScrollV 或 CScrollH。

MQL5 / C++
if(CElement::ClassName()=="")
  {
   ::Print(__FUNCTION__," > 使用滚动条的派生类 (CScrollV 或者 CScrollH).");
   class="kw">return(false);
  }
m_area_width       =(CElement::ClassName()=="CScrollV")? CElement::XSize() : CElement::YSize();
m_area_length      =(CElement::ClassName()=="CScrollV")? CElement::YSize() : CElement::XSize();
m_thumb_width      =m_area_width-class="num">2;
m_thumb_steps_total=items_total-visible_items_total+class="num">1;

enum ENUM_THUMB_MOUSE_STATE
  {
   THUMB_NOT_PRESSED     =class="num">0,
   THUMB_PRESSED_OUTSIDE =class="num">1,
   THUMB_PRESSED_INSIDE  =class="num">2
  };

class="type">void CScroll::CheckMouseButtonState(class="kw">const class="type">bool mouse_state)
  {

滚动条拖拽状态与配色联动的实现细节

在 MT5 自定义控件里,滚动条滑块(thumb)的按下、松开、移出区域都要靠 mouse_state 与 m_clamping_area_mouse 这套状态机区分。若鼠标未按下,直接调 ZeroThumbVariables() 清零并 return,避免后续误判。 按下时要先判断 m_clamping_area_mouse 是否已在别的区域置位,若是则直接退出,防止跨区重复触发。滑块在区域内按下会置为 THUMB_PRESSED_INSIDE,区域外则是 THUMB_PRESSED_OUTSIDE;非下拉类元件还会锁住所在的 CWindow 表单并记录激活 ID,保证拖动期间别的控件不抢焦点。 ZeroThumbVariables() 里把 m_thumb_size_fixing 设 0、m_clamping_area_mouse 回到 THUMB_NOT_PRESSED;非下拉元件同时解锁表单、把激活 ID 重置为 WRONG_VALUE。这一对函数是滑块交互干净退出的关键。 ChangeObjectsColor() 用 m_clamping_area_mouse 决定滑块配色:未按时用 hover 色,区域内按下切到 pressed 色。实测若漏掉 IsLocked() 判断,多控件同屏时会出现 A 控件滚动条染上 B 控件颜色的串色现象,开 MT5 拖一下就能复现。

MQL5 / C++
if(!mouse_state)
  {
   class=class="str">"cmt">//--- 变量清零
   ZeroThumbVariables();
   class="kw">return;
  }
class=class="str">"cmt">//--- 如果已按下
if(mouse_state)
  {
   class=class="str">"cmt">//--- 如果按键在另一个区域内按下,就退出
   if(m_clamping_area_mouse!=THUMB_NOT_PRESSED)
     class="kw">return;
   class=class="str">"cmt">//--- 在滑轨区域之外
   if(!m_thumb.MouseFocus())
     m_clamping_area_mouse=THUMB_PRESSED_OUTSIDE;
   class=class="str">"cmt">//--- 在滑轨区域之内
   else
     {
      m_clamping_area_mouse=THUMB_PRESSED_INSIDE;
      class=class="str">"cmt">//--- 如果不是下拉元件
      if(!CElement::IsDropdown())
        {
         class=class="str">"cmt">//--- 屏蔽表单并保存活动元件的ID
         m_wnd.IsLocked(true);
         m_wnd.IdActivatedElement(CElement::Id());
        }
     }
  }

class="type">void CScroll::ZeroThumbVariables(class="type">void)
  {
   class=class="str">"cmt">//--- 如果不是一个下拉元件
   if(!CElement::IsDropdown())
     {
      class=class="str">"cmt">//--- 解除表单屏蔽并重置活动元件ID
      m_wnd.IsLocked(false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
     }
   m_thumb_size_fixing   =class="num">0;
   m_clamping_area_mouse =THUMB_NOT_PRESSED;
  }

class CScroll : class="kw">public CElement
  {
class="kw">public:
   class=class="str">"cmt">//--- 改变滚动条对象的颜色
   class="type">void ChangeObjectsColor(class="type">void);
  };

class="type">void CScroll::ChangeObjectsColor(class="type">void)
  {
   class=class="str">"cmt">//--- 如果表单被屏蔽并且当前活动的元件ID不同,就退出
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
     class="kw">return;
   class=class="str">"cmt">//--- 列表视图滚动条按钮的颜色
   if(!m_scroll_state)
     {
      m_inc.State(m_inc.MouseFocus());
      m_dec.State(m_dec.MouseFocus());
     }
   class=class="str">"cmt">//--- 如果光标在滚动条区域之内
   if(m_thumb.MouseFocus())
     {
      class=class="str">"cmt">//--- 如果松开了鼠标左键
      if(m_clamping_area_mouse==THUMB_NOT_PRESSED)
        {
         m_scroll_state=false;
         m_thumb.BackColor(m_thumb_color_hover);
         m_thumb.Color(m_thumb_border_color_hover);
        }
      class=class="str">"cmt">//--- 在滑轨上按下了鼠标左键
      else if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE)
        {
         m_scroll_state=true;
         m_thumb.BackColor(m_thumb_color_pressed);
         m_thumb.Color(m_thumb_border_color_pressed);
        }
     }
   class=class="str">"cmt">//--- 如果光标在滚动条区域之外
   else
     {
让小布替你跑这套
滚动条类的像素与状态参数较繁琐,小布盯盘已内置常见品种面板的诊断视图,打开对应品种页即可看到现成控件结构,你只需照骨架改业务数据。

常见问题

滚动条是辅助元件,随列表视图等组合控件间接引入即可,硬塞进 WndContainer 会增加耦合,改动一处容易波及其他界面部件。
基类放共有栏位与方法,宽度定义按轴区分:垂直由 X 轴尺寸决定,水平由 Y 轴尺寸决定,默认 15 像素,派生类只补各自独特属性。
至少开放背景边框色、滑块区边框色、不同状态背景色、按钮各状态图标,方便不同主题面板统一换肤而不动逻辑。
小布盯盘侧重现成诊断与结构展示,不直接吐 MQL5 类文件,但可对照其内置面板反推 Scrolls.mqh 的基类字段布局。
按 15 像素默认宽度挑内置图标,并约束图标位于通用背景内边距中,派生类构造时按类型赋名避免坐标串位。