包含图形用户界面 (GUI) 的 EA 交易: 创建面板 (第一部分)·进阶篇
🖥️

包含图形用户界面 (GUI) 的 EA 交易: 创建面板 (第一部分)·进阶篇

(2/3)· 还在用 Print 调试 EA?把多品种信号塞进一个面板,肉眼盯盘效率直接换档

含代码示例偏理论 第 2/3 篇
很多人在 EA 里堆了一堆 IndicatorRelease 和注释输出,却从没想过把控件分层。结果就是切换品种要重初始化、信号散在日志里找不到。面板不是装饰,结构错了后面全得返工。

◍ GUI 元件清单与讲解顺序

做 MT5 面板时,这套程序直接吃下了 10 类 GUI 元件:控件表单 CWindow、状态条 CStatusBar、页面组 CTabs、输入栏位 CTextEdit、按钮 CButton、带下拉列表的组合框 CComboBox、复选框 CCheckBox、表格 CTable、标准图表 CStandardChart、进度条 CProgressBar。 实际工程里,像按钮或输入栏位这种往往要摆好几个实例,但讲解只挑每组里的一个元件拆,避免重复。 顺序就按上面列的来,从 CWindow 一路走到 CProgressBar,你开 MT5 的 MQL5 向导建个空 EA 把这几个类先 include 进来,能直观看到头文件依赖。

「搭一个能装控件的 MT5 表单壳」

做 GUI 第一步是把容器表单挂进程序窗口队列。调用 CWndContainer::AddWindow() 并传入 CWindow 对象,之后在 CreateWindow() 之前把属性一次设齐:尺寸、抬头字号、图表内拖动、边框拖拽改大小,以及四个按钮的可见性。 关闭、折叠、工具提示、全屏这四个按钮各自可单独开关,也能绑定 tooltip 文本。比如关闭按钮触发主表单的退出确认,全屏按钮再把表单铺满图表区域,再点一次还原。 属性设完传图表 ID、子窗口索引、标题和初始坐标 (1,1) 调 CreateWindow()。建议每加一种新元件就编译一次看效果,避免后期排错成本堆积。下面这段代码把 750×450 的表单和四个按钮的 tooltip 一次配好,直接抄进 CProgram 类就能跑。 [CODE] //+------------------------------------------------------------------+

//创建控件表单            

//+------------------------------------------------------------------+ bool CProgram::CreateWindow(const string caption_text) { //--- 把窗口指针加到窗口数组中 CWndContainer::AddWindow(m_window1); //--- 属性 m_window1.XSize(750); m_window1.YSize(450); m_window1.FontSize(9); m_window1.IsMovable(true); m_window1.ResizeMode(true); m_window1.CloseButtonIsUsed(true); m_window1.CollapseButtonIsUsed(true); m_window1.TooltipsButtonIsUsed(true); m_window1.FullscreenButtonIsUsed(true); //--- 设置工具提示 m_window1.GetCloseButtonPointer().Tooltip("Close"); m_window1.GetTooltipButtonPointer().Tooltip("Tooltips"); m_window1.GetFullscreenButtonPointer().Tooltip("Fullscreen"); m_window1.GetCollapseButtonPointer().Tooltip("Collapse/Expand"); //--- 创建表单 if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,1,1)) return(false); //--- return(true); } [/CODE] 代码逐行拆解: CWndContainer::AddWindow(m_window1); —— 把表单对象注册进 GUI 容器,缺这步后面所有元件都挂不上。 m_window1.XSize(750); / YSize(450); —— 表单宽高,按你控件布局改这两个数。 FontSize(9) —— 抬头字号,小屏可降到 8。 IsMovable(true) / ResizeMode(true) —— 允许图表内拖动和拖边框改尺寸。 CloseButtonIsUsed 到 FullscreenButtonIsUsed 四个 true —— 四个按钮全开,不需要的设 false 即可。 GetCloseButtonPointer().Tooltip("Close") 等四行 —— 给各按钮绑悬浮提示文字。 CreateWindow(m_chart_id,m_subwin,caption_text,1,1) —— 在指定图表子窗口、坐标 (1,1) 生成表单,失败返回 false。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建控件表单                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateWindow(class="kw">const class="type">class="kw">string caption_text)
  {
class=class="str">"cmt">//--- 把窗口指针加到窗口数组中
   CWndContainer::AddWindow(m_window1);
class=class="str">"cmt">//--- 属性
   m_window1.XSize(class="num">750);
   m_window1.YSize(class="num">450);
   m_window1.FontSize(class="num">9);
   m_window1.IsMovable(true);
   m_window1.ResizeMode(true);
   m_window1.CloseButtonIsUsed(true);
   m_window1.CollapseButtonIsUsed(true);
   m_window1.TooltipsButtonIsUsed(true);
   m_window1.FullscreenButtonIsUsed(true);
class=class="str">"cmt">//--- 设置工具提示
   m_window1.GetCloseButtonPointer().Tooltip("Close");
   m_window1.GetTooltipButtonPointer().Tooltip("Tooltips");
   m_window1.GetFullscreenButtonPointer().Tooltip("Fullscreen");
   m_window1.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
class=class="str">"cmt">//--- 创建表单
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,class="num">1,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//---
   class="kw">return(true);
  }

