DoEasy 控件基准列表:从基类抽离出 ListBox 与 ButtonListBox 的底层骨架(基础篇)
🧱

DoEasy 控件基准列表:从基类抽离出 ListBox 与 ButtonListBox 的底层骨架(基础篇)

(1/3)· 多数人直接抄控件代码却不懂基类职责,导致后续列表对象越写越乱

案例拆解新手友好 第 1/3 篇
很多交易者拿到 WinForms 列表控件就急着改显示逻辑,却没意识到基类没抽离时,CheckedListBox、ListBox、ButtonListBox 会各自重复一套列表管理代码。一旦终端更新禁掉继承类访问私密方法,整个交易类调用链会静默失效,界面与逻辑一起崩。

◍ 给 ListBox 和 ButtonListBox 立个基类

在 DoEasy 库里做 WinForms 风格控件时,ListBox 和 ButtonListBox 有很多重复逻辑:条目增删、选中态维护、坐标重排。直接各自写一份,后期改一个行为就得动两处,维护成本偏高。 抽一个基准列表对象类出来,把通用成员收拢到底层,两个具体控件只管自己特有的绘制与交互。这样库里新增别的列表类控件时,也能直接继承,少写冗余代码。 原文示例标注于 2022-10-31 13:00 发布,当时附带 1 132 次示例查看记录,说明这类基础封装在实际搬用中有明确需求。外汇与贵金属 MT5 环境里跑自定义控件属高风险调试,建议先在策略测试器隔离验证再挂实盘图表。

先打地基:WinForms 列表基类与两个实验对象

这一节不急着堆功能,而是把上一篇文章里做的 CheckedListBox 往上抽一层。思路很清楚:既然后面还要做一大堆带「列表」性质的 WinForms 控件,不如先写一个基准列表类,把行渲染、数据绑定、鼠标交互这些共性逻辑全收进去。 这个基类要能挂接不同来源的数据——函数库内部、终端环境、甚至本地数据库都行,列表行只负责把条目名画出来,点选和悬停改背景色先跑通。眼下 ListBox 对象就是这么来的:构造时塞进去什么名字就显示什么,没有实际业务用途,纯粹是为了攒够样本,搞清楚事件处理器到底该接收哪些字段。 顺手还做了一个按钮列表对象。因为 ListBox 的每一行本来就是基于 CButton 类画的,再包一层「按钮组成的列表」水到渠成,类似 CheckedListBox 包 CheckBox 的关系。MS Visual Studio 标准控件里没这玩意,属于自创实验件。 做这些看似没用的对象,核心目的只有一个:对象种类越多,越能反推事件系统需要的数据结构。等样本齐了,事件功能才不会返工。

「把私密交易方法挪回受保护区」

MT5 客户端最近一次更新后,CTrading 类的 OpenPosition() 和 PlaceOrder() 从继承类访问私密方法会被直接拦截,库类直接罢工。修复方式是在 Trading.mqh 的受保护区域重新声明这两个模板方法,私密区域只留转发。 具体改动落在 \MQL5\Include\DoEasy\ 目录下多个头文件:Defines.mqh 的图形元素枚举加了 3 个新类型,画布整数属性从 83 扩到 85;Data.mqh 增删了消息索引项并补了对应文本;GBaseObj.mqh 给 TypeElementDescription() 加了带类型参数的重载,旧方法把自身类型传进去复用。 按钮群组逻辑也调了:Button.mqh 里左键点击事件把群组索引写进日志,方便核对。切换按钮若带群组标志,同组按下其一会释放其余,但已按下的再点不会弹起,保证组内恒有一个按下。 下面这段是 Trading.mqh 里受保护区重声明的核心片段,注意 protected 标签和模板参数写法,直接抄进你的库就能编译过。

MQL5 / C++
class=class="str">"cmt">//--- Return the error handling method
  ENUM_ERROR_CODE_PROCESSING_METHOD  ResultProccessingMethod(const class="type">uint result_code);
class=class="str">"cmt">//--- Correct errors
  ENUM_ERROR_CODE_PROCESSING_METHOD  RequestErrorsCorrecting(class="type">MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const class="type">uint spread_multiplier,CSymbol *symbol_obj,CTradeObj *trade_obj);

