图形界面 X: 简单快速开发库的更新 (版本 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 已编进资源,否则句柄取不到会在日志报错。
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_plots 和 indicator_buffers 都设成 0,说明这个指标不画任何线,纯粹给 EA 当容器用,窗口高度靠自定义事件 ON_SUBWINDOW_CHANGE_HEIGHT (25) 后续去改。
开 MT5 验证很简单:建一个空指标,抄下面这段属性声明,编译后通过 EA 的 #resource 引用,挂上 EU 或 XAUUSD 图表看子窗口是否独立出现。外汇和贵金属杠杆高,子窗口 EA 一样可能因滑点错单,先用模拟盘跑通再上实盘。
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,可能让无关指标误响应。
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 会多出一个窗口,外汇贵金属杠杆高,测试请用模拟盘。
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交易的图形界面设置子窗口 } } }