把状态条挂到窗口底部自动伸缩

做 MT5 面板时,状态条如果写死坐标,窗口一缩放就错位。用 EasyAndFast 的思路,把它绑到主窗口指针上,后续只调主元件就能带飞整组子控件。 具体做法是先调用 MainPointer 把表单对象传进去,再开 AutoXResizeMode 让宽度跟着表单走,右侧留 1 像素缩进;AnchorBottomWindowSide 设 true,高度变化时贴底自适应。本例状态条分 3 段,宽度数组给的是 {0,200,110},第一段弹性占剩余空间,后两段固定。 代码里 CreateStatusBar 跑完,立刻用 SetValue(0,"For Help, press F1") 写第一段文案,最后必须 CWndContainer::AddToElementsArray(0,m_status_bar) 登记——表单只有一个,索引填 0,漏了这步运行时找不到对象。 这种绑定逻辑在库里是通用范式,其余控件照此定制属性即可,不用每次重写布局数学。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建状态条                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateStatusBar(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap)
  {
class="macro">#define STATUS_LABELS_TOTAL class="num">3
class=class="str">"cmt">//--- 保存窗口指针
   m_status_bar.MainPointer(m_window1);
class=class="str">"cmt">//--- 属性
   m_status_bar.AutoXResizeMode(true);
   m_status_bar.AutoXResizeRightOffset(class="num">1);
   m_status_bar.AnchorBottomWindowSide(true);
class=class="str">"cmt">//--- 设置分段的数量以及它们的属性
   class="type">int width[STATUS_LABELS_TOTAL]={class="num">0,class="num">200,class="num">110};
   for(class="type">int i=class="num">0;i<STATUS_LABELS_TOTAL;i++)
      m_status_bar.AddItem(width[i]);
class=class="str">"cmt">//--- 创建控件
   if(!m_status_bar.CreateStatusBar(x_gap,y_gap))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 在状态条分段中设置文字
   m_status_bar.SetValue(class="num">0,"For Help, press F1");
class=class="str">"cmt">//--- 把对象加到总的对象组数组中
   CWndContainer::AddToElementsArray(class="num">0,m_status_bar);
   class="kw">return(true);
  }

◍ 页面组的对齐与自适应缩进

