图形界面 X: 高级列表和表格管理。代码优化 (集成构建 7)(基础篇)
◍ 用 ListView 做可排序的多列行情表
在 MT5 的 GUI 开发中,标准控件对多列数据支持很弱。想做一个能实时刷新的行情列表,得自己用 Canvas 或 CListView 扩展。 下面这段来自官方示例的 CListView 子类,演示了如何给列表加列头、插入行并绑定数据。注意 OnScroll 和 OnClick 的重写,这是交互流畅的关键。 外汇与贵金属行情波动剧烈,这类自定义面板只解决展示效率,不预测方向,实盘前务必在策略测试器里验证刷新开销。
class CListViewEx : class="kw">public CListView { class="kw">protected: class="kw">virtual class="type">void OnScroll(class="type">void); class="kw">virtual class="type">void OnClick(class="type">void); class="kw">public: class="type">bool AddColumn(class="type">class="kw">string text,class="type">int width); class="type">bool AddItem(class="type">int col,class="type">class="kw">string text); };
函数库重构与控件可编程化
这一版函数库在规划层面做了拆分,把原先耦合在一起的界面逻辑下沉为可独立调用的控件类,编译后的体积较旧版平均缩减约 18%,在 MT5 终端反复加载 EA 时卡顿感明显降低。 滚动条和列表现在支持用代码直接驱动,不再只能靠鼠标事件被动响应。CTable 类型的表格重写了底层绘制循环,万行级数据刷新耗时从旧版的 120ms 降到 45ms 左右,这对盯盘时实时画订单流表很有用。 随库附带了一个测试控件的小程序,打开就能看到滚动条、列表、表格被脚本推着跑的效果。外汇与贵金属品种波动剧烈,用这类自定义控件做高频刷新面板时,需自行压低刷新频率以防终端掉帧。
「运行时接管控件属性」
这套界面函数库的目标不是堆功能,而是把之前做好的列表、表格和滚动条,变成能在 EA 或指标跑起来之后随时改参数的对象。原文建议先翻系列首篇《图形界面 I》了解目录约定,库文件按归档结构平铺到同一目录才能编译通过。 这一节要落地的动作很具体:给已有控件补一批方法,让 MQL 程序在运行时以编程方式读写它们的属性。比如列表行高、表格列宽、滚动条位置,不再写死在初始化里,而是随行情或事件动态调整。 代码层面会持续做规范化重构,删掉冗余分支、统一命名,让可读性优先于微优化。对读者来说,直接价值是:下载最新库,打开 MT5 把控件方法挂到 OnTimer 或自定义事件里,就能看到界面随 tick 实时变。外汇和贵金属品种波动快,这类动态界面能降低盯盘误读,但自动刷新逻辑写错也会闪退,属高风险调试项。
◍ 把重复代码抽进中间基类
控件函数库里和界面元素相关的代码,之前散落在各个控件类里反复重写。这次重构把高频重复的逻辑抽出来,原 CElement 改名 CElementBase 作为最底层基类,新插入一层 CElement 专门承载通用方法。 被上移的方法包括:存表单指针、校验指针可用性、校验激活控件 ID、绝对坐标计算、相对边缘坐标计算。基类放 ElementBase.mqh,中间类放 Element.mqh,后者 include 前者以及 Window.mqh,因为 CWindow 类型必须提前可见。
class="macro">#include "ElementBase.mqh" class="macro">#include "Controls\Window.mqh" class CElement : class="kw">public CElementBase { class="kw">protected: CWindow *m_wnd; class=class="str">"cmt">// 控件附属表单指针 class="kw">public: CElement(class="type">void); ~CElement(class="type">void); class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class=class="str">"cmt">// 保存表单指针 class="type">bool CheckWindowPointer(class="type">void); class=class="str">"cmt">// 检查表单指针 class="type">bool CheckIdActivatedElement(class="type">void); class=class="str">"cmt">// 检查激活控件ID class="type">int CalculateX(const class="type">int x_gap); class=class="str">"cmt">// 算绝对X class="type">int CalculateY(const class="type">int y_gap); class=class="str">"cmt">// 算绝对Y class="type">int CalculateXGap(const class="type">int x); class=class="str">"cmt">// 算相对X间隙 class="type">int CalculateYGap(const class="type">int y); class=class="str">"cmt">// 算相对Y间隙 };
class="type">bool CElement::CheckIdActivatedElement(class="type">void) { class="kw">return (m_wnd.IdActivatedElement()==CElementBase::Id()); } class=class="str">"cmt">// 比对激活ID与自身ID class="type">int CElement::CalculateX(const class="type">int x_gap) { class="kw">return ((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x_gap : m_wnd.X()+x_gap); } class=class="str">"cmt">// 右锚用X2减,否则X加 class="type">int CElement::CalculateY(const class="type">int y_gap) { class="kw">return ((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y_gap : m_wnd.Y()+y_gap); } class=class="str">"cmt">// 底锚用Y2减,否则Y加 class="type">int CElement::CalculateXGap(const class="type">int x) { class="kw">return ((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x : x-m_wnd.X()); } class=class="str">"cmt">// 相对X间隙 class="type">int CElement::CalculateYGap(const class="type">int y) { class="kw">return ((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y : y-m_wnd.Y()); } class=class="str">"cmt">// 相对Y间隙
class="macro">#include "ElementBase.mqh" class="macro">#include "Controls\Window.mqh" class CElement : class="kw">public CElementBase { class="kw">protected: CWindow *m_wnd; class="kw">public: CElement(class="type">void); ~CElement(class="type">void); class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class="type">bool CheckWindowPointer(class="type">void); class="type">bool CheckIdActivatedElement(class="type">void); class="type">int CalculateX(const class="type">int x_gap); class="type">int CalculateY(const class="type">int y_gap); class="type">int CalculateXGap(const class="type">int x); class="type">int CalculateYGap(const class="type">int y); }; class="type">bool CElement::CheckIdActivatedElement(class="type">void) { class="kw">return (m_wnd.IdActivatedElement()==CElementBase::Id()); } class="type">int CElement::CalculateX(const class="type">int x_gap) { class="kw">return ((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x_gap : m_wnd.X()+x_gap); } class="type">int CElement::CalculateY(const class="type">int y_gap) { class="kw">return ((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y_gap : m_wnd.Y()+y_gap); } class="type">int CElement::CalculateXGap(const class="type">int x) { class="kw">return ((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x : x-m_wnd.X()); } class="type">int CElement::CalculateYGap(const class="type">int y) { class="kw">return ((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y : y-m_wnd.Y()); }