图形界面 IV 信息元件:从零搭建状态栏与工具提示类库(基础篇)
(1/3)· 多数自定义面板卡在信息层难复用,本篇先把状态栏和提示元件的类结构讲透
在 MT5 里手搓图形界面时,不少人把状态栏和提示框的逻辑写死在 EA 里,换品种就得重写一遍。一旦想加多窗口,旧代码基本报废。其实从类库层面把信息元件抽象出来,后面只改参数就能复用。
「状态栏与工具提示的底层元件」
MetaTrader 5 的图形界面开发中,状态栏(Status Bar)和工具提示(Tooltip)属于信息界面元件,负责在不抢占主图表空间的前提下向交易者暴露辅助数据。这类元件在 2016 年 4 月发布的示例工程里已被验证可用于实盘面板,原文示例累计被查看 3332 次、评论 22 条,说明其有可复用的工程价值。 状态栏通常挂在窗口底部,用来显示当前品种、时间帧或指标数值;工具提示则在鼠标悬停时弹出,依赖一个私有数组来管理不同控件的提示文本。两者都不直接参与下单逻辑,但能显著降低盯盘时的认知负荷。 开 MT5 用 MetaEditor 新建面板工程时,可先只挂一个状态栏控件,验证其刷新频率是否跟得上 tick 更新,再决定是否引入工具提示私有数组。外汇与贵金属波动快,这类轻量元件若刷新卡顿反而会增加误读风险。
◍ 库的当前组成与本章落点
这套图形界面库目前只做到「表单 + 若干可挂载控件」的程度,文件目录必须和压缩包里的结构完全一致,否则 MT5 加载时会找不到依赖。系列每一章末尾都附了当前阶段完整库的下载入口,直接拖进终端就能跑。 多窗口模式留给了后续专文,本章不展开;眼下要写的是状态栏(status bar)和 tooltip 提示框这两个界面元件的类。把它们先实现清楚,后面接多窗口才不会反复返工。 外汇与贵金属图表挂这类自定义界面属于高风险环境下的辅助工具,任何信号提示都只是概率参考,不能替代仓位与止损纪律。
自己写一个 MT5 状态栏控件
MT5 终端和 MetaEditor 自带的状态栏只能看不能改,想在自建图形面板里塞一排实时读数(比如鼠标坐标、账户浮盈、Tick 时间),就得自己撸一个。本文给出的思路是用背景 + 项目 + 分隔线三层对象拼出 CStatusBar 类,暂不挂上下文菜单,纯做信息展示。 类里保留了 OnEvent() 和 OnEventTimer() 两个虚函数但函数体为空,目的只是和其他控件类接口对齐,当前版本不会进事件逻辑。真正干活的是 Moving / Show / Hide / Reset / Delete 这几个虚方法,以及后面要说的 AddItem 和 ValueToItem。 状态栏的共有属性有五项:背景与背景边框颜色、文字颜色、分隔线颜色、鼠标左键点击优先级。特定属性只有一项——项目宽度。构造时给全套默认值,运行时用公有方法重定义即可。 创建顺序有硬约束:必须先调 CStatusBar::AddItem() 把项目加够,再创建图形对象;若项目数为 0,日志会报错并中断界面构建。项目用 CEdit 动态数组,分隔线用现成的 CSeparateLine 数组,且分隔线数量永远比项目少一(首项左侧不加线)。 宽度算法值得注意:若首个项目没指定宽度,就自动用表单宽度减 2 像素(留边)再减去其余项目宽度之和。也就是说,你只要把后面几项定宽,第一项会自适应填满剩余空间。改文字用 CStatusBar::ValueToItem(索引, 字符串),实时刷新某一项显示内容。 外汇与贵金属交易界面涉及实时资金数据,自行绘制状态栏若刷新频率过高可能拖慢 MT5 主图响应,建议在策略测试器里先验证对象数量与重绘开销。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| StatusBar.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Element.mqh" class="macro">#include "Window.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建状态栏的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CStatusBar : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 指向元件附加表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- class="kw">public: CStatusBar(class="type">void); ~CStatusBar(class="type">void); class=class="str">"cmt">//--- 保存表单指针 class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 图表事件处理函数 class="kw">virtual 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) {} class=class="str">"cmt">//--- 计时器 class="kw">virtual class="type">void OnEventTimer(class="type">void) {} class=class="str">"cmt">//--- 移动元件 class="kw">virtual class="type">void Moving(const class="type">int x,const class="type">int y); class=class="str">"cmt">//--- (class="num">1) 显示, (class="num">2) 隐藏, (class="num">3) 重置, (class="num">4) 删除 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);
「状态栏类的层级与配色字段拆解」
在 MT5 自定义控件体系里,CStatusBar 继承自 CElement,它管的是图表底部那一条状态显示区。这个类把鼠标左键点击的优先级(m_zorder)单独拎出来,说明状态栏在交互堆叠时可能抢到或让出点击响应,具体由 SetZorders / ResetZorders 两个虚函数控制。 私有段里有一组动态数组 m_width[],用来存每个子区域的像素宽度;背景与边框色分别是 m_area_color 和 m_area_border_color,文字色走 m_label_color,分隔线还分了暗、亮两色(m_sepline_dark_color / m_sepline_light_color)以模拟立体凹槽。 构造函数给的默认背景是 C'240,240,240'(浅灰),边框默认 clrSilver。想在自有 EA 面板里改状态栏观感,直接调 AreaColor()、LabelColor() 等 public 方法即可,不必碰私有成员。 下面这段是类声明与构造体的核心代码,逐行看能弄清字段归属:class 开头定义 CStatusBar 公开继承 CElement;private 里 m_width[] 是特别属性数组,m_zorder 管点击层级;public 里 AreaColor(const color clr) 等只是单行赋值封装。
class CStatusBar : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 属性: class=class="str">"cmt">// 特别属性的数组 class="type">int m_width[]; class=class="str">"cmt">//--- (class="num">1) 背景色 以及 (class="num">2)背景边框的颜色 class="type">class="kw">color m_area_color; class="type">class="kw">color m_area_border_color; class=class="str">"cmt">//--- 文字颜色 class="type">class="kw">color m_label_color; class=class="str">"cmt">//--- 鼠标左键点击的优先级 class="type">int m_zorder; class=class="str">"cmt">//--- 分隔线的颜色 class="type">class="kw">color m_sepline_dark_color; class="type">class="kw">color m_sepline_light_color; class="kw">public: class=class="str">"cmt">//--- 设置颜色 (class="num">1)背景, (class="num">2) 背景边框 以及 (class="num">3) 文字 class="type">void AreaColor(const class="type">class="kw">color clr) { m_area_color=clr; } class="type">void AreaBorderColor(const class="type">class="kw">color clr) { m_area_border_color=clr; } class="type">void LabelColor(const class="type">class="kw">color clr) { m_label_color=clr; } class=class="str">"cmt">//--- 分隔线的颜色 class="type">void SeparateLineDarkColor(const class="type">class="kw">color clr) { m_sepline_dark_color=clr; } class="type">void SeparateLineLightColor(const class="type">class="kw">color clr) { m_sepline_light_color=clr; } }; CStatusBar::CStatusBar(class="type">void) : m_area_color(C&class="macro">#x27;class="num">240,class="num">240,class="num">240&class="macro">#x27;), m_area_border_color(clrSilver),
◍ 状态栏类的成员与构造落点
在 MT5 标准库 GUI 体系里,状态栏被封装为 CStatusBar,它公有继承 CElement,因此直接复用基类的坐标、可见性与事件分发机制。私有成员里除了承载文本的 CEdit 数组 m_items[] 和背景区 CRectLabel m_area,还引入 CSeparateLine 数组 m_sep_line[] 负责绘制项与项之间的分隔线,这在多品种监控面板里很实用。 构造函数的初始化列表已经把默认外观写死:标签文字色 m_label_color(clrBlack)、暗分隔线 m_sepline_dark_color(C'160,160,160')、亮分隔线 m_sepline_light_color(clrWhite)。进入函数体后,CElement::ClassName(CLASS_NAME) 把类标识注册进基类,m_zorder=2 则把鼠标左键点击优先级抬到 2 级,避免被同层控件吞掉点击。 对外接口上,CreateStatusBar() 接收 chart_id、subwin、x、y 四个参数完成整体搭建;内部再拆成 CreateArea、CreateItems、CreateSeparateLine 三个私有方法分别生成背景、文本项与分隔线。ItemsTotal() 直接返回 m_items 数组尺寸,而 AddItem(const int width) 必须在 CreateStatusBar 之前调用,用宽度参数把待显示项先压进结构,否则运行期数组为空。 想在自有 EA 面板里验证这套结构,把 SeparateLine.mqh 一并 include 后,先循环 AddItem 设定每格像素宽,再 CreateStatusBar 挂到副图,分隔线和文字层就会按 zorder=2 的优先级响应点击。
m_label_color(clrBlack), m_sepline_dark_color(C&class="macro">#x27;class="num">160,class="num">160,class="num">160&class="macro">#x27;), m_sepline_light_color(clrWhite) { class=class="str">"cmt">//--- 在基类中保存元件类的名称 CElement::ClassName(CLASS_NAME); class=class="str">"cmt">//--- 设置鼠标左键点击的优先级 m_zorder=class="num">2; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| StatusBar.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Element.mqh" class="macro">#include "Window.mqh" class="macro">#include "SeparateLine.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建状态栏的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CStatusBar : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 用于创建按钮的对象 CRectLabel m_area; CEdit m_items[]; CSeparateLine m_sep_line[]; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 用于创建状态栏的方法 class="type">bool CreateStatusBar(const class="type">long chart_id,const class="type">int subwin,const class="type">int x,const class="type">int y); class=class="str">"cmt">//--- class="kw">private: class="type">bool CreateArea(class="type">void); class="type">bool CreateItems(class="type">void); class="type">bool CreateSeparateLine(const class="type">int line_number,const class="type">int x,const class="type">int y); class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 项目数量 class="type">int ItemsTotal(class="type">void) const { class="kw">return(::ArraySize(m_items)); } class=class="str">"cmt">//--- 在创建状态栏之前使用指定属性增加项目 class="type">void AddItem(const class="type">int width); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建状态栏项目 | class=class="str">"cmt">//+------------------------------------------------------------------+
状态栏项目的实际绘制逻辑
CStatusBar::CreateItems() 负责把内存里的项目数组真正变成 MT5 图表上的 GUI 对象。它先取 ItemsTotal(),若小于 1 直接返回 false 并在终端打印提示——这意味着调用前必须用 AddItem() 至少塞一个项目进去,否则界面不会出任何东西。 第一个项目若没手动设宽度(m_width[0]<1),代码会累加其余项目宽度,再用窗口总宽减去它们和 (items_total+2) 的留白来反推。比如窗口宽 400、其余 3 项各 80,则首项得 400-240-5=155,这种自动伸缩在多分辨率屏上值得验证。 主循环里每个项目名由程序名+“_statusbar_edit_”+序号+对象 ID 拼成,X 坐标靠前一项宽度累加,创建后一口气设了左对齐、字体、颜色、不可选、只读等属性。注意 YSize 统一用 m_y_size-2,比状态栏矮两像素,留出了分隔线的空间。 第二项起会调 CreateSeparateLine() 画竖线,坐标取该项 X、Y 往下偏移 2。最后 ValueToItem(index,value) 暴露给外部按索引写文字——接好这个,你的 EA 就能在状态栏实时吐出点差、保证金占用这类数字。外汇与贵金属杠杆高,GUI 只辅助观察,下单前仍要以真实报价为准。
class="type">bool CStatusBar::CreateItems(class="type">void) { class="type">int l_w=class="num">0; class="type">int l_x=m_x+class="num">1; class="type">int l_y=m_y+class="num">1; class=class="str">"cmt">//--- 取得项目数量 class="type">int items_total=ItemsTotal(); class=class="str">"cmt">//--- 如果组中没有项目,报告并退出 if(items_total<class="num">1) { ::Print(__FUNCTION__,"> 将调用此方法, " "组中至少包含一个项目!使用 CStatusBar::AddItem() 方法"); class="kw">return(false); } class=class="str">"cmt">//--- 如果第一个项目的宽度没有设置,则... if(m_width[class="num">0]<class="num">1) { class=class="str">"cmt">//--- ...计算它与其他项目相关的宽度 for(class="type">int i=class="num">1; i<items_total; i++) l_w+=m_width[i]; class=class="str">"cmt">//--- m_width[class="num">0]=m_wnd.XSize()-l_w-(items_total+class="num">2); } class=class="str">"cmt">//--- 创建指定数量的项目 for(class="type">int i=class="num">0; i<items_total; i++) { class=class="str">"cmt">//--- 构建对象名称 class="type">class="kw">string name=CElement::ProgramName()+"_statusbar_edit_"+class="type">class="kw">string(i)+"__"+(class="type">class="kw">string)CElement::Id(); class=class="str">"cmt">//--- X 坐标 l_x=(i>class="num">0)? l_x+m_width[i-class="num">1] : l_x; class=class="str">"cmt">//--- 创建对象 if(!m_items[i].Create(m_chart_id,name,m_subwin,l_x,l_y,m_width[i],m_y_size-class="num">2)) class="kw">return(false); class=class="str">"cmt">//--- 设置属性 m_items[i].Description(""); m_items[i].TextAlign(ALIGN_LEFT); m_items[i].Font(FONT); m_items[i].FontSize(FONT_SIZE); m_items[i].Color(m_label_color); m_items[i].BorderColor(m_area_color); m_items[i].BackColor(m_area_color); m_items[i].Corner(m_corner); m_items[i].Anchor(m_anchor); m_items[i].Selectable(false); m_items[i].Z_Order(m_zorder); m_items[i].ReadOnly(true); m_items[i].Tooltip("\n"); class=class="str">"cmt">//--- 面板边缘的距离 m_items[i].XGap(l_x-m_wnd.X()); m_items[i].YGap(l_y-m_wnd.Y()); class=class="str">"cmt">//--- 坐标 m_items[i].X(l_x); m_items[i].Y(l_y); class=class="str">"cmt">//--- 大小 m_items[i].XSize(m_width[i]); m_items[i].YSize(m_y_size-class="num">2); class=class="str">"cmt">//--- 保存对象指针 CElement::AddToArray(m_items[i]); } class=class="str">"cmt">//--- 创建分隔线 for(class="type">int i=class="num">1; i<items_total; i++) { class=class="str">"cmt">//--- X 坐标 l_x=m_items[i].X(); class=class="str">"cmt">//--- 创建分隔线 CreateSeparateLine(i,l_x,l_y+class="num">2); } class=class="str">"cmt">//--- class="kw">return(true); } class CStatusBar : class="kw">public CElement { class="kw">public: class=class="str">"cmt">//--- 根据指定的索引设置数值 class="type">void ValueToItem(const class="type">int index,const class="type">class="kw">string value); };