做 MT5 自定义面板时,页面组(Tab 容器)的摆放逻辑直接影响后续表单缩放是否变形。核心做法是:文字中间对齐、贴在工作区顶部,大小交给主元件区域自动调节,本例里不用手写宽高。 相对于主元件右缘留 3 像素、底缘留 25 像素缩进,表单被拖大时这两个偏移量保持不变,控件不会糊在一起。 加页面时把名称与宽度传进方法即可,下面这段创建两个页签(Trade / Positions,各宽 100)的代码可直接抄进 EA 的 CProgram 类验证。 别把缩进写成死数 右底偏移用 AutoXResizeRightOffset(3) 和 AutoYResizeBottomOffset(25) 锁定,比在 CreateTabs 外算坐标稳,窗口改尺寸也不会错位。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建页面组 class="num">1     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateTabs1(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap)
  {
class="macro">#define TABS1_TOTAL class="num">2
class=class="str">"cmt">//--- 保存指向主元件的指针
   m_tabs1.MainPointer(m_window1);
class=class="str">"cmt">//--- 属性
   m_tabs1.IsCenterText(true);
   m_tabs1.PositionMode(TABS_TOP);
   m_tabs1.AutoXResizeMode(true);
   m_tabs1.AutoYResizeMode(true);
   m_tabs1.AutoXResizeRightOffset(class="num">3);
   m_tabs1.AutoYResizeBottomOffset(class="num">25);
class=class="str">"cmt">//--- 使用指定的属性增加页面
   class="type">class="kw">string tabs_names[TABS1_TOTAL]={"Trade","Positions"};
   for(class="type">int i=class="num">0; i<TABS1_TOTAL; i++)
      m_tabs1.AddTab(tabs_names[i],class="num">100);
class=class="str">"cmt">//--- 创建控件
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 把对象加到总的对象组数组中
   CWndContainer::AddToElementsArray(class="num">0,m_tabs1);
   class="kw">return(true);
  }

「交易品种过滤与手数栏位的控件落地」

做 MT5 面板时,符号过滤输入栏位的主元件是 CTextEdit,外面套一个 CTabs 页面组。调用 CTabs::AddToElementsArray(0, m_symb_filter) 把该栏位挂到索引 0 的页面,否则运行时不会在对应页出现。 默认填入 "USD" 时,程序会扫出所有含 USD 的品种;多个符号用逗号分隔,例如 "EURUSD,GBPUSD,AUDUSD" 这种写法能被正常解析。栏位带复选框,取消勾选后文本被忽略、自动纳入侦测到的全部货币对。 宽度策略要注意:输入栏位设了 AutoXResizeMode(true) 且右侧留 90 像素缩进,Request 按钮才能永远贴着右边。CTextEdit 内部包了 CTextBox,想改具体行为得用 GetTextBoxPointer() 拿子元件指针再调,比如左侧缩进 100、空内容提示 "Example: EURUSD,GBP,NOK"、点击自动全选。 数字型栏位(如 Lot)靠 SpinEditMode(true) 切换,代码里设了 XSize=80、最大 1000、最小 0.01、步长 0.01、小数 2 位、默认值 0.1。外汇与贵金属杠杆高,这类手数上限若误设过大,实盘可能触发保证金暴雷,建议先开模拟盘验证。 下面这段是上述两个栏位的创建函数,可直接抄进 EA 的面板类里改。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建一个复选框,带有 "Symbols filter" 输入栏位                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateSymbolsFilter(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 保存指向主元件的指针
   m_symb_filter.MainPointer(m_tabs1);
class=class="str">"cmt">//---为页面做保留
   m_tabs1.AddToElementsArray(class="num">0,m_symb_filter);
class=class="str">"cmt">//--- 属性
   m_symb_filter.SetValue("USD"); class=class="str">"cmt">// "EUR,USD" "EURUSD,GBPUSD" "EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCHF"
   m_symb_filter.CheckBoxMode(true);
   m_symb_filter.AutoXResizeMode(true);
   m_symb_filter.AutoXResizeRightOffset(class="num">90);
   m_symb_filter.GetTextBoxPointer().XGap(class="num">100);
   m_symb_filter.GetTextBoxPointer().AutoXResizeMode(true);
   m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true);
   m_symb_filter.GetTextBoxPointer().DefaultText("Example: EURUSD,GBP,NOK");
class=class="str">"cmt">//--- 创建控件
   if(!m_symb_filter.CreateTextEdit(text,x_gap,y_gap))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 启用复选框
   m_symb_filter.IsPressed(true);
