图形界面标准图表控件:从基类到可滚动子图的基础搭建(基础篇)
📊

图形界面标准图表控件:从基类到可滚动子图的基础搭建(基础篇)

(1/3)·自定义MQL应用里嵌一个能横滚的价格子图,比反复切窗口省事得多

案例拆解新手友好 第 1/3 篇
在自定义面板里想顺手看多个时间帧,很多人还在手动开一堆主图窗口。其实给程序塞一个标准图表控件,横滚看子图内容就能少切好几次屏。

「标准图表控件的类封装起点」

在 MT5 客户端里,标准图表控件可以通过自定义类做集成编译,避免每次手动拖拽对象。Anatoli Kazharski 在 2016 年 12 月的示例显示,该控件方案发布后获得 5579 次查看与 51 条互动,说明社区对原生图表对象复用有真实需求。 这一类封装的核心动作是开发创建标准图表控件的类,把坐标轴、数据序列、鼠标事件收进同一个实例。后续还要测控件应用、优化库引擎的定时器和事件处理器,否则在高频刷新时容易掉帧。 顺带一提,树形视图和文件导航控件也在这套优化里,新增了文件夹与文件图标,方便在文件导航里直接区分资源类型。外汇与贵金属图表加载这类控件时需注意:客户端卡顿会放大滑点风险,建议先在模拟盘验证。

为什么给库补一个图表控件类

正在开发的图形界面库还缺一个关键控件:图表容器。交易终端启动后默认就是价格图表,若能在自定义面板里直接管理子图表,手动切窗口的次数能少一大半。 前一篇讲过多时间帧同窗监视的思路,但那个版本只能静态铺排。这次要写的类支持在子图表里水平滚动内容,行为和主图表窗口一致,方便直接嵌进 EA 或指标的界面里。 本篇同时继续压库代码的 CPU 占用,具体做法后面章节拆。外汇与贵金属交易本身高风险,这类界面工具只解决操作效率,不替代行情判断。

◍ 给子图表控件打地基:从基类到水平滚动指针

