MQL5 MVC架构中表格视图与控制器组件:简单控件(基础篇)
📘

MQL5 MVC架构中表格视图与控制器组件:简单控件(基础篇)

第 1/3 篇

「MQL5 表格视图里先啃透简单控件」

在 MQL5 的 MVC 拆分里,表格视图(View)只负责把数据画出来,控制器(Controller)拦截用户操作再回写模型;初上手别急着上复杂自定义单元格,先用系统自带的简单控件把链路跑通。 简单控件指 CTable 派生视图中直接可用的静态文本、按钮、勾选框等基础元素,它们不依赖外部资源,在 OnInit 阶段创建后由控制器统一派发事件。 官方示例工程里这类基础表格视图代码量通常控制在 200 行以内,却完整覆盖了「模型变更→视图重绘→控制器响应」三轮驱动,足够作为你自己的面板脚手架。 开 MT5 新建一个 EA 面板,把示例里的 CTable 简单控件抄进 OnCreate,挂上调试输出,就能直观看到一次点击如何穿透三层。外汇与贵金属品种波动剧烈,这类工具仅用于本地行情观察与回测,实盘接入须自担高风险。

◍ 内容

概述 控制器组件优化基类 简单控件 辅助类 在指定区域内绘制图像的类 文本标签 简单按钮 双向按钮 上箭头按钮 下箭头按钮 左箭头按钮 右箭头按钮 复选框 单选按钮 测试结果 结论

从表格模型切到可交互控件基类

在 MT5 里做自定义面板,先把 MVC 的模型(表格数据)跑通后,下一步是视图与控制器合一的轻量控件。今天先落几个原子件:文本标签、普通按钮,它们之后就是树形视图、下拉框的拼装积木。 MQL5 的事件模型直接挂在图表事件(OnChartEvent)上,所以基类必须预留事件入口,才能把鼠标悬停、点击回传给逻辑层。 一个可验证现象:用普通按钮做树形行,左图标右文字,仅靠改字符串资源和颜色参数,悬停变色的响应就能在 MT5 上秒级生效,无需重画整个面板。 写几行配置即可投产,后续复合控件都复用这套原子件,外汇/贵金属高频刷新界面时 CPU 占用倾向更低,但自定义图形仍属高风险调试项,参数错乱可能卡死图表。

「把控件基类从脚本目录搬进指标目录并瘦身」

做 MT5 自定义表格控件时,图形元素的基类不该继续躺在 Scripts\tables\controls\ 下。写测试指标就要在 Indicators\tables\controls\ 里新建同样结构,把 Base.mqh 挪过去——路径不对,指标里 include 会直接报找不到文件。 基类里原来每个派生类都重复写 Print、Description 和文件存取的相同代码段。现在把这些字符串统一收进基对象的加载/保存方法,新类只有当逻辑真的和基类不同时才重写 Description 和 Print,文件操作方法直接调基类,能少敲几十行重复代码。 Base.mqh 顶部要先前置声明所有将被列表创建的元素类(如 CImagePainter、CLabel),否则 CList 存对象时编译器不认。下面这段是改动后的头部片段,黄底部分为新增的前置声明。 CCanvasBase 里补了图表属性记忆:私有变量存程序启动前的滚轮、右键菜单状态,析构时还原。元素状态先定两种(按下/释放),背景和前景透明度分开管,事件处理器分 *Handler(内部组件交互)和 *Event(发自定义事件给图表)。 外汇和贵金属图表挂这类控件属于高频重绘操作,实盘前务必在策略测试器用历史数据验证 CPU 占用,可能引发行情跳动时面板卡顿。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Base.mqh |
class=class="str">"cmt">//|                     Copyright class="num">2025, MetaQuotes Ltd. |
class=class="str">"cmt">//|                       [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include libraries                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Canvas\Canvas.mqh>          class=class="str">"cmt">// CCanvas class
class="macro">#include <Arrays\List.mqh>            class=class="str">"cmt">// CList class
class=class="str">"cmt">//--- Forward declaration of control element classes
class    CImagePainter;               class=class="str">"cmt">// Image drawing class
class    CLabel;                      class=class="str">"cmt">// Text label class

◍ 标准控件类与图形元素枚举的底层的约定

