图形界面 IV: 多窗口模式与优先级系统 (第二章)·综合运用
◍ 按钮触发与对象销毁的底层逻辑
在 MT5 自定义控件的事件分支里,靠按钮文本比对来路由界面是最直白的写法。当 sparam 与 m_icon_button1 的文本一致,直接调 m_window2.Show() 弹出第二个窗口;若命中 m_icon_button6,则转向 m_window3。这种硬匹配在元件少于十个时够用,元件一多就会变成易错的 if 堆叠。 销毁阶段先复位主窗口索引为 0,再取 WindowsTotal() 得到窗口总数。遍历时把 W_MAIN 类型窗口置为激活态,其余对话框强制 State(false) 屏蔽,保证重置后只留主界面。 真正清场靠双层循环:外层走窗口,内层走该窗口 ElementsTotal(w) 个元件。指针无效就 continue 跳过,有效则执行 Delete() 释放对象,随后用 ArrayFree 把 m_objects、m_elements、m_context_menus 三个数组一并清空。最后 m_wnd 与 m_windows 也释放,整套 UI 对象归零。外汇与贵金属 GUI 脚本若漏掉 ArrayFree,可能造成终端内存占用偏高,属高风险调试项。
class=class="str">"cmt">//--- 如果文字匹配 if(sparam==m_icon_button1.Text()) { class=class="str">"cmt">//--- 显示窗口 class="num">2 m_window2.Show(); } class=class="str">"cmt">//--- 如果文字匹配 if(sparam==m_icon_button6.Text()) { class=class="str">"cmt">//--- 显示窗口 class="num">3 m_window3.Show(); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 删除全部对象 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CWndEvents::Destroy(class="type">void) { class=class="str">"cmt">//--- 设置主窗口的索引 m_active_window_index=class="num">0; class=class="str">"cmt">//--- 取得窗口数量 class="type">int window_total=CWndContainer::WindowsTotal(); class=class="str">"cmt">//--- 在窗口数组中迭代 for(class="type">int w=class="num">0; w<window_total; w++) { class=class="str">"cmt">//--- 激活主窗口 if(m_windows[w].WindowType()==W_MAIN) m_windows[w].State(true); class=class="str">"cmt">//--- 屏蔽对话框窗口 else m_windows[w].State(class="kw">false); } class=class="str">"cmt">//--- 清空元件数组 for(class="type">int w=class="num">0; w<window_total; w++) { class="type">int elements_total=CWndContainer::ElementsTotal(w); for(class="type">int e=class="num">0; e<elements_total; e++) { class=class="str">"cmt">//--- 如果指针无效,转到后面执行 if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID) class="kw">continue; class=class="str">"cmt">//--- 删除元件对象 m_wnd[w].m_elements[e].Delete(); } class=class="str">"cmt">//--- 清空元件数组 ::ArrayFree(m_wnd[w].m_objects); ::ArrayFree(m_wnd[w].m_elements); ::ArrayFree(m_wnd[w].m_context_menus); } class=class="str">"cmt">//--- 清空表单数组 ::ArrayFree(m_wnd); ::ArrayFree(m_windows); }
「用清零与恢复事件理顺左键优先级」
早期靠 ON_OPEN_DIALOG_BOX 和 ON_CLOSE_DIALOG_BOX 来管理界面元件的左键点击优先级,用户虽然能手动给每个对象设优先级数值,但组合控件一复杂就容易乱套,还常搞不清谁高谁低。 直接在 Defines.mqh 里补两个事件标识就能解耦:ON_ZERO_PRIORITIES 取值 14 表示清零,ON_SET_PRIORITIES 取值 15 表示恢复。凡是带下拉能力的类(目前只有上下文菜单 CContextMenu)要在 Show() 和 Hide() 里发这两个自定义事件。 CWndEvents 作为消息总入口,在 ChartEventCustom() 里按标识分流到 OnZeroPriorities() 与 OnSetPriorities()。清零时遍历活动窗口全部元件,把除消息 lparam 携带的 ID、上下文菜单及菜单项之外的对象优先级归零——因为多个上下文菜单可能叠着开,不能误伤。收到恢复标识就把全窗口元件的左键优先级还原。 别把正态当圣经 实际挂 MT5 跑这套时,先确认图表上同时弹出两个上下文菜单,看底层按钮是否真的点不动,再关掉上层菜单验证优先级自动复原,比只读代码更直观。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Defines.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define ON_ZERO_PRIORITIES(class="num">14) class=class="str">"cmt">// 重置鼠标左键点击优先级 class="macro">#define ON_SET_PRIORITIES(class="num">15) class=class="str">"cmt">// 恢复鼠标左键点击优先级 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 显示上下文菜单 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CContextMenu::Show(class="type">void) { 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">//--- 屏蔽表单 class=class="str">"cmt">//--- 发送鼠标左键优先级清零的信号 ::EventChartCustom(m_chart_id,ON_ZERO_PRIORITIES,CElement::Id(),class="num">0.0,""); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 隐藏上下文菜单 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CContextMenu::Hide(class="type">void) { 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">//--- 发送恢复鼠标左键点击优先级的信号 ::EventChartCustom(m_chart_id,ON_SET_PRIORITIES,class="num">0,class="num">0.0,""); } class CWndEvents : class="kw">public CWndContainer { class="kw">private: class=class="str">"cmt">//--- 重置鼠标左键点击优先级 class="type">bool OnZeroPriorities(class="type">void); class=class="str">"cmt">//--- 恢复鼠标左键点击优先级 class="type">bool OnSetPriorities(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CHARTEVENT_CUSTOM 事件 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CWndEvents::ChartEventCustom(class="type">void) { 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">//--- 如果信号是清除指定表单上所有元件的颜色 class=class="str">"cmt">//--- 如果信号是重设鼠标左键点击优先级
GUI 元件的点击优先级清零与恢复
在 MT5 自定义面板开发里,鼠标左键点击的层级冲突是常见坑。当某个元件被激活时,需要把其余元件的 Z 轴优先级清空,避免误触;等交互结束再统一恢复。 下面这段事件处理先拦截两个自定义信号:收到清零指令就调用 OnZeroPriorities,收到恢复指令就调用 OnSetPriorities,两者都会提前 return 阻断后续逻辑。 清零函数只处理当前激活窗口内的元件总数(ElementsTotal 返回值),用 for 从 0 遍历到 elements_total-1。传入 id 的元件本身不动,上下文菜单类(CMenuItem / CContextMenu)也被 continue 跳过,其余一律 ResetZorders。 恢复函数逻辑更直接:同样遍历全部元件,无差别调用 SetZorders 把层级权重写回。两个函数都靠 m_id 是否等于 CHARTEVENT_CUSTOM 加偏移量来做信号甄别,不匹配就返回 false 让主循环继续。 开 MT5 新建一个空面板,把这两段塞进你的 CWndEvents 类,用自定义事件 ON_ZERO_PRIORITIES / ON_SET_PRIORITIES 触发,能在图表上直观看到按钮抢焦点现象消失。外汇与贵金属图表插件开发属高风险调试,参数错误可能让 EA 面板失联。
if(OnZeroPriorities()) class="kw">return; class=class="str">"cmt">//--- 如果信号是恢复鼠标左键点击优先级 if(OnSetPriorities()) class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ON_ZERO_PRIORITIES 事件 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CWndEvents::OnZeroPriorities(class="type">void) { class=class="str">"cmt">//--- 如果信号是把鼠标左键点击优先级清零 if(m_id!=CHARTEVENT_CUSTOM+ON_ZERO_PRIORITIES) class="kw">return(class="kw">false); class=class="str">"cmt">//--- class="type">int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(class="type">int e=class="num">0; e<elements_total; e++) { class=class="str">"cmt">//--- 把除了传入id的元件之外的元件优先级清零 if(m_lparam!=m_wnd[m_active_window_index].m_elements[e].Id()) { class=class="str">"cmt">//--- ... 以及除了上下文菜单 if(m_wnd[m_active_window_index].m_elements[e].ClassName()=="CMenuItem" || m_wnd[m_active_window_index].m_elements[e].ClassName()=="CContextMenu") class="kw">continue; class=class="str">"cmt">//--- m_wnd[m_active_window_index].m_elements[e].ResetZorders(); } } class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ON_SET_PRIORITIES 事件 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CWndEvents::OnSetPriorities(class="type">void) { class=class="str">"cmt">//--- 如果信号是恢复鼠标左键点击优先级 if(m_id!=CHARTEVENT_CUSTOM+ON_SET_PRIORITIES) class="kw">return(class="kw">false); class=class="str">"cmt">//--- class="type">int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(class="type">int e=class="num">0; e<elements_total; e++) m_wnd[m_active_window_index].m_elements[e].SetZorders(); class=class="str">"cmt">//--- class="kw">return(true); }
◍ 记住这一条就够了
这一部分的四章走完,图形界面库已经能跑状态栏、工具提示、多窗口和左键优先级,结构图定在表单加若干控件的形态。想验证就下 easyandfastgui_mql5.zip(170.51 KB),在 MT5 里加载看多窗口切换是否符合预期。 作者留过话:同时开三个活动面板的功能当时还没做,只计划往后补,所以现在一次仍只能有一个活动面板。你要做自定义面板,先照现有优先级系统调,别假设多活动窗已支持。 外汇和贵金属 GUI 自动化高风险,库再顺手也只是工具,实盘前用策略测试器跑通再上。