图形界面 IV: 多窗口模式与优先级系统 (第二章)·综合运用
📘

图形界面 IV: 多窗口模式与优先级系统 (第二章)·综合运用

第 3/3 篇

◍ 按钮触发与对象销毁的底层逻辑

在 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,可能造成终端内存占用偏高,属高风险调试项。

MQL5 / C++
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 跑这套时,先确认图表上同时弹出两个上下文菜单,看底层按钮是否真的点不动,再关掉上层菜单验证优先级自动复原,比只读代码更直观。

MQL5 / C++
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 面板失联。

MQL5 / C++
  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 自动化高风险,库再顺手也只是工具,实盘前用策略测试器跑通再上。

常见问题

左键事件被图表底层抢走了。要在按钮按下时清零左键优先级、弹起时恢复,才能稳定触发按钮逻辑。
对象销毁时没恢复事件优先级。销毁 GUI 元件前必须显式把优先级复位,否则残留状态会串到图表。
可以。把小布接进你的看盘流程,它能扫描界面逻辑、提示清零与恢复事件是否配对,省去手动排错。
按绘制顺序和层级显式设优先级,后绘制的置顶并独占左键,避免穿透到下层元件。
记住一条:任何元件按下就清零、松开就恢复。守住这个闭环,多窗口乱触发基本消失。