图形界面标准图表控件:从基类到可滚动子图的基础搭建(基础篇)
(1/3)·自定义MQL应用里嵌一个能横滚的价格子图,比反复切窗口省事得多
「标准图表控件的类封装起点」
在 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缩进,算出偏移量,主图先移子图跟上。
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 更安全。
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 的行为。
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 开关,决定子图是否跟随主图时间轴缩放。关掉它之后,副图时间标尺独立,适合做跨周期背离观测,但需手动同步光标位置。
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;