在写标准图表控件类之前,先得往 Object.mqh 里塞一个 CSubChart 基类,它从标准库的 CChartObjectSubChart 继承,多带了一些属性。所有图形对象都得在库控件使用前先这么处理。CChartObjectSubChart 本身就有建子图、改坐标维度、切品名周期、显隐尺度的现成方法。 真正干活的是 CStandardChart 类,放在 StandardChart.mqh 里统一定义控件基础行为。因为控件支持水平滚动,得让用户知道鼠标正处于滚动态,所以引入 Pointer.mqh 里的 CPointer 类,把鼠标图标换成主图水平滚动那个白边黑双箭头(文末附了黑/蓝两版)。鼠标指针枚举里补一个 MP_X_SCROLL,并在 CPointer::SetPointerBmp() 的 switch 里加对应 case 挂上资源。 Moving() 方法有两个模式,靠第三个参数切换,默认 false 表示仅当表单处于移动模式才动;设 true 则强制移动,某些场景能明显降 CPU 负载。为了知道表单是不是在移动,CWindow 类加了 ClampingAreaMouse() 返回鼠标左键按下区域,每个控件的 Moving() 里都要插一段判断,CSimpleButton 的 Show() 就是传 true 强制刷新坐标的范例。 建标准图表前,要用 AddSubChart() 传品名和周期,里面先调 CheckSymbol():先查市场观察窗,没有就去常用列表找,找到了就加进市场观察,否则建不出对应子图(会退化成主图同品种)。成功返回 true,失败返回 false 且不增数组,日志报消息。 建图分三个方法,其中私有方法管水平滚动时的光标图标。SubChartsTotal() 是公有的取数量的辅助。CreateSubCharts() 先查数组里有没有图,没有就退方法报日志;有就按总宽除以数量算每个对象宽,循环里按锚点建对象,建完存标识符和属性。 水平滚动真启用后,CHARTEVENT_MOUSE_MOVE 里调 HorizontalScroll():锁表单、算光标像素位移、显图标;平移值相对末根用 CHART_END,负数才继续,正数直接退;通过就关掉 AUTOSCROLL 和 SHIFT 按算值平移。松左键解锁隐藏图标。ZeroHorizontalScrollVariables() 清辅助变量,ResetCharts() 可跳回末根。 子图上左键点击也接了:Defines.mqh 加 ON_CLICK_SUB_CHART,CStandardChart::OnClickSubChart() 校验名字和 ID 后发带事件符、控件符、子图索引、品名的消息。 导航方面,MT5 新版有 CHART_QUICK_NAVIGATION 属性(ChartSetInteger/GetInteger 控),回车空格会唤起左下命令行跳日期换品种;若你的程序自己处理这俩键,记得关掉该属性免被终端拦。库里用 CCalendar 更省事,封装成单参数 SubChartNavigate():禁主图 AUTOSCROLL/SHIFT,日期大于今开就跳末根退出,否则算左移根数——先取今开到指定日的总根数、可见根数、今开根数加2缩进,算出偏移量,主图先移子图跟上。

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=class="str">"cmt">//--- 文件内的类列表, 用于快速导航 (Alt+G)
...
class CSubChart;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 具有额外属性的 Subchart 对象类                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSubChart : class="kw">public CChartObjectSubChart
  {
class="kw">protected:

「子图坐标与边距的存取封装」

在 MT5 自定义子图类里,坐标和尺寸不是直接暴露的公有变量,而是用一组内联存取函数包起来。这样外部只管拿 X()、Y() 读位置,或用 X(const int x) 写位置,类内部状态不会被乱改。 m_x、m_y 是子图左上角相对主图的像素坐标;m_x_size、m_y_size 是宽高;X2() 直接返回 m_x+m_x_size,Y2() 返回 m_y+m_y_size,相当于右下角坐标,用来做命中检测很方便。 边距那组也类似:m_x_gap、m_y_gap 存的是子图离边缘的留白,XGap() / YGap() 读写。m_mouse_focus 是 bool,标记鼠标是否悬停在该子图区域——做交互式面板时靠它决定高亮还是穿透事件。 下面这段是原始声明与函数实现,逐行拆完你就能照抄进自己的 CSubChart 类。注意所有 getter 都 return 成员变量,setter 只做赋值,没有边界检查,实际用的时候自己加 clamp 更安全。

MQL5 / C++
  class="type">int               m_x;
  class="type">int               m_y;
  class="type">int               m_x2;
  class="type">int               m_y2;
  class="type">int               m_x_gap;
  class="type">int               m_y_gap;
  class="type">int               m_x_size;
  class="type">int               m_y_size;
  class="type">bool              m_mouse_focus;
  class=class="str">"cmt">//---
class="kw">public:
                     CSubChart(class="type">void);
                    ~CSubChart(class="type">void);
  class=class="str">"cmt">//--- 坐标
  class="type">int               X(class="type">void)                { class="kw">return(m_x);       }
  class="type">void              X(class="kw">const class="type">int x)         { m_x=x;             }
  class="type">int               Y(class="type">void)                { class="kw">return(m_y);       }
  class="type">void              Y(class="kw">const class="type">int y)         { m_y=y;             }
  class="type">int               X2(class="type">void)               { class="kw">return(m_x+m_x_size);  }
  class="type">int               Y2(class="type">void)               { class="kw">return(m_y+m_y_size);  }
  class=class="str">"cmt">//--- 自边缘点的边距 (xy)
  class="type">int               XGap(class="type">void)             { class="kw">return(m_x_gap);   }
  class="type">void              XGap(class="kw">const class="type">int x_gap)  { m_x_gap=x_gap;     }
  class="type">int               YGap(class="type">void)             { class="kw">return(m_y_gap);   }
  class="type">void              YGap(class="kw">const class="type">int y_gap)  { m_y_gap=y_gap;     }
  class=class="str">"cmt">//--- 大小
  class="type">int               XSize(class="type">void)            { class="kw">return(m_x_size);  }

子图对象的尺寸与焦点存取器

这段类定义给出了 CSubChart 里几个关键字段的读写接口:X 轴尺寸、Y 轴尺寸以及鼠标焦点状态。它们都以成对函数出现,无参版本返回私有成员,带参版本写入私有成员,是典型的 MQL5 封装写法。 构造器把全部坐标与尺寸成员初始化为 0,m_mouse_focus 显式置为 false,意味着新子图默认不抢占鼠标事件。析构器为空,说明该层不持有需要手动释放的句柄。 下面把存取器逐行拆一下: void XSize(const int x_size) { m_x_size=x_size; } —— 写 X 尺寸,传入整数像素宽。 int YSize(void) { return(m_y_size); } —— 读 Y 尺寸,返回当前高度。 void YSize(const int y_size) { m_y_size=y_size; } —— 写 Y 尺寸。 bool MouseFocus(void) { return(m_mouse_focus); } —— 读焦点标志,true 表示图表允许该子图响应鼠标。 void MouseFocus(const bool focus) { m_mouse_focus=focus; } —— 写焦点标志,控制子图是否吞掉鼠标消息。 在 MT5 里建自定义子图面板时,若忘了给 MouseFocus(true) 赋值,悬浮提示和点击拖拽可能全部失效。外汇与贵金属图表多屏联动场景下,这种焦点竞争容易引发事件错位,属高风险调试点,建议开 MT5 自建类验证一下默认 false 的行为。

MQL5 / C++
class="type">void      XSize(class="kw">const class="type">int x_size)      { m_x_size=x_size;      }
class="type">int       YSize(class="type">void)                   { class="kw">return(m_y_size);     }
class="type">void      YSize(class="kw">const class="type">int y_size)       { m_y_size=y_size;      }
class=class="str">"cmt">//--- 焦点
class="type">bool      MouseFocus(class="type">void)              { class="kw">return(m_mouse_focus); }
class="type">void      MouseFocus(class="kw">const class="type">bool focus)  { m_mouse_focus=focus;  }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造器                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSubChart::CSubChart(class="type">void) : m_x(class="num">0),
               m_y(class="num">0),
               m_x2(class="num">0),
               m_y2(class="num">0),
               m_x_gap(class="num">0),
               m_y_gap(class="num">0),
               m_x_size(class="num">0),
               m_y_size(class="num">0),
               m_mouse_focus(class="kw">false)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 析构器                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSubChart::~CSubChart(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     ChartObjectSubChart.mqh                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ChartObject.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 子图对象的定位与品种绑定接口

在 MT5 的图表对象体系里,SubChart 类从 CChartObject 派生,专门用来在主图里嵌一块独立报价区域。它暴露的接口覆盖了位置、尺寸、锚点以及底层品种和周期,写 EA 或指标时可以直接拿来控制副图布局。 高亮部分第一组管的是偏移与边角:X_Distance 和 Y_Distance 用 int 返回或设置子图到锚点的像素距离,Corner 则接收 ENUM_BASE_CORNER 决定贴哪条边。实测中若 Corner 设为 CORNER_LEFT_UPPER 且 X_Distance 为 10,子图会紧贴主图左上角内侧 10 像素处。 第二组是尺寸控制,X_Size 与 Y_Size 均以 int 表达像素宽高,调用带参版本返回 bool 表示设置是否成功。若 Y_Size 传 0,MT5 可能按默认比例绘制,倾向出现压缩变形。 品种与周期绑定在第三组:Symbol 方法读写子图对应的交易品种字符串,Period 用 int 接收 timeframe 常量(如 PERIOD_M15=15),Scale 控制纵向缩放比例。外汇与贵金属波动剧烈,副图叠加多周期时高风险,参数错配可能让图形失焦。 最后一组 DateScale 是 bool 开关,决定子图是否跟随主图时间轴缩放。关掉它之后,副图时间标尺独立,适合做跨周期背离观测,但需手动同步光标位置。

MQL5 / C++
class CChartObjectSubChart : class="kw">public CChartObject
  {
class="kw">public:
                      CChartObjectSubChart(class="type">void);
                     ~CChartObjectSubChart(class="type">void);
   class=class="str">"cmt">//--- 访问对象属性的方法
   class="type">int                X_Distance(class="type">void) class="kw">const;
   class="type">bool               X_Distance(class="kw">const class="type">int X) class="kw">const;
   class="type">int                Y_Distance(class="type">void) class="kw">const;
   class="type">bool               Y_Distance(class="kw">const class="type">int Y) class="kw">const;
   ENUM_BASE_CORNER   Corner(class="type">void) class="kw">const;
   class="type">bool               Corner(class="kw">const ENUM_BASE_CORNER corner) class="kw">const;
   class="type">int                X_Size(class="type">void) class="kw">const;
   class="type">bool               X_Size(class="kw">const class="type">int size) class="kw">const;
   class="type">int                Y_Size(class="type">void) class="kw">const;
   class="type">bool               Y_Size(class="kw">const class="type">int size) class="kw">const;
   class="type">class="kw">string             Symbol(class="type">void) class="kw">const;
   class="type">bool               Symbol(class="kw">const class="type">class="kw">string symbol) class="kw">const;
   class="type">int                Period(class="type">void) class="kw">const;
   class="type">bool               Period(class="kw">const class="type">int period) class="kw">const;
   class="type">int                Scale(class="type">void) class="kw">const;
   class="type">bool               Scale(class="kw">const class="type">int scale) class="kw">const;
   class="type">bool               DateScale(class="type">void) class="kw">const;
   class="type">bool               DateScale(class="kw">const class="type">bool scale) class="kw">const;
交给小布盯盘看盘口
这些多时间帧对照的思路,小布盯盘的AIGC已内置,打开对应品种页就能直接看分屏节奏,你只管决策。

常见问题

它基于标准库的 CChartObjectSubChart 扩展,并通过 CSubChart 基类补充了子图表专用属性,便于在库控件里统一创建。
可以,小布盯盘的品种页已用AIGC把多时间帧子图监控做了内置呈现,不用自己写MQL也能对照看盘。
主图窗口天然支持横滚,但嵌入自定义界面的子图默认没有这能力;加上横滚和指针图标,用户体验才接近原生图表。
它是所有图形对象在入库前的统一扩展点,集中放子图表专属属性,后续控件创建时不用重复声明。