图形界面 VII: 页面控件 (第二章)·综合运用
📘

图形界面 VII: 页面控件 (第二章)·综合运用

第 3/3 篇

EA面板控件的类内布局与坐标硬编码

在 MT5 用标准库做 EA 面板时,CProgram 类把界面元素全塞进私有成员:文字标签表格、编辑框表格、绘制表格各一个,外加 4 个复选框、4 个带复选框的编辑框、4 个带复选框的组合框,以及一条分隔线。这种集中声明方便后续在 CreateExpertPanel 里统一调度,但耦合度也高,改一个控件容易牵动整屏重绘。 CreateExpertPanel 只做一件事:调 CreateTabs 生成分页,失败直接 return(false),成功则 m_chart.Redraw() 强制重绘图表并返回 true。注意这里没有处理控件层级冲突,若标签页和表格坐标重叠,视觉上会叠在一起。 坐标全靠宏写死。三种表格的 X 偏移都是 5 像素、Y 偏移 65 像素;分隔线偏到 X=300、Y=70;复选框 1 在 (18,75),复选框 2 已经跳到 (18,175)——也就是说两个复选框垂直间距 100 像素。开 MT5 把这套宏抄进自己的 EA,改 GAP_Y 数值就能压扁或拉宽控件排布,立刻看得到面板变形。 别把坐标宏当可移植配置。换图表分辨率或 DPI 缩放后,写死的像素值可能导致控件溢出窗口边缘,真要适配多屏得改成基于图表宽高的比例计算。

MQL5 / C++
class="type">bool CProgram::CreateExpertPanel(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(!CreateTabs())
      class="kw">return(false);
class=class="str">"cmt">//--- 重绘图表
   m_chart.Redraw();
   class="kw">return(true);
  }
