图形界面列表视图元件:从滚动条复用到类封装的基础铺垫(基础篇)
(1/3)·多数自定义面板卡在列表溢出与滚动不同步,本篇先把底层元件拆清楚
MT5 列表视图元件的骨架
在 MT5 自定义指标或 EA 的图形界面里,列表视图(ListView)是用来承载多行可选项的基础控件,常见于符号切换、周期选择、信号源清单等场景。它和按钮、编辑框不同,核心职责是「纵向罗列 + 单选/多选」,而不是触发动作。 从一篇 2016-06-02 发布的开发者示例看,该文在发布后累计获得 5 670 次查看与 9 条留言,说明这类底层 GUI 元件在长期交易工具开发中仍有稳定需求。 要落地一个列表视图,通常先写一个封装类,把创建、插入行、取选中项、清列表这些方法收进去;之后在 OnInit 里实例化,在 OnChartEvent 里响应选择事件。下一步就是先跑通「设置项测试」,确认列宽、滚动、字体在 MT5 上的实际表现。
「从滚动条类到列表视图的衔接」
上一章已经把垂直和水平滚动条的类写完了,这一章直接拿它们来用。核心动作是开发一个列表视图(List View)元件类,而垂直滚动条会是这个元件的一个内置组成部分,不是外部拼凑。 本章还会把“按住滚动条按钮不松手就持续滚动列表”的机制实出来,这属于交互层细节,很多自制 UI 库会漏掉。 收尾部分会用一段真实 MQL 应用程序做详细测试,也就是说代码不是孤立类,而是能直接挂进 EA/指标里跑。外汇与贵金属品种波动剧烈、杠杆高风险极大,任何 UI 辅助都只是观察工具,不构成方向判断。
◍ 列表视图元件的构成与滚动逻辑
MT5 图形界面里的列表视图,本质是在有限可视区里承载超额条目的选择器。当项目总数超过工作区能直接显示的数量,可视部分只保留一屏,其余靠垂直滚动条拉动——这一点在写 EA 面板时要先想清楚,否则界面会撑爆图表。 它的底层不是单个对象,而是背景、项目数组、垂直滚动条控件三块拼起来的。背景定边界,项目数组存内容,滚动条管溢出,三者坐标和事件必须联动,否则点选会错位。 实际开发里,项目总数和可视项目数是两个独立变量。比如可视区高 200 像素、单行 20 像素,那最多稳显 10 条,第 11 条起就必须走滚动,这类数值要写死在布局计算里方便后续调参。
搭一个可挂滚动条的列表视图类
在 MT5 自定义库里做列表视图元件,先建 ListView.mqh 把 CListView 类放进去,再到 WndContainer.mqh 用 #include "ListView.mqh" 挂接。这个类要支持垂直滚动条,所以 ListView.mqh 里必须再 #include "Scrolls.mqh",否则滚动逻辑编不过。 CListView 的构造函数里就得把基础属性定死:列表视图总高、背景边框色、不同状态下项目背景色与文字色。项目数硬性下限是 2,因为只含一个项目的列表视图在 GUI 上没法正常渲染,ListSize() 和 VisibleListSize() 方法里都做了小于 2 就拒绝的设置。 项目用 CEdit 类型实例数组批量建 OBJ_EDIT 图形对象。坐标和宽度计算会避开背景边框,且相邻项目故意重叠 1 像素——这是为了避免鼠标掠过高亮时项目间露缝。背景高度和滚动条高度算的时候也得把这 1 像素吃进去。 默认第一个项目高亮。CreateList() 末尾才标出被选项并存文字;HighlightSelectedItem() 开头先查滚动条状态,滑轨可动就直接退出,避免和拖拽冲突。CreateScrollV() 里先比总数和可见数,可见数 >= 总数就不建滚动条。 用户可关掉“鼠标悬停高亮”,默认就是关的。文字对齐用单独方法设,左/右/中三选一,默认左对齐。数据数组先用 ValueToList() 做越界保护再喂给列表。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| WndContainer.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "ListView.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ListView.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="macro">#include "Scrolls.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建列表视图的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CListView : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 指向元件附加表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- class="kw">public: CListView(class="type">void); ~CListView(class="type">void); class=class="str">"cmt">//--- (class="num">1) Stores the form pointer class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class=class="str">"cmt">//--- class="kw">public:
「列表视图类的事件与外观接口」
MQL5 里 CListView 继承自 CElement,把图表控件常用的生命周期都收口成虚函数:OnEvent 接收图表事件 id 与 lparam/dparam/sparam 三参数,OnEventTimer 管计时器脉冲,Moving 在控件被拖到 (x,y) 坐标时触发。 显示状态由四个独立虚函数分管——Show 显示、Hide 隐藏、Reset 重置、Delete 删除,层级权限则交给 SetZorders 与 ResetZorders 处理鼠标左键的 z-order 属性,互不耦合。 构造函数只做一件事:调用 CElement::ClassName(CLASS_NAME) 把类名字写进基类,析构函数为空实现。私有成员里 m_item_y_size 控制单行高度,m_item_color 与 m_item_color_hover / m_item_color_selected 三色分离,分别对应常态、悬停、选中;文本色也按同样三态拆分。 对外暴露的两个 setter 极简:ItemYSize(10) 直接把 m_item_y_size 写成 10 像素,AreaBorderColor(C'200,200,200') 把背景边框色赋给 m_area_border_color。开 MT5 新建 EA 引用该类,改 m_item_y_size 从默认 20 调到 14,列表密度会明显变大,可立刻编译验证。
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="kw">virtual class="type">void OnEventTimer(class="type">void); class="kw">virtual class="type">void Moving(class="kw">const class="type">int x,class="kw">const class="type">int y); 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="kw">virtual class="type">void SetZorders(class="type">void); class="kw">virtual class="type">void ResetZorders(class="type">void); }; CListView::CListView(class="type">void) { CElement::ClassName(CLASS_NAME); } CListView::~CListView(class="type">void) { } class CListView : class="kw">public CElement { class="kw">private: class="type">int m_area_zorder; class="type">color m_area_border_color; class="type">int m_item_zorder; class="type">int m_item_y_size; class="type">color m_item_color; class="type">color m_item_color_hover; class="type">color m_item_color_selected; class="type">color m_item_text_color; class="type">color m_item_text_color_hover; class="type">color m_item_text_color_selected; class="kw">public: class="type">void ItemYSize(class="kw">const class="type">int y_size) { m_item_y_size=y_size; } class="type">void AreaBorderColor(class="kw">const class="type">color clr) { m_area_border_color=clr; }
◍ 列表视图控件的配色与构造初始化
在 MT5 自定义图形界面里,列表视图(CListView)的项目配色被拆成了六种状态:常态、悬停、选中,各自对应背景色与文字色。源码用一组 setter 把颜色写进私有成员,方便在运行时动态换肤。 构造函数里给出了默认视觉参数:单行高度固定为 18 像素,边框取浅灰 C'235,235,235',选中背景是 C'51,153,255'(蓝),选中文字为白。悬停背景仅比白色稍深一档 C'240,240,240',肉眼能察觉但不刺眼。 鼠标层级也在这里定死:列表区 m_area_zorder=1,项目 m_item_zorder=2,意味着条目永远压在底板之上,点击命中优先走项目层。开 MT5 新建个 CListView 实例,不改参数就能直接看到这套默认蓝白选中效果。
class="type">void ItemColor(class="kw">const class="type">color clr) { m_item_color=clr; } class="type">void ItemColorHover(class="kw">const class="type">color clr) { m_item_color_hover=clr; } class="type">void ItemColorSelected(class="kw">const class="type">color clr) { m_item_color_selected=clr; } class="type">void ItemTextColor(class="kw">const class="type">color clr) { m_item_text_color=clr; } class="type">void ItemTextColorHover(class="kw">const class="type">color clr) { m_item_text_color_hover=clr; } class="type">void ItemTextColorSelected(class="kw">const class="type">color clr) { m_item_text_color_selected=clr; } }; CListView::CListView(class="type">void) : m_item_y_size(class="num">18), m_area_border_color(C&class="macro">#x27;class="num">235,class="num">235,class="num">235&class="macro">#x27;), m_item_color(clrWhite), m_item_color_hover(C&class="macro">#x27;class="num">240,class="num">240,class="num">240&class="macro">#x27;), m_item_color_selected(C&class="macro">#x27;class="num">51,class="num">153,class="num">255&class="macro">#x27;), m_item_text_color(clrBlack), m_item_text_color_hover(clrBlack), m_item_text_color_selected(clrWhite) { m_area_zorder =class="num">1; m_item_zorder =class="num">2; }