基于MQL5中表模型的表类和表头类:应用MVC概念(基础篇)
用 MVC 拆开 MQL5 里的表模型
在 MQL5 里做复杂面板时,直接把数据和绘制混在一起会迅速失控。把表(CTable)和表头(CHeader)拆成独立类,再套一层 MVC 思路——模型管数据、视图管绘制、控制器管交互——能让代码在 MT5 里更好维护。 原文给出的示例发布于 2026 年 1 月 7 日 09:30,作者 Artyom Trishkin,示例页面显示 292 次浏览、0 条评论,说明这套表模型示例在发布初期偏冷门但结构完整。 表头类只负责列名、列宽与排序状态,表类持有行数据与单元格引用。两者通过事件ID通信,不在彼此内部写死绘制逻辑,这就是 MVC 在 MQL5 面板里的最小落地形态。
◍ 从静态表到可操控列头
上一篇里我们已经用 MVC 模板在 MQL5 里把单元格、行和表模型的类搭了出来,数据能以结构化方式进表。但那张表是写死的——列不能加、不能删、也不能改名字。 现在要补上表类和表头。表头不只是拿来显示列名的装饰,它是管列的工具:没有表头类,表能跑,但列结构就锁死了,只能做静态表。 要让列能被动态控制,得先给表模型加方法——改结构、增列、删列,这些方法留给表头类调用。这一步是后面视图和控制器的基础,也是做出能真正操作数据的界面的关键节点。
「把表模型拆成可插拔的构件」
原表模型只认二维数组,实战里经常碰见结构先定、数据后灌,或者数据本身就是动态链表的情况。这次给表模型类补了四套初始化入口:二维数组直构、空表预建(指定行列数)、矩阵灌入、以及从 CList 嵌套链表构建。后者用 CList 存行、行内再挂 CList 存单元格,动态增删不费劲。 把上一篇文章里散在脚本里的类收进 Tables.mqh,挪到 MQL5\Scripts\Tables\ 下统一管理。CListObj 的 CreateElement() 要能 new 出这些新类型,所以头里得加前向声明;宏里把单元格最小宽度锁在 19 字符,日期时间文本才不会把右边框顶歪、日志里表格错位。 空值不再写 0,字符串空行、实数 DBL_MAX、整数 LONG_MAX 视作空。CTableCell 受保护区加设置空值的方法,取格式化串时遇空返空行,清数据也调它而非置零。所有返回对象描述的方法改虚函数,CreateNewCell() 系列重命名为以 Cell 开头,行访问以 Row 开头,调起来不混。 CMqlParamObj 包一层 MqlParam,能塞进 CList 当属性单元。主 CList 是行数组,嵌套 CList 是单元格数组,历史交易列表就是三层结构:主表→每行交易→价格/量/时间等属性。DataListCreator 静态类负责快速拼这种列表,拼完丢给 CreateTableModel(CList &list_param) 即可还原表。 新方法里有个细节:double 单元格的显示精度从 CMqlParamObj 的 long 值取。建财务结果列时可顺带传精度,datetime 传显示标志、color 传颜色名标志,写进 integer_value 或 double_value 字段,不传则整列默认零。 列级操作补了三个方法:按索引插新列(先尾插再前移)、设整列数据类型、设整列精度。没表头的表是静态的,列控只在有表头时生效。下面这段是新增的构造与前向声明骨架,可直接抄进包含文件比对。
<span class="keyword">class="type">void</span> CreateTableModel(T &array[][]); <span class="keyword">class="type">void</span> CreateTableModel(<span class="keyword">const</span> <span class="keyword">class="type">uint</span> num_rows, <span class="keyword">const</span> <span class="keyword">class="type">uint</span> num_columns); <span class="keyword">class="type">void</span> CreateTableModel(<span class="keyword">const</span> <span class="keyword">matrix</span> &row_data); <span class="keyword">class="type">void</span> CreateTableModel(CList &list_param); <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Include libraries |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#include </span><Arrays\List.mqh> <span class="comment">class=class="str">"cmt">//--- Forward declaration of classes</span> <span class="keyword">class</span> CTableCell; <span class="comment">class=class="str">"cmt">// Table cell class</span> <span class="keyword">class</span> CTableRow; <span class="comment">class=class="str">"cmt">// Table row class</span> <span class="keyword">class</span> CTableModel; <span class="comment">class=class="str">"cmt">// Table model class</span>
表格对象的类型与常量定义
在 MT5 自定义表格控件的底层,先得把对象类型码和内存布局定清楚。下面这段声明把列头、表头、整表以及基于参数数组的表都抽象成了独立类,方便后续用统一链表管理。 宏里有个关键数字:CELL_WIDTH_IN_CHARS 被写死成 19,意味着每个单元格按 19 个等宽字符渲染,超出的内容在 GUI 上可能被截断,接数据前最好先估一下字段长度。 ENUM_OBJECT_TYPE 里从 10000 起跳,OBJECT_TYPE_TABLE_CELL 是 10000,依次加一,到 OBJECT_TYPE_TABLE_BY_PARAM 为止。这种偏移能避开 MT5 内置图形对象 ID,避免类型冲突。 CListObj::CreateElement 只露出个 switch 入口,实际分支会按 m_element_type 去 new 对应的上面那几个类。你打开 MT5 的 MetaEditor,搜 CListObj 就能看到完整分发逻辑,改表结构从这儿动手最稳。
class CColumnCaption; class=class="str">"cmt">// Table column header class class CTableHeader; class=class="str">"cmt">// Table header class class CTable; class=class="str">"cmt">// Table class class CTableByParam; class=class="str">"cmt">// Table class based on parameter array class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Macros | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Macros | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define MARKER_START_DATA -class="num">1 class=class="str">"cmt">// Data start marker in a file class="macro">#define MAX_STRING_LENGTH class="num">128 class=class="str">"cmt">// Maximum length of a class="type">class="kw">string in a cell class="macro">#define CELL_WIDTH_IN_CHARS class="num">19 class=class="str">"cmt">// Table cell width in characters class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enumerations | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enumerations | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_OBJECT_TYPE class=class="str">"cmt">// Enumeration of object types { OBJECT_TYPE_TABLE_CELL=class="num">10000, class=class="str">"cmt">// Table cell OBJECT_TYPE_TABLE_ROW, class=class="str">"cmt">// Table row OBJECT_TYPE_TABLE_MODEL, class=class="str">"cmt">// Table model OBJECT_TYPE_COLUMN_CAPTION, class=class="str">"cmt">// Table column header OBJECT_TYPE_TABLE_HEADER, class=class="str">"cmt">// Table header OBJECT_TYPE_TABLE, class=class="str">"cmt">// Table OBJECT_TYPE_TABLE_BY_PARAM, class=class="str">"cmt">// Table based on the parameter array data }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| List element creation method | class=class="str">"cmt">//+------------------------------------------------------------------+ CObject *CListObj::CreateElement(class="type">void) { class=class="str">"cmt">//--- Create a new object depending on the object type in m_element_type class="kw">switch(this.m_element_type)
◍ 表格对象的工厂分发与单元格联合体存储
在 MT5 标准库的对象体系里,表格相关控件靠一个 switch 工厂来实例化:传入 OBJECT_TYPE_TABLE 这类枚举,就 new 出对应的 CTable、CTableHeader 或 CColumnCaption,未知类型直接返回 NULL,调用方必须判空。 下面这段是工厂里处理表格族的分支,高亮部分就是表格整体与表头、列标题的创建入口: CTableCell 用了一个 union 来塞下三种值:double、long 和定长 ushort 数组(MAX_STRING_LENGTH 上限)。同一块内存按类型解释,省掉继承多态的开销,但也要求写之前明确知道此刻存的是哪一类。 SetValueS 里调 StringToShortArray 把字符串压进 ushort_value,取值时 ShortArrayToString 再转回来,并做 TrimLeft / TrimRight 清掉首尾空白。若你自己在 EA 里复用这个联合体,注意 MAX_STRING_LENGTH 默认 255,超长串会被截断,回测时可能静默丢字符。 外汇与贵金属图表上挂这类自定义表格做信号监控,属于高风险操作环境,任何控件内存异常都可能拖慢 tick 处理,建议先在策略测试器里跑通再上实盘。
{
case OBJECT_TYPE_TABLE_CELL : class="kw">return new CTableCell();
case OBJECT_TYPE_TABLE_ROW : class="kw">return new CTableRow();
case OBJECT_TYPE_TABLE_MODEL : class="kw">return new CTableModel();
case OBJECT_TYPE_COLUMN_CAPTION : class="kw">return new CColumnCaption();
case OBJECT_TYPE_TABLE_HEADER : class="kw">return new CTableHeader();
case OBJECT_TYPE_TABLE : class="kw">return new CTable();
case OBJECT_TYPE_TABLE_BY_PARAM : class="kw">return new CTableByParam();
class="kw">default : class="kw">return NULL;
}
}
class CTableCell : class="kw">public CObject
{
class="kw">protected:
class=class="str">"cmt">//--- Combining for storing cell values(class="type">class="kw">double, class="type">long, class="type">class="kw">string)
union DataType
{
class="kw">protected:
class="type">class="kw">double double_value;
class="type">long long_value;
class="type">class="kw">ushort ushort_value[MAX_STRING_LENGTH];
class="kw">public:
class=class="str">"cmt">//--- Set values
class="type">void SetValueD(const class="type">class="kw">double value) { this.double_value=value; }
class="type">void SetValueL(const class="type">long value) { this.long_value=value; }
class="type">void SetValueS(const class="type">class="kw">string value) { ::StringToShortArray(value,ushort_value); }
class=class="str">"cmt">//--- Return values
class="type">class="kw">double ValueD(class="type">void) const { class="kw">return this.double_value; }
class="type">long ValueL(class="type">void) const { class="kw">return this.long_value; }
class="type">class="kw">string ValueS(class="type">void) const
{
class="type">class="kw">string res=::ShortArrayToString(this.ushort_value);
res.TrimLeft();
res.TrimRight();「单元格空值的底层赋值逻辑」
表格控件的单元格类在收尾处给出了成员变量定义,从 m_datatype_value 到 m_editable 共 11 个字段,分别承载数值、类型、绑定对象、行列坐标与显示精度等信息。其中 m_digits 控制浮点展示位数,m_time_flags 管日期格式,m_editable 决定该格是否允许终端内直接改写。 SetEmptyValue 是这套结构的最后一环:它按当前 m_datatype 分支处理“空”语义。整型、时间、颜色三类统一写 LONG_MAX(9223372036854775807),双精度写 DBL_MAX(约 1.7976931348623157e+308),避免和正常业务值撞车。 在 MT5 里自己写自定义表格时,直接复用这个分支能少踩很多坑——尤其别手填 0 或 -1 当空值,后面做排序或条件着色会出诡异结果。外汇与贵金属图表加载此类控件时波动刷新频繁,空值判定错乱可能让面板闪跳,属高风险操作环境,建议先在回测器外的脚本里单步验证。
class=class="str">"cmt">//--- Variables DataType m_datatype_value; class=class="str">"cmt">// Value ENUM_DATATYPE m_datatype; class=class="str">"cmt">// Data type CObject *m_object; class=class="str">"cmt">// Cell object ENUM_OBJECT_TYPE m_object_type; class=class="str">"cmt">// Object type in the cell class="type">int m_row; class=class="str">"cmt">// Row index class="type">int m_col; class=class="str">"cmt">// Column index class="type">int m_digits; class=class="str">"cmt">// Data representation accuracy class="type">uint m_time_flags; class=class="str">"cmt">// Date/time display flags class="type">bool m_color_flag; class=class="str">"cmt">// Color name display flag class="type">bool m_editable; class=class="str">"cmt">// Editable cell flag class=class="str">"cmt">//--- Set "empty value" class="type">void SetEmptyValue(class="type">void) { class="kw">switch(this.m_datatype) { case TYPE_LONG : case TYPE_DATETIME : case TYPE_COLOR : this.SetValue(LONG_MAX); class="kw">break; case TYPE_DOUBLE : this.SetValue(DBL_MAX); class="kw">break;