图形界面 XI: 集成标准图形库 (统合构建 16)(基础篇)
📘

图形界面 XI: 集成标准图形库 (统合构建 16)(基础篇)

第 1/3 篇

「把标准图形库塞进自己的 EA」

在 MT5 里做自定义面板,最省事的办法不是从零画控件,而是直接复用标准图形库(Standard Library 的 CAppDialog 一系)。Anatoli Kazharski 在 2017-10-18 发布的统合构建 16 里,把图形界面 XI 的示例整合进了一套可复用函数库,原帖显示 4 次收藏、202 次查看、93 条关联线索,说明这套接法在老交易员里流通很广。 这套构建主要动了函数库规划:把图表属性测试、曲线属性测试、动态内摆线图表三个独立 demo 合并到同一工程,并修掉了前次更新里测试程序的若干返回值 bug。你在 MT5 安装目录的 MQL5/Include/ChartObjects 下能翻到对应头文件,直接 #include 就能在自己的脚本里起一个带坐标拾取和曲线重绘的对话框。 外汇和贵金属品种上跑这类图形界面要留意:Tick 刷新频率高时,内摆线重绘会吃主线程,回测和实盘都可能掉帧,建议先在 EURUSD 的 M1 上验证渲染开销再上真仓。

从临时折线图到新版图表控件的演进

在系列第九部分第二章里,曾把一个画折线图的类临时塞进界面函数库,但当时这部分能力很弱,只能算凑合能用。 近期发布的 CGraphic 类新版本图形库补齐了科学图表绘制能力,部分函数说明已随《可视化!类似于 R 语言 PLOT 的 MQL5 图形库》一文公开。 本次函数库更新直接引入基于新版本 CGraphic 的图表控件,意味着不同类型数据的可视化在 MT5 里落地更轻量,不用再自己拼临时方案。 建议打开 MT5 把第九部分第二章的折线图示例和新版控件对照跑一遍,看渲染层和参数调用差异。

◍ 函数库重构后图表类的接驳方式

早前开发用的绘图函数库里塞了一份 CCanvas 的副本,全局重构之后这份副本已经多余,直接换回标准库原版即可。对比重构前两次统合构建里提到的旧版,新函数库体积大约砍掉 10%,部分模块近乎缩了 40%。 现在图表绘制交给 CGraphic 类,所以在 Objects.mqh 里要包含 Graphic.mqh。因为 CCanvas 已经在 Graphic.mqh 的包含链里,整个函数库都能顺带用它,不用再单独引。 原来的 CLineChart 改名叫 CGraph,内部只留控件通用属性和状态管理的方法;坐标轴用 CAxis、曲线用 CCurve,都由 CGraphic 统管,CColorGenerator 负责生成曲线配色。想从外部调图表,用 CGraphic::GetGraphicPointer() 拿实例指针就行。 下面这段是 Objects.mqh 的精简摘出,重点看高亮那行包含语句,以及 CGraph 类如何只留基础管理、把绘图委托给 m_graph 成员。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  Objects.mqh |
class=class="str">"cmt">//|                                                                 版权所有 class="num">2015, MetaQuotes 软件公司  |
class=class="str">"cmt">//|                                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Enums.mqh"
class="macro">#include "Defines.mqh"
class="macro">#include "Fonts.mqh"
class="macro">#include "Colors.mqh"
class="macro">#include <Graphics\Graphic.mqh>
class="macro">#include <ChartObjects\ChartObjectSubChart.mqh>
...
class CGraph : class="kw">public CElement
  {
class="kw">public:
   class=class="str">"cmt">//--- 图表事件处理程序
   class="kw">virtual class="type">void        OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam);
   class=class="str">"cmt">//--- 移动控件
   class="kw">virtual class="type">void        Moving(class="kw">const class="type">bool only_visible=true);
   class=class="str">"cmt">//--- 管理
   class="kw">virtual class="type">void        Show(class="type">void);
   class="kw">virtual class="type">void        Hide(class="type">void);
   class="kw">virtual class="type">void        Reset(class="type">void);
   class="kw">virtual class="type">void        Delete(class="type">void);
   class=class="str">"cmt">//--- 应用最新的变化
   class="kw">virtual class="type">void        Update(class="kw">const class="type">bool redraw=false);
   class=class="str">"cmt">//---
