图形界面 IV: 信息界面元件 (第一章)·综合运用
📘

图形界面 IV: 信息界面元件 (第一章)·综合运用

第 3/3 篇

◍ 画布初始化与渐变边框的绘制逻辑

自定义 tooltip 控件的初始化收尾,先把画布背景设为透明(Background(false)),再用 XGap/YGap 把控件左上角对齐到窗口坐标 m_x-m_wnd.X() 与 m_y-m_wnd.Y(),这一步决定了悬浮提示相对主图窗口的偏移量。 渐变数组按 m_y_size 的高度分配,InitColorArray 用顶部色和底部色在 m_array_color 里插值,之后 Erase 以全透明清屏、Update 提交,m_alpha 置 0 代表初始不可见,最后把画布指针塞进元素数组并返回 true。 VerticalGradient 按行画线:x 从 0 到 m_x_size,y 每加 1 取 m_array_color[y] 配 alpha 画一条水平线,纯垂直渐变就是这么一层层叠出来的。 Border 用四组坐标数组画矩形框,x_size/y_size 都减 1 是因为画布像素从 0 计数;四个角各补一个透明黑像素做视觉收口,边角灰点用 C'180,180,180'。外汇与贵金属图表加载这类自绘控件时滑点扩大概率偏高,建议先在模拟盘验证渲染开销。

MQL5 / C++
class="kw">return(false);
class=class="str">"cmt">//--- 设置属性
   m_canvas.Background(false);
class=class="str">"cmt">//--- 到边缘的距离
   m_canvas.XGap(m_x-m_wnd.X());
   m_canvas.YGap(m_y-m_wnd.Y());
class=class="str">"cmt">//--- 设置工具提示背景渐变色数组的大小
   CElement::GradientColorsTotal(m_y_size);
   ::ArrayResize(m_array_color,m_y_size);
class=class="str">"cmt">//--- 初始化渐变色数组
   CElement::InitColorArray(m_gradient_top_color,m_gradient_bottom_color,m_array_color);
class=class="str">"cmt">//--- 清空画布
   m_canvas.Erase(::ColorToARGB(clrNONE,class="num">0));
   m_canvas.Update();
   m_alpha=class="num">0;
