"cmt">/""> "cmt">/"">
图形界面 IV: 信息界面元件 (第一章)·进阶篇
📘

图形界面 IV: 信息界面元件 (第一章)·进阶篇

第 2/3 篇

「给状态栏某一项塞入指定文本」

在 MT5 自定义状态栏类里,ValueToItem 负责把外部传进来的字符串写进特定下标的项。调用前必须确认下标合法,否则直接 return 不做任何事。 下面这段是核心实现,注意它先取 m_items 数组长度做边界判断:

MQL5 / C++
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);
  }
逐行拆解:第 4 行用 ::ArraySize 拿到 m_items 实际长度;第 5 行三重判断——数组为空、下标为负、下标越界任一成立就退出;第 8 行调用 Description 方法把 value 写进对应项。 你在 EA 里若动态刷新状态栏,记得下标从 0 起算,且数组至少要有 1 个元素,否则这函数静默失效,界面不报错但也不更新。外汇与贵金属交易高风险,这类 UI 辅助不影响下单逻辑,但调试时容易因静默 return 漏看状态。

MQL5 / C++
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 跑通再上实盘。

MQL5 / C++
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 次/秒。

MQL5 / C++
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();
逐行看:static int count=0 只初始化一次,保住在函数调用间的累加值;if(count<500) 判断还没攒够 500 毫秒就退出,TIMER_STEP_MSC 通常是定时器周期(比如 50 毫秒,那大约要 10 次才放行);放行后 count 归零,m_status_bar.ValueToItem(1,...) 把本地日期+秒写进索引 1 的槽位(索引从 0 起算,所以这是第二个),最后 m_chart.Redraw() 强制重绘。 你开 MT5 新建个 EA,把 OnTimer 周期设成 50,粘这段代码,状态栏第二项就会每半秒跳一次本地时间。外汇和贵金属行情受杠杆与跳空影响大,这类状态栏只做辅助显示,别拿它当进出场信号。

MQL5 / C++
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 执行,建议在策略测试器外用真实图表验证。

MQL5 / C++
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 就能验证多行堆叠效果。 外汇与贵金属波动剧烈,这类界面辅助不改变订单逻辑,但高频刷新提示可能占用主线程,实盘前请在策略测试器里跑一遍压力表现。

MQL5 / C++
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 响应,属高风险自定义绘制。

MQL5 / C++
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))

常见问题

直接定位到该状态栏项的文本属性赋值即可,不用重建整个状态栏,改完调用重绘函数就能看到更新。
在原EA状态栏基础上叠加第二段容器,把两类信息分属不同段,各自刷新互不影响,复制原结构改坐标最省事。
小布可接管这类重复监控,把状态栏文本与悬浮提示的异常变动做成自动播报,你只管看结论。
给控件绑鼠标事件,进入时改背景或边框色、离开复原,用定时器或事件回调都能做半秒级响应。
补行前先算画布基准坐标,按字体高度偏移落点,定位用相对画布左上角的值,可避免提示框错位。