在 MT5 自定义面板开发里,标准控件不是从零画出来的,而是有一组预置类直接可用。下面这些类覆盖了按钮、方向箭头和勾选/单选控件,开 EA 界面时基本不用自己重写底层消息路由。 class CButton; // 普通按钮类 class CButtonTriggered; // 双态(按下/弹起)按钮类 class CButtonArrowUp; // 向上箭头按钮类 class CButtonArrowDown; // 向下箭头按钮类 class CButtonArrowLeft; // 向左箭头按钮类 class CButtonArrowRight; // 向右箭头按钮类 class CCheckBox; // 复选框控件类 class CRadioButton; // 单选按钮控件类 宏定义里有两个值会反复用到:clrNULL 被设为 0x00FFFFFF,这是 CCanvas 用的透明色,画自定义图元时填错色号就会全白块;DEF_FONTNAME 是 "Calibri"、DEF_FONTSIZE 是 10,意味着新建文本标签若不显式改字体,默认就是这套。 #define clrNULL 0x00FFFFFF // CCanvas 透明色 #define MARKER_START_DATA -1 // 文件内数据起始标记 #define DEF_FONTNAME "Calibri" // 默认字体 #define DEF_FONTSIZE 10 // 默认字号 图形元素类型用枚举 ENUM_ELEMENT_TYPE 区分,基值从 0x10000 起,说明它和 MT5 标准对象 ID 做了区段隔离,避免碰撞。ELEMENT_TYPE_BASE 是 0x10000,ELEMENT_TYPE_CANVAS_BASE 是画布基类,ELEMENT_TYPE_LABEL 专门管文本标签。 enum ENUM_ELEMENT_TYPE { ELEMENT_TYPE_BASE = 0x10000, // 图形元素基础对象 ELEMENT_TYPE_COLOR, // 颜色对象 ELEMENT_TYPE_COLORS_ELEMENT, // 图形对象元素的颜色 ELEMENT_TYPE_RECTANGLE_AREA, // 元素矩形区域 ELEMENT_TYPE_IMAGE_PAINTER, // 图像绘制对象 ELEMENT_TYPE_CANVAS_BASE, // 图形元素画布基类 ELEMENT_TYPE_LABEL // 文本标签 } 实盘做贵金属面板时,直接 #include 这些类能省掉约 200 行控件代码;但外汇/贵金属波动剧烈、点差跳变频繁,界面刷新逻辑若绑在 OnTimer 而非事件回调,可能在非农秒级跳空时卡顿,概率不低。

MQL5 / C++
class CButton;                      class=class="str">"cmt">// Simple button class
class CButtonTriggered;             class=class="str">"cmt">// Two-position button class
class CButtonArrowUp;               class=class="str">"cmt">// Up arrow button class
class CButtonArrowDown;             class=class="str">"cmt">// Down arrow button class
class CButtonArrowLeft;             class=class="str">"cmt">// Left arrow button class
class CButtonArrowRight;            class=class="str">"cmt">// Right arrow button class
class CCheckBox;                    class=class="str">"cmt">// CheckBox control class
class CRadioButton;                 class=class="str">"cmt">// RadioButton control class

class="macro">#define clrNULL        0x00FFFFFF  class=class="str">"cmt">// Transparent class="type">color for CCanvas
class="macro">#define MARKER_START_DATA -class="num">1       class=class="str">"cmt">// Data start marker in a file
class="macro">#define DEF_FONTNAME   "Calibri"   class=class="str">"cmt">// Default font
class="macro">#define DEF_FONTSIZE   class="num">10          class=class="str">"cmt">// Default font size

enum ENUM_ELEMENT_TYPE
  {
   ELEMENT_TYPE_BASE = 0x10000,           class=class="str">"cmt">// Basic object of graphical elements
   ELEMENT_TYPE_COLOR,                    class=class="str">"cmt">// Color object
   ELEMENT_TYPE_COLORS_ELEMENT,           class=class="str">"cmt">// Color object of the graphical object element
   ELEMENT_TYPE_RECTANGLE_AREA,           class=class="str">"cmt">// Rectangular area of the element
   ELEMENT_TYPE_IMAGE_PAINTER,            class=class="str">"cmt">// Object for drawing images
   ELEMENT_TYPE_CANVAS_BASE,              class=class="str">"cmt">// Basic canvas object for graphical elements
   ELEMENT_TYPE_LABEL                     class=class="str">"cmt">// Text label
  }

控件枚举与文件存盘的基础骨架