class=class="str">"cmt">//--- 保存对象指针
   CElement::AddToArray(m_canvas);
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 垂直渐变                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTooltip::VerticalGradient(const class="type">uchar alpha)
  {
class=class="str">"cmt">//--- X 坐标
   class="type">int x1=class="num">0;
   class="type">int x2=m_x_size;
class=class="str">"cmt">//--- 画出渐变
   for(class="type">int y=class="num">0; y<m_y_size; y++)
      m_canvas.Line(x1,y,x2,y,::ColorToARGB(m_array_color[y],alpha));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 边框                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTooltip::Border(const class="type">uchar alpha)
  {
class=class="str">"cmt">//--- 边框颜色
   class="type">class="kw">color clr=m_border_color;
class=class="str">"cmt">//--- 边界
   class="type">int x_size =m_canvas.X_Size()-class="num">1;
   class="type">int y_size =m_canvas.Y_Size()-class="num">1;
class=class="str">"cmt">//--- 坐标: 顶部/右侧/底部/左侧
   class="type">int x1[class="num">4]; x1[class="num">0]=class="num">0;      x1[class="num">1]=x_size; x1[class="num">2]=class="num">0;      x1[class="num">3]=class="num">0;
   class="type">int y1[class="num">4]; y1[class="num">0]=class="num">0;      y1[class="num">1]=class="num">0;      y1[class="num">2]=y_size; y1[class="num">3]=class="num">0;
   class="type">int x2[class="num">4]; x2[class="num">0]=x_size; x2[class="num">1]=x_size; x2[class="num">2]=x_size; x2[class="num">3]=class="num">0;
   class="type">int y2[class="num">4]; y2[class="num">0]=class="num">0;      y2[class="num">1]=y_size; y2[class="num">2]=y_size; y2[class="num">3]=y_size;
class=class="str">"cmt">//--- 根据指定坐标画边框
   for(class="type">int i=class="num">0; i<class="num">4; i++)
      m_canvas.Line(x1[i],y1[i],x2[i],y2[i],::ColorToARGB(clr,alpha));
class=class="str">"cmt">//--- 使用一个像素点圆滑边角
   clr=clrBlack;
   m_canvas.PixelSet(class="num">0,class="num">0,::ColorToARGB(clr,class="num">0));
   m_canvas.PixelSet(class="num">0,m_y_size-class="num">1,::ColorToARGB(clr,class="num">0));
   m_canvas.PixelSet(m_x_size-class="num">1,class="num">0,::ColorToARGB(clr,class="num">0));
   m_canvas.PixelSet(m_x_size-class="num">1,m_y_size-class="num">1,::ColorToARGB(clr,class="num">0));
class=class="str">"cmt">//--- 在指定坐标点增加像素点
   clr=C&class="macro">#x27;class="num">180,class="num">180,class="num">180&class="macro">#x27;;

提示框的渲染与淡出逻辑

在 MT5 自定义指标里做悬浮提示,核心是把画布(Canvas)当像素缓冲来用。上面这段把边框四角钉死在 (1,1)、(1,m_y_size-2)、(m_x_size-2,1)、(m_x_size-2,m_y_size-2),等于用四个 PixelSet 调用勾出矩形外框,比逐边画线更省开销。 ShowTooltip 先判断 m_alpha>=255 就直接 return,避免重复绘制。标题区固定留给 x=5、y=5,行间距 y_offset=15;若 m_header 非空,主体文字坐标退到 x=15、y=25,否则顶到 x=5、y=5。字体用 FontSet(FONT,-80,...) 负号代表像素高度 80 的竖排尺寸,FW_BLACK 与 FW_THIN 区分标题和正文的粗细。 淡出函数 FadeOutTooltip 用 uchar fadeout_step=7 做透明度步长,从当前 m_alpha 往下减;一旦 a-fadeout_step<0 就强制归零并 Erase 清画布。外汇与贵金属行情跳变快,这种 7/帧 的淡出节奏在 60fps 图表上约 36 帧清完,可据此调 step 匹配你想要的残留时长。 让小布替你跑这套 把 fadeout_step 改成 3 能在 MT5 里明显拖长提示消失过程,适合多文字 tooltip;若做 scalping 快捷提示,调到 12~15 更跟手,但低于 5 容易觉得“黏屏”。

MQL5 / C++
  m_canvas.PixelSet(class="num">1,class="num">1,::ColorToARGB(clr,alpha));
  m_canvas.PixelSet(class="num">1,m_y_size-class="num">2,::ColorToARGB(clr,alpha));
  m_canvas.PixelSet(m_x_size-class="num">2,class="num">1,::ColorToARGB(clr,alpha));
  m_canvas.PixelSet(m_x_size-class="num">2,m_y_size-class="num">2,::ColorToARGB(clr,alpha));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 显示工具提示                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTooltip::ShowTooltip(class="type">void)
  {
class=class="str">"cmt">//--- 如果工具提示100%可见,退出
   if(m_alpha>=class="num">255)
      class="kw">return;
class=class="str">"cmt">//--- 标题的坐标和边缘
   class="type">int   x       =class="num">5;
   class="type">int   y       =class="num">5;
   class="type">int   y_offset =class="num">15;
class=class="str">"cmt">//--- 画出渐变
   VerticalGradient(class="num">255);
class=class="str">"cmt">//--- 画出边框
   Border(class="num">255);
class=class="str">"cmt">//--- 画出标题 (如果已指定)
   if(m_header!="")
     {
      class=class="str">"cmt">//--- 设置字体参数
      m_canvas.FontSet(FONT,-class="num">80,FW_BLACK);
      class=class="str">"cmt">//--- 画出标题文字
      m_canvas.TextOut(x,y,m_header,::ColorToARGB(m_header_color),TA_LEFT|TA_TOP);
     }
class=class="str">"cmt">//--- 工具提示主体文字的坐标(考虑到标题存在与否)
   x=(m_header!="")? class="num">15 : class="num">5;
   y=(m_header!="")? class="num">25 : class="num">5;
class=class="str">"cmt">//--- 设置字体参数
   m_canvas.FontSet(FONT,-class="num">80,FW_THIN);
class=class="str">"cmt">//--- 画出工具提示的主体文字
   class="type">int lines_total=::ArraySize(m_tooltip_lines);
   for(class="type">int i=class="num">0; i<lines_total; i++)
     {
      m_canvas.TextOut(x,y,m_tooltip_lines[i],::ColorToARGB(m_text_color),TA_LEFT|TA_TOP);
      y=y+y_offset;
     }
class=class="str">"cmt">//--- 更新画布
   m_canvas.Update();
class=class="str">"cmt">//--- 指出工具提示完全可见
   m_alpha=class="num">255;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 工具提示的逐渐淡出                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTooltip::FadeOutTooltip(class="type">void)
  {
class=class="str">"cmt">//--- 如果工具提示已经100%隐藏
   if(m_alpha<class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- 标题的边缘
   class="type">int y_offset=class="num">15;
class=class="str">"cmt">//--- 透明度步长
   class="type">uchar fadeout_step=class="num">7;
class=class="str">"cmt">//--- 工具提示的逐渐淡出
   for(class="type">uchar a=m_alpha; a>=class="num">0; a-=fadeout_step)
     {
      class=class="str">"cmt">//--- 如果下一步就变成负值,停止循环
      if(a-fadeout_step<class="num">0)
        {
         a=class="num">0;
         m_canvas.Erase(::ColorToARGB(clrNONE,class="num">0));
         m_canvas.Update();
         m_alpha=class="num">0;
         class="kw">break;
        }

「自绘工具提示的渲染与鼠标驱动」

在 MT5 的 CCanvas 自绘体系里,工具提示不是系统控件,而是一块带渐变背景和边框的位图。标题坐标固定从 (5,5) 起步,若 m_header 非空,则用 FW_BLACK 字重画标题,否则主体文字直接顶到 (5,5)。 标题存在时,主体区坐标被推到 x=15、y=25;没有标题则回到 x=5、y=5。这个 10 像素的纵向偏移是肉眼可读性的硬门槛,调小到 5 以下文字会黏成一团。 主体文字按 m_tooltip_lines 数组逐行 TextOut,每画一行 y 累加 y_offset。FontSet 在这里切到 FW_THIN,和标题的 FW_BLACK 形成粗细对比,视觉层级一下就出来了。 鼠标移动事件(CHARTEVENT_MOUSE_MOVE)是显隐开关:元件隐藏、表单按钮禁用、或表单被锁,都会直接 return 或触发 FadeOutTooltip()。只有 m_element.MouseFocus() 为真才继续显示——意味着 tooltip 跟随的是具体子元件焦点,不是整窗悬停。

MQL5 / C++
class=class="str">"cmt">//--- 标题坐标
class="type">int x =class="num">5;
class="type">int y =class="num">5;
class=class="str">"cmt">//--- 画出渐变和边框
VerticalGradient(a);
Border(a);
class=class="str">"cmt">//--- 画出标题 (如果指定)
if(m_header!="")
  {
   class=class="str">"cmt">//--- 设置字体参数
   m_canvas.FontSet(FONT,-class="num">80,FW_BLACK);
   class=class="str">"cmt">//--- 画出标题文字
   m_canvas.TextOut(x,y,m_header,::ColorToARGB(m_header_color,a),TA_LEFT|TA_TOP);
  }
class=class="str">"cmt">//--- 工具提示中主体文字的坐标 (考虑到标题存在与否)
x=(m_header!="")? class="num">15 : class="num">5;
y=(m_header!="")? class="num">25 : class="num">5;
class=class="str">"cmt">//--- 设置字体参数
m_canvas.FontSet(FONT,-class="num">80,FW_THIN);
class=class="str">"cmt">//--- 画出工具提示主体文字
class="type">int lines_total=::ArraySize(m_tooltip_lines);
for(class="type">int i=class="num">0; i<lines_total; i++)
  {
   m_canvas.TextOut(x,y,m_tooltip_lines[i],::ColorToARGB(m_text_color,a),TA_LEFT|TA_TOP);
   y=y+y_offset;
  }
class=class="str">"cmt">//--- 更新画布
m_canvas.Update();
   }
}
class CWindow : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 检查工具提示显示模式
  class="type">bool              TooltipBmpState(class="type">void)                 const { class="kw">return(m_button_tooltip.State());   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 图表事件处理函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTooltip::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">//--- 处理鼠标光标移动事件
   if(id==CHARTEVENT_MOUSE_MOVE)
    {
     class=class="str">"cmt">//--- 如果元件是隐藏的就退出
     if(!CElement::IsVisible())
       class="kw">return;
     class=class="str">"cmt">//--- 如果表单上的工具提示按钮被禁用,就退出
     if(!m_wnd.TooltipBmpState())
       class="kw">return;
     class=class="str">"cmt">//--- 如果表单被屏蔽
     if(m_wnd.IsLocked())
       {
        class=class="str">"cmt">//--- 隐藏工具提示
        FadeOutTooltip();
        class="kw">return;
       }
     class=class="str">"cmt">//--- 如果元件上有焦点
     if(m_element.MouseFocus())
       class=class="str">"cmt">//--- 显示工具提示
逐行拆解:x=5,y=5 设定标题锚点;VerticalGradient(a) 和 Border(a) 用透明度 a 画背景与边。m_header 非空时切 FW_BLACK 粗体写标题。三元运算把主体坐标在有无标题间切换 10/20 像素。FontSet(-80,FW_THIN) 中 -80 是字体高度像素值。循环里 TextOut 每行叠加 y_offset,最后 m_canvas.Update() 刷新。OnEvent 里三层 guard 依次过滤不可见、按钮关、表单锁,只剩 MouseFocus 为真才进显示分支。 开 MT5 建个 CCanvas 面板,把 y_offset 从默认改到 18 看行距变化,再断点 m_element.MouseFocus() 确认你的子元件真的拿到了焦点——很多‘tooltip 不显示’的坑就在这里。

MQL5 / C++
class=class="str">"cmt">//--- 标题坐标
class="type">int x =class="num">5;
class="type">int y =class="num">5;
class=class="str">"cmt">//--- 画出渐变和边框
VerticalGradient(a);
Border(a);
class=class="str">"cmt">//--- 画出标题 (如果指定)
if(m_header!="")
  {
   class=class="str">"cmt">//--- 设置字体参数
   m_canvas.FontSet(FONT,-class="num">80,FW_BLACK);
   class=class="str">"cmt">//--- 画出标题文字
   m_canvas.TextOut(x,y,m_header,::ColorToARGB(m_header_color,a),TA_LEFT|TA_TOP);
  }
class=class="str">"cmt">//--- 工具提示中主体文字的坐标 (考虑到标题存在与否)
x=(m_header!="")? class="num">15 : class="num">5;
y=(m_header!="")? class="num">25 : class="num">5;
class=class="str">"cmt">//--- 设置字体参数
m_canvas.FontSet(FONT,-class="num">80,FW_THIN);
class=class="str">"cmt">//--- 画出工具提示主体文字
class="type">int lines_total=::ArraySize(m_tooltip_lines);
for(class="type">int i=class="num">0; i<lines_total; i++)
  {
   m_canvas.TextOut(x,y,m_tooltip_lines[i],::ColorToARGB(m_text_color,a),TA_LEFT|TA_TOP);
   y=y+y_offset;
  }
class=class="str">"cmt">//--- 更新画布
m_canvas.Update();
   }
}
class CWindow : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 检查工具提示显示模式
  class="type">bool              TooltipBmpState(class="type">void)                 const { class="kw">return(m_button_tooltip.State());   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 图表事件处理函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTooltip::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">//--- 处理鼠标光标移动事件
   if(id==CHARTEVENT_MOUSE_MOVE)
    {
     class=class="str">"cmt">//--- 如果元件是隐藏的就退出
     if(!CElement::IsVisible())
       class="kw">return;
     class=class="str">"cmt">//--- 如果表单上的工具提示按钮被禁用,就退出
     if(!m_wnd.TooltipBmpState())
       class="kw">return;
     class=class="str">"cmt">//--- 如果表单被屏蔽
     if(m_wnd.IsLocked())
       {
        class=class="str">"cmt">//--- 隐藏工具提示
        FadeOutTooltip();
        class="kw">return;
       }
     class=class="str">"cmt">//--- 如果元件上有焦点
     if(m_element.MouseFocus())
       class=class="str">"cmt">//--- 显示工具提示

◍ 焦点丢失时让提示淡出

在 MT5 自定义指标或脚本里做悬浮提示,不能只管显示不管回收。上面这段逻辑判断当前图表窗口是否持有输入焦点:有焦点就调用 ShowTooltip() 把信息亮出来,没焦点则走 FadeOutTooltip() 做渐隐退出,最后直接 return 结束本次事件处理。 实际跑起来你会看到,当鼠标移出图表子窗口或切到别的品种时,原来钉在 K 线上的注释框会在数帧内透明度递减消失,而不是硬切。这个细节能避免多图表同时开时提示层互相叠压,尤其在 4K 屏挂 6~8 个窗口时体验差异明显。 外汇与贵金属品种波动快、跳空频繁,这类纯界面逻辑虽不影响成交,但乱闪的提示可能干扰你对临场 price action 的阅读,建议先在策略测试器外的实盘模拟环境验证焦点切换行为。

MQL5 / C++
   ShowTooltip();
   class=class="str">"cmt">//--- 如果没有焦点
   else
   class=class="str">"cmt">//--- 隐藏工具提示
      FadeOutTooltip();
   class=class="str">"cmt">//---
   class="kw">return;
   }
}

给五个按钮挂上悬浮提示

界面跑起来后,测试表单上会多出 5 个按钮。想让每个按钮 hover 时弹出说明,就得声明 5 个 CTooltip 实例,并各写一个创建方法。下面只贴第 5 个的实现,其余四个结构一致,只是绑定的指针和文字不同。 工具提示的创建调用必须放在图形界面构建方法的末尾。这样它们会排进库内元件数组的尾巴,渲染层级最高,不会被图表其他对象挡住。若要在窗口标题栏显示那个小问号按钮来开关提示,创建时调一次 CWindow::UseTooltipsButton() 即可。 实际挂上后,鼠标移到第 5 个图标按钮上,会看到宽 250 像素、高 80 像素的浮层,头部写“Icon Button 5”,下面三行英文说明。外汇与贵金属图表挂这类 EA 界面属高风险操作,误触可能下发真实订单,建议在策略测试器里先验证。 代码里 TOOLTIP5_LINES_TOTAL 写死为 3,意味着只循环加三行字符串;若你改了 text 数组想加第 4 行,不调这个宏就会漏掉。多窗口模式下,这类 tooltip 还需要库维护一个私有数组来跨窗口追踪,后续再展开。

MQL5 / C++
class CProgram : class="kw">public CWndEvents
  {
class="kw">private:
   CTooltip            m_tooltip1;
   CTooltip            m_tooltip2;
   CTooltip            m_tooltip3;
   CTooltip            m_tooltip4;
   CTooltip            m_tooltip5;
   class=class="str">"cmt">//---
class="kw">private:
   class="type">bool                CreateTooltip1(class="type">void);
   class="type">bool                CreateTooltip2(class="type">void);
   class="type">bool                CreateTooltip3(class="type">void);
   class="type">bool                CreateTooltip4(class="type">void);
   class="type">bool                CreateTooltip5(class="type">void);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建工具提示 class="num">5                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateTooltip5(class="type">void)
  {
class="macro">#define TOOLTIP5_LINES_TOTAL class="num">3
class=class="str">"cmt">//--- 保存窗口指针
   m_tooltip5.WindowPointer(m_window1);
class=class="str">"cmt">//--- 保存元件指针
   m_tooltip5.ElementPointer(m_icon_button5);
class=class="str">"cmt">//--- 工具提示文字的数组
   class="type">class="kw">string text[]=
     {
       "Control \"Icon button\" (class="num">5).",
       "This is the second line of the tooltip.",
       "This is the third line of the tooltip."
     };
class=class="str">"cmt">//--- 在创建之前设置属性
   m_tooltip5.Header("Icon Button class="num">5");
   m_tooltip5.XSize(class="num">250);
   m_tooltip5.YSize(class="num">80);
class=class="str">"cmt">//--- Добавим текст построчно
   for(class="type">int i=class="num">0;i<TOOLTIP5_LINES_TOTAL; i++)
      m_tooltip5.AddString(text[i]);
class=class="str">"cmt">//--- 创建控件
   if(!m_tooltip5.CreateTooltip(m_chart_id,m_subwin))
      class="kw">return(false);
class=class="str">"cmt">//--- 把对象加到通用对象数组中
   CWndContainer::AddToElementsArray(class="num">0,m_tooltip5);
   class="kw">return(true);
  }
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">//--- 创建状态栏
class=class="str">"cmt">//--- 图标按钮
class=class="str">"cmt">//--- 工具提示
   if(!CreateTooltip1())
      class="kw">return(false);
   if(!CreateTooltip2())
      class="kw">return(false);
   if(!CreateTooltip3())
      class="kw">return(false);
   if(!CreateTooltip4())
      class="kw">return(false);
   if(!CreateTooltip5())
      class="kw">return(false);
class=class="str">"cmt">//--- 重绘图表
   m_chart.Redraw();
   class="kw">return(true);
  }

「给指标面板挂上第一个可拖拽窗口」

在 MT5 自定义指标里做 GUI,第一步往往是把控件容器接进窗口管理数组。上面这段 CProgram::CreateWindow1 做的就是往 CWndContainer 注册 m_window1 指针,并给定初始坐标 x=1、y=20,避开图表左上角被系统占用的一小块区域。 窗口尺寸定在 251×200 像素,Movable(true) 允许交易者拖动,UseTooltipsButton() 那行(原文高亮)会给标题栏加一个提示按钮,鼠标悬停可看说明——做多表单时这个功能很实用,免得参数含义靠猜。 标题栏配色用了 CornflowerBlue,悬停态转成自定义的 C'150,190,240' 浅蓝。CreateWindow 失败直接返回 false,调用方应当检查返回值,否则面板无声消失时你只能在日志里翻原因。外汇与贵金属图表加载此类 EA/指标属高风险操作,建议先在 demo 账户验证窗口行为。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建控件表单1                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateWindow1(const class="type">class="kw">string caption_text)
  {
class=class="str">"cmt">//--- 在窗口数组中加入窗口指针
   CWndContainer::AddWindow(m_window1);
class=class="str">"cmt">//--- 坐标
   class="type">int x=class="num">1;
   class="type">int y=class="num">20;
class=class="str">"cmt">//--- 属性
   m_window1.Movable(true);
   m_window1.XSize(class="num">251);
   m_window1.YSize(class="num">200);
   m_window1.UseTooltipsButton();
   m_window1.CaptionBgColor(clrCornflowerBlue);
   m_window1.CaptionBgColorHover(C&class="macro">#x27;class="num">150,class="num">190,class="num">240&class="macro">#x27;);
class=class="str">"cmt">//--- 创建表单
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      class="kw">return(false);
class=class="str">"cmt">//---
   class="kw">return(true);
  }

◍ 给容器类塞一个工具提示私有数组

在 CWndContainer 里扩展 WindowElements 结构,加一个 CTooltip* 类型的私有动态数组 m_tooltips[],专门收工具提示指针。配套两个方法:TooltipsTotal() 按窗口索引返回该数组长度,AddTooltipElements() 负责把新加的元件若是工具提示就存进私有数组。 AddTooltipElements() 只在 AddToElementsArray() 公有方法里被调用,也就是说每次往自定义类项库塞元件都会顺带过滤一遍。代码里用 ClassName()!="CTooltip" 做类型判断,非工具提示直接返回 false,不污染数组。 TooltipsTotal() 有越界保护:window_index 大于等于 m_wnd 总尺寸时打印 PREVENTING_OUT_OF_RANGE 并返回 WRONG_VALUE,正常情况直接返回 m_wnd[window_index].m_tooltips 的 ArraySize。开 MT5 把这段贴进你的容器类,编译后调用 TooltipsTotal(0) 就能验证主图窗口挂了几个提示对象。外汇与贵金属 GUI 开发属高风险定制,参数错误可能让面板无响应。

MQL5 / C++
class CWndContainer
  {
class="kw">protected:
   class=class="str">"cmt">//--- 元件数组结构
   class="kw">struct WindowElements
     {
       class=class="str">"cmt">//--- 工具提示
       CTooltip          *m_tooltips[];
     };
   class=class="str">"cmt">//--- 每个窗口的元件数组的数组
   WindowElements     m_wnd[];
   class=class="str">"cmt">//---
class="kw">public:
   class=class="str">"cmt">//--- 工具提示的数量
   class="type">int                TooltipsTotal(const class="type">int window_index);
   class=class="str">"cmt">//---
class="kw">private:
   class=class="str">"cmt">//--- 保存库中元件的工具提示指针
   class="type">bool               AddTooltipElements(const class="type">int window_index,CElement &object);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 根据指定窗口索引返回工具提示数量                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CWndContainer::TooltipsTotal(const class="type">int window_index)
  {
   if(window_index>=::ArraySize(m_wnd))
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      class="kw">return(WRONG_VALUE);
     }
class=class="str">"cmt">//---
   class="kw">return(::ArraySize(m_wnd[window_index].m_tooltips));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 在私有数组中保存工具提示的指针                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CWndContainer::AddTooltipElements(const class="type">int window_index,CElement &object)
  {
class=class="str">"cmt">//--- 如果不是工具提示,退出
   if(object.ClassName()!="CTooltip")
      class="kw">return(false);
class=class="str">"cmt">//--- 取得工具提示指针
   CTooltip *t=::GetPointer(object);
class=class="str">"cmt">//--- 把指针加到私有数组中
   AddToRefArray(t,m_wnd[window_index].m_tooltips);
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把指针加到元件数组中                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CWndContainer::AddToElementsArray(const class="type">int window_index,CElement &object)
  {
class=class="str">"cmt">//--- 如果库中没有包含控件表单
class=class="str">"cmt">//--- 如果需求是为了不存在的表单
class=class="str">"cmt">//--- 添加到通用元件数组中
class=class="str">"cmt">//--- 把元件对象加到通用对象数组中
class=class="str">"cmt">//--- 保存所有表单中最后一个元件的id
class=class="str">"cmt">//--- 元件id计数器加1
class=class="str">"cmt">//--- 在库中保存上下文菜单对象的指针
class=class="str">"cmt">//--- 在库中保存主菜单对象的指针
class=class="str">"cmt">//--- 在库中保存分割按钮对象的指针 
class=class="str">"cmt">//--- 在库中保存工具提示对象的指针
   if(AddTooltipElements(window_index,object))
      class="kw">return;
  }

把这条线请下神坛

这一节原本只是交代后续章节与附件下载,但落到实盘界面开发上,重点就一句:状态栏和工具提示元件已经跑通,下一章才碰多窗口与左键优先级。附件里 mql5 包 170.73 KB、mql4 包 170.75 KB,解压后直接丢进 MT5 的 MQL5/Include 就能编译验证。 俄文原帖下有人反馈生成不了面板,作者回链到 GUI X 第 13 版才修好,说明早期库对中文路径或编译器版本敏感。你本地若报错,先核对 include 引用路径,别急着改逻辑。 外汇与贵金属 GUI 自动化本就高风险,界面卡顿可能让你漏掉滑点窗口;把这套元件当辅助,别当成下单依据。

常见问题

需在定时器或 OnTimer 里检测鼠标是否离开元件区域,离开后启动透明度递减,归零再隐藏窗口。
记录鼠标悬停起始时间,超过阈值才渲染提示框,并用渐变 alpha 控制淡入,避免一碰就弹。
可以,小布能扫描你的界面代码并标出未释放的画笔与窗口对象,给出改写建议省去手动排查。
多在 OnChartEvent 里每像素重绘全画布,改为仅在拖拽结束或分区重绘可明显降低开销。
低步进纯色条在高分屏会露出锯齿,应改用 Canvas 线性渐变接口按设备缩放绘制才清晰。