class="kw">private:
   class=class="str">"cmt">//--- 调整大小
   class="type">void                Resize(class="kw">const class="type">int width,class="kw">const class="type">int height);
   class=class="str">"cmt">//--- 更改窗口右边缘的宽度
   class="kw">virtual class="type">void        ChangeWidthByRightWindowSide(class="type">void);
   class=class="str">"cmt">//--- 更改窗口底部边缘的高度
   class="kw">virtual class="type">void        ChangeHeightByBottomWindowSide(class="type">void);
   };
class CGraph : class="kw">public CElement
  {
class="kw">private:
   class=class="str">"cmt">//--- 用于创建控件的对象
   CGraphic            m_graph;
   class=class="str">"cmt">//---
class="kw">public:
   class=class="str">"cmt">//--- 返回指向图表的指针
   CGraphic            *GetGraphicPointer(class="type">void) { class="kw">return(::GetPointer(m_graph)); }
   };

「绘图库的依赖骨架怎么搭」

要在 MT5 里做自定义图形界面,先得理清几个头文件的依赖关系。Graphic.mqh 作为总入口,会依次引入 ArrayObj.mqh(对象数组容器)、Curve.mqh(曲线绘制)和 Axis.mqh(坐标轴),再挂上 ColorGenerator.mqh 处理配色。 Curve.mqh 自身并不独立,它进一步依赖 Object.mqh 做基类封装,以及 Canvas\Canvas.mqh 接管像素级绘制。也就是说,一条曲线从数据到屏幕,至少要经过四层包含链。 打开 MT5 的 MQL5/Include 目录核对这些文件是否存在,是编译自定义指标前最该做的一步。缺一个头文件,编译器就会在 include 行直接报错,外汇与贵金属图表的高波动环境更要求绘图逻辑零报错运行。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Graphic.mqh |
class=class="str">"cmt">//|                         版权所有 class="num">2016-class="num">2017, MetaQuotes 软件公司  |
class=class="str">"cmt">//|                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Arrays\ArrayObj.mqh>
class="macro">#include "Curve.mqh"
class="macro">#include "Axis.mqh"
class="macro">#include "ColorGenerator.mqh"
...
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Curve.mqh |
class=class="str">"cmt">//|                         版权所有 class="num">2016-class="num">2017, MetaQuotes 软件公司  |
class=class="str">"cmt">//|                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Object.mqh>
class="macro">#include <Canvas\Canvas.mqh>
...

用四个选项卡调出 CGraphic 图表属性