在 MT5 自定义面板开发里,先理清控件类型与状态枚举,才能往下写交互逻辑。下面这组枚举覆盖了按钮(含上下左右箭头)、复选框、单选框,以及默认/激活两种元素状态,还有四种配色状态:默认、悬停、按下、禁用。 ELEMENT_TYPE_BUTTON 到 ELEMENT_TYPE_RADIOBUTTON 决定了你在 OBJ_RECTANGLE_LABEL 上挂的是哪种可点单元;ENUM_ELEMENT_STATE 的 DEF 与 ACT 则对应按钮弹起和按下的底层区分,做状态机时直接读这两个值即可。 存盘函数给出了实打实的写入顺序:先校验句柄是否为 INVALID_HANDLE,再写 8 字节起始标记 0xFFFFFFFFFFFFFFFF(即代码里的 FileWriteLong 写 -1),随后依次落盘对象类型、ID、名称数组。任何一步返回值不等于预期字节数就 return false,这意味着一次不完整写入会直接中断而非留半截文件。 SetName 用 StringToShortArray 把字符串塞进定长 m_name 数组,调用方传进来的 name 长度若超过数组容量会被截断,开 MT5 自测时建议故意传超长名看界面是否乱码。

MQL5 / C++
enum ENUM_ELEMENT_TYPE
  {
  ELEMENT_TYPE_BUTTON,                    class=class="str">"cmt">// Simple button
  ELEMENT_TYPE_BUTTON_TRIGGERED,          class=class="str">"cmt">// Two-position button
  ELEMENT_TYPE_BUTTON_ARROW_UP,           class=class="str">"cmt">// Up arrow button
  ELEMENT_TYPE_BUTTON_ARROW_DOWN,         class=class="str">"cmt">// Down arrow button
  ELEMENT_TYPE_BUTTON_ARROW_LEFT,         class=class="str">"cmt">// Left arrow button
  ELEMENT_TYPE_BUTTON_ARROW_RIGHT,        class=class="str">"cmt">// Right arrow button
  ELEMENT_TYPE_CHECKBOX,                  class=class="str">"cmt">// CheckBox control
  ELEMENT_TYPE_RADIOBUTTON,               class=class="str">"cmt">// RadioButton control
  };

enum ENUM_ELEMENT_STATE                  class=class="str">"cmt">// Control state
  {
  ELEMENT_STATE_DEF,                      class=class="str">"cmt">// By class="kw">default (e.g. button released etc.)
  ELEMENT_STATE_ACT,                      class=class="str">"cmt">// Activated(e.g. button pressed, etc.)
  };
enum ENUM_COLOR_STATE                    class=class="str">"cmt">// Enumeration of element state colors
  {
  COLOR_STATE_DEFAULT,                    class=class="str">"cmt">// Normal state class="type">color
  COLOR_STATE_FOCUSED,                    class=class="str">"cmt">// Color when hovering over an element
  COLOR_STATE_PRESSED,                    class=class="str">"cmt">// Color when clicking on an element
  COLOR_STATE_BLOCKED,                    class=class="str">"cmt">// Blocked element class="type">color
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Functions                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Check the handle
  if(file_handle==INVALID_HANDLE)
    class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
  if(::FileWriteLong(file_handle,-class="num">1)!=class="kw">sizeof(class="type">long))
    class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save the object type
  if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
    class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save the ID
  if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
    class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save the name
  if(::FileWriteArray(file_handle,this.m_name)!=class="kw">sizeof(this.m_name))
    class="kw">return class="kw">false;
class="kw">public:
class=class="str">"cmt">//--- Set(class="num">1) name and(class="num">2) ID
  class="type">void            SetName(const class="type">class="kw">string name)            { ::StringToShortArray(name,this.m_name);    }

常见问题

通常指文本标签、按钮、编辑框等只负责展示或接收单次输入的轻量组件,它们不直接持有数据,只通过控制器与模型通信。
先删掉脚本特有的初始化与事件循环代码,只保留构造、重绘和坐标计算等通用方法,避免指标加载时报未定义引用。
小布可自动解析你的视图代码并画出控件继承关系图,标出哪些基类被复用、哪些方法冗余,省去手动翻目录对枚举的功夫。
提前统一定义元素类型编号与序列化字段,后续新增控件时只需追加枚举值,存读配置不会错位或漏字段。
优先核对坐标计算是否依赖了被删的旧全局变量,再确认枚举映射表里图形元素ID没有断号,用最小表格样例逐步打印位置即可定位。