class CProgram : class="kw">public CWndEvents
  {
class="kw">private:
   class=class="str">"cmt">//--- 文字标签型表格
   CLabelsTable      m_labels_table;
   class=class="str">"cmt">//--- 编辑框型表格
   CTable            m_table;
   class=class="str">"cmt">//--- 绘制型表格
   CCanvasTable      m_canvas_table;
   class=class="str">"cmt">//--- 复选框
   CCheckBox         m_checkbox1;
   CCheckBox         m_checkbox2;
   CCheckBox         m_checkbox3;
   CCheckBox         m_checkbox4;
   class=class="str">"cmt">//--- 带有复选框的编辑框
   CCheckBoxEdit     m_checkboxedit1;
   CCheckBoxEdit     m_checkboxedit2;
   CCheckBoxEdit     m_checkboxedit3;
   CCheckBoxEdit     m_checkboxedit4;
   class=class="str">"cmt">//--- 带有复选框的组合框
   CCheckComboBox    m_checkcombobox1;
   CCheckComboBox    m_checkcombobox2;
   CCheckComboBox    m_checkcombobox3;
   CCheckComboBox    m_checkcombobox4;
   class=class="str">"cmt">//--- 分隔线
   CSeparateLine     m_sep_line;
   class=class="str">"cmt">//---
class="kw">private:
   class=class="str">"cmt">//--- 文字标签型表格
class="macro">#define TABLE1_GAP_X(class="num">5)
class="macro">#define TABLE1_GAP_Y(class="num">65)
   class="type">bool                CreateLabelsTable(class="type">void);
   class=class="str">"cmt">//--- 编辑框型表格
class="macro">#define TABLE2_GAP_X(class="num">5)
class="macro">#define TABLE2_GAP_Y(class="num">65)
   class="type">bool                CreateTable(class="type">void);
   class=class="str">"cmt">//--- 绘制型表格
class="macro">#define TABLE3_GAP_X(class="num">5)
class="macro">#define TABLE3_GAP_Y(class="num">65)
   class="type">bool                CreateCanvasTable(class="type">void);
   class=class="str">"cmt">//--- 分隔线
class="macro">#define SEP_LINE_GAP_X(class="num">300)
class="macro">#define SEP_LINE_GAP_Y(class="num">70)
   class="type">bool                CreateSepLine(class="type">void);
   class=class="str">"cmt">//--- 复选框
class="macro">#define CHECKBOX1_GAP_X(class="num">18)
class="macro">#define CHECKBOX1_GAP_Y(class="num">75)
   class="type">bool                CreateCheckBox1(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKBOX2_GAP_X(class="num">18)
class="macro">#define CHECKBOX2_GAP_Y(class="num">175)
   class="type">bool                CreateCheckBox2(class="kw">const class="type">class="kw">string text);

「面板控件的XY硬偏移与分页挂接」

在 MT5 自定义面板里,复选框和带复选框的编辑/组合框都靠宏定义锁死相对窗口的偏移量。比如 CHECKBOX3_GAP_X 是 315、CHECKBOX3_GAP_Y 是 75,而 CHECKBOXEDIT 系列左侧两列 X 取 40、右侧两列 X 取 337,Y 从 105 起每 30 像素排一行,组合框则从 Y=205 往下再排两行。 这些偏移不是随意写的:左列控件 X=40 与右列 X=337 之间留了约 297 像素,足够在中间塞一条分隔线。分隔线本身在 CreateSepLine() 里被挂到 m_tabs 的第 4 个页面(索引 3),尺寸写死为宽 2、高 210,颜色用 C'213,223,229' 做暗边、clrWhite 做亮边,形成浅浮雕效果。 直接把下面这段抄进你的 EA 面板类,改 GAP 宏就能重排布局;外汇和贵金属图表上挂这类面板属于高风险操作,参数乱改可能导致控件重叠点不到。

MQL5 / C++
class="macro">#define CHECKBOX3_GAP_X(class="num">315)
class="macro">#define CHECKBOX3_GAP_Y(class="num">75)
  class="type">bool                CreateCheckBox3(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKBOX4_GAP_X(class="num">315)
class="macro">#define CHECKBOX4_GAP_Y(class="num">175)
  class="type">bool                CreateCheckBox4(class="kw">const class="type">class="kw">string text);
  class=class="str">"cmt">//--- 带有复选框的编辑框
class="macro">#define CHECKBOXEDIT1_GAP_X(class="num">40)
class="macro">#define CHECKBOXEDIT1_GAP_Y(class="num">105)
  class="type">bool                CreateCheckBoxEdit1(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKBOXEDIT2_GAP_X(class="num">40)
class="macro">#define CHECKBOXEDIT2_GAP_Y(class="num">135)
  class="type">bool                CreateCheckBoxEdit2(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKBOXEDIT3_GAP_X(class="num">337)
class="macro">#define CHECKBOXEDIT3_GAP_Y(class="num">105)
  class="type">bool                CreateCheckBoxEdit3(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKBOXEDIT4_GAP_X(class="num">337)
class="macro">#define CHECKBOXEDIT4_GAP_Y(class="num">135)
  class="type">bool                CreateCheckBoxEdit4(class="kw">const class="type">class="kw">string text);
  class=class="str">"cmt">//--- 带有复选框的组合框
class="macro">#define CHECKCOMBOBOX1_GAP_X(class="num">40)
class="macro">#define CHECKCOMBOBOX1_GAP_Y(class="num">205)
  class="type">bool                CreateCheckComboBox1(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKCOMBOBOX2_GAP_X(class="num">40)
class="macro">#define CHECKCOMBOBOX2_GAP_Y(class="num">235)
  class="type">bool                CreateCheckComboBox2(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKCOMBOBOX3_GAP_X(class="num">337)
class="macro">#define CHECKCOMBOBOX3_GAP_Y(class="num">205)
  class="type">bool                CreateCheckComboBox3(class="kw">const class="type">class="kw">string text);
class="macro">#define CHECKCOMBOBOX4_GAP_X(class="num">337)
class="macro">#define CHECKCOMBOBOX4_GAP_Y(class="num">235)
  class="type">bool                CreateCheckComboBox4(class="kw">const class="type">class="kw">string text);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建分隔线                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateSepLine(class="type">void)
  {
class=class="str">"cmt">//--- 保存窗口指针
  m_sep_line.WindowPointer(m_window1);
class=class="str">"cmt">//--- 附加到第四个页面
  m_tabs.AddToElementsArray(class="num">3,m_sep_line);
class=class="str">"cmt">//--- 坐标
  class="type">int x=m_window1.X()+SEP_LINE_GAP_X;
  class="type">int y=m_window1.Y()+SEP_LINE_GAP_Y;
class=class="str">"cmt">//--- 大小
  class="type">int x_size=class="num">2;
  class="type">int y_size=class="num">210;
class=class="str">"cmt">//--- 在创建之前设置属性
  m_sep_line.DarkColor(C&class="macro">#x27;class="num">213,class="num">223,class="num">229&class="macro">#x27;);
  m_sep_line.LightColor(clrWhite);

◍ EA面板装配的控件清单与收尾动作

在 CProgram::CreateExpertPanel 里,面板的搭建就是一连串 Create* 调用的串行校验:任一个返回 false 就整体退出,保证半成品界面不会挂到图表上。从文字标签表格、编辑框表格、绘制型表格,到分隔线、4 个独立复选框、4 个带复选框的编辑框、4 个带复选框的组合框,都是先建对象再压入元素库。 值得注意的是,所有控件建完之后,代码用 m_tabs.ShowTabElements() 只暴露当前活动标签页对应的控件,避免不同页的元件互相叠图。最后 m_chart.Redraw() 强制重绘一次,界面才真正刷新出来。 这套逻辑在 MT5 里可以直接验证:把 CreateExpertPanel 拆开单步跑,若某个 CreateCheckBoxEdit* 漏写 return false 保护,面板可能在控件缺失时仍显示空框。外汇与贵金属 EA 界面开发属高风险定制,参数错误可能导致图表卡顿或逻辑失效。

MQL5 / C++
  m_sep_line.TypeSepLine(V_SEP_LINE);
class=class="str">"cmt">//--- 创建元件
  if(!m_sep_line.CreateSeparateLine(m_chart_id,m_subwin,class="num">0,x,y,x_size,y_size))
     class="kw">return(false);
class=class="str">"cmt">//--- 把元件指针加到库中
  CWndContainer::AddToElementsArray(class="num">0,m_sep_line);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建EA的面板                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateExpertPanel(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">//...
class=class="str">"cmt">//--- 文字标签型表格
  if(!CreateLabelsTable())
     class="kw">return(false);
class=class="str">"cmt">//--- 编辑框型表格
  if(!CreateTable())
     class="kw">return(false);
class=class="str">"cmt">//--- 创建绘制型表格
  if(!CreateCanvasTable())
     class="kw">return(false);
class=class="str">"cmt">//--- 分隔线
  if(!CreateSepLine())
     class="kw">return(false);
class=class="str">"cmt">//--- 复选框
  if(!CreateCheckBox1("Checkbox class="num">1"))
     class="kw">return(false);
  if(!CreateCheckBox2("Checkbox class="num">2"))
     class="kw">return(false);
  if(!CreateCheckBox3("Checkbox class="num">3"))
     class="kw">return(false);
  if(!CreateCheckBox4("Checkbox class="num">4"))
     class="kw">return(false);
class=class="str">"cmt">//--- 带有复选框的编辑框
  if(!CreateCheckBoxEdit1("Checkbox Edit class="num">1:"))
     class="kw">return(false);
  if(!CreateCheckBoxEdit2("Checkbox Edit class="num">2:"))
     class="kw">return(false);
  if(!CreateCheckBoxEdit3("Checkbox Edit class="num">3:"))
     class="kw">return(false);
  if(!CreateCheckBoxEdit4("Checkbox Edit class="num">4:"))
     class="kw">return(false);
class=class="str">"cmt">//--- 带有复选框的组合框
  if(!CreateCheckComboBox1("CheckCombobox class="num">1:"))
     class="kw">return(false);
  if(!CreateCheckComboBox2("CheckCombobox class="num">2:"))
     class="kw">return(false);
  if(!CreateCheckComboBox3("CheckCombobox class="num">3:"))
     class="kw">return(false);
  if(!CreateCheckComboBox4("CheckCombobox class="num">4:"))
     class="kw">return(false);
class=class="str">"cmt">//--- 只显示活动页面的控件
  m_tabs.ShowTabElements();
class=class="str">"cmt">//--- 重绘图表
  m_chart.Redraw();
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建图标页面的类                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIconTabs : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 标签边缘
  class="type">int            m_icon_x_gap;

图标与文字标签的边距怎么设

在 MT5 自定义指标或面板的 C++ 风格类里,图标和文字标签的留白不是写死的,而是用四个整型成员存起来,再由公开方法在运行时赋值。 m_icon_x_gap 与 m_icon_y_gap 控制图标相对锚点的横纵偏移,m_label_x_gap 与 m_label_y_gap 则管文字标签的边缘间距。这四个变量默认不初始化,若不在构造后调方法设值,绘制时偏移量可能是随机脏值。 公开接口 IconXGap / IconYGap / LabelXGap / LabelYGap 都只接一个 const int 参数,直接写进对应成员变量。比如 IconXGap(8) 就把图标横距钉成 8 像素。 实盘面板若图标和文字挤在一起,优先查这四个 gap 是否漏设;在 1920×1080 屏上,图标横距设 6~10、标签纵距设 4 通常可读性较好,但高 DPI 屏要按比例放大。外汇与贵金属图表插件改动界面参数属低风险操作,但误写负距可能造成元素出界不可见。

MQL5 / C++
  class="type">int                m_icon_x_gap;
  class=class="str">"cmt">//--- 图标边缘
  class="type">int                m_icon_y_gap;
  class=class="str">"cmt">//--- 文字标签边缘
  class="type">int                m_label_x_gap;
  class="type">int                m_label_y_gap;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 标签边缘
  class="type">void               IconXGap(class="kw">const class="type">int x_gap)                  { m_icon_x_gap=x_gap;                 }
  class="type">void               IconYGap(class="kw">const class="type">int y_gap)                  { m_icon_y_gap=y_gap;                 }
  class=class="str">"cmt">//--- 文字标签边缘
  class="type">void               LabelXGap(class="kw">const class="type">int x_gap)                { m_label_x_gap=x_gap;                }
  class="type">void               LabelYGap(class="kw">const class="type">int y_gap)                { m_label_y_gap=y_gap;                }
};

「把控件塞进标签页时踩过的坑」

这套 GUI 库走到第七阶段,表格和页面控件已经成型:表格由 CLabelsTable、CTable、CCanvasTable 三个类负责,页面切换则交给 CTabs 和 CIconTabs 两个类。下一阶段预告会补齐静态/下拉日历、树形视图和文件浏览器,但真正动手前,先看看别人在挂载控件时是怎么卡住的。 评论区里 barcla 在 2021 年 6 月贴过一组编译报错:Calendar.mqh 第 443 行起连续找不到 LeftTransp_black.bmp、calendar_today.bmp 等 19 个资源位图,根因是压缩包里 Images\EasyAndFastGUI\Controls 目录未随代码发布。遇到这种缺资源的情况,直接比对 easyandfastgui_mql5.zip(319.19 KB)内的目录结构,缺哪几个 bmp 就自己补哪几个,否则 CTable 和 CTabs 都编不过。 更隐蔽的是对象挂载作用域问题。Jefferson 和 Mohammad-Reza 都碰到过:用 CLabel 或自写标签类加到标签页时,编译器报 WindowPointer 未定义或参数转换不允许(MainPanel.mqh 233/235 行)。matinz 的解法是单独建 Include\EasyAndFastGUI\Controls\MyLabel.mqh,从派生类里重写 WindowPointer 绑定,才能把对象真正挂到面板而不是飘在图表上。 下面这段是一个把数值微调框挂到标签页 1 的最小实现,注意 m_tabs.AddToElementsArray(1, m_StartTS) 这一行决定它落在第几个页: 别把对象直接丢图表上 CSpinEdit 若只调了 X()/Y() 而不走 WindowPointer(m_window),删除图表后控件对象仍残留。先绑主窗指针,再 AddToElementsArray 进对应页,是避坑的硬顺序。

MQL5 / C++
class="kw">input class="type">class="kw">double whenToTrail=class="num">10.3;
class CProgram: class="kw">public CWndEvents
{
class="kw">protected:
   CSpinEdit          m_StartTS;
class=class="str">"cmt">//Code
class="kw">protected:
   class="type">bool               CreatetrailStop(class="kw">const class="type">int x_gap, class="kw">const class="type">int y_gap, class="kw">const class="type">class="kw">string text);
};
class="type">bool CProgram::CreatetrailStop(class="kw">const class="type">int x_gap, class="kw">const class="type">int y_gap, class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 保存指向主元素的指针
   m_StartTS.WindowPointer(m_window);
class=class="str">"cmt">//--- 坐标
   class="type">int x = m_window.X() + x_gap;
   class="type">int y = m_window.Y() + y_gap;
class=class="str">"cmt">//--- 为标签页预留
   m_tabs.AddToElementsArray(class="num">1, m_StartTS);
class=class="str">"cmt">//--- 属性
   m_StartTS.XSize(class="num">95);
   m_StartTS.YSize(class="num">15);
   m_StartTS.EditXSize(class="num">40);
   m_StartTS.MinValue(class="num">0.01);
   m_StartTS.StepValue(class="num">0.01);
   m_StartTS.SetDigits(class="num">1);
   m_StartTS.SetValue(NormalizeDouble(whenToTrail,class="num">1));

◍ 自定义标签控件的封装与坐标落位

在 MT5 面板开发里,把标准库 CLabel 再包一层是常见做法。上面这段先重置了 SpinEdit 的模式位,随后用 CreateSpinEdit 在指定子窗口与坐标生成控件,成功就丢进元素数组统一管理。 m_StartTS.ResetMode(true); //--- 创建一个控件 if(!m_StartTS.CreateSpinEdit(m_chart_id, m_subwin, text, x, y)) return(false); //--- 将对象添加到对象组的通用数组中 CWndContainer::AddToElementsArray(0, m_StartTS); return(true); 拆解:ResetMode(true) 清空旧状态避免复用冲突;CreateSpinEdit 的 x、y 是相对图表窗口的像素坐标,子窗口 ID 错填会直接 return false;AddToElementsArray(0,...) 的 0 表示归到 0 号元素组,后续遍历刷新只跑这一组。 标签标题的落位更有讲究。代码里 XSize 设 140、YSize 设 18,再按 OVERVIE_TITLE_LABEL_GAP_X/Y 的偏移量算绝对坐标,意味着标签实际占位是一条 140×18 的窄条,间距常量改 5 像素就能肉眼看出排版松紧变化。 MyLabel 类把 CLabel 指针、文本、四态颜色(正常/关闭/悬停/锁定)和 zorder 全收进私有成员,公开方法只留构造和创建入口。这种写法让一个标签页里的十几个标签共用一套颜色数组,改配色不用逐个控件点。外汇和贵金属 GUI 面板属于高频重绘场景,控件层级 zorder 设错可能造成点击穿透,实盘前请在策略测试器里把面板挂不同品种验证。

MQL5 / C++
m_StartTS.ResetMode(true);
class=class="str">"cmt">//--- 创建一个控件
if(!m_StartTS.CreateSpinEdit(m_chart_id, m_subwin, text, x, y))
   class="kw">return(false);
class=class="str">"cmt">//--- 将对象添加到对象组的通用数组中
CWndContainer::AddToElementsArray(class="num">0, m_StartTS);
class="kw">return(true);

复选框控件的接口与事件骨架

在 MT5 自定义面板上做复选框,核心是把标签创建、状态读写和图表事件分开封装。上面这段类声明里,CreateLabel 负责往指定图表子窗口的 (x,y) 坐标塞一个文本标签,而私有的 CreateLabel2 通常用来补建附属对象,两者分工能避免主线程里反复判空。 对外暴露的 Setter 都很轻量:LabelXGap / LabelYGap 只改 m_label_x_gap 与 m_label_y_gap 两个整型页边距,LabelColor 直接写 m_label_color。LabelText 用 const 方法返回 m_label.Description(),意味着取说明文字不会触发重绘,回读状态成本极低。 事件侧靠四个 virtual 撑住交互:OnEvent 接图表消息,OnEventTimer 跑定时刷新,Moving 处理拖拽重定位,Show/Hide/Reset/Delete 管生命周期。SetZorders 与 ResetZorders 控制鼠标左键命中优先级——多个控件叠在同一个子窗口时,这个优先级决定了哪层先吃到点击。 外汇与贵金属图表挂这类控件属于高风险环境下的 UI 实验,建议先在模拟账户把 OnEvent 里的 CHARTEVENT_OBJECT_CLICK 分支打日志,确认坐标映射无误再接交易逻辑。

MQL5 / C++
  class="type">bool                CreateLabel(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">class="kw">string text,class="kw">const class="type">int x,class="kw">const class="type">int y);
  class=class="str">"cmt">//---
class="kw">private:
  class="type">bool                CreateLabel2(class="type">void);
  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=class="str">"cmt">//--- (class="num">1) 背景颜色,(class="num">2) 文本标签的页边距
  class="type">void                LabelXGap(class="kw">const class="type">int x_gap)                      { m_label_x_gap=x_gap;                            }
  class="type">void                LabelYGap(class="kw">const class="type">int y_gap)                      { m_label_y_gap=y_gap;                            }
  class=class="str">"cmt">//--- 不同状态下文字的颜色
  class="type">void                LabelColor(class="kw">const class="type">class="kw">color clr)                     { m_label_color=clr;                              }
  class=class="str">"cmt">//---(class="num">1)复选框说明,(class="num">2)返回/设置复选框按钮的状态
  class="type">class="kw">string              LabelText(class="type">void)                                 class="kw">const { class="kw">return(m_label.Description());           }
  class=class="str">"cmt">//--- 更改颜色
  class="type">void                ChangeObjectsColor(class="type">void);
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//-- 图表事件处理程序
  class="kw">virtual class="type">void        OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">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(class="kw">const class="type">int x,class="kw">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);
  class=class="str">"cmt">//---(class="num">1)设置、(class="num">2)重置鼠标左键按下的优先级
  class="kw">virtual class="type">void        SetZorders(class="type">void);
  class="kw">virtual class="type">void        ResetZorders(class="type">void);
  class=class="str">"cmt">//-- 重置颜色

「Label 控件的构造与鼠标事件接管」

在 MT5 自定义 GUI 里,MyLabel 类把标签的默认间距和颜色锁死在构造函数:水平间隙 20 像素、垂直间隙 2 像素、文字色 clrBlack。这两个 gap 值直接决定标签在表单里的占位,想紧凑排布就得改这两个常量后重编译。 基类构造函数里还做了两件关键事:用 CElement::ClassName(CLASS_NAME) 把类标识注入基类,便于事件路由;把 m_zorder 设 0、m_area_zorder 设 1,让标签本体点击优先级低于其热区,避免误触。 OnEvent 只认两种事件:CHARTEVENT_MOUSE_MOVE 时先判隐藏和子窗口号,再用四点坐标比对设 MouseFocus;CHARTEVENT_OBJECT_CLICK 时转给私有的 OnClickLabel。注意若元素隐藏或鼠标不在同子窗口,函数直接 return,不耗后续逻辑。 定时器回调 OnEventTimer 里有个隐蔽开关:仅当表单未被 m_wnd.IsLocked() 锁定时才跑 ChangeObjectsColor()。这意味着你若用脚本锁住窗口,标签配色就会被冻结,适合做只读面板。

MQL5 / C++
class="kw">virtual class="type">void        ResetColors(class="type">void);
class="kw">virtual class="type">void        SetText(class="type">class="kw">string text);
class="kw">virtual class="type">void        SetFontSize(class="type">int fontSize);
class=class="str">"cmt">//---
class="kw">private:
class=class="str">"cmt">//--- 处理对元素的按压
   class="type">bool             OnClickLabel(class="kw">const class="type">class="kw">string clicked_object);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造函数                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
MyLabel::MyLabel(class="type">void) :
   m_label_x_gap(class="num">20),
   m_label_y_gap(class="num">2),
   m_label_color(clrBlack)
   {
class=class="str">"cmt">//--- 在基类中存储元素类的名称
   CElement::ClassName(CLASS_NAME);
class=class="str">"cmt">//--- 设置点击鼠标左键的优先级
   m_zorder     =class="num">0;
   m_area_zorder =class="num">1;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 销毁器                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
MyLabel::~MyLabel(class="type">void)
   {
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 事件处理                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">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(CElement::m_subwin!=CElement::m_mouse.SubWindowNumber())
        class="kw">return;
     class=class="str">"cmt">//--- 检查元素上的焦点
     CElement::MouseFocus(m_mouse.X()>X() && m_mouse.X()<X2() && m_mouse.Y()>Y() && m_mouse.Y()<Y2());
     class="kw">return;
     }
class=class="str">"cmt">//--- 处理对象上的鼠标左键点击
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
     class=class="str">"cmt">//--- 按下复选框
     if(OnClickLabel(sparam))
        class="kw">return;
     }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 定时器                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::OnEventTimer(class="type">void)
   {
class=class="str">"cmt">//--- 如果表单未被阻止
   if(!m_wnd.IsLocked())
     class=class="str">"cmt">//--- 更改元素对象的颜色
     ChangeObjectsColor();
   }

◍ 给自定义标签挂上坐标与悬停变色

在 MT5 自定义面板里画一个可跟随窗口移动的文本标签,核心是两个函数:CreateLabel 负责接图表 ID、子窗口号和像素坐标,CreateLabel2 才真正往图表上塞 OBJ_LABEL 对象。注意 CreateLabel 里 m_id 取的是 m_wnd.LastId()+1,意味着同一窗口内每新建一个元素 ID 自增 1,避免对象名撞车。 CreateLabel2 生成对象名时拼了 ProgramName()+"_lable_"+Id(),这里原文拼写是 lable 而非 label,复制代码时别手改,否则运行时找不到指针。坐标计算用 CElement::X()+m_label_x_gap,gap 在 CreateLabel 里已通过 XGap(CElement::X()-m_wnd.X()) 算过一次窗口左缘偏移。 Moving 函数只在 IsVisible() 为真时重算坐标,隐藏状态直接 return,所以最小化窗口后再恢复,标签位置不会漂移。ChangeObjectsColor 调用了 CElement::ChangeObjectColor,把鼠标焦点状态、普通色 clrBlack、悬停色 m_label_color_hover 和梯度阵列传进去,实现光标划过时的颜色过渡——外汇与贵金属图表挂这类 UI 高风险,对象多了会拖慢 Tick 响应,建议实盘前在策略测试器里数一下同屏标签数。 下面这段是原文里标签类的关键实现,逐行拆完可直接塞进你的 EA 面板工程编译验证。

MQL5 / C++
class="type">bool MyLabel::CreateLabel(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">class="kw">string text,class="kw">const class="type">int x,class="kw">const class="type">int y)
  {
class=class="str">"cmt">//--- 如果没有表格指针,则离开
   if(!CElement::CheckWindowPointer(::CheckPointer(m_wnd)))
      class="kw">return(false);
class=class="str">"cmt">//--- 初始化变量
   m_id        =m_wnd.LastId()+class="num">1;
   m_chart_id  =chart_id;
   m_subwin    =subwin;
   m_x         =x;
   m_y         =y;
   m_label_text =text;
class=class="str">"cmt">//--- 边缘的边距
   CElement::XGap(CElement::X()-m_wnd.X());
   CElement::YGap(CElement::Y()-m_wnd.Y());
class=class="str">"cmt">//--- 创建一个元素
   if(!CreateLabel2())
      class="kw">return(false);
class=class="str">"cmt">//--- 如果窗口是对话框或已最小化,则隐藏元素
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
class=class="str">"cmt">//---
   class="kw">return(true);
  }
class="type">bool MyLabel::CreateLabel2(class="type">void)
  {
class=class="str">"cmt">//--- 生成对象名称
   class="type">class="kw">string name=CElement::ProgramName()+"_lable_"+(class="type">class="kw">string)CElement::Id();
class=class="str">"cmt">//--- 坐标
   class="type">int x =CElement::X()+m_label_x_gap;
   class="type">int y =CElement::Y()+m_label_y_gap;
class=class="str">"cmt">//--- 根据状态显示文字颜色
   class="type">class="kw">color label_color=clrBlack;class=class="str">"cmt">//(m_check_button_state) ? m_label_color : m_label_color_off;
class=class="str">"cmt">//--- 设置对象
   if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
      class="kw">return(false);
class=class="str">"cmt">//--- 设置属性
   m_label.Description(m_label_text);
   m_label.Font(FONT);
   m_label.FontSize(FONT_SIZE);
   m_label.Color(label_color);
   m_label.Corner(m_corner);
   m_label.Anchor(m_anchor);
   m_label.Selectable(false);
   m_label.Z_Order(m_zorder);
   m_label.Tooltip("\n");
class=class="str">"cmt">//--- 边缘的边距
   m_label.XGap(x-m_wnd.X());
   m_label.YGap(y-m_wnd.Y());
class=class="str">"cmt">//--- 初始化梯度阵列
   CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
class=class="str">"cmt">//--- 存储对象指针
   CElement::AddToArray(m_label);
   class="kw">return(true);
  }
class="type">void MyLabel::Moving(class="kw">const class="type">int x,class="kw">const class="type">int y)
  {
class=class="str">"cmt">//--- 离开,如果元素是隐藏的
   if(!CElement::IsVisible())
      class="kw">return;
class=class="str">"cmt">//-- 在元素字段中存储缩进
   CElement::X(x+XGap());
   CElement::Y(y+YGap());
class=class="str">"cmt">//-- 在对象的字段中存储坐标
   m_label.X(x+m_label.XGap());
   m_label.Y(y+m_label.YGap());
class=class="str">"cmt">//-- 更新图形对象的坐标
   m_label.X_Distance(m_label.X());
   m_label.Y_Distance(m_label.Y());
  }
class="type">void MyLabel::ChangeObjectsColor(class="type">void)
  {
class=class="str">"cmt">//--- 如果元素被阻止,则离开
class=class="str">"cmt">//---
   class="type">class="kw">color label_color=clrBlack;class=class="str">"cmt">//(m_check_button_state) ? m_label_color : m_label_color_off;
   CElement::ChangeObjectColor(m_label.Name(),CElement::MouseFocus(),OBJPROP_COLOR,label_color,m_label_color_hover,m_label_color_array);
  }

标签控件的显隐与层级操作

在 MT5 自定义面板里,标签类控件的可见性不能靠单对象开关,必须遍历元素附属的所有图形对象统一处理。下面这段 MyLabel 的方法集展示了标准做法:Show() 先判断 IsVisible(),已显示就直接 return,避免重复刷新;否则对 ObjectsElementTotal() 返回的每个对象调用 Timeframes(OBJ_ALL_PERIODS),让它们在所有周期可见。 Hide() 逻辑对称,用 OBJ_NO_PERIODS 剥离所有周期绑定实现隐藏,并把 IsVisible 置 false。Reset() 则对非下拉元素做 Hide()+Show() 的硬重绘,下拉元素直接跳过。 层级方面,SetZorders() 把 m_label 的 Z_Order 设为成员变量 m_zorder,ResetZorders() 归零。Delete() 除了 m_label.Delete() 还会清空对象数组并把 MouseFocus 复位——注意它把 IsVisible 重置为 true,意味着对象内存释放但逻辑状态默认可见,调参时要留意这个反直觉设定。 OnClickLabel() 在悬停时改色并抛 EventChartCustom 事件,自定义 ID 用 CElement::Id(),附带 Description 文本,EA 或脚本可据此联动。外汇与贵金属图表挂这类控件属高风险环境,事件驱动逻辑务必在模拟盘验证。

MQL5 / C++
class="type">void MyLabel::Show(class="type">void)
  {
class=class="str">"cmt">//--- 离开,如果元素已经可见
   if(CElement::IsVisible())
       class="kw">return;
class=class="str">"cmt">//--- 使所有对象可见
   for(class="type">int i=class="num">0; i<CElement::ObjectsElementTotal(); i++)
       CElement::Object(i).Timeframes(OBJ_ALL_PERIODS);
class=class="str">"cmt">//--- 可见性状态
   CElement::IsVisible(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 隐藏组合框|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::Hide(class="type">void)
  {
class=class="str">"cmt">//--- 离开,如果元素已经可见
   if(!CElement::IsVisible())
       class="kw">return;
class=class="str">"cmt">//--- 隐藏所有对象
   for(class="type">int i=class="num">0; i<CElement::ObjectsElementTotal(); i++)
       CElement::Object(i).Timeframes(OBJ_NO_PERIODS);
class=class="str">"cmt">//--- 可见性状态
   CElement::IsVisible(false);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 重绘|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::Reset(class="type">void)
  {
class=class="str">"cmt">//--- 离开,如果这是一个下拉元素
   if(CElement::IsDropdown())
       class="kw">return;
class=class="str">"cmt">//--- 隐藏和显示
   Hide();
   Show();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 删除|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::Delete(class="type">void)
  {
class=class="str">"cmt">//--- 删除对象
   m_label.Delete();
class=class="str">"cmt">//-- 清空对象数组
   CElement::FreeObjectsArray();
class=class="str">"cmt">//-- 按默认值初始化变量
   CElement::MouseFocus(false);
   CElement::IsVisible(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//eth the priorities|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::SetZorders(class="type">void)
  {
   m_label.Z_Order(m_zorder);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 重置优先级|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::ResetZorders(class="type">void)
  {
   m_label.Z_Order(class="num">0);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 重置元素对象的颜色|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::ResetColors(class="type">void)
  {
class=class="str">"cmt">//-- 重置颜色
   m_label.Color(clrBlack);
class=class="str">"cmt">//-- 将焦点归零
   CElement::MouseFocus(false);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 点击元素标题|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool MyLabel::OnClickLabel(class="kw">const class="type">class="kw">string clicked_object)
  {
class=class="str">"cmt">//--- 鼠标光标当前位于元素上方
   m_label.Color(m_label_color_hover);
class=class="str">"cmt">//--- 发送相关信息
   ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),class="num">0,m_label.Description());
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MyLabel::SetText(class="type">class="kw">string text){
   m_label.Description(text);
}
class="type">void MyLabel::SetFontSize(class="type">int fontSize){
   m_label.FontSize(fontSize);
}
   class=class="str">"cmt">//-- 标签
   MyLabel            m_overview_title_label;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建概述标题标签|
class=class="str">"cmt">//+------------------------------------------------------------------+

「收束」

上面这段 CMainPanel::CreateOverviewTitleLabel 把概览标题标签的挂载逻辑收清楚了:先通过 WindowPointer 把面板窗口指针传给标签对象,再用 AddToElementsArray(0,...) 把它塞进第一组选项卡的元素数组,坐标按窗口 X/Y 加 OVERVIE_TITLE_LABEL_GAP_X/Y 偏移。 标签本体固定 140×18 像素、字号 18,CreateLabel 失败直接返回 false,成功才进容器总数组。开 MT5 把这段塞进你的面板类,改一下 GAP 常量就能看到标题落位变化。 外汇与贵金属图表挂自定义面板本就是高风险操作环境,参数没对齐可能界面错位,动手前先在模拟盘验证。

MQL5 / C++
class="type">bool CMainPanel::CreateOverviewTitleLabel(class="kw">const class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 传递面板对象
   m_overview_title_label.WindowPointer(m_window);
class=class="str">"cmt">//--- 附加到第一组选项卡的第四个选项卡上
   m_main_panel_tabs.AddToElementsArray(class="num">0,m_overview_title_label);
class=class="str">"cmt">//--- 坐标
   class="type">int x=m_window.X()+OVERVIE_TITLE_LABEL_GAP_X;
   class="type">int y=m_window.Y()+OVERVIE_TITLE_LABEL_GAP_Y;
class=class="str">"cmt">//--- 创建前设置属性
   m_overview_title_label.XSize(class="num">140);
   m_overview_title_label.YSize(class="num">18);
   m_overview_title_label.SetText(text);
   m_overview_title_label.SetFontSize(class="num">18);
   class=class="str">"cmt">//m_overview_title_label.
class=class="str">"cmt">//--- 创建控制
   if(!m_overview_title_label.CreateLabel(m_chart_id,m_subwin,text,x,y))
       class="kw">return(false);
class=class="str">"cmt">//--- 将对象添加到对象组的通用数组中
   CWndContainer::AddToElementsArray(class="num">0,m_overview_title_label);
   class="kw">return(true);
  }

常见问题

在类里用硬编码偏移量定义每个控件的左上角坐标,再按页面宽度做边界判断;装配前先列控件清单,避免重叠或超出画布。
常见坑是分页挂接没重置子控件原点,导致坐标累加偏移;封装自定义标签控件时显式落位坐标可解决。
可以,把面板截图或控件坐标表发给小布,它能标出越界和重叠区域,并给出边距调整建议。
图标与文字间留4–6像素、标签外缘距控件边框留8像素较稳妥,具体按分辨率微调。
需反注册所有分页事件并置空控件指针,最后调用销毁方法;漏掉任一都可能下次加载时报空指针。