图形界面 X: 简单快速开发库的更新 (版本 3)(基础篇)
📘

图形界面 X: 简单快速开发库的更新 (版本 3)(基础篇)

第 1/3 篇

◍ X 图形库 3.0 更新的实测入口

X 图形界面库在 2016-10-21 推到 3.0 版,作者 Anatoli Kazharski,原帖统计显示 10 387 次浏览、11 条讨论,说明这套快速开发封装在当时 MT5 社群里已有稳定受众。 更新点集中在控件创建与事件回调的简化,目标是不写几百行 WinAPI 风格代码也能在 EA 面板里堆出按钮、编辑框和图表对象。 要验证是否值得迁移,最直接的是拉一个测试用 EA:把旧版库面板换成 3.0 的 CPanel 派生写法,看编译后资源占用与点击响应延迟。外汇与贵金属品种上挂这类面板需注意,实时 Tick 密集时界面重绘可能抢主线程,属高风险环境下的性能权衡。

「库的版本演进与 MT5 定向」

本系列第一篇文章已交代这套图形界面库的设计目标,每章末尾会附上当时阶段完整版的下载链接,解压后需按压缩包内的目录层级原样放置文件,否则 MT5 加载时会找不到依赖。 当前展示的是第 3 版开发库,定位为简单快速构建界面,这一版修掉了旧版的若干缺陷并补进新功能,具体改动在对应章节里有逐条说明。 从版本 3 起,库只面向 MetaTrader 5 维护。MT4 在底层结构上有本质差异与限制,硬移植成本过高;若确有在 MT4 上使用的强需求,可去自由职业服务板块发单,找开发者做适配。 对交易者而言,直接拉 MT5 版库跑 demo 即可验证兼容性,不必在 MT4 环境上耗时间。

把EA塞进指标子窗口

想在MT5里给EA做一套不挡主图的交易面板,思路是借一个零计算的占位指标 SubWindow.ex5 开出子窗口,再把EA界面画进去。做法很直接:主文件里以资源形式包含该指标,并把 EXPERT_IN_SUBWINDOW 常量置为 true(默认 false,界面会落在主图表窗)。外汇/贵金属杠杆高,实盘前务必在策略测试器用模拟品种跑通。 指标本身不知道EA要用什么高度画图,所以双方靠自定义事件 ON_SUBWINDOW_CHANGE_HEIGHT 通信。EA在指标首次 OnCalculate(prev_calculated=0)时发消息,带窗口像素高度(long)和自身名(string);指标回同样的ID、高度值与EA名,EA在 OnChartEvent 里校验名字后调子窗口高度。 引擎层 CWndEvents::DetermineSubwindow 会在程序类型为EA且模式开启时取 SubWindow.ex5 句柄,读当前子窗口数作为编号,存好EA子窗编号、总数与指标短名。若用户在图表另增/删指标导致编号漂移,CheckExpertSubwindowNumber 会循环重定位并更新所有控件;子窗被手动删掉时EA也自行退出并留日志。 下方为模式开关常量定义片段,改 true 前先确认 SubWindow.ex5 已编进资源,否则句柄取不到会在日志报错。

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=class="str">"cmt">//--- "子窗口中的EA交易" 模式

◍ EA 塞进子窗口时指标怎么挂

想把 EA 跑在图表下方的独立子窗口,而不是主图,第一步是在 EA 源码里用 #resource 把配套指标编译进 exe。上面那段里 #resource \Indicators\SubWindow.ex5 就是干这个:把 SubWindow 指标作为资源嵌进 EA,运行时不依赖外部文件,MT5 加载 EA 时会自动带出这个子窗口指标。 被嵌入的指标本身必须声明 #property indicator_separate_window,否则它默认画在主图,EA 就进不了子窗口。示例里还把 indicator_plotsindicator_buffers 都设成 0,说明这个指标不画任何线,纯粹给 EA 当容器用,窗口高度靠自定义事件 ON_SUBWINDOW_CHANGE_HEIGHT (25) 后续去改。 开 MT5 验证很简单:建一个空指标,抄下面这段属性声明,编译后通过 EA 的 #resource 引用,挂上 EU 或 XAUUSD 图表看子窗口是否独立出现。外汇和贵金属杠杆高,子窗口 EA 一样可能因滑点错单,先用模拟盘跑通再上实盘。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_plots   class="num">0
class="macro">#class="kw">property indicator_buffers class="num">0
class="macro">#class="kw">property indicator_minimum class="num">0.0
class="macro">#class="kw">property indicator_maximum class="num">0.0
class="macro">#define ON_SUBWINDOW_CHANGE_HEIGHT(class="num">25)
class="type">int OnInit(class="type">void)
  {
   ::IndicatorSetString(INDICATOR_SHORTNAME,PROGRAM_NAME);
   class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
  }
