基于MQL5中表模型的表类和表头类:应用MVC概念·进阶篇
(2/3)·只建好单元格和行还不够,缺了表头类的表连增删列都做不到
「表格单元格对象的取值接口」
在 MT5 自建表格控件时,每个单元格通常被封装成一个 C++ 风格的类对象,对外只暴露只读的取值方法,避免在渲染层误改数据源。 下面这段接口声明给出了单元格的核心元数据:行号 m_row、列号 m_col 通过 Row() 与 Col() 返回 uint;Datatype() 回传 ENUM_DATATYPE 枚举,Digits() 给出价格小数位(如黄金报价的 2 位或外汇的 5 位)。 IsEditable() 标记该格是否允许终端用户直接改值,ColorNameFlag() 与 DatetimeFlags() 则控制颜色名解析和时间格式标记,这些开关直接影响 CTable 绘制时的表现。 真正取数走三个类型化方法:ValueD() 拿 double(报价、指标值),ValueL() 拿 long(成交量、tick 数),另有字符串通道未在节选内展开。写 EA 时若跨格读数,先用 Datatype() 判断分支再调对应 Value,能少踩一半运行时错误。
class="type">uint Row(class="type">void) const { class="kw">return this.m_row; } class="type">uint Col(class="type">void) const { class="kw">return this.m_col; } ENUM_DATATYPE Datatype(class="type">void) const { class="kw">return this.m_datatype; } class="type">int Digits(class="type">void) const { class="kw">return this.m_digits; } class="type">uint DatetimeFlags(class="type">void) const { class="kw">return this.m_time_flags; } class="type">bool ColorNameFlag(class="type">void) const { class="kw">return this.m_color_flag; } class="type">bool IsEditable(class="type">void) const { class="kw">return this.m_editable; } class="type">class="kw">double ValueD(class="type">void) const { class="kw">return this.m_datatype_value.ValueD(); } class="type">long ValueL(class="type">void) const { class="kw">return this.m_datatype_value.ValueL(); }
◍ 通用变量类的取值与类型描述实现
在 MT5 的自定义通用数据容器里,取值不能只靠一个函数硬转,得按内部标记 m_datatype 分流。下面这段实现把 double / long / datetime / color / string 五种类型的输出格式化全收进一个 Value() 里,外部调用时不用关心底层存的是什么。
class="type">class="kw">string ValueS(class="type">void) const { class="kw">return this.m_datatype_value.ValueS(); } class=class="str">"cmt">//--- Return the value as a formatted class="type">class="kw">string class="type">class="kw">string Value(class="type">void) const { class="kw">switch(this.m_datatype) { case TYPE_DOUBLE : class="kw">return(this.ValueD()!=DBL_MAX ? ::DoubleToString(this.ValueD(),this.Digits()) : ""); case TYPE_LONG : class="kw">return(this.ValueL()!=LONG_MAX ? ::IntegerToString(this.ValueL()) : ""); case TYPE_DATETIME: class="kw">return(this.ValueL()!=LONG_MAX ? ::TimeToString(this.ValueL(),this.m_time_flags) : ""); case TYPE_COLOR : class="kw">return(this.ValueL()!=LONG_MAX ? ::ColorToString((class="type">class="kw">color)this.ValueL(),this.m_color_flag) : ""); class="kw">default : class="kw">return this.ValueS(); } } class=class="str">"cmt">//--- Return a description of the stored value type class="type">class="kw">string DatatypeDescription(class="type">void) const { class="type">class="kw">string type=::StringSubstr(::EnumToString(this.m_datatype),class="num">5); type.Lower(); class="kw">return type; }
class="type">class="kw">string ValueS(class="type">void) const { class="kw">return this.m_datatype_value.ValueS(); } class=class="str">"cmt">//--- Return the value as a formatted class="type">class="kw">string class="type">class="kw">string Value(class="type">void) const { class="kw">switch(this.m_datatype) { case TYPE_DOUBLE : class="kw">return(this.ValueD()!=DBL_MAX ? ::DoubleToString(this.ValueD(),this.Digits()) : ""); case TYPE_LONG : class="kw">return(this.ValueL()!=LONG_MAX ? ::IntegerToString(this.ValueL()) : ""); case TYPE_DATETIME: class="kw">return(this.ValueL()!=LONG_MAX ? ::TimeToString(this.ValueL(),this.m_time_flags) : ""); case TYPE_COLOR : class="kw">return(this.ValueL()!=LONG_MAX ? ::ColorToString((class="type">class="kw">color)this.ValueL(),this.m_color_flag) : ""); class="kw">default : class="kw">return this.ValueS(); } } class=class="str">"cmt">//--- Return a description of the stored value type class="type">class="kw">string DatatypeDescription(class="type">void) const { class="type">class="kw">string type=::StringSubstr(::EnumToString(this.m_datatype),class="num">5); type.Lower(); class="kw">return type; }
表格行与参数对象的类骨架
在 MT5 自定义表格控件的实现里,CTableRow 继承自 CObject,内部用 CListObj 维护一行中的所有单元格,并用 m_index 记录行号。注意 m_cell_tmp 只是一个用于列表查找的临时 CTableCell 对象,并不参与渲染,纯粹降低遍历时的重复构造开销。 行索引的读写被做成内联方法:SetIndex() 直接赋值 m_index,Index() 加 const 修饰返回,避免在回测或实时刷新时意外篡改行序。CellsPositionUpdate() 则负责把当前行号与列号一次性同步给所有单元格,保证 GUI 重绘时坐标不漂移。 CellAddNew() 提供了 5 个重载版本,分别接收 double / long / datetime / color / string,意味着你往一行里追加数据时可以不用手动 new 再 AddNewCell(),直接丢原始类型进去就能拿到新建的 CTableCell 指针。实际写 EA 面板时,这套重载能省掉大量类型转换样板代码。 CMqlParamObj 紧跟在表格行定义之后,同样继承 CObject,但原文此处只开了 protected 和 public 区段,具体成员被截断。如果你要在自己的指标里序列化 MqlParam 结构,可以先照这个空壳把类建出来,再补 MqlParam 的 type/integer_value/double_value/string_value 字段。
class="type">void ClearData(class="type">void) { this.SetEmptyValue(); } class="kw">virtual class="type">class="kw">string Description(class="type">void); class="type">void Print(class="type">void); class CTableRow : class="kw">public CObject { class="kw">protected: CTableCell m_cell_tmp; class=class="str">"cmt">// Cell object to search in the list CListObj m_list_cells; class=class="str">"cmt">// List of cells class="type">uint m_index; class=class="str">"cmt">// Row index class="type">bool AddNewCell(CTableCell *cell); class="kw">public: class="type">void SetIndex(const class="type">uint index) { this.m_index=index; } class="type">uint Index(class="type">void) const { class="kw">return this.m_index; } class="type">void CellsPositionUpdate(class="type">void); CTableCell *CellAddNew(const class="type">class="kw">double value); CTableCell *CellAddNew(const class="type">long value); CTableCell *CellAddNew(const class="type">class="kw">datetime value); CTableCell *CellAddNew(const class="type">class="kw">color value); CTableCell *CellAddNew(const class="type">class="kw">string value); }; class CMqlParamObj : class="kw">public CObject { class="kw">protected: class="kw">public:
「用包装类接管 MqlParam 的读写」
在 MT5 里直接操作 MqlParam 结构体容易漏字段,写个轻量包装类把 type 与三个值字段一次性接管更稳。下面这段把外部传入的 param 完整拷进成员 m_param,四个字段一个不落。 Description() 里先用 EnumToString 拿到类型名,再从偏移 5 截取掉前缀 'TYPE_' 并转小写,比如 TYPE_DOUBLE 变成 double。随后按 type 分流取值:字符串走 ValueS(),浮点/双精度走 DoubleToString(ValueD()),整型分支在原文截断处未展示但由 ValueL() 兜住。 实盘调用前,建议在 MT5 的 EA 初始化里用一组已知参数跑一次 Description(),肉眼核对输出字符串是否符合 'double: 1.500000' 这种形态,能提前暴露字段赋值错位。外汇与贵金属杠杆高,参数对象若传错类型可能导致下单量异常,验证通过再接信号逻辑。
class="type">MqlParam m_param; class=class="str">"cmt">//--- Set the parameters class="type">void Set(const class="type">MqlParam ¶m) { this.m_param.type=param.type; this.m_param.double_value=param.double_value; this.m_param.integer_value=param.integer_value; this.m_param.string_value=param.string_value; } class=class="str">"cmt">//--- Return the parameters class="type">MqlParam Param(class="type">void) const { class="kw">return this.m_param; } ENUM_DATATYPE Datatype(class="type">void) const { class="kw">return this.m_param.type; } class="type">class="kw">double ValueD(class="type">void) const { class="kw">return this.m_param.double_value; } class="type">long ValueL(class="type">void) const { class="kw">return this.m_param.integer_value;} class="type">class="kw">string ValueS(class="type">void) const { class="kw">return this.m_param.string_value; } class=class="str">"cmt">//--- Object description class="kw">virtual class="type">class="kw">string Description(class="type">void) { class="type">class="kw">string t=::StringSubstr(::EnumToString(this.m_param.type),class="num">5); t.Lower(); class="type">class="kw">string v=""; class="kw">switch(this.m_param.type) { case TYPE_STRING : v=this.ValueS(); class="kw">break; case TYPE_FLOAT : case TYPE_DOUBLE : v=::DoubleToString(this.ValueD()); class="kw">break;
◍ 把 MqlParam 包成可打印的单元格对象
在 MT5 里用 MqlParam 往指标缓冲或自定义结构塞数据时,裸参数无法直接看内容。下面这段把 TYPE_DATETIME 单独格式化成「日期 时分秒」,其余类型强转 string,再用 StringFormat 拼成 <类型>值 的形态,调试时一眼能读出每行数据。 CMqlParamObj 的析构为空,构造支持从已有 MqlParam 引用直接拷贝,说明它只是轻量包装,不持有独立生命周期资源。 DataListCreator 提供两个静态方法:AddNewRowToDataList 在总表 CList 里挂一个新行,失败返回 NULL;AddNewCellParamToRow 把包装好的单元格塞进行,row.Add 返回小于 0 时立刻 delete 防内存泄漏。外汇与贵金属 EA 跑这类链表前,务必在策略测试器开内存泄漏检查,杠杆品种滑点可能让行数暴涨。
case TYPE_DATETIME: v=::TimeToString(this.ValueL(),TIME_DATE|TIME_MINUTES|TIME_SECONDS); class="kw">break; class="kw">default : v=(class="type">class="kw">string)this.ValueL(); class="kw">break; } class="kw">return(::StringFormat("<%s>%s",t,v)); } class=class="str">"cmt">//--- Constructors/destructor CMqlParamObj(class="type">void){} CMqlParamObj(const class="type">MqlParam ¶m) { this.Set(param); } ~CMqlParamObj(class="type">void){} }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class for creating lists of data | class=class="str">"cmt">//+------------------------------------------------------------------+ class DataListCreator { class="kw">public: class=class="str">"cmt">//--- Add a new row to the CList list_data list class="kw">static CList *AddNewRowToDataList(CList *list_data) { CList *row=new CList; if(row==NULL || list_data.Add(row)<class="num">0) class="kw">return NULL; class="kw">return row; } class=class="str">"cmt">//--- Create a new CMqlParamObj parameter object and add it to CList class="kw">static class="type">bool AddNewCellParamToRow(CList *row,class="type">MqlParam ¶m) { CMqlParamObj *cell=new CMqlParamObj(param); if(cell==NULL) class="kw">return false; if(row.Add(cell)<class="num">0) { class="kw">delete cell; class="kw">return false;
表格模型里的数据类型识别
CTableModel 继承自 CObject,内部用 m_list_rows 维护行对象列表,m_row_tmp 则作为列表检索时的临时行载体。 构造入口被重载了四次:模板方法吃二维数组、两个 uint 指定行列数建空表、matrix 直接灌入行数据、以及从现成 CList 引用构建。这种多源入口在写自定义报表面板时很实用,尤其当你从指标缓冲或 AI 特征矩阵直接塞数据的时候。 GetCorrectDatatype 这个函数把字符串类型名映射回 MQL5 的 ENUM_DATATYPE。逻辑很直白:bool/char/uchar/short/ushort/int/uint/long/ulong 全归 TYPE_LONG;float/double 归 TYPE_DOUBLE;其余走日期等分支。 在 MT5 里新建个 EA 把这段类骨架贴进去,改两行 type_name 测试,能立刻看到返回枚举值的变化,方便后续做单元格渲染分支。外汇与贵金属波动剧烈,任何模型输出都只是概率参考,实盘前务必在策略测试器跑历史数据。
class CTableModel : class="kw">public CObject { class="kw">protected: CTableRow m_row_tmp; class=class="str">"cmt">// Row object to search in the list CListObj m_list_rows; class=class="str">"cmt">// List of table rows class=class="str">"cmt">//--- Create a table model from a two-dimensional array class="kw">template<class="kw">typename T> class="type">void CreateTableModel(T &array[][]); class="type">void CreateTableModel(const class="type">uint num_rows,const class="type">uint num_columns); class="type">void CreateTableModel(const matrix &row_data); class="type">void CreateTableModel(CList &list_param); class=class="str">"cmt">//--- Return the correct data type ENUM_DATATYPE GetCorrectDatatype(class="type">class="kw">string type_name) { class="kw">return ( class=class="str">"cmt">//--- Integer value type_name=="class="type">bool" || type_name=="class="type">char" || type_name=="class="type">uchar" || type_name=="class="type">short"|| type_name=="class="type">class="kw">ushort" || type_name=="class="type">int" || type_name=="class="type">uint" || type_name=="class="type">long" || type_name=="class="type">ulong" ? TYPE_LONG : class=class="str">"cmt">//--- Real value type_name=="class="type">class="kw">float"|| type_name=="class="type">class="kw">double" ? TYPE_DOUBLE : class=class="str">"cmt">//--- Date/time value