图形界面 X: 简单快速开发库的更新 (版本 2)(基础篇)
◍ 用现成库给 MT5 界面提速
给 MT5 写自定义面板,最磨人的从来不是逻辑,而是按钮、标签、输入框这些控件的布局代码。Anatoli Kazharski 维护的「图形界面 X」简单快速开发库在 2016-10-21 发布了 2.0 更新,把这类重复劳动压到了最低。 该库在站内的示例帖累计获得 4175 次查看、42 条讨论,说明做 EA 界面的人确实吃这套封装。更新后控件创建改为声明式调用,少写一半样板代码,新手也能在半小时内拼出一个带参数输入的交易小面板。 外汇与贵金属交易自带高杠杆高风险,界面只是工具,信号逻辑错了再好看的面板也救不了账户。开 MT5 把库拖进 Include 目录,先跑通示例里的测试程序,比直接抄网上的散装代码稳得多。
「库结构演进与文件落位」
这套图形界面库在前面章节发布后做过一轮轻量重构:原本散落在多个控件类里、重复率高的成员方法,已经统一收进 CElement 基类。对交易者来说,直接好处是 CPU 负载有所下降,具体降幅随控件数量而异,在数十个面板元素同屏时体感更明显。 随重构还塞进了若干外观微调和小修,不属于功能级变更,但会影响你肉眼看到的控件边距与配色。库的目标在第一篇《图形界面 I》里已拆解过,这里不重复。 每章末尾都附有当前阶段库的完整压缩包。解压后务必按归档内的目录层级原样丢进 MT5 的 MQL5/Include 对应路径,放错一级目录编译就会报找不到头文件。 想验证优化是否生效,可以开 MT5 随便挂一个用了该库旧版的面板,再换新版同控件数量跑一下,对着专家日志里的 tick 处理耗时做对比。外汇与贵金属行情高波动时 GUI 卡顿会放大,这类底层减负在高风控场景里值得盯。
GUI 库重构:鼠标参数集中与窗体自适应
这次库更新把默认配色改成最浅色层,无论图表是浅色还是深色背景都能拉开对比。这样做的好处是自定义控件类在构造时只需指定极少属性,开发时不用反复调色,直接借用标准调色板即可。 新增的 CMouse 类把鼠标和光标参数收拢到单一对象里。它依赖标准库 CChart 的 SubwindowY() 算相对 Y 坐标,构造时绑定图表、析构时解绑。以往每个控件都自己处理 CHARTEVENT_MOUSE_MOVE,现在统一在 CMouse 内填充字段,所有控件只读取一次——CPU 负载降了,类代码也更干净。 CElement 基类吃进了三块重复逻辑:按图形对象名取控件 ID 的方法、CheckWindowPointer() 检查表单指针、以及 AutoXResizeMode() 控件右边界绑窗体模式。以自动宽度为例,设了模式后右边距跟随表单右边,默认关闭、偏移 0,表单因 CHARTEVENT_CHART_CHANGE 变宽时,ON_WINDOW_CHANGE_SIZE 消息驱动 CWndEvents::OnWindowChangeSize() 批量改宽。已落地到 CTabs、CStatusBar、CTreeView 等 12 个控件类。 顺带补了俩实用接口:CTabs/CIconTabs 的 SelectTab() 支持代码切页;CTable 再点已选行可取消,且禁编辑单元被点时抛事件带字符串参数。外汇与贵金属 EA 界面开发高风险,MT4/MT5 某些错误仅前者复现,跨平台编译前务必本地验证。
class="macro">#include <Charts\Chart.mqh> class CMouse { class="kw">private: CChart m_chart; class="kw">public: CMouse(class="type">void); ~CMouse(class="type">void); }; class=class="str">"cmt">// 中文逐行拆解: class=class="str">"cmt">// class="macro">#include <Charts\Chart.mqh> 引入标准库图表类,供后续算坐标 class=class="str">"cmt">// class CMouse 声明鼠标参数管理类 class=class="str">"cmt">// class="kw">private: 私有段开始 class=class="str">"cmt">// CChart m_chart; 图表实例,用于子窗口Y坐标换算 class=class="str">"cmt">// class="kw">public: 公有段 class=class="str">"cmt">// CMouse(class="type">void); 构造函数,内部绑定图表 class=class="str">"cmt">// ~CMouse(class="type">void); 析构函数,解除绑定
◍ 把鼠标坐标封装成可复用的类
在 MT5 里做价格行为辅助工具,第一步往往是把鼠标在图表上的位置变成代码里能直接读的数据。下面这套 CMouse 类的构造与析构,演示了如何把图表对象绑定到生命周期:构造时 Attach() 拿到当前图表 ID,析构时 Detach() 解除绑定,避免指标卸载后还挂着引用。 类内部用 m_x / m_y 存像素坐标,m_subwin 存光标所在子窗口编号,m_time 和 m_level 分别把 X 映射成时间、Y 映射成价格——这意味着你点一下图表,就能同时知道「什么时间、什么价位」。m_left_button_state 记录左键按下或松开,做手动标注类脚本时直接用。 对外只暴露只读 getter:X()、Y() 回坐标,SubWindowNumber()、Time()、Level() 回窗口号与时间价格,LeftButtonState() 回按键状态。全部 const 修饰,调用侧不能改内部状态,减少副作用。 事件入口只留了一个 OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam),后续把 CHARTEVENT_MOUSE_MOVE 等事件喂进来,就能在 lparam/dparam 里解出坐标并写进成员变量。开 MT5 新建 EA,把这段类骨架贴进头文件,编译通过即代表绑定逻辑没问题,可接着补事件分支。
CMouse::CMouse(class="type">void) { class=class="str">"cmt">//--- 取得当前的图表 ID m_chart.Attach(); } CMouse::~CMouse(class="type">void) { class=class="str">"cmt">//--- 解除与图表的绑定 m_chart.Detach(); } class CMouse { class="kw">private: class=class="str">"cmt">//--- 坐标 class="type">int m_x; class="type">int m_y; class=class="str">"cmt">//--- 光标所在窗口的编号 class="type">int m_subwin; class=class="str">"cmt">//--- 对应 X 坐标的时间 class="type">class="kw">datetime m_time; class=class="str">"cmt">//--- 对应 Y 坐标的水平(价格) class="type">class="kw">double m_level; class=class="str">"cmt">//--- 鼠标左键的状态 (按下/松开) class="type">bool m_left_button_state; class="kw">public: class=class="str">"cmt">//--- 返回坐标 class="type">int X(class="type">void) const { class="kw">return(m_x); } class="type">int Y(class="type">void) const { class="kw">return(m_y); } class=class="str">"cmt">//--- 返回 (class="num">1) 光标当前所在窗口的编号, (class="num">2) 对应 X 坐标的时间, class=class="str">"cmt">// (class="num">3) 对应 Y 坐标的价格水平 class="type">int SubWindowNumber(class="type">void) const { class="kw">return(m_subwin); } class="type">class="kw">datetime Time(class="type">void) const { class="kw">return(m_time); } class="type">class="kw">double Level(class="type">void) const { class="kw">return(m_level); } class=class="str">"cmt">//--- 返回鼠标左键的状态 (按下/松开) class="type">bool LeftButtonState(class="type">void) const { class="kw">return(m_left_button_state); } }; class CMouse { class=class="str">"cmt">//--- 事件处理函数 class="type">void OnEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam); };