class="type">int OnCalculate(class="kw">const class="type">int rates_total,

「让指标子窗口跟着EA高度走」

在MT5里,自定义指标和EA分处不同程序域,但图形对象常需要共用子窗口。若EA调整了自身子窗口高度,指标若不知情就会错位,界面元素可能叠在主图或留白区域。 这段逻辑的核心,是借 ChartEvent 的自定义事件做跨程序通知。指标在 OnInit 后的首次计算(prev_calculated<1)就向图表广播 ON_SUBWINDOW_CHANGE_HEIGHT 事件,并带上 PROGRAM_NAME 作身份标记;EA收到后若发现 sparam 匹配,便用 IndicatorSetInteger(INDICATOR_HEIGHT, (int)lparam) 把子窗口高度设成传来的数值。 指标侧的 CWindow::OnEvent 更谨慎:先排除自身发来的消息,再确认发送方是 PROGRAM_EXPERT 类型,且开启了 m_height_subwindow_mode 固定高度模式,才计算 m_subwindow_height——最小化时取标题高+3,否则取背景全高+3,再调 ChangeSubwindowHeight 落地。 开MT5新建一个指标挂EA同图,把这段事件收发接上,拖拽EA子窗口大小就能看到指标窗口同步伸缩;外汇与贵金属图表多窗口联动本身无风险,但自动化改界面参数若逻辑漏判 sparam,可能让无关指标误响应。

MQL5 / C++
class="kw">const class="type">int      prev_calculated,
class="kw">const class="type">int      begin,
class="kw">const class="type">class="kw">double &price[])
  {
class=class="str">"cmt">//--- 如果初始化成功
   if(prev_calculated<class="num">1)
      class=class="str">"cmt">//--- 向EA交易发送消息并取得子窗口的大小
      ::EventChartCustom(class="num">0,ON_SUBWINDOW_CHANGE_HEIGHT,class="num">0,class="num">0.0,PROGRAM_NAME);
class=class="str">"cmt">//---
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent 函数                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(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">//--- 处理改变EA交易子窗口高度的事件
   if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT)
     {
      class=class="str">"cmt">//--- 只接收来自指定EA交易名称的消息
      if(sparam==PROGRAM_NAME)
         class="kw">return;
      class=class="str">"cmt">//--- 改变子窗口高度
      ::IndicatorSetInteger(INDICATOR_HEIGHT,(class="type">int)lparam);
     }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 图表事件处理函数                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CWindow::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">//--- 处理改变EA交易子窗口高度的事件
   if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT)
     {
      class=class="str">"cmt">//--- 如果消息是来自EA交易,退出
      if(sparam==PROGRAM_NAME)
         class="kw">return;
      class=class="str">"cmt">//--- 如果程序不是EA交易,退出
      if(CElement::ProgramType()!=PROGRAM_EXPERT)
         class="kw">return;
      class=class="str">"cmt">//--- 如果没有设置固定子窗口高度模式,退出
      if(!m_height_subwindow_mode)
         class="kw">return;
      class=class="str">"cmt">//--- 计算并设置子窗口的高度
      m_subwindow_height=(m_is_minimized)? m_caption_height+class="num">3 : m_bg_full_height+class="num">3;
      ChangeSubwindowHeight(m_subwindow_height);
      class="kw">return;
     }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 改变指标子窗口的高度                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+

EA子窗口高度与句柄的接管逻辑

