DoEasy. 控件 (第 1 部分): 第一步(基础篇)
📘

DoEasy. 控件 (第 1 部分): 第一步(基础篇)

第 1/3 篇

从零搭一个 WinForms 风格面板对象类

在 MT5 里做自定义控件,第一步不是画按钮,而是把面板对象抽象成一个可复用的 C++ 类。DoEasy 库的思路是先用一个 WinForms 风格的面板对象类打底,后续所有控件都挂在这个面板上,避免每次重画坐标轴与边框。 原文示例发布于 2022 年 6 月 14 日,截至归档页显示阅读量 1925、评论 2 条,说明这类底层封装需求在量化社群里并不冷门,但真正动手改库的人少。 改进库类的核心动作是重写对象生命周期:创建时注册到图表、销毁时清理资源、重绘时只动脏区。测试阶段建议直接拖一个空面板到 EURUSD 的 M5 图,观察是否随窗口缩放稳定贴边——外汇与贵金属杠杆高,面板遮挡报价区会直接影响下单判断,验证时务必留好右侧空间。

「为什么先搭控件再补旧坑」

这一篇开启一个新系列,目标是用 MQL5 仿照 Windows 窗体样式写一套控件。MS Visual Studio 元素面板里列了八大组:全部 Windows 窗体、标准控件、容器、菜单和工具条、数据、组件、打印、对话框,但函数库并不需要全复刻,只挑最关键的做。 切换主题不是旧坑不填,而是之前的图形对象函数库越写越难管,没有控件根本玩不转。先把控件按 Windows 窗体风格补齐,回头再续上前面的主题,那时工具链才完整。 底层已经有一个基于 CCanvas 的图形元素对象类,所有图形对象都从它派生;窗体类站在图形元素之上,自带移动和操纵方法。面板类将继承窗体类,并往里加新属性,它既是窗口基础,也是装其它控件的容器,支持父面板套子面板多层嵌套。 动手写面板前,得先回头修一遍现有函数库对象类——前面主题里没改完的缺陷和遗漏,要边补边修正,否则控件挂上去也是歪的。

◍ 给 DoEasy 库补上延迟与对冲两个新属性

