自定义图形控件表单:从零搭出可拖拽可管理的 MT5 界面容器(基础篇)
(1/2)· 还在用零散图形对象拼界面?表单让你把按钮标签收进一个能整体移动隐藏的矩形里
在 MT5 里搭一个自定义表单控件
MetaTrader 5 自带的图形对象不足以覆盖复杂交互需求,自己写控件才有掌控感。2013 年 11 月 6 日发布的表单示例帖累计浏览 3102 次、评论 26 条,说明这类底层控件在 MQ5 社区有稳定需求。 表单(Form)本质是一个可拖拽的容器,内部承载按钮、编辑框、标签等子控件。核心思路是用 CObject 派生类管理坐标与消息路由,在 OnChartEvent 里把鼠标和键盘事件分发给当前焦点控件。 下面这段是表单类最小骨架,先跑通再扩展。注意所有子对象坐标都相对表单左上角,避免直接在绝对坐标上画。 外汇与贵金属图表上叠加自绘表单会占用主图资源,高频刷新可能引发卡顿,实盘前务必在模拟盘验证性能。
class CForm : class="kw">public CObject { class="kw">protected: class="type">int m_x; class=class="str">"cmt">// 表单左上角X class="type">int m_y; class=class="str">"cmt">// 表单左上角Y class="type">int m_w; class=class="str">"cmt">// 宽度 class="type">int m_h; class=class="str">"cmt">// 高度 CArrayObj m_controls; class=class="str">"cmt">// 子控件数组 class="kw">public: CForm(class="type">int x,class="type">int y,class="type">int w,class="type">int h):m_x(x),m_y(y),m_w(w),m_h(h){} class="type">void Add(CObject* c){ m_controls.Add(c); } class="kw">virtual class="type">void OnEvent(const class="type">int id,const class="type">long& l,const class="type">class="kw">double& d,const class="type">class="kw">string& s) { for(class="type">int i=class="num">0;i<m_controls.Total();i++) m_controls.At(i).OnEvent(id,l,d,s); } };
◍ 表单与控件容器的底层改动
在 MT5 自定义图形界面里,表单(Frame)不是装饰,而是一块被明确圈定的矩形区域,控件只在它内部绘制与排布。更关键的是,表单充当容器:隐藏、显示、整体平移都能一次作用于内部所有子控件,省去逐个遍历的开销。 本篇对应的类库已并入 IncGUI_v3.mqh(前身是 IncGUI.mqh)。新增 CFrame 等价于早先 CWorkPiece::Frame() 产出的框架;CButton 封装常规 OBJ_BUTTON;CLabel 在 OBJ_LABEL 基础上支持用 "\n" 做多行文本。CColorSchemes 随之补进了新控件的配色槽位。 CHMenu / CVMenu 的改动指向两级下拉菜单能力,后文“创建主菜单”会展开。修掉的两个硬伤值得注意:CWorkPiece::Frame() 原先无法给矩形标签设子窗口编号;CListMS 的 Selected()/SetSelected() 此前不校验数组尺寸,空列表调用会越界。 外汇与贵金属图表加载这类 EA 界面属高风险操作,建议先在策略测试器离线跑通再上真实账户。
「用矩形标签和按钮搭出可拖拽表单」
MT5 里做自定义面板,地基是图形对象 OBJ_RECTANGLE_LABEL 当容器,再往里塞 OBJ_BUTTON。视觉上就是个带顶栏的矩形:左边抓手图标管拖动,右边最小化方块和叉号管收起或关闭。拖的时候按住移动钮,图表上点哪左上角就落哪;若点在图表边缘外,系统会自动回拉保证整块面板可见。 表单分三种型:0 型只有基础三钮;1 型多 Cancel / Apply;2 型多 Close。代码层面靠 CFormBase 基类与 CFormTemplate 子类分工,前者管显示隐藏刷新事件,后者当模板被复制重命名后承载具体控件逻辑。 同步是个坑。标准控件各自独立,表单却强制绑定内部控件——你显隐表单时,里面按钮也得跟着显隐。若手动逐个调 Show()/Hide(),多表单项目会乱成一团。正确做法是在 CFormTemplate 副本里重写受保护区的虚方法,基类按事件自动回调,你只填控件操作。 OnCancelEvent 返回 false 能拦住关闭,适合弹确认窗;OnApplyEvent 里存控件值,下次 OnInitEvent 读回,EA 重启也能复原界面状态。外汇和贵金属图表挂这类面板,遇极端波动可能事件堆积,回测前先在模拟盘验证交互。
class="kw">virtual class="type">void MainProperties() {} class="kw">virtual class="type">void OnInitEvent() {} class="kw">virtual class="type">void OnShowEvent() {} class="kw">virtual class="type">void OnHideEvent() {} class="kw">virtual class="type">void EventsHandler(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="kw">virtual class="type">void OnWindowChangeEvent(class="type">int aSubWindow) {} class="kw">virtual class="type">bool OnCancelEvent() {class="kw">return(true);} class="kw">virtual class="type">bool OnApplyEvent() {class="kw">return(true);}
从零拉起一个 GUI 表单子类
在 MT5 里用 IncGUI_v3 体系搭界面,第一步是把 IncGUI_v3.mqh 包含进来,再把库里的 CFormTemplate 类整段抄到 EA 文件末尾或独立 include 里,重命名成你自己的子类(例里叫 CForm)。这动作等价于可视编辑器里点「新建表单」——项目里立刻多出一个带事件骨架的表单文件。 表单名不在 Init() 里传,而是在 MainProperties() 里给 m_Name 赋值。子类只声明一次,所以调 Init() 时不用反复改名字;唯一能塞进 Init() 的是 State 参数,非强制:传 1 表单初始最大化,传 2 最小化,不传就走默认。 控件接入走标准四连:OnInit() 调 Init()、OnDeinit() 调 Deinit()、OnChartEvent() 调 Event(),遇到 CHARTEVENT_CHART_CHANGE 再补一句 SetSubWindow()。跑起来后表单能拖、能最小、能关,之后再往里堆按钮文本框都行。 下面这段是模板子类的最小可编译形态,直接复制到 EA 里就能在图表上看到 200×150、带「应用/取消」按钮的窗体。外汇贵金属界面自动化本质是高杠杆环境下的辅助工具,实盘前务必在模拟盘验证事件响应。
class="macro">#include <IncGUI_v3.mqh> class CFormTemplate: class="kw">public CFormBase{ class="kw">public: class="kw">protected: class="type">void MainProperties() { m_Name = "Form"; class=class="str">"cmt">// 表单名. 表单的所有控件名必须有这个前缀. m_Width = class="num">200; class=class="str">"cmt">// 表单宽度 m_Height = class="num">150; class=class="str">"cmt">// 表单高度 m_Type = class="num">1; class=class="str">"cmt">// 表单类型: class="num">0 - 无按钮, class="num">1 - 带 "应用" 和 "取消" 按钮, class="num">2 - 带 "关闭" 按钮 m_Caption = "FormCaption"; class=class="str">"cmt">// 表单标题 m_Movable = true; class=class="str">"cmt">// 可移动表单 (带手势图像按钮,显示在左上角) m_Resizable = true; class=class="str">"cmt">// 表单最小/最大允许 (带正方形图像按钮 class=class="str">"cmt">// 显示在右上角) m_CloseButton = true; class=class="str">"cmt">// 表单关闭允许 (带交叉图像按钮显示在右上角) } class="type">void OnInitEvent() {} class="type">void OnShowEvent(class="type">int aLeft, class="type">int aTop) {} class="type">void OnHideEvent() {} class="type">void OnWindowChangeEvent(class="type">int aSubWindow) {} class="type">void EventsHandler(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="type">bool OnApplyEvent() {class="kw">return(true);} class="type">bool OnCancelEvent() {class="kw">return(true);} };