在 MT5 里用 CWindow 类做 GUI,若图形界面跑在子窗口且程序是指标,直接调 IndicatorSetInteger(INDICATOR_HEIGHT, height) 就能改高度;若是 EA,则要靠 EventChartCustom 发自定义事件给随附的 SubWindow.ex5 占位指标去间接改,脚本类型则直接 return 不动窗口。 EA 侧识别子窗口时,先判断 EXPERT_IN_SUBWINDOW 开关,关了就留在主图。开着就用 iCustom(Symbol(), Period(), "::Indicators\\SubWindow.ex5") 拿占位指标句柄,拿不到会打印 INVALID_HANDLE 错误,这通常意味着资源没编译进 ex5。 拿到句柄后,用 ChartGetInteger(m_chart_id, CHART_WINDOWS_TOTAL) 读当前子窗口总数,这个整数就是后续给 EA 界面定位的依据。开 MT5 把 SubWindow.ex5 以资源形式包进 EA,挂一个欧美图表验证,CHART_WINDOWS_TOTAL 会多出一个窗口,外汇贵金属杠杆高,测试请用模拟盘。

MQL5 / C++
class="type">void CWindow::ChangeSubwindowHeight(class="kw">const class="type">int height)
  {
class=class="str">"cmt">//--- 如果图形界面不在子窗口中或者程序是脚本类型
   if(CElement::m_subwin<=class="num">0 || CElement::m_program_type==PROGRAM_SCRIPT)
        class="kw">return;
class=class="str">"cmt">//--- 如果子窗口高度需要改变
   if(height>class="num">0)
     {
      class=class="str">"cmt">//--- 如果程序是指标类型
       if(CElement::m_program_type==PROGRAM_INDICATOR)
         {
          if(!::IndicatorSetInteger(INDICATOR_HEIGHT,height))
             ::Print(__FUNCTION__,"> 修改指标子窗口高度失败!错误编号: ",::GetLastError());
         }
      class=class="str">"cmt">//--- 如果程序是EA交易类型
      else
         {
         class=class="str">"cmt">//--- 发送消息给 SubWindow.ex5 指标,通知它窗口高度必须改变
         ::EventChartCustom(m_chart_id,ON_SUBWINDOW_CHANGE_HEIGHT,(class="type">long)height,class="num">0,PROGRAM_NAME);
         }
     }
  }
class CWndEvents : class="kw">public CWndContainer
  {
class="kw">protected:
   class=class="str">"cmt">//--- EA交易子窗口的句柄
   class="type">int                m_subwindow_handle;
   class=class="str">"cmt">//--- EA交易子窗口的名称
   class="type">class="kw">string             m_subwindow_shortname;
   class=class="str">"cmt">//--- 在设置EA子窗口后子窗口的数量
   class="type">int                m_subwindows_total;
  };
class="type">void CWndEvents::DetermineSubwindow(class="type">void)
  {
   if(PROGRAM_TYPE==PROGRAM_EXPERT)
     {
      class=class="str">"cmt">//--- 如果EA的图形界面要求放在主窗口中,退出
      if(!EXPERT_IN_SUBWINDOW)
         class="kw">return;
      class=class="str">"cmt">//--- 取得占位指标(空白子窗口)的句柄
      m_subwindow_handle=iCustom(Symbol(),Period(),"::Indicators\\SubWindow.ex5");
      class=class="str">"cmt">//--- 如果没有这样的指标,在记录中写错误报告
      if(m_subwindow_handle==INVALID_HANDLE)
         ::Print(__FUNCTION__,"> 在以下目录获取指标句柄出错 ::Indicators\\SubWindow.ex5 !");
      else
        {
         class=class="str">"cmt">//--- 取得图表中子窗口的数量
         class="type">int subwindows_total=(class="type">int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL);
         class=class="str">"cmt">//--- 为EA交易的图形界面设置子窗口
        }
     }
  }

常见问题

在库初始化时指定父窗口为指标子窗口句柄,并调用对应挂载函数,实测入口在库基础示例的窗口绑定段。
可以,指标仍按原方式附加到该子窗口,但需确认EA未占用同ID资源,否则会绘制冲突。
小布可读取当前图表窗口结构,提示EA与指标是否共用句柄或高度区,帮你快速定位遮挡风险。
接管子窗口句柄后,在EA里写高度跟随逻辑,库3.0已提供对应回调接口,改参数即可。
3.0把窗口绑定、句柄接管、高度同步做成内置方法,原来要手写几十行的挂载代码现在调函数就行。