class=class="str">"cmt">//--- 把对象加到对象组的统一数组中
   CWndContainer::AddToElementsArray(class="num">0,m_symb_filter);
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建 "Lot" 输入栏位                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateLot(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 保存指向主元件的指针
   m_lot.MainPointer(m_tabs1);
class=class="str">"cmt">//---为页面做保留
   m_tabs1.AddToElementsArray(class="num">0,m_lot);
class=class="str">"cmt">//--- 属性
   m_lot.XSize(class="num">80);
   m_lot.MaxValue(class="num">1000);
   m_lot.MinValue(class="num">0.01);
   m_lot.StepValue(class="num">0.01);
   m_lot.SetDigits(class="num">2);
   m_lot.SpinEditMode(true);
   m_lot.SetValue((class="type">class="kw">string)class="num">0.1);
   m_lot.GetTextBoxPointer().XSize(class="num">50);
   m_lot.GetTextBoxPointer().AutoSelectionMode(true);
   m_lot.GetTextBoxPointer().AnchorRightWindowSide(true);
class=class="str">"cmt">//--- 创建控件
   if(!m_lot.CreateTextEdit(text,x_gap,y_gap))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 把对象加到对象组的统一数组中
   CWndContainer::AddToElementsArray(class="num">0,m_lot);
   class="kw">return(true);
  }

给EA面板接上卖单触发键

在 MT5 的 EA 图形界面里塞一个卖单按钮,核心不是画个方块,而是把交互状态拆清楚:常态、鼠标悬停、左键按下这三套背景与文字、边框色必须分别指定,否则实战里根本分不清按钮死活。 下面这段 CreateSell 把卖出键宽度锁在 80 像素,文字双向居中,背景用 C'255,51,51'(红),悬停变 C'255,100,100',按下转 C'195,0,0';文字恒白,边框恒黑。买入键同理,仅背景色组不同,其余属性照搬即可。 外汇与贵金属杠杆高,这类一键下单按钮若没做按下态反馈,容易在剧烈波动中重复发单,建议在 CWndContainer 注册后自己跑一遍悬停/点击回测。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建 &class="macro">#x27;卖出&class="macro">#x27; 按钮                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateSell(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 保存指向主元件的指针
   m_sell.MainPointer(m_tabs1);
class=class="str">"cmt">//---为页面做保留
   m_tabs1.AddToElementsArray(class="num">0,m_sell);
class=class="str">"cmt">//--- 属性
   m_sell.XSize(class="num">80);
   m_sell.IsCenterText(true);
   m_sell.BackColor(C&class="macro">#x27;class="num">255,class="num">51,class="num">51&class="macro">#x27;);
   m_sell.BackColorHover(C&class="macro">#x27;class="num">255,class="num">100,class="num">100&class="macro">#x27;);
   m_sell.BackColorPressed(C&class="macro">#x27;class="num">195,class="num">0,class="num">0&class="macro">#x27;);
   m_sell.LabelColor(clrWhite);
   m_sell.LabelColorHover(clrWhite);
   m_sell.LabelColorPressed(clrWhite);
   m_sell.BorderColor(clrBlack);
   m_sell.BorderColorHover(clrBlack);
   m_sell.BorderColorPressed(clrBlack);
class=class="str">"cmt">//--- 创建控件
   if(!m_sell.CreateButton(text,x_gap,y_gap))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 把元件指针加到数据库中
   CWndContainer::AddToElementsArray(class="num">0,m_sell);
   class="kw">return(true);
  }
把控件树交给小布生成
这些分级表单和页面组的骨架,小布盯盘的 AIGC 已内置模板,打开对应品种页就能直接拖出类似结构,你只填交易逻辑。

常见问题

标准库偏底层需自行处理坐标重绘,EasyAndFast 用表单层级封装了布局与事件,开发者专注功能即可。
通过表单保留句柄并仅切换可见页,不销毁窗口对象,从而在不重新加载 EA 情况下变更显示内容。
小布支持把本文的层级描述转成可预览的控件树草图,用于需求讨论,但实盘逻辑仍需在 MT5 内编译。
可接 spread、账号净值或最后成交时间,用库提供的文本更新接口按帧刷新,不占主图空间。
MQL5 输入框默认字符串往返,需显式 NormalizeDouble 并拦截非数字输入,否则下单手数会越界报错。