想在 MT5 里快速摸透 CGraphic 的图表属性,最直白的法子是跑一个带图形界面的测试程序:顶部放一个 CTabs 控件,切出背景、缩进和历史、网格、坐标轴四个选项卡,下方实时画两条随机数值曲线。 背景选项卡管的是底色、主/辅文本内容与颜色、以及主/辅文字字号;缩进和历史选项卡覆盖左/右/顶/底缩进、图例宽度与字号、标记大小、公用缩进和标尺标记大小;网格选项卡负责网格线色、零线色、网格底色、节点圆点开关与半径颜色;坐标轴选项卡用左侧单选按钮切换配置,可改自动缩放、最大最小数值与公差、数字大小与最大长度、名称字号与颜色、初始步长、数字最大数量及轴名称。 CGraphic 和 CAxis 都暴露了对应的公共 get/set 方法,下面这段结构体和类片段就给出了背景属性的存储与读取实现,复制进 MQ5 文件即可编译验证。 外汇与贵金属图表可视化改造属于高风险定制,参数误设可能导致图形渲染异常,建议先在模拟环境跑通再挂实盘。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 结构 CBackground                                                                 |
class=class="str">"cmt">//| 用法: 二维图形上的背景                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct CBackground
  {
   class="type">uint                clr;
   class="type">uint                clr_main;
   class="type">uint                clr_sub;
   class="type">class="kw">string              main;
   class="type">class="kw">string              sub;
   class="type">int                 size_main;
   class="type">int                 size_sub;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类 CGraphic                                                                     |
class=class="str">"cmt">//| 用法: 绘制二维图形的类                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CGraphic
  {
class="kw">protected:
   class=class="str">"cmt">//--- 图形元素
   CBackground         m_background;          class=class="str">"cmt">// 背景
   class=class="str">"cmt">//---
class="kw">public:
   class=class="str">"cmt">//--- 获取背景属性
   class="type">uint                BackgroundColor(class="type">void)      class="kw">const { class="kw">return(m_background.clr);       }
   class="type">uint                BackgroundMainColor(class="type">void)  class="kw">const { class="kw">return(m_background.clr_main);  }
   class="type">uint                BackgroundSubColor(class="type">void)   class="kw">const { class="kw">return(m_background.clr_sub);   }
   class="type">class="kw">string              BackgroundMain(class="type">void)       class="kw">const { class="kw">return(m_background.main);      }
   class="type">class="kw">string              BackgroundSub(class="type">void)        class="kw">const { class="kw">return(m_background.sub);       }

◍ 给背景和曲线结构留出接口

自定义图形类里,背景参数和曲线统计都先做成轻量结构,方便后续在 MT5 指标里动态改色、改字、数节点。背景这块用一组 getter 和 setter 包住私有成员,主副标题的颜色、文字、字号都能单独调。 下面这段就是背景相关的存取接口,m_background 是内部结构体,外部只管传值: int BackgroundMainSize(void) const { return(m_background.size_main); } int BackgroundSubSize(void) const { return(m_background.size_sub); } //--- 设置背景属性 void BackgroundColor(const uint clr) { m_background.clr=clr; } void BackgroundMainColor(const uint clr) { m_background.clr_main=clr; } void BackgroundSubColor(const uint clr) { m_background.clr_sub=clr; } void BackgroundMain(const string main) { m_background.main=main; } void BackgroundSub(const string sub) { m_background.sub=sub; } void BackgroundMainSize(const int size) { m_background.size_main=size; } void BackgroundSubSize(const int size) { m_background.size_sub=size; } 接着定义了 CCurveHistory 结构,专门记历史曲线在二维图上的体量:名字宽度 name_width、名字字号 name_size、符号字号 symbol_size,以及总数 count_total、点数 count_points、线数 count_lines、直方图数 count_histogram、自定义数 count_custom。这些 int 字段在跑回测或实时绘图时,能直接反映当前图形负载,比如 count_points 上到几千就可能拖慢重绘。 CGraphic 类把 CCurveHistory 实例 m_history 放在 protected 段,意味着子类能直接读历史统计,却不必暴露给外部调用者。你在写自己的面板时,继承这个类就能省掉重复计数逻辑。

MQL5 / C++
class="type">int BackgroundMainSize(class="type">void) class="kw">const { class="kw">return(m_background.size_main); }
class="type">int BackgroundSubSize(class="type">void) class="kw">const { class="kw">return(m_background.size_sub); }
class=class="str">"cmt">//--- 设置背景属性
class="type">void BackgroundColor(class="kw">const class="type">uint clr) { m_background.clr=clr; }
class="type">void BackgroundMainColor(class="kw">const class="type">uint clr) { m_background.clr_main=clr; }
class="type">void BackgroundSubColor(class="kw">const class="type">uint clr) { m_background.clr_sub=clr; }
class="type">void BackgroundMain(class="kw">const class="type">class="kw">string main) { m_background.main=main; }
class="type">void BackgroundSub(class="kw">const class="type">class="kw">string sub) { m_background.sub=sub; }
class="type">void BackgroundMainSize(class="kw">const class="type">int size) { m_background.size_main=size; }
class="type">void BackgroundSubSize(class="kw">const class="type">int size) { m_background.size_sub=size; }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 结构 CCurveHistory                                                                 |
class=class="str">"cmt">//| 用法: 二维图形上的历史曲线                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct CCurveHistory
  {
   class="type">int name_width;
   class="type">int name_size;
   class="type">int symbol_size;
   class="type">int count_total;
   class="type">int count_points;
   class="type">int count_lines;
   class="type">int count_histogram;
   class="type">int count_custom;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类 CGraphic                                                                 |
class=class="str">"cmt">//| 用法: 绘制二维图形的类                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CGraphic
  {
class="kw">protected:
   class=class="str">"cmt">//--- 图形元素
   CCurveHistory      m_history;          class=class="str">"cmt">// 历史

常见问题

先把绘图相关功能从 EA 主循环里抽离,单独用图表类封装,再在 EA 里实例化调用,避免直接在 OnTick 里画。
放弃手动维护数组重绘,改为继承或调用 CGraphic 派生类,把数据更新和属性配置分开处理。
可以,把代码贴给小布,它能指出图表类实例化位置、依赖缺失和回调函数错配等常见问题。
至少预留背景层、曲线容器和坐标转换三个接口头,后续换皮肤或加指标不用动核心。
在选项卡初始化时把背景刷色和曲线样式做成可重写虚函数,新需求直接派生覆盖即可。