首页 / 交易学院 / 自定义图形控件(三):用表单 Form 把控件打包成可管理的窗口
自定义图形控件(三):用表单 Form 把控件打包成可管理的窗口
🧩

自定义图形控件(三):用表单 Form 把控件打包成可管理的窗口

MQL5 编程 难度 · 进阶 2011 12 分钟阅读
GUI表单IncGUICFormBaseMQL5 界面

前两篇讲了单个控件的创建原则和现成控件库。但真实 EA 的界面往往是一组控件协同工作:参数输入、按钮触发、状态显示。把它们散落在图表上既难维护也难调试。表单(Form)就是解决这个问题的容器。

一、什么是表单(Form)

表单是屏幕上被特别指定的矩形区域,控件按它来排布;同时它又是一个容器,能统一管理内部所有控件——一同显示、一同隐藏、一同移动。外表上,表单是个带标题栏的矩形:左上角是移动按钮(手型图标),右上角是最小化和关闭按钮。根据有无按钮,可分 0 型(基础)、1 型(带 Cancel/Apply)、2 型(带 Close)。

标准控件是独立的,但表单“强制”关联网内控件:表单隐藏时,内部控件必须一起隐藏,否则界面会乱。因此表单和关联网件的 Init/Show/Hide 必须同步调用,这是用表单的第一条铁律。

二、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 按钮。

四、创建一个新表单的逐步流程

流程很固定: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 里校验并保存值。

Init/Show/Hide 三件套必须同步:只调表单的 Show 而不调内部控件的 Show,控件不会显示。反过来隐藏时内部控件会漏在图表上。所有范例都把这三者集中在表单的对应虚方法里,正是为了杜绝这种不一致。

六、主菜单:CHMenu + CVMenu 两级

IncGUI 还顺手升级了 CHMenu(水平菜单)和 CVMenu(垂直菜单),支持两级下拉(水平条 + 选项卡)。通过 SolvePosX/SolvePosY 等方法计算子菜单坐标,点击 Label 时返回和文本框一致的值。要建多级树状菜单理论上可行但极复杂,文章止步于两级——对绝大多数 EA 界面已经够用。

七、小结

表单是把零散控件变成“一个可管理窗口”的关键抽象。记住三件事:CFormBase 管机制、CFormTemplate 管具体表单逻辑;Init/Show/Hide 必须与内部控件同步;OnApplyEvent/OnCancelEvent 返回 false 能拦截关闭,用来做“确认退出”或“校验输入”。掌握表单后,你的 EA 界面就从“一堆浮窗”升级成“有结构的面板”。

常见问题

普通控件是独立浮在图表上的;表单是一个矩形容器,内部控件随表单一起移动、最小化、关闭、显示、隐藏。表单还提供标题栏、移动/关闭按钮和 Apply/Cancel 交互,是组织复杂界面的基本单位。
CFormBase 是基类,提供 Init/Show/Hide/Event 等通用机制和生命周期虚方法;CFormTemplate 是模板,不直接用,你要为每个具体表单复制它并重命名成独立子类,在子类里填 MainProperties/OnInitEvent 等逻辑。基类升级不影响子类。
关键在同步:在表单的 OnInitEvent 里对所有内部控件调 Init、OnShowEvent 里调 Show、OnHideEvent 里调 Hide。只显示表单而不显示内部控件,控件不会出现;隐藏表单时内部控件要一起隐藏,否则会漏在图表上。
返回 false 会中断表单关闭逻辑,表单保持打开。可以利用这一点在用户点 Apply 时校验输入值,发现错误就返回 false 并提示用户,阻止表单关闭;OnCancelEvent 同理可用于“确认是否退出”。
IncGUI 的表单(CFormBase/CFormTemplate)更轻、更聚焦“窗口容器+控件同步”,适合已有控件要打包成窗口的场景;标准库 CAppDialog/CPanelDialog 更系统、控件更全,适合从零搭复杂面板。两者思路相通,会一个另一个也容易上手。