终端 3260 更新后,MQL5 在 ENUM_SYMBOL_INFO_INTEGER 里加了 SYMBOL_SUBSCRIPTION_DELAY,只针对订阅类品种,且必须在市场观察中选中才能取到,否则返回 ERR_MARKET_NOT_SELECTED (4302)。延迟通常出现在试用数据源上,做订阅行情解析时若漏掉这个前置条件,读取会直接报错。 账户端新增 ACCOUNT_HEDGE_ALLOWED,仅在对冲账户有意义:禁用时同一品种不允许互逆持仓或挂单,强开卖单会返回 TRADE_RETCODE_HEDGE_PROHIBITED。这条对跨品种对冲策略的订单路由判断很关键,尤其在监管限制账户上。 我们把这两个属性接进 DoEasy 的对象层。账户类 Account.mqh 里,整数型属性数量从 11 扩到 12,构造函数对 MQL5 版本低于 3245 的情况强制写 false,>=3245 才从终端属性取真值,MQL4 永远 false。品种类 Symbol.mqh 同理,整数型属性从 40 到 41,GetSubscriptionDelay() 在老终端返回 0。 图形基类也动了:GBaseObj 的 SetChartID() 现在接受 0 或 NULL 并自动回退到 ChartID(),GCnvElement 加了元素可用性标志和默认字体值。Form 类把 GetList() 改名 GetListElements(),新增逆初始化方法,能从继承类里干净卸掉父类动态对象。 下面这段是消息索引枚举的改动片段,高亮行就是为 <3245 终端补的提示索引,复制进 Data.mqh 即可用。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| List of the library&class="macro">#x27;s text message indices                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_MESSAGES_LIB
  {
   MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST,      class=class="str">"cmt">// Beginning of the parameter list
   MSG_LIB_PARAMS_LIST_END,                           class=class="str">"cmt">// End of the parameter list
   MSG_LIB_PROP_NOT_SUPPORTED,                        class=class="str">"cmt">// Property not supported
   MSG_LIB_PROP_NOT_SUPPORTED_MQL4,                   class=class="str">"cmt">// Property not supported in MQL4
   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_2155,          class=class="str">"cmt">// Property not supported in MetaTrader class="num">5 versions lower than class="num">2155
   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245,          class=class="str">"cmt">// Property not supported in MetaTrader class="num">5 versions lower than class="num">3245
   MSG_LIB_PROP_NOT_SUPPORTED_POSITION,               class=class="str">"cmt">// Property not supported for position
   

枚举常量与本地化字符串的版本断点

在 MQL5 标准库的属性枚举里,MSG_SYM_PROP_BACKGROUND_COLOR 指向 Market Watch 中品种的背景色,MSG_SYM_PROP_SUBSCRIPTION_DELAY 则描述订阅制品种推送报价的延迟字段。账户侧有 MSG_ACC_PROP_FIFO_CLOSE 强制 FIFO 平仓标志,以及 MSG_ACC_PROP_HEDGE_ALLOWED 决定可否开反向仓和挂单。 本地化字符串表暴露了明确的构建号断点:部分属性在 MetaTrader5 build 低于 2155 时不支持,另一批(如订阅延迟、对冲允许、Panel 控件)则要求 build 不低于 3245。如果你的终端还停在老版本,直接调用这些枚举会返回不支持错误。 画布参数也写死在宏里:PAUSE_FOR_CANV_UPDATE 为 16 毫秒的画布刷新间隔,CLR_CANV_NULL 定义为 0x00FFFFFF,即带 alpha 通道画布的透明零值。开 MT5 按 F4 搜这些宏,比对自家终端 build 号就能验证兼容性。

MQL5 / C++
class="macro">#define PAUSE_FOR_CANV_UPDATE(class="num">16)            class=class="str">"cmt">// Canvas update frequency
class="macro">#define CLR_CANV_NULL(0x00FFFFFF)     class=class="str">"cmt">// Zero for the canvas with the alpha channel

「画布对象的底层常量与类型枚举」

在 MT5 自定义界面库里,画布上所有文字与图形的默认外观都由一组宏定死。前景色 CLR_FORE_COLOR 取 C'0x2D,0x43,0x48'(深蓝灰),默认字体 Calibri、字号 8,表单工作区外留白 OUTER_AREA_SIZE = 16 像素,这些数值直接决定你后来拖控件时的边距观感。 对象类型用 ENUM_OBJECT_DE_TYPE 枚举管理,起点 OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1,说明图形对象ID接续了集合ID之后分配,不会撞号。往下分了图形基类、图元、表单、阴影,以及 WinForms 风格的面板 GWF_PANEL。 动画子系统也挂在同一枚举下:单帧 GFRAME、文本帧 GFRAME_TEXT、矩形帧 GFRAME_QUAD、几何帧 GFRAME_GEOMETRY,最后由 GANIMATIONS 统管。开 MT5 新建 EA 时,把这段枚举原样贴进头文件,就能用 Switch 按类型分流鼠标事件。

MQL5 / C++
class="macro">#define CLR_FORE_COLOR(C&class="macro">#x27;0x2D,0x43,0x48&class="macro">#x27;)      class=class="str">"cmt">// Default class="type">color for texts of objects on canvas
class="macro">#define DEF_FONT("Calibri")              class=class="str">"cmt">// Default font
class="macro">#define DEF_FONT_SIZE(class="num">8)                      class=class="str">"cmt">// Default font size
class="macro">#define OUTER_AREA_SIZE(class="num">16)                     class=class="str">"cmt">// Size of one side of the outer area around the form workspace
class=class="str">"cmt">//--- Graphical object parameters
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| List of library object types                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
class=class="str">"cmt">//--- Graphics
   OBJECT_DE_TYPE_GBASE =   COLLECTION_ID_LIST_END+class="num">1,               class=class="str">"cmt">// "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                         class=class="str">"cmt">// "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                            class=class="str">"cmt">// Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                    class=class="str">"cmt">// "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                          class=class="str">"cmt">// Shadow object type
class=class="str">"cmt">//--- WinForms
   OBJECT_DE_TYPE_GWF_PANEL,                                        class=class="str">"cmt">// WinForms Panel object type
class=class="str">"cmt">//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                           class=class="str">"cmt">// "Single animation frame" object type
   OBJECT_DE_TYPE_GFRAME_TEXT,                                      class=class="str">"cmt">// "Single text animation frame" object type
   OBJECT_DE_TYPE_GFRAME_QUAD,                                      class=class="str">"cmt">// "Single rectangular animation frame" object type
   OBJECT_DE_TYPE_GFRAME_GEOMETRY,                                  class=class="str">"cmt">// "Single geometric animation frame" object type
   OBJECT_DE_TYPE_GANIMATIONS                                       class=class="str">"cmt">// "Animations" object type
  };

