图形界面 VII: 页面控件 (第二章)·综合运用
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 缩放后,写死的像素值可能导致控件溢出窗口边缘,真要适配多屏得改成基于图表宽高的比例计算。
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 宏就能重排布局;外汇和贵金属图表上挂这类面板属于高风险操作,参数乱改可能导致控件重叠点不到。
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 界面开发属高风险定制,参数错误可能导致图表卡顿或逻辑失效。
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 屏要按比例放大。外汇与贵金属图表插件改动界面参数属低风险操作,但误写负距可能造成元素出界不可见。
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 进对应页,是避坑的硬顺序。
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 设错可能造成点击穿透,实盘前请在策略测试器里把面板挂不同品种验证。
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 分支打日志,确认坐标映射无误再接交易逻辑。
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()。这意味着你若用脚本锁住窗口,标签配色就会被冻结,适合做只读面板。
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 面板工程编译验证。
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 或脚本可据此联动。外汇与贵金属图表挂这类控件属高风险环境,事件驱动逻辑务必在模拟盘验证。
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 常量就能看到标题落位变化。 外汇与贵金属图表挂自定义面板本就是高风险操作环境,参数没对齐可能界面错位,动手前先在模拟盘验证。
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); }