图形界面 IV: 信息界面元件 (第一章)·进阶篇
「给状态栏某一项塞入指定文本」
在 MT5 自定义状态栏类里,ValueToItem 负责把外部传进来的字符串写进特定下标的项。调用前必须确认下标合法,否则直接 return 不做任何事。 下面这段是核心实现,注意它先取 m_items 数组长度做边界判断:
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 根据指定索引设置数值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CStatusBar::ValueToItem(const class="type">int index,const class="type">class="kw">string value) { class=class="str">"cmt">//--- 检查是否超出数组范围 class="type">int array_size=::ArraySize(m_items); if(array_size<class="num">1 || index<class="num">0 || index>=array_size) class="kw">return; class=class="str">"cmt">//--- 设置传入的文字 m_items[index].Description(value); }
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 根据指定索引设置数值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CStatusBar::ValueToItem(const class="type">int index,const class="type">class="kw">string value) { class=class="str">"cmt">//--- 检查是否超出数组范围 class="type">int array_size=::ArraySize(m_items); if(array_size<class="num">1 || index<class="num">0 || index>=array_size) class="kw">return; class=class="str">"cmt">//--- 设置传入的文字 m_items[index].Description(value); }
拿现成EA改出双段状态栏
验证状态栏控件最快的办法,是复用系列前一篇里那个带菜单和五个 CIconButton 的 EA:把其余控件全删掉,只留菜单和按钮,再挂一个 CStatusBar 实例。StatusBar.mqh 已经在库里,直接在 CProgram 类里写创建方法和成员对象即可。 创建时给状态栏设两个项目,第一项宽度传 0,系统会自动按剩余空间计算;第二项写死 110 像素宽。创建完后给第一项填«For Help, press F1»做静态提示,第二项留着显示本地时间。 要让第二项每 500 毫秒刷一次,只需在 OnTimerEvent 里补一段更新代码。实测下来状态栏高度设 24、Y 偏移 175 时,贴在原面板底部不挡 K 线。外汇与贵金属图表加载这类自定义 EA 属高风险操作,参数错乱可能卡死终端,建议先开模拟盘 MT5 跑通再上实盘。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建应用程序的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CProgram : class="kw">public CWndEvents { class="kw">private: class=class="str">"cmt">//--- 状态栏 CStatusBar m_status_bar; class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 状态栏 class="macro">#define STATUSBAR1_GAP_X(class="num">1) class="macro">#define STATUSBAR1_GAP_Y(class="num">175) class="type">bool CreateStatusBar(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建交易面板 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateTradePanel(class="type">void) { class=class="str">"cmt">//--- 为控件创建表单1 class=class="str">"cmt">//--- 创建控件: class=class="str">"cmt">// 主菜单 class=class="str">"cmt">//--- 上下文菜单 class=class="str">"cmt">//--- 创建状态栏 if(!CreateStatusBar()) class="kw">return(false); class=class="str">"cmt">//--- 重绘图表 m_chart.Redraw(); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建状态栏 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateStatusBar(class="type">void) { class="macro">#define STATUS_LABELS_TOTAL class="num">2 class=class="str">"cmt">//--- 传入面板对象 m_status_bar.WindowPointer(m_window1); class=class="str">"cmt">//--- 坐标 class="type">int x=m_window1.X()+STATUSBAR1_GAP_X; class="type">int y=m_window1.Y()+STATUSBAR1_GAP_Y; class=class="str">"cmt">//--- 宽度th class="type">int width[]={class="num">0,class="num">110}; class=class="str">"cmt">//--- 在创建之前设置属性 m_status_bar.YSize(class="num">24); class=class="str">"cmt">//--- 指定部分的数量并设置它们的属性 for(class="type">int i=class="num">0; i<STATUS_LABELS_TOTAL; i++) m_status_bar.AddItem(width[i]); class=class="str">"cmt">//--- 创建控件 if(!m_status_bar.CreateStatusBar(m_chart_id,m_subwin,x,y)) class="kw">return(false); class=class="str">"cmt">//--- 设置状态栏第一个项目的文字 m_status_bar.ValueToItem(class="num">0,"For Help, press F1"); class=class="str">"cmt">//--- 把对象加到通用对象数组中 CWndContainer::AddToElementsArray(class="num">0,m_status_bar); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 计时器 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::OnTimerEvent(class="type">void) { CWndEvents::OnTimerEvent();
◍ 用定时器给状态栏做半秒级刷新
想在 MT5 状态栏第二个槽位显示本地时间,直接每帧写会被图表重绘拖死,更好的做法是靠 OnTimer 节流。下面这段逻辑用了一个静态计数器,每触发一次定时器累加 TIMER_STEP_MSC,不到 500 毫秒就直接 return,等于把刷新频率压到约 2 次/秒。
class=class="str">"cmt">//--- 每500毫秒更新一次状态栏的第二个项目 class="kw">static class="type">int count=class="num">0; if(count<class="num">500) { count+=TIMER_STEP_MSC; class="kw">return; } class=class="str">"cmt">//--- count=class="num">0; m_status_bar.ValueToItem(class="num">1,TimeToString(TimeLocal(),TIME_DATE|TIME_SECONDS)); m_chart.Redraw();
class=class="str">"cmt">//--- 每500毫秒更新一次状态栏的第二个项目 class="kw">static class="type">int count=class="num">0; if(count<class="num">500) { count+=TIMER_STEP_MSC; class="kw">return; } class=class="str">"cmt">//--- count=class="num">0; m_status_bar.ValueToItem(class="num">1,TimeToString(TimeLocal(),TIME_DATE|TIME_SECONDS)); m_chart.Redraw();
「给MT5控件挂上可高亮的悬浮提示」
做交易面板时,按钮或输入框的悬停说明不能限制字数,还得能高亮个别词。MQL5 原生 OBJ_LABEL 做不到,只能自己用 CRectCanvas 画一个独立类 CTooltip,思路和之前画分隔线的 CSeparateLine 一致,但坐标要相对宿主元件算:实例里工具提示底边比元件底边低 1 像素。 类里除了表单指针 m_wnd,还要存宿主元件指针 m_element,用 ElementPointer() 注入。用户可设标题和文字行数组(AddString 逐行加),背景渐变、边框色、标题色、文字色和 alpha 透明度走默认。CreateCanvas() 私有方法会把渐变数组大小设为画布高度像素数,初始化后清空,alpha 为 0 表示不透明,最后把对象塞进通用数组。 淡入靠 ShowTooltip():先查 m_alpha,≥255 就说明已 100% 可见直接退出;否则按是否给标题决定画不画 FW_BLACK 粗体标题,主体文字用 FW_THIN 纤细字,循环把用户数组每行写进画布并调 Y 坐标。淡出 FadeOutTooltip() 反过来,alpha 已 0 就退,否则按步长重绘到全透明。光标进元件区且开启提示模式才显,离开或表单屏蔽则藏。 外汇与贵金属界面开发属高风险环境,图形对象资源占用异常可能拖慢 EA 执行,建议在策略测试器外用真实图表验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Tooltip.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 CTooltip : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 指向元件附加表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- 工具提示将要附加的元件的指针 CElement *m_element; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- (class="num">1) 保存表单指针, (class="num">2) 保存元件指针 class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class="type">void ElementPointer(CElement &object) { m_element=::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 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);
给图表悬浮提示补一行文字的实现
CTooltip 类继承自 CElement,专门负责在 MT5 图表上绘制鼠标悬浮提示框。它的私有成员里,m_tooltip_lines[] 是动态字符串数组,用来逐行存提示内容;m_alpha 控制透明度(0 为完全不透明),m_text_color、m_header_color、m_border_color 分别管正文、标题和边框颜色。 构造函数里能看到一组硬编码默认值:文字色 clrDimGray,标题色 C'50,50,50',边框 C'118,118,118',背景渐变从 clrWhite 到 C'208,208,235'。这些数值直接决定了提示框在深色图表上的可视对比度,想改观感就动这几行。 往提示框塞内容靠 AddString(const string text)。调用一次就往 m_tooltip_lines[] 追加一行,运行时悬浮窗会按数组顺序自上而下渲染。开 MT5 建个 EA 把 CTooltip 实例化,循环调 AddString 就能验证多行堆叠效果。 外汇与贵金属波动剧烈,这类界面辅助不改变订单逻辑,但高频刷新提示可能占用主线程,实盘前请在策略测试器里跑一遍压力表现。
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=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CTooltip::CTooltip(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CTooltip::~CTooltip(class="type">void) { } class CTooltip : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 属性: class=class="str">"cmt">// 标题 class="type">class="kw">string m_header; class=class="str">"cmt">//--- 工具提示的文字行数组 class="type">class="kw">string m_tooltip_lines[]; class=class="str">"cmt">//--- alpha 通道的数值 (工具提示的透明度) class="type">uchar m_alpha; class=class="str">"cmt">//--- 颜色设置 (class="num">1) 文字, (class="num">2) 标题 以及 (class="num">3) 背景边框 class="type">class="kw">color m_text_color; class="type">class="kw">color m_header_color; class="type">class="kw">color m_border_color; class=class="str">"cmt">//--- 背景渐变色数组 class="type">class="kw">color m_array_color[]; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 工具提示标题 class="type">void Header(const class="type">class="kw">string text) { m_header=text; } class=class="str">"cmt">//--- 为工具提示增加文字行 class="type">void AddString(const class="type">class="kw">string text); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CTooltip::CTooltip(class="type">void) : m_header(""), m_alpha(class="num">0), m_text_color(clrDimGray), m_header_color(C&class="macro">#x27;class="num">50,class="num">50,class="num">50&class="macro">#x27;), m_border_color(C&class="macro">#x27;class="num">118,class="num">118,class="num">118&class="macro">#x27;), m_gradient_top_color(clrWhite), m_gradient_bottom_color(C&class="macro">#x27;class="num">208,class="num">208,class="num">235&class="macro">#x27;) { class=class="str">"cmt">//--- 在基类中保存元件类的名称 CElement::ClassName(CLASS_NAME); }
◍ 提示框定位与画布落点
CTooltip 的 CreateTooltip 在拿到窗口和元件指针后,才计算自己的坐标:m_x 直接取元件的 X(),m_y 取元件底边 Y2() 再加 1 像素,避免贴边重叠。这一行决定了悬浮提示永远挂在触发元件的正下方外缘。 若 m_wnd 或 m_element 指针无效,函数会打印具体缺哪个指针并直接返回 false,不会继续创建。调试时若提示框不出现,先确认 WindowPointer 和 ElementPointer 是否已经传过对象。 画布名由程序名加 '_help_tooltip_' 再加元件 Id 拼成,用 CreateBitmapLabel 在图表子窗口的 (m_x,m_y) 处建位图标签。外汇与贵金属图表上叠加这类自绘层存在重绘开销,高频刷新时可能拖慢 MT5 响应,属高风险自定义绘制。
class="type">void CTooltip::AddString(const class="type">class="kw">string text) { class=class="str">"cmt">//--- 把数组大小增加一个元件 class="type">int array_size=::ArraySize(m_tooltip_lines); ::ArrayResize(m_tooltip_lines,array_size+class="num">1); class=class="str">"cmt">//--- 保存传入的参数值 m_tooltip_lines[array_size]=text; } class CTooltip : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 用于创建工具提示的对象 CRectCanvas m_canvas; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 创建工具提示的方法 class="type">bool CreateTooltip(const class="type">long chart_id,const class="type">int subwin); class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 为工具提示创建画布 class="type">bool CreateCanvas(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建工具提示对象 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTooltip::CreateTooltip(const class="type">long chart_id,const class="type">int subwin) { class=class="str">"cmt">//--- 如果没有表单指针 if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > 在创建工具提示之前,该类必须 " "传入表单指针: CTooltip::WindowPointer(CWindow &object)."); class="kw">return(false); } class=class="str">"cmt">//--- 如果没有元件指针就退出 if(::CheckPointer(m_element)==POINTER_INVALID) { ::Print(__FUNCTION__," > 在创建工具提示之前,该类必须 " "传入元件指针: CTooltip::ElementPointer(CElement &object)."); class="kw">return(false); } class=class="str">"cmt">//--- 指针初始化 m_id =m_wnd.LastId()+class="num">1; m_chart_id =chart_id; m_subwin =subwin; class=class="str">"cmt">//--- 坐标取自元件 m_x =m_element.X(); m_y =m_element.Y2()+class="num">1; class=class="str">"cmt">//--- 到边缘的距离 CElement::XGap(m_x-m_wnd.X()); CElement::YGap(m_y-m_wnd.Y()); class=class="str">"cmt">//--- 创建工具提示 if(!CreateTooltip()) class="kw">return(false); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建用于画图的画布 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTooltip::CreateCanvas(class="type">void) { class=class="str">"cmt">//--- 构造对象名称 class="type">class="kw">string name=CElement::ProgramName()+"_help_tooltip_"+(class="type">class="kw">string)CElement::Id(); class=class="str">"cmt">//--- 创建画布 if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE)) class="kw">return(false); class=class="str">"cmt">//--- 附加到图表 if(!m_canvas.Attach(m_chart_id,name,m_subwin,class="num">1))