class=class="str">"cmt">//--- (class="num">1) Open a position, (class="num">2) place a pending order
class="kw">protected:
  class="kw">template<class="kw">typename SL,class="kw">typename TP>
  class="type">bool                OpenPosition(const class="type">ENUM_POSITION_TYPE type,
                                  const class="type">class="kw">double volume,
                                  const class="type">class="kw">string symbol,
                                  const class="type">ulong magic=ULONG_MAX,
                                  const SL sl=class="num">0,
                                  const TP tp=class="num">0,
                                  const class="type">class="kw">string comment=NULL,
                                  const class="type">ulong deviation=ULONG_MAX,
                                  const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
  class="kw">template<class="kw">typename PR,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP>
  class="type">bool                PlaceOrder( const ENUM_ORDER_TYPE order_type,

◍ 挂单请求的形参结构与私有索引查找

这段声明暴露了封装层下挂单方法接受的完整参数集:手数 volume、交易品种 symbol、挂单价格 price 为必填,而价格限制 price_limit、止损 sl、止盈 tp 默认填 0,magic 默认 ULONG_MAX,comment 默认 NULL,有效期 expiration 默认 0,时间类型与成交类型默认 WRONG_VALUE 交由内部校正。 private 段给出了三个未导出的定位函数:GetIndexPendingRequestByID 按 1 字节 id 找挂单请求在列表中的下标,GetIndexPendingRequestByOrder 按订单 ticket 找,GetIndexPendingRequestByPosition 按持仓 ticket 找。这三个重载让引擎能在异步回执回来时 O(1) 定位上下文,而不是遍历全表。 随后公开的枚举 ENUM_GRAPH_ELEMENT_TYPE 把图形元素分成三类:GRAPH_ELEMENT_TYPE_STANDARD 标准对象、GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED 扩展标准对象、GRAPH_ELEMENT_TYPE_SHADOW_OBJ 影子对象。影子对象通常用于在图表上映射尚未成交的挂单,便于和小布盯盘的可视化层对齐。 开 MT5 把这段声明贴进 EA 头文件,确认你的挂单封装是否也用 ticket 三级索引;若没做,异步撤单时容易错删。外汇与贵金属杠杆高,参数错配可能瞬间放大亏损。

MQL5 / C++
const class="type">class="kw">double volume,
const class="type">class="kw">string symbol,
const PR price,
const PL price_limit=class="num">0,
const SL sl=class="num">0,
const TP tp=class="num">0,
const class="type">ulong magic=ULONG_MAX,
const class="type">class="kw">string comment=NULL,
const class="type">class="kw">datetime expiration=class="num">0,
const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
class="kw">private:
class=class="str">"cmt">//--- Return the request object index in the list by(class="num">1) ID,
class=class="str">"cmt">//--- (class="num">2) order ticket, (class="num">3) position ticket in the request
  class="type">int       GetIndexPendingRequestByID(const class="type">uchar id);
  class="type">int       GetIndexPendingRequestByOrder(const class="type">ulong ticket);
  class="type">int       GetIndexPendingRequestByPosition(const class="type">ulong ticket);
class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| The list of graphical element types                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
  GRAPH_ELEMENT_TYPE_STANDARD,                class=class="str">"cmt">// Standard graphical object
  GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,       class=class="str">"cmt">// Extended standard graphical object
  GRAPH_ELEMENT_TYPE_SHADOW_OBJ,              class=class="str">"cmt">// Shadow object

图形元素枚举与画布整数属性对照

在 MT5 自定义 UI 开发中,图形元素类型由一组枚举常量区分。基础层包含 GRAPH_ELEMENT_TYPE_ELEMENT(图元)、GRAPH_ELEMENT_TYPE_FORM(窗体)、GRAPH_ELEMENT_TYPE_WINDOW(窗口)三类,其上再派生 WinForms 体系。 WinForms 分支下进一步划分为容器类(WF_PANEL、WF_GROUPBOX、WF_CONTAINER)与标准控件类(WF_LABEL、WF_BUTTON、WF_CHECKBOX、WF_RADIOBUTTON)。其中列表类控件有三个易混淆成员:WF_ELEMENTS_LIST_BOX 是元素列表基类,WF_LIST_BOX 是普通列表框,WF_BUTTON_LIST_BOX 则是按钮式列表框,三者均用于承载多条目交互。 画布元素的整数属性枚举 ENUM_CANV_ELEMENT_PROP_INTEGER 从 CANV_ELEMENT_PROP_ID = 0 起编,依次定义类型、状态色等。值得留意的是 CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,它控制 ListBox 是否按水平多列排布,默认不开启,调成 1 后可在窄面板里塞下更多币种监控项。外汇与贵金属行情波动剧烈,此类自定义面板仅作辅助观察,实操仍需警惕滑点风险。

MQL5 / C++
enum ENUM_GRAPH_ELEMENT_TYPE
  {
  GRAPH_ELEMENT_TYPE_ELEMENT,                                          class=class="str">"cmt">// Element
  GRAPH_ELEMENT_TYPE_FORM,                                             class=class="str">"cmt">// Form
  GRAPH_ELEMENT_TYPE_WINDOW,                                           class=class="str">"cmt">// Window
  class=class="str">"cmt">//--- WinForms
  GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                                     class=class="str">"cmt">// Panel object underlay
  GRAPH_ELEMENT_TYPE_WF_BASE,                                         class=class="str">"cmt">// Windows Forms Base
  class=class="str">"cmt">//--- &class="macro">#x27;Container&class="macro">#x27; object types are to be set below
  GRAPH_ELEMENT_TYPE_WF_CONTAINER,                                    class=class="str">"cmt">// Windows Forms container base object
  GRAPH_ELEMENT_TYPE_WF_PANEL,                                        class=class="str">"cmt">// Windows Forms Panel
  GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                                     class=class="str">"cmt">// Windows Forms GroupBox
  class=class="str">"cmt">//--- &class="macro">#x27;Standard control&class="macro">#x27; object types are to be set below
  GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                                  class=class="str">"cmt">// Windows Forms base standard control
  GRAPH_ELEMENT_TYPE_WF_LABEL,                                         class=class="str">"cmt">// Windows Forms Label
  GRAPH_ELEMENT_TYPE_WF_BUTTON,                                        class=class="str">"cmt">// Windows Forms Button
  GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                                      class=class="str">"cmt">// Windows Forms CheckBox
  GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,                                   class=class="str">"cmt">// Windows Forms RadioButton
  GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,                             class=class="str">"cmt">// Base list object of Windows Forms elements
  GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                                      class=class="str">"cmt">// Windows Forms ListBox
  GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,                              class=class="str">"cmt">// Windows Forms CheckedListBox
  GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,                               class=class="str">"cmt">// Windows Forms ButtonListBox
  };

enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
  CANV_ELEMENT_PROP_ID = class="num">0,                                           class=class="str">"cmt">// Element ID
  CANV_ELEMENT_PROP_TYPE,                                             class=class="str">"cmt">// Graphical element type
  CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,                      class=class="str">"cmt">// Color of control checkbox when clicking on the control
  CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,                      class=class="str">"cmt">// Color of control checkbox when hovering the mouse over the control
  CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN                            class=class="str">"cmt">// Horizontal display of columns in the ListBox control
  };

「画布控件枚举与排序属性的收口定义」

画布元素库把整数属性总数钉死在 85 个,其中 0 个被排除在排序之外,这意味着全部整型字段都能直接拿来给图形控件排次序。 双重宏 FIRST_CANV_ELEMENT_DBL_PROP 与 FIRST_CANV_ELEMENT_STR_PROP 用整数总数减跳过数做偏移,字符串类排序起点落在 SORT_BY_CANV_ELEMENT_NAME_OBJ,说明库内部按 int→double→string 三段式布局属性表。 枚举 ENUM_SORT_CANV_ELEMENT_MODE 里新增了 ListBox 多列与列宽两个整型排序键,配合 MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX 等 WinForms 映射常量,能让你在 MT5 里按列宽或勾选色状态重排自定义面板。 开 MT5 把这段枚举贴进 include,搜 SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH 看编译器能否定位,就能验证你用的标准库版本是否带这套 ListBox 排序扩展。

MQL5 / C++
class="macro">#define CANV_ELEMENT_PROP_INTEGER_TOTAL(class="num">85)              class=class="str">"cmt">// Total number of integer properties
class="macro">#define CANV_ELEMENT_PROP_INTEGER_SKIP(class="num">0)              class=class="str">"cmt">// Number of integer properties not used in sorting
class="macro">#define FIRST_CANV_ELEMENT_DBL_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
class="macro">#define FIRST_CANV_ELEMENT_STR_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
  SORT_BY_CANV_ELEMENT_ID = class="num">0,
  SORT_BY_CANV_ELEMENT_TYPE,
  SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_DOWN,
  SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_OVER,
  SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN,
  SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH,
  SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,
  SORT_BY_CANV_ELEMENT_NAME_RES,
  SORT_BY_CANV_ELEMENT_TEXT,
  };
  MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,
  MSG_GRAPH_ELEMENT_TYPE_WF_LABEL,
  MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX,
  MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,
  MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON,
  MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,
  MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX,
  MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,
  MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,
交给小布盯盘看库结构
这些控件基类的继承关系和小布盯盘里的 AIGC 诊断已内置,打开对应品种页即可看到类依赖提示,把重复劳动交给小布,你专注决策。

常见问题

基类统一处理列表项管理、鼠标悬停与选择状态,后续 CheckedListBox、ListBox、ButtonListBox 都能复用,避免每类重写一套列表逻辑。
小布盯盘内置的 AIGC 可识别这类 MQL5 库结构并给出依赖视图,但实盘外汇贵金属高风险,界面原型不等于可执行策略,仅作开发辅助。
它是一次实验性扩展,用 CButton 对象组成列表行,验证按钮组列表的数据绑定与事件结构,为后续事件功能积累认知。
把原私密方法移到受保护部分,让继承类可访问,同时更新 Defines.mqh 的图形元素类型枚举以兼容新画布属性。
当前仅是未来功能的可视化占位,用来明确事件所需数据与结构,本身不具备数据绑定实用价值。