自定义图形控件(三):用表单 Form 把控件打包成可管理的窗口
前两篇讲了单个控件的创建原则和现成控件库。但真实 EA 的界面往往是一组控件协同工作:参数输入、按钮触发、状态显示。把它们散落在图表上既难维护也难调试。表单(Form)就是解决这个问题的容器。
一、什么是表单(Form)
表单是屏幕上被特别指定的矩形区域,控件按它来排布;同时它又是一个容器,能统一管理内部所有控件——一同显示、一同隐藏、一同移动。外表上,表单是个带标题栏的矩形:左上角是移动按钮(手型图标),右上角是最小化和关闭按钮。根据有无按钮,可分 0 型(基础)、1 型(带 Cancel/Apply)、2 型(带 Close)。
二、CFormBase 与 CFormTemplate:基类/子类分工
表单功能由 CFormBase(基类)和 CFormTemplate(子类)两个类承载。CFormBase 提供通用机制:Init 初始化、SetPos/Show 显示、Hide 隐藏、Refresh 刷新、Event 事件处理,和别的控件接口一致。CFormTemplate 是模板,不直接用,你要为“每个具体表单”复制它并重命名成独立子类(如 CForm)。
这种基类/子类拆分解决了三个问题:多个表单共存时代码不串、表单与关联网件同步显示隐藏、不同表单的逻辑清晰隔离。而且以后升级 CFormBase 不会影响你已写好的子类。
// 从模板复制并重命名的子类(每个表单一份)
class CFormTemplate : public CFormBase {
protected:
void MainProperties() { // 表单外观
m_Name = "Form"; m_Width=200; m_Height=150;
m_Type = 1; // 0/1/2 型
m_Caption = "FormCaption";
m_Movable = true; m_CloseButton = true;
}
void OnInitEvent() {}
void OnShowEvent(int aLeft,int aTop) {}
void OnHideEvent() {}
bool OnApplyEvent() { return(true); }
bool OnCancelEvent() { return(true); }
};
三、表单的生命周期方法
CFormBase 在受保护区留了一组虚方法,由基类在对应时机自动调用,你只需要在子类里“填空”:MainProperties 设外观、OnInitEvent 初始化内部控件、OnShowEvent 显示内部控件、OnHideEvent 隐藏内部控件、EventsHandler 转发图表事件、OnApplyEvent/OnCancelEvent 处理 Apply/Cancel 按钮。
- MainProperties():由 Init() 调用,设置表单类型、宽高、标题、是否可移动/关闭
- OnInitEvent():由 Init() 调用,初始化并显示所有内部控件
- OnShowEvent(int aLeft,int aTop):由 Show() 调用,传入工作区左上角坐标
- OnHideEvent():由 Hide() 调用,统一隐藏内部控件
- EventsHandler():由 Event() 调用,把所有图表事件重定向到内部控件
- OnApplyEvent()/OnCancelEvent():点 Apply/Cancel/关闭时触发,返回 false 可阻止关闭
四、创建一个新表单的逐步流程
流程很固定:1) 包含 IncGUI_v3.mqh;2) 把 CFormTemplate 代码复制到你的 EA 文件并重命名成独立子类;3) 在 MainProperties() 里设外观变量;4) 声明类对象 CForm frm;5) 在 OnInit 调 Init、OnDeinit 调 Deinit、OnChartEvent 调 Event,必要时在 CHARTEVENT_CHART_CHANGE 调 SetSubWindow。
这套流程和你用可视编辑器(如 Excel VBA)建表单几乎一一对应:MainProperties 就像设表单属性,复制模板就像点“新建表单”自动生成带事件骨架的文件。差别只是 MQL5 里要手写,但结构极度规整。
// MainProperties() 里设定外观(节选)
void MainProperties() {
m_Name = "Form";
m_Width = 200; m_Height = 150;
m_Type = 1; // 1 型:带 Cancel/Apply
m_Caption = "FormCaption";
m_Movable = true; m_CloseButton = true;
}
五、向表单添加控件
加控件也是一套标准步骤:先声明控件(公开区便于表单外用,受保护区则全在表单内);可选地为每个控件加“恢复值”变量,用于用户改了又点 Cancel 时回滚;在 OnInitEvent 里对所有控件调 Init、载入保存数据、设默认值;在 OnShowEvent 里带坐标调 Show;在 OnHideEvent 调 Hide;在 EventsHandler 里调 Event 并处理控件间交互;可选在 OnApplyEvent 里校验并保存值。
六、主菜单:CHMenu + CVMenu 两级
IncGUI 还顺手升级了 CHMenu(水平菜单)和 CVMenu(垂直菜单),支持两级下拉(水平条 + 选项卡)。通过 SolvePosX/SolvePosY 等方法计算子菜单坐标,点击 Label 时返回和文本框一致的值。要建多级树状菜单理论上可行但极复杂,文章止步于两级——对绝大多数 EA 界面已经够用。
七、小结
表单是把零散控件变成“一个可管理窗口”的关键抽象。记住三件事:CFormBase 管机制、CFormTemplate 管具体表单逻辑;Init/Show/Hide 必须与内部控件同步;OnApplyEvent/OnCancelEvent 返回 false 能拦截关闭,用来做“确认退出”或“校验输入”。掌握表单后,你的 EA 界面就从“一堆浮窗”升级成“有结构的面板”。