◍ 账户与品种属性枚举的排序边界

在 MT5 的底层枚举设计里,账户整数属性被收进 ENUM_ACCOUNT_PROP_INTEGER,其中 ACCOUNT_PROP_HEDGE_ALLOWED 决定该账户能否开反向仓和挂单,ACCOUNT_PROP_FIFO_CLOSE 则标记是否强制 FIFO 平仓。这两个字段直接决定你的套利和对冲策略在某账户上是否跑得通。 宏定义给出硬数字:ACCOUNT_PROP_INTEGER_TOTAL 为 12,ACCOUNT_PROP_INTEGER_SKIP 为 0,意味着参与排序的整数属性共 12 个、无跳过项。FIRST_ACC_DBL_PROP 与 FIRST_ACC_STR_PROP 据此偏移,把双精度与字符串属性接续排在后段。 排序枚举 ENUM_SORT_ACCOUNT_MODE 里,SORT_BY_ACCOUNT_HEDGE_ALLOWED 和 SORT_BY_ACCOUNT_FIFO_CLOSE 对应前面两个整数属性;从 SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP 起,排序切入余额、信用等真实数值属性。写多账户扫描器时,按这个偏移量取属性才不会越界。 品种侧另有 ENUM_SYMBOL_PROP_INTEGER,例如 SYMBOL_PROP_OPTION_MODE 标出期权类型(取自 ENUM_SYMBOL_OPTION_MODE)。黄金期权或外汇奇异期权品种,先读这个字段再决定报价解析逻辑,能避开用错合约结构的坑。外汇与贵金属杠杆高,属性误读可能导致下单被拒或保证金计算偏差。

MQL5 / C++
class=class="str">"cmt">//--- Managing graphical objects
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Account integer properties                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_INTEGER
  {
  ...
   ACCOUNT_PROP_FIFO_CLOSE,                                                 class=class="str">"cmt">// Flag of a position closure by FIFO rule only
   ACCOUNT_PROP_HEDGE_ALLOWED                                              class=class="str">"cmt">// Permission to open opposite positions and set pending orders
   };
class="macro">#define ACCOUNT_PROP_INTEGER_TOTAL(class="num">12)                                   class=class="str">"cmt">// Total number of integer properties
class="macro">#define ACCOUNT_PROP_INTEGER_SKIP(class="num">0)                                    class=class="str">"cmt">// Number of integer account properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Possible account sorting criteria                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define FIRST_ACC_DBL_PROP(ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP)
class="macro">#define FIRST_ACC_STR_PROP(ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_ACCOUNT_MODE
  {
   ... 
   SORT_BY_ACCOUNT_FIFO_CLOSE,                                             class=class="str">"cmt">// Sort by the flag of a position closure by FIFO rule only
   SORT_BY_ACCOUNT_HEDGE_ALLOWED,                                          class=class="str">"cmt">// Sort by permission to open opposite positions and set pending orders
class=class="str">"cmt">//--- Sort by real properties
   SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP,                           class=class="str">"cmt">// Sort by an account balance in the deposit currency
   SORT_BY_ACCOUNT_CREDIT,                                                 class=class="str">"cmt">// Sort by credit in a deposit currency
   ... 
   SORT_BY_ACCOUNT_COMPANY                                                 class=class="str">"cmt">// Sort by a name of a company serving an account
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Symbol integer properties                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_SYMBOL_PROP_INTEGER
  {
   class=class="str">"cmt">//--- ...
   SYMBOL_PROP_OPTION_MODE,                                                class=class="str">"cmt">// Option type(from the ENUM_SYMBOL_OPTION_MODE enumeration)
   };

常见问题

先搭一个 WinForms 风格的面板基类(控件对象类),把位置、尺寸、背景等基础属性封装好,再往上叠按钮等子控件,避免后期反复改底层。
控件体系不定下来,旧逻辑补丁会越打越乱;先把面板和交互对象标准化,再补旧坑能少改好几轮代码。
小布可以基于你的场景口述生成控件对象类的骨架代码与基础属性,你再把延迟、对冲等业务属性接进去即可。
不同版本间常量增删会导致旧图加载错位,用版本断点隔离本地化字符串与枚举,能保证历史图表不崩。
画布类型枚举别越界混用,账户与品种属性枚举按固定边界排序,超边界的取值直接丢弃,防止遍历时下标溢出。