MQL5中表格模型的实现:应用MVC概念(基础篇)
「用 MVC 把 MT5 表格数据拆干净」
在 MQL5 里做复杂面板时,把数据和显示搅在一起会迅速失控。引入 MVC(模型-视图-控制器)后,表格的数据存储、界面渲染、交互逻辑三者解耦,后续改列、加行、换样式都不用动核心逻辑。 本系列以链表做底层存储:每个单元格是一个对象,行是单元格的容器,模型类再管理所有行。这样动态增删行的时间复杂度稳定,不会像动态数组那样频繁重分配。 原文示例发布于 2026 年 1 月 5 日,作者 Artyom Trishkin,截至收录时该文在站内有 290 次查看、9 条评论——说明社区对「MT5 下怎么正经写表格」需求很实。你可以直接开 MT5 新建 EA,把模型类独立成 .mqh,先跑通空表渲染再填数据。
用 MVC 给 MT5 表格代码拆层
在 MQL5 里写复杂面板或报表,最怕逻辑、绘图、交互全搅在一个脚本里。把 MVC 套进来,模型管数据和规则,视图只负责把内容画到图表或控件,控制器接鼠标键盘事件再回写模型,后期改一处不会崩三处。 具体到表格这种高频数据结构,可拆成三个类:单元格类存单点值、行类用链表攒一列单元格、模型类再用链表攒多行。MQL5 标准库的 CList 链表类(CObject 派生)天然支持指针存储与遍历,比写死二维数组更利于动态增删行。 外汇与贵金属行情跳动快、点差滑点风险高,这类表格若用于实时报价缓存,需自行处理异步刷新与线程安全,不可假设数据永远连续。
◍ 用戏剧班子理解MVC三层分工
把交易终端里的一个功能模块想成一场戏:剧本决定情节走向,对应模型(Model),它只管数据和规则;舞台是观众眼里看到的界面,对应视图(View);导演调度演员、接住台下反应,对应控制器(Controller)。这三层各管一摊,代码边界就清晰了。 落到MT5里建一张行情表,模型知道眼下有几行几列、某个单元格该存 double 还是 string;视图只负责把这些格子画到窗口上;你点一下“加一行”,控制器拦截动作、改模型、再命令视图重绘。职责拆开后,单测模型不必启动界面,改皮肤不影响计算逻辑。 项目小的时候这么切反而累赘——本来三五个函数能写完,硬拆三层纯属开销。但一旦表格要支持多周期切换、多人协作维护,分层结构会让扩展和复用明显省力。外汇与贵金属插件开发高风险,架构乱了后期Debug成本会指数上升。 我们的表格直接用独立对象堆出来:单元格对象存实数/整数/字符串之一,带取值设值接口;行对象持有一列单元格对象,管位置增删;模型对象持有行对象列表,统管行列与访问权限。一个4x4模型就是四行、每行四个单元格对象挂上去。理论到这,下一步直接写代码。
「用标准库基类搭表格对象池」
在 MT5 里做表格模型,最省事的做法是直接吃 MQL5 标准库的基类,让每个表格元素都从 CObject 派生。这样它们天生带虚析构和类型标识,能统一塞进 CArrayObj 这种对象列表里管理,不用自己写容器。 为了验证方便,这一版先把所有类写在同一个脚本 .mq5 文件里,编译后 F4 打开就能看到全部定义,改一处立刻重编。等逻辑跑通,再拆到 .mqh 包含文件里做模块隔离,避免后期文件膨胀。 外汇与贵金属品种波动大、点差跳变频繁,这类对象池若在 OnTick 里高频 new/delete,要留意内存碎片与延迟,建议用预分配数组而非实时构造。
用链表撑起表格数据的底层存储
CList 链表比 CArrayObj 更适合做表格数据容器,它原生提供访问当前对象左右相邻节点的方法,在行内挪单元格、整行移动或增删时不用手动维护索引,链表自己会重排。 但标准库的 CList 在从文件加载时,依赖虚方法 CreateElement() 造新对象,而基类里这个方法直接 return NULL,不继承重写就没法落地文件读写。 要补这个缺口,得自己从 CList 继承一个类,比如 CListObj,并前置声明所有要塞进列表的对象类(单元格、行、表模型),否则编译器不认类型。下方代码就是脚本头部包含 List.mqh 并做类前置声明的实际片段,注意背景高亮行是原始 include 与 forward declaration 的位置。 [CODE 片段] CListObj 里只多一个变量记当前待建对象类型,因为 CreateElement() 签名锁死不能加参,只能先把类型写进成员变量,等虚方法被调用时再按类型 new 对应对象并回指针。 文件保存时标准库先写起始标记 -1、再写对象类型、然后逐属性写;读回时先读类型和数量,循环里把类型赋给 m_element_type 再调 CreateElement(),新控件以后只需往 ENUM_OBJECT_TYPE 加枚举并在建对象分支补 case。 这套改完,链表就能存已知类型对象、落盘再恢复,MT5 里建个 TableModel\ 目录跑通脚本即可验证。
class="macro">#include <Arrays\List.mqh> class=class="str">"cmt">//--- Forward declaration of classes class CTableCell; class=class="str">"cmt">// Table cell class class CTableRow; class=class="str">"cmt">// Table row class class CTableModel; class=class="str">"cmt">// Table model class
◍ 表格对象的底层枚举与类型解析
在 MT5 里自建表格类控件时,先用宏和枚举把对象边界钉死能少踩很多坑。文件数据起点标记设为 -1,单元格字符串长度上限锁在 128,这两个常量直接决定后续读写缓冲不会越界。 对象类型枚举从 10000 开始编号:TABLE_CELL、TABLE_ROW、TABLE_MODEL 分别对应单元格、行和表模型。比较模式给了三种粒度——按列、按行、按行列交叉,排序和查找时按需切换即可。 TypeDescription 函数把枚举名拆成可读中文式描述:以 '_' 切分 EnumToString 的结果,跳过前两段,从索引 2 起把单词首字母大写、其余转小,再拼回。比如 OBJECT_TYPE_TABLE_CELL 会落成 'Table Cell'。复制下面代码到 MT5 脚本里跑一遍,Print(TypeDescription(OBJECT_TYPE_TABLE_CELL)) 就能验证输出。 外汇与贵金属行情波动剧烈,这类自定义控件多用于离线回测面板,实盘加载前请在策略测试器确认资源占用。
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 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 }; enum ENUM_CELL_COMPARE_MODE class=class="str">"cmt">// Table cell comparison modes { CELL_COMPARE_MODE_COL, class=class="str">"cmt">// Comparison by column number CELL_COMPARE_MODE_ROW, class=class="str">"cmt">// Comparison by class="type">class="kw">string number CELL_COMPARE_MODE_ROW_COL, class=class="str">"cmt">// Comparison by row and column }; class="type">class="kw">string TypeDescription(const ENUM_OBJECT_TYPE type) { class="type">class="kw">string array[]; class="type">int total=StringSplit(EnumToString(type),StringGetCharacter("_",class="num">0),array); class="type">class="kw">string result=""; for(class="type">int i=class="num">2;i<total;i++) { array[i]+=" "; array[i].Lower(); array[i].SetChar(class="num">0,class="type">class="kw">ushort(array[i].GetChar(class="num">0)-0x20)); result+=array[i]; } result.TrimLeft(); result.TrimRight(); class="kw">return result; }