图形界面 II 菜单项元件:从零搭建 MT5 自定义主菜单基类(基础篇)
(1/3)· 多数 EA 面板直接堆按钮,却不懂菜单项类的继承结构,后期扩展寸步难行
从零搭一个 MT5 菜单项元件
在 MT5 的图形界面开发里,菜单项元件是构建自定义面板时最容易被忽略、却最影响操作效率的一环。2016 年 3 月 Anatoli Kazharski 给出的示例里,仅菜单相关代码就被社区下载查看 4488 次、评论 7 条,说明这套思路在实盘工具化需求中一直有市场。 核心做法是把菜单项抽象成一个独立类,主菜单只负责挂载,具体行为由类内部处理。这样你在 EA 或指标里加一个「一键平仓」「切换周期」的入口,就不用每次重写整块界面逻辑。 高风险提示:外汇与贵金属杠杆交易波动剧烈,任何自定义界面工具都只是辅助,不能替代仓位与风险控制。开 MT5 用 MetaEditor 新建类继承 CElement,先跑通一个附加菜单项的空壳,比直接抄完整库更利于排查消息映射问题。
◍ 这套界面库现在还差哪些拼图
第一系列已经把图形界面库的主体骨架搭了出来,核心是一个叫控件表单(CWindow)的主元件。想看完整脉络的,去每章末尾的链接列表下对应阶段的库文件,按压缩包里的目录结构原样丢进相同文件夹就能编译跑通。 目前 CWndContainer 和 CWndEvents 两个类都还没封板。前者管所有元件和对象指针的存放,后续加新控件必然要扩;后者吃图表事件和控件内部事件,逻辑也会跟着涨。 CWindow 本身也不是终态,前面提的多窗口模式要到本系列第四部分才落地,顺带会演示上下文菜单——它虽挂在主菜单名下,但能塞进别的控件里复用。 遇到同类型方法在多个类里重复而文章略写的情况,直接翻附件里的源码补上那段再看下文,别卡在空白引用上。外汇与贵金属自动化界面开发高风险,未完工类随时改动,验证请以你本地附件版本为准。
「拆解 MT5 主菜单的界面构成」
打开 MT5 终端,左上角那排就是主菜单,鼠标左键点开任意一项会弹出下拉列表。MetaEditor 代码编辑器同样带这套结构,二者在视觉和交互上高度一致。 下拉列表里混着几类控件:纯按钮项点开多是设置或信息窗口;双态复选框用来切换某过程或界面区块的显隐,自身外观随状态变;单选项目一组只能选一个,相当于开关;还有一类点了会在右侧再弹一层上下文菜单。 要把这套复杂元件写进代码,全塞一个类既不现实也难维护。最小拆分是四个类:CMenuItem 管菜单项,CSeparateLine 画分隔线,CContextMenu 用 CMenuItem 对象拼装,CMenuBar 作为主菜单同样由 CMenuItem 组成。 落到实做,菜单项是所有结构的核心,下一步直接开写 CMenuItem 类即可在 MT5 里验证这套组合逻辑。
菜单项控件的类骨架与编译器陷阱
在 Controls 目录下另建 MenuItem.mqh,让 CMenuItem 直接继承前面讲过的 CElement。基类里声明的标准虚方法先只留声明不写实现,因为后面要借一个编译特性来逼出缺失方法。 菜单项不是单一形态:主菜单靠悬停变色标题,上下文菜单带图标,含子菜单的在项目右侧画右箭头。可拆成背景、标签、标题、上下文菜单指示四部分。类型用 ENUM_TYPE_MENU_ITEM 枚举区分——MI_SIMPLE 默认普通项、MI_HAS_CONTEXT_MENU 带子菜单、MI_CHECKBOX 复选、MI_RADIOBUTTON 单选,枚举统一收进 Enums.mqh。 CMenuItem 里要挂的字段不少:背景/边框色变更规则、左键优先级统一为0等效点背景、标签边距与三态字体色(正常/悬停/禁用)、可用与勾选/单选/子菜单状态变量、单选组ID、以及 m_index_number 索引。索引既拼图形对象名也用于点击识别,CreateMenuItem() 入参就靠它。 每个控件都得持有一个 CWindow 表单指针,方法命名为 WindowPointer(),用 GetPointer() 取回。CreateMenuItem() 开头先用 CheckPointer() 验指针,无效就打印明确报错并 return false。标识编号沿表单指针从 CWndContainer 计数器取,最小化窗口时 Hide() 要藏对象、清变量、重置颜色。 对象命名比 CWindow 复杂:独立项有唯一标识没问题,组内项必须拼上程序名_元件_部件_索引_标识,否则同名图形对象只会显示一个。CreateIcon() 里 MI_SIMPLE 和 MI_HAS_CONTEXT_MENU 无图标直接 return true,复选/单选缺图标用附件默认图。
class="macro">#include "Element.mqh"
class="macro">#include "Element.mqh"◍ 菜单项类的骨架与私有对象
在 MT5 自定义 GUI 里,菜单项不是凭空画的,而是从 CElement 继承出一个 CMenuItem 类,把矩形标签、图标位图、文字标签和箭头位图四个子对象组合在一起。 看类声明前半段,构造函数 CMenuItem(void) 与析构函数都留空,说明对象生命周期交给外部容器管理,本身不申请额外资源。公开接口里 OnEvent、OnEventTimer、Moving、Show、Hide、Reset、Delete 以及 Z 轴优先级方法全部是 virtual,意味着子类可以重写点击和绘制逻辑。 后半段类定义补了 private 成员:m_area 是 CRectLabel 背景热区,m_icon 与 m_arrow 是 CBmpLabel 位图,m_label 是 CLabel 文本。CreateMenuItem 用 chart_id、window、index_number、label_text 和坐标 x,y 七个参数完成实例化,回测面板或右键菜单都可复用这一签名。 想验证就开 MT5 的 MetaEditor,新建继承 CElement 的空类,把这四个私有对象声明贴进去编译,能跑通说明你的 GUI 基类链路没断。外汇与贵金属图表上挂这类自定义控件,仍存在滑点扩大与报价中断的高风险,仅作界面辅助。
class CMenuItem : class="kw">public CElement { class=class="str">"cmt">//--- class="kw">public: CMenuItem(class="type">void); ~CMenuItem(class="type">void); class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- Chart event handler 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">//--- Timer class="kw">virtual class="type">void OnEventTimer(class="type">void); class=class="str">"cmt">//--- Moving element 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">//--- Showing, hiding, resetting, deleting 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">//--- Setting, resetting priorities for the left mouse click class="kw">virtual class="type">void SetZorders(class="type">void); class="kw">virtual class="type">void ResetZorders(class="type">void); }; CMenuItem::CMenuItem(class="type">void) { } CMenuItem::~CMenuItem(class="type">void) { } class CMenuItem : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- Objects for creating a menu item CRectLabel m_area; CBmpLabel m_icon; CLabel m_label; CBmpLabel m_arrow; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- Methods for creating a menu item class="type">bool CreateMenuItem(class="kw">const class="type">long chart_id,class="kw">const class="type">int window,class="kw">const class="type">int index_number,class="kw">const class="type">class="kw">string label_text,class="kw">const class="type">int x,class="kw">const class="type">int y);
「菜单项类的私有成员与类型枚举」
在 MT5 自定义 GUI 里,菜单项的交互差异靠一个枚举收口。ENUM_TYPE_MENU_ITEM 定义了四种基础形态:MI_SIMPLE(0) 是纯点击项,MI_HAS_CONTEXT_MENU(1) 带右键展开,MI_CHECKBOX(2) 做开关,MI_RADIOBUTTON(3) 做互斥单选。 CMenuItem 类继承自 CElement,构造时默认把 m_type_menu_item 置为 MI_SIMPLE,也就是不传参就是普通项。类型通过 TypeMenuItem() 的读写重载暴露,写方法直接赋值私有成员,读方法加 const 返回,避免外部篡改。 类内部把视觉状态拆得很细:背景有常态、禁用、悬停三套色(m_area_color / m_area_color_off / m_area_color_hover),文字标签同样三套(m_label_color 系列),图标则分开记录开、关两张图路径。zorder 和坐标 gap 也单独成变量,说明这套框架把「状态驱动重绘」当核心来做。 开 MT5 建个空 EA 把这段类骨架贴进 include,改 m_type_menu_item 默认值看菜单渲染差异,是验证状态机最简路径。外汇与贵金属图表挂这类自定义控件仍属高风险操作,参数误用可能导致面板刷新异常。
class="kw">private: class="type">bool CreateArea(class="type">void); class="type">bool CreateIcon(class="type">void); class="type">bool CreateLabel(class="type">void); class="type">bool CreateArrow(class="type">void); class=class="str">"cmt">//--- }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enumeration of the menu item types | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_TYPE_MENU_ITEM { MI_SIMPLE =class="num">0, MI_HAS_CONTEXT_MENU =class="num">1, MI_CHECKBOX =class="num">2, MI_RADIOBUTTON =class="num">3 }; class CMenuItem : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- Menu item properties ENUM_TYPE_MENU_ITEM m_type_menu_item; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- Setting and getting the type class="type">void TypeMenuItem(class="kw">const ENUM_TYPE_MENU_ITEM type) { m_type_menu_item=type; } ENUM_TYPE_MENU_ITEM TypeMenuItem(class="type">void) class="kw">const { class="kw">return(m_type_menu_item); } class=class="str">"cmt">//--- }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CMenuItem::CMenuItem(class="type">void) : m_type_menu_item(MI_SIMPLE) { } class CMenuItem : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- Background properties class="type">int m_area_zorder; class="type">class="kw">color m_area_border_color; class="type">class="kw">color m_area_color; class="type">class="kw">color m_area_color_off; class="type">class="kw">color m_area_color_hover; class=class="str">"cmt">//--- Label properties class="type">class="kw">string m_icon_file_on; class="type">class="kw">string m_icon_file_off; class=class="str">"cmt">//--- Text label properties class="type">class="kw">string m_label_text; class="type">int m_label_x_gap; class="type">int m_label_y_gap; class="type">class="kw">color m_label_color; class="type">class="kw">color m_label_color_off; class="type">class="kw">color m_label_color_hover; class=class="str">"cmt">//--- Properties of the indication of the context menu