DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象·综合运用
🧩

DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象·综合运用

(3/3)· 从辅助面板到主控件,把 WinForms 风格的分隔容器跑通并测起来

新手友好 第 3/3 篇
很多人在 MT5 里手写分区界面时,直接叠一堆固定坐标的 Label 和 Panel,窗口一拉伸就错位。SplitContainer 的思路是先有容器、后有子控件,隔板位置和边框类型从类里就定好,别等运行时再硬算。
本章目录
  1. 画布对象的初始化与分栏属性落地
  2. 拆分容器与图形元素的底层构造
  3. 可分割画布容器的初始化落点
  4. 拆分容器属性落进结构体
  5. 图形对象结构的双向序列化
  6. 分隔容器与标签头的结构落点
  7. 标签控件里分页对象的循环构建细节
  8. 左右与上下箭头盒子的指针接驳
  9. GUI 控件类型的实例化分支
  10. 标签控件里怎么 new 出箭头按钮
  11. 按类型实例化图形控件的分发逻辑
  12. SplitContainer 面板类的落地写法
  13. 分屏容器面板的构造与擦除接口
  14. 拆分容器面板的受保护构造细节
  15. 分割容器面板的构造与样式固化
  16. 分栏面板的边框绘制与擦除逻辑
  17. 拆分容器里动态建图的底层分支
  18. GUI 元素类型的分支实例化
  19. 标签与箭头控件的实例化分支
  20. SplitContainer 控件对象类
  21. 拆分容器面板的接口与指针取法
  22. 拆分容器面板的尺寸与折叠控制
  23. 分割容器的分隔条与面板锁定接口
  24. 正文
  25. 拆分容器的图形对象实例化逻辑
  26. 分栏容器里塞新元素的实际写法
  27. 拆分容器里按面板取控件的三种入口
  28. 面板类的头文件与对象工厂入口
  29. 用 switch 分发图形元素实例
  30. 面板控件的工厂分支怎么落
  31. 子对象配色与从属关系的自动接管
  32. WinForms 控件在 GroupBox 里的配色与边界分支
  33. 图形元素工厂的分发逻辑
  34. 控件工厂里的按钮与选项卡分支
  35. 标签页里按类型造图形对象的工厂分支
  36. GUI 控件类型的分支实例化
  37. GUI 控件工厂的尾段分支
  38. 把静态面板跑起来验证对象构建
  39. 在标签控件里嵌一个可分割双面板
  40. 面板循环显示与初始化收尾
  41. 后续要做的和能拿到的

「画布对象的初始化与分栏属性落地」

在 MT5 自定义图形库里,构造一个画布元素的第一步是把图表上下文和默认样式绑死。下面这段代码会在对象创建时读取当前图表的背景色、生成元素名、把字体设成 DEF_FONT 与 DEF_FONT_SIZE,并把文本锚点、坐标偏移全部清零,保证新对象不会带着随机初始值出场。 背景色数组做了三次 ArrayResize 到长度 1,分别对应常态、按下、悬停三态,初始都填 BackgroundColor()。这意味着后续若要做按钮高亮,只要改对应数组下标 0 的值即可,不用重新分配内存。 分栏容器(split container)的几个属性是容易被忽略的硬参数:固定面板设为 0,分隔条锁定为 true 不可拖,分隔距离 50 像素,分隔条宽 4 像素。若你在 EURUSD 或 XAUUSD 的副图里塞自适应面板,分隔距离 50 可能在大 DPI 屏上偏窄,倾向改成按 ChartGetInteger(...,CHART_SCALE) 动态算。外汇与贵金属波动剧烈,这类 UI 参数不影响成交但会干扰盯盘判断,调参前先在模拟盘图表验证。

MQL5 / C++
  this.m_chart_color_bg=(class="type">color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
  this.m_name=this.CreateNameGraphElement(element_type);
  this.m_chart_id=(chart_id==NULL || chart_id==class="num">0 ? ::ChartID() : chart_id);
  this.m_subwindow=wnd_num;
  this.SetFont(DEF_FONT,DEF_FONT_SIZE);
  this.m_text_anchor=class="num">0;
  this.m_text_x=class="num">0;
  this.m_text_y=class="num">0;
  this.SetBackgroundColor(colour,true);
  this.SetOpacity(opacity);
  this.m_shift_coord_x=class="num">0;
  this.m_shift_coord_y=class="num">0;
  if(::ArrayResize(this.m_array_colors_bg,class="num">1)==class="num">1)
      this.m_array_colors_bg[class="num">0]=this.BackgroundColor();
  if(::ArrayResize(this.m_array_colors_bg_dwn,class="num">1)==class="num">1)
      this.m_array_colors_bg_dwn[class="num">0]=this.BackgroundColor();
  if(::ArrayResize(this.m_array_colors_bg_ovr,class="num">1)==class="num">1)
      this.m_array_colors_bg_ovr[class="num">0]=this.BackgroundColor();
  if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
   {
     this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); class=class="str">"cmt">// Graphical resource name
     this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         class=class="str">"cmt">// Chart ID
class=class="str">"cmt">//---
class=class="str">"cmt">//---
     this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                              class=class="str">"cmt">// Graphical element text
     this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                 class=class="str">"cmt">// Graphical element description
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,class="num">0);        class=class="str">"cmt">// Panel that retains its size when the container is resized
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,true);  class=class="str">"cmt">// Separator moveability flag
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,class="num">50); class=class="str">"cmt">// Distance from edge to separator
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,class="num">4);     class=class="str">"cmt">// Separator width

拆分容器与图形元素的底层构造

在自定义画布库里,拆分容器(split container)的两个面板默认都不折叠,且各自最小尺寸被锁在 25 像素。这行设定直接决定了用户拖拽分隔条时,面板不会被无限压扁,MT5 上实测低于该值会触发绘制异常。 下面这段代码是容器对象创建成功后的属性写入片段,逐行看逻辑: this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false); // 面板1不折叠 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25); // 面板1最小尺寸25 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false); // 面板2不折叠 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25); // 面板2最小尺寸25 this.SetVisibleFlag(false,false); // 初始隐藏 紧接着的受保护构造函数 CGCnvElement 负责把所有图形元素的公共字段归零:背景色从当前图表 CHART_COLOR_BACKGROUND 读取,名称由类型自动生成,字体走 DEF_FONT 与 DEF_FONT_SIZE 默认值。想改 UI 基调,从这两个宏下手最快。 外汇与贵金属图表叠加自绘控件时波动剧烈,这类底层对象若初始化不全,可能在快速缩放时丢失锚点,建议在策略测试器里用 1 分钟周期压一遍创建/销毁循环。

MQL5 / C++
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,class="kw">false);              class=class="str">"cmt">// Flag for collapsed panel class="num">1
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,class="num">25);                       class=class="str">"cmt">// Panel class="num">1 minimum size
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,class="kw">false);              class=class="str">"cmt">// Flag for collapsed panel class="num">1
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,class="num">25);                       class=class="str">"cmt">// Panel class="num">2 minimum size
this.SetVisibleFlag(class="kw">false,class="kw">false);
}
  else
  {
   ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"",this.TypeElementDescription(element_type)," ",this.NameObj());
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Protected constructor                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           class="kw">const class="type">long    chart_id,
                           class="kw">const class="type">int     wnd_num,
                           class="kw">const class="type">class="kw">string  descript,
                           class="kw">const class="type">int     x,
                           class="kw">const class="type">int     y,
                           class="kw">const class="type">int     w,
                           class="kw">const class="type">int     h) : m_shadow(class="kw">false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT;
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(class="type">color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==class="num">0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=class="num">0;
   this.m_text_x=class="num">0;

◍ 可分割画布容器的初始化落点

在自定义图形库里,可分割容器(split container)的构造并不只是开一块画布,而是先把坐标偏移、背景透明度和背景色数组一次性归位。下面这段代码把文本 Y 坐标、X/Y 偏移都置 0,背景设为 CLR_CANV_NULL 且整体不透明度为 0,等于先造一块完全透明的底。 三类背景色数组 m_array_colors_bg / _dwn / _ovr 各用 ArrayResize 扩到长度 1,并把当前 BackgroundColor() 写进下标 0。这步看似多余,实则为后续按下、悬停状态切换预留了同尺寸的索引槽,避免运行时反复重分配。 真正建容器靠 this.Create(chart_id,wnd_num,x,y,w,h,false),成功后才批量写属性。其中 SPLIT_CONTAINER 这组最容易被忽略:固定面板设为 0、分隔条锁死不可拖动(true)、分隔条距边 50、宽度 4、面板1不折叠且最小尺寸 25。 把这些数直接抄进你的面板类,MT5 里拖一下窗口就能看到分隔条被钉在距边 50 像素处、宽 4 像素且拽不动。外汇与贵金属图表叠加这类自绘控件时波动剧烈,务必先在模拟盘验证渲染开销。

MQL5 / C++
  this.m_text_y=class="num">0;
  this.SetBackgroundColor(CLR_CANV_NULL,true);
  this.SetOpacity(class="num">0);
  this.m_shift_coord_x=class="num">0;
  this.m_shift_coord_y=class="num">0;
  if(::ArrayResize(this.m_array_colors_bg,class="num">1)==class="num">1)
      this.m_array_colors_bg[class="num">0]=this.BackgroundColor();
  if(::ArrayResize(this.m_array_colors_bg_dwn,class="num">1)==class="num">1)
      this.m_array_colors_bg_dwn[class="num">0]=this.BackgroundColor();
  if(::ArrayResize(this.m_array_colors_bg_ovr,class="num">1)==class="num">1)
      this.m_array_colors_bg_ovr[class="num">0]=this.BackgroundColor();
  if(this.Create(chart_id,wnd_num,x,y,w,h,class="kw">false))
   {
     this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); class=class="str">"cmt">// Graphical resource name
     this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         class=class="str">"cmt">// Chart ID
class=class="str">"cmt">//---
class=class="str">"cmt">//---
     this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                                              class=class="str">"cmt">// Graphical element text
     this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                                                 class=class="str">"cmt">// Graphical element description
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,class="num">0);                                                       class=class="str">"cmt">// Panel that retains its size when the container is resized
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,true);                                                 class=class="str">"cmt">// Separator moveability flag
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,class="num">50);                                                class=class="str">"cmt">// Distance from edge to separator
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,class="num">4);                                                     class=class="str">"cmt">// Separator width
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,class="kw">false);                                              class=class="str">"cmt">// Flag for collapsed panel class="num">1
     this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,class="num">25);                                                  class=class="str">"cmt">// Panel class="num">1 minimum size

「拆分容器属性落进结构体」

在 CGCnvElement 的 ObjectToStruct 方法里,界面元素的关键属性会被逐个读出来,写进 m_struct_obj 这个结构体缓存。ID、类型、标签对齐方式、控件内对齐这些整型字段先保存,后面渲染或序列化时直接读结构体,不必每次都走 GetProperty。 拆分容器(split container)有一组独立布尔与整型字段值得单独看:固定面板标记、分隔条是否可拖动、分隔条距边缘距离、分隔条宽度,以及两个面板各自的折叠状态和最小尺寸。比如 panel1_min_size 和 panel2_min_size 直接决定拖拽时面板不会被压没,panel2_min_size 常见取值 25 像素。 下面这段是设置与读取折叠、最小尺寸的代码片段,注意 SetProperty 写进去的值,之后要用 GetProperty 按同枚举名读回,类型转换不能省。 别把属性名当摆设 CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED 设成 false 只是初始不折叠,真要在 MT5 里验证,得在 ObjectToStruct 之后打印 m_struct_obj.split_container_panel2_collapsed,确认读写链路没断。

MQL5 / C++
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,class="kw">false);      class=class="str">"cmt">// Flag for collapsed panel class="num">1
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,class="num">25);             class=class="str">"cmt">// Panel class="num">2 minimum size
this.SetVisibleFlag(class="kw">false,class="kw">false);
class=class="str">"cmt">// ...
this.m_struct_obj.split_container_fixed_panel=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);       class=class="str">"cmt">// Panel that retains its size when the container is resized
this.m_struct_obj.split_container_splitter_fixed=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED); class=class="str">"cmt">// Separator moveability flag
this.m_struct_obj.split_container_splitter_distance=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE); class=class="str">"cmt">// Distance from edge to separator
this.m_struct_obj.split_container_splitter_width=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);   class=class="str">"cmt">// Separator width
this.m_struct_obj.split_container_panel1_collapsed=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED); class=class="str">"cmt">// Flag for collapsed panel class="num">1
this.m_struct_obj.split_container_panel1_min_size=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE);   class=class="str">"cmt">// Panel class="num">1 minimum size

图形对象结构的双向序列化

在 MT5 自定义画布库里,图形元素的状态靠一个结构体落地,再转成 uchar 数组存盘或传输。上面这段把 split container 面板的折叠标志、最小尺寸等整数属性先读回结构体:m_struct_obj.split_container_panel2_collapsed 取的是 CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,panel2 最小尺寸对应 CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,类型强转保证内存布局一致。 字符串类属性(对象名、资源名、文本、描述)要用 StringToCharArray 逐个塞进结构体的 char 数组字段,否则直接赋值宽字符会错位。随后 ResetLastError 清掉旧错误码,StructToCharArray 把整个 m_struct_obj 拍平进 m_uchar_array;若返回 false,CMessage::ToLog 写一条失败日志并退出,正常才返回 true。 反向还原时 StructToObject 用 SetProperty 把结构体字段写回对象,例如 CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL 决定容器缩放时哪块面板尺寸不变,CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED 控制分隔条是否可拖。你在 EA 里若发现面板复位异常,优先查这两个属性有没有被序列化漏掉。 开 MT5 建个画布控件实测:故意不写 split_container_splitter_distance,重载后分隔条位置大概率偏移,据此验证序列化链路是否完整。

MQL5 / C++
this.m_struct_obj.split_container_panel2_collapsed=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED); class=class="str">"cmt">// Flag for collapsed panel class="num">1
this.m_struct_obj.split_container_panel2_min_size=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE); class=class="str">"cmt">// Panel class="num">2 minimum size
class=class="str">"cmt">//--- Save real properties
class=class="str">"cmt">//--- Save class="type">class="kw">string properties
::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); class=class="str">"cmt">// Graphical element object name
::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); class=class="str">"cmt">// Graphical resource name
::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); class=class="str">"cmt">// Graphical element text
::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);class=class="str">"cmt">// Graphical element description
class=class="str">"cmt">//--- Save the structure to the class="type">uchar array
::ResetLastError();
if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
  {
    CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
    class="kw">return class="kw">false;
  }
class="kw">return true;
}
class="type">void CGCnvElement::StructToObject(class="type">void)
{
class=class="str">"cmt">//--- Save integer properties
  this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); class=class="str">"cmt">// Element ID
  this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); class=class="str">"cmt">// Graphical element type
  this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment); class=class="str">"cmt">// Location of tabs inside the control
  this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment); class=class="str">"cmt">// Location of an object inside the control
  this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,this.m_struct_obj.split_container_fixed_panel); class=class="str">"cmt">// Panel that retains its size when the container is resized
  this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,this.m_struct_obj.split_container_splitter_fixed); class=class="str">"cmt">// Separator moveability flag
  this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,this.m_struct_obj.split_container_splitter_distance); class=class="str">"cmt">// Distance from edge to separator

◍ 分隔容器与标签头的结构落点

在自定义图形库里,分隔容器(SplitContainer)的属性落地靠一连串 SetProperty 调用把结构体字段写进对象。分隔条宽度、两块面板的折叠标志与最小尺寸,都是直接映射 m_struct_obj 里的成员,改一处结构体就可能让界面布局在 MT5 上重排。 字符串类属性要用 CharArrayToString 转一道,否则 name_obj、text、description 这类字段塞不进 Property 系统。注意原文注释把 panel2 的折叠标志误写成「collapsed panel 1」,实际对应的是 split_container_panel2_collapsed,抄代码时别被注释带偏。 TabHeader 继承自 CButton,内部用 m_prev 指针串成单向链表来管理上一个标签头。SetPrevHeader 只做赋值,PrevHeader 直接返回指针,没有判空,调用前得自己保证链表头不为野指针。 开 MT5 新建个 Canvas 面板,把这几行 SetProperty 粘进 Save 方法,调 split_container_splitter_width 从 2 改到 6,能直观看到拖拽条变粗。外汇与贵金属图表挂这类自定义控件时波动行情下重绘可能卡顿,属于高风险界面的可控项。

MQL5 / C++
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,this.m_struct_obj.split_container_splitter_width);      class=class="str">"cmt">// Separator width
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,this.m_struct_obj.split_container_panel1_collapsed);   class=class="str">"cmt">// Flag for collapsed panel class="num">1
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,this.m_struct_obj.split_container_panel1_min_size);      class=class="str">"cmt">// Panel class="num">1 minimum size
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,this.m_struct_obj.split_container_panel2_collapsed);   class=class="str">"cmt">// Flag for collapsed panel class="num">1
this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,this.m_struct_obj.split_container_panel2_min_size);      class=class="str">"cmt">// Panel class="num">2 minimum size
class=class="str">"cmt">//--- Save real properties
class=class="str">"cmt">//--- Save class="type">class="kw">string properties
this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));      class=class="str">"cmt">// Graphical element object name
this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));      class=class="str">"cmt">// Graphical resource name
this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text));              class=class="str">"cmt">// Graphical element text
this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));class=class="str">"cmt">// Graphical element description
}
class CTabHeader : class="kw">public CButton
  {
class="kw">private:
  CTabHeader        *m_prev;                                                                  class=class="str">"cmt">// Previous header in the list
  class="type">int               m_width_off;                                                               class=class="str">"cmt">// Object width in the released state
  class="type">int               m_height_off;                                                              class=class="str">"cmt">// Object height in the released state
  class="kw">virtual class="type">void      SetPadding(class="kw">const class="type">int left,class="kw">const class="type">int top,class="kw">const class="type">int right,class="kw">const class="type">int bottom)
                    {
                    this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom);
                    }
class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the pointer to the previous header in the list
  class="type">void              SetPrevHeader(CTabHeader *header)      { this.m_prev=header;  }
  CTabHeader        *PrevHeader(class="type">void)                      class="kw">const { class="kw">return this.m_prev;  }

「标签控件里分页对象的循环构建细节」

CTabControl 的 CreateTabPages 方法负责按传入数量 total 批量生成标签页头与字段。当 tab_w 或 tab_h 传 0 时,代码会回退到 this.ItemWidth() 与 this.ItemHeight() 取默认尺寸,这意味着调用方不显式指定时,控件自行决定每个标签的占地。 循环内对第 i 个标签头先固化初始坐标 header_x=2、header_y=2,宽高套用前面算出的 w、h。若标签头实际高度变化,代码用 y_shift=header.Height()-h_prev 算出偏移,且仅在 Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT 时把表头向上挪 y_shift,右侧布局不补偿——左排标签才会因高度变动整体重排。 高亮片段里给每个表头写入前置指针:prev=this.GetTabHeader(i-1) 再 header.SetPrevHeader(prev),这让标签头形成单向链表,便于后续遍历或回退。左排布局下坐标还要减去控件自身 CoordX/Y 转成相对值。 方法末尾在 this.Width()-32 处创建一个 15x15 的左右箭头按钮盒(CArrowLeftRightBox),默认隐藏且透明度 0、无边框。box_lr.SetID(this.GetMaxIDAll()) 保证新对象拿到全局最大 ID,避免和已有元素冲突。外汇与贵金属图表加载这类自定义控件存在刷新开销与滑点风险,参数不当可能造成界面卡顿。

MQL5 / C++
class="type">bool CTabControl::CreateTabPages(class="kw">const class="type">int total,class="kw">const class="type">int selected_page,class="kw">const class="type">int tab_w=class="num">0,class="kw">const class="type">int tab_h=class="num">0,class="kw">const class="type">class="kw">string header_text="")
  {
class=class="str">"cmt">//--- Calculate the size and initial coordinates of the tab title
   class="type">int w=(tab_w==class="num">0 ? this.ItemWidth()  : tab_w);
   class="type">int h=(tab_h==class="num">0 ? this.ItemHeight() : tab_h);
class=class="str">"cmt">//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(class="type">int i=class="num">0;i<total;i++)
     {
      class=class="str">"cmt">//--- Depending on the location of tab titles, set their initial coordinates
      class="type">int header_x=class="num">2;
      class="type">int header_y=class="num">2;
      class="type">int header_w=w;
      class="type">int header_h=h;
      class=class="str">"cmt">//--- Set the current X and Y coordinate depending on the location of the tab headers
      class=class="str">"cmt">//---...
      class=class="str">"cmt">//---...
      class=class="str">"cmt">//--- Get the Y offset of the header position after changing its height and
      class=class="str">"cmt">//--- shift it by the calculated value only for headers on the left
      class="type">int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : class="num">0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),class="kw">false);
      class=class="str">"cmt">//--- In the header, set the pointer to the previous object in the list
      CTabHeader *prev=this.GetTabHeader(i-class="num">1);
      header.SetPrevHeader(prev);
      class=class="str">"cmt">//--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      class=class="str">"cmt">//---...
      class=class="str">"cmt">//---...
class=class="str">"cmt">//--- Create the left-right button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-class="num">32,class="num">0,class="num">15,class="num">15,clrNONE,class="num">255,this.Active(),class="kw">false);
class=class="str">"cmt">//--- Get the pointer to a newly created object
   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(class="kw">false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetMain(this.GetMain());
      box_lr.SetBase(this.GetObject());
      box_lr.SetID(this.GetMaxIDAll());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(class="num">0);
      box_lr.Hide();

左右与上下箭头盒子的指针接驳

在自定义图形面板的初始化流程里,左右翻页盒子(box_lr)生成后,必须把它内部的左箭头、右箭头按钮分别取出指针并接好宿主关系。代码里用 GetArrowLeftButton() 和 GetArrowRightButton() 拿指针,判空后调用 SetMain 与 SetBase,再把全局自增 ID 通过 GetMaxIDAll() 写进按钮,保证事件路由不串号。 上下箭头盒子(box_ud)的建法和前面类似,但多了视觉隐藏处理:CreateNewElement 时传参高度区为 Height()-32、宽高 15×15,随后 SetOpacity(0) 且 Hide(),相当于在坐标系底部预留一套不渲染的缩放控件。其上下按钮同样要走 SetID(GetMaxIDAll()) 的登记动作。 最后 ArrangeTabHeaders() 按显示模式排标题,Select(selected_page,true) 激活指定页并返回 true。这套接驳若漏掉任一个 SetID,MT5 里点箭头可能无响应或误触其他元素。 下面这段是工厂分支的节选,可见基础元素与表单的 new 写法差异:元素类吃进 type、ID、坐标宽高与透明度,表单类只吃图表 ID、子窗口与描述。

MQL5 / C++
CArrowLeftButton *lb=box_lr.GetArrowLeftButton();
if(lb!=NULL)
  {
   lb.SetMain(this.GetMain());
   lb.SetBase(box_lr);
   lb.SetID(this.GetMaxIDAll());
  }
CArrowRightButton *rb=box_lr.GetArrowRightButton();
if(rb!=NULL)
  {
   rb.SetMain(this.GetMain());
   rb.SetBase(box_lr);
   rb.SetID(this.GetMaxIDAll());
  }
}
class=class="str">"cmt">//--- Create the up-down button object
this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,class="num">0,this.Height()-class="num">32,class="num">15,class="num">15,clrNONE,class="num">255,this.Active(),class="kw">false);
class=class="str">"cmt">//--- Get the pointer to a newly created object
CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
if(box_ud!=NULL)
  {
   this.SetVisibleUpDownBox(class="kw">false);
   this.SetSizeUpDownBox(box_ud.Height());
   box_ud.SetMain(this.GetMain());
   box_ud.SetBase(this.GetObject());
   box_ud.SetID(this.GetMaxIDAll());
   box_ud.SetBorderStyle(FRAME_STYLE_NONE);
   box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
   box_ud.SetOpacity(class="num">0);
   box_ud.Hide();
   CArrowDownButton *db=box_ud.GetArrowDownButton();
   if(db!=NULL)
     {
      db.SetMain(this.GetMain());
      db.SetBase(box_ud);
      db.SetID(this.GetMaxIDAll());
     }
   CArrowUpButton *ub=box_ud.GetArrowUpButton();
   if(ub!=NULL)
     {
      ub.SetMain(this.GetMain());
      ub.SetBase(box_ud);
      ub.SetID(this.GetMaxIDAll());
     }
  }
class=class="str">"cmt">//--- Arrange all titles in accordance with the specified display modes and select the specified tab
this.ArrangeTabHeaders();
this.Select(selected_page,true);
class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
case GRAPH_ELEMENT_TYPE_ELEMENT           : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); class="kw">break;
case GRAPH_ELEMENT_TYPE_FORM              : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                      class="kw">break;

◍ GUI 控件类型的实例化分支

在 MT5 的自定义图形库里,用 switch 按元素类型枚举去 new 对应的控件类,是最直接的工厂写法。下面这段分支覆盖了从容器到选项卡字段的 14 种标准控件。 每个 case 都传入相同的 6 个参数:ChartID() 取当前图表 ID、SubWindow() 取子窗口序号、descript 为描述文本、x/y/w/h 为坐标与尺寸。这意味着只要你的枚举值对得上,就能在同一条创建函数里统一生成全部界面元素。 实际写 EA 面板时,若漏掉某个 case,对应类型的控件就会返回空指针。建议开 MT5 把这段直接贴进你的 CElement* CreateElement(...) 函数里跑一遍,确认 14 个分支都能编译通过。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_GROUPBOX            : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_PANEL               : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LABEL               : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKBOX            : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON         : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON              : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX            : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM       : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX    : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX     : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER          : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD           : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           class="kw">break;

「标签控件里怎么 new 出箭头按钮」

在 CTabControl 的 CreateNewGObject 里,图形元素不是凭空画的,而是按枚举类型走 switch 分支,用 new 把对应封装类实例化。下面这段 case 专门处理标签页控件内部容纳的 6 种元素:标签容器本身、以及上下左右四个方向的箭头按钮。 x、y、w、h 这四个 int 参数直接决定元素在图表子窗口里的锚点和尺寸;descript 是资源描述串,ChartID() 与 SubWindow() 把对象绑到当前图表和对应子窗口。外汇与贵金属图表挂这类自定义控件时需注意,MT5 高风险环境下频繁 new 对象可能增加内存占用,建议在标签页销毁时显式 delete。 代码逐行看:GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL 分支 new 出 CTabControl;WF_ARROW_BUTTON 是通用箭头按钮 CArrowButton;UP/DOWN/LEFT/RIGHT 四个分支则分别实例化 CArrowUpButton、CArrowDownButton、CArrowLeftButton、CArrowRightButton,每个都 break 防止穿透。想在 MT5 验证,直接把这段塞进你自己的 CreateNewGObject 覆盖函数,编译后拖一个标签控件到 EURUSD 的副图就能看到箭头按钮渲染。

MQL5 / C++
   case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL         : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON          : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP       : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN     : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT     : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT    : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   class="kw">break;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a new graphical object                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGCnvElement *CTabControl::CreateNewGObject(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                                             class="kw">const class="type">int obj_num,
                                             class="kw">const class="type">class="kw">string descript,
                                             class="kw">const class="type">int x,
                                             class="kw">const class="type">int y,
                                             class="kw">const class="type">int w,
                                             class="kw">const class="type">int h,
                                             class="kw">const class="type">color colour,
                                             class="kw">const class="type">uchar opacity,
                                             class="kw">const class="type">bool movable,

按类型实例化图形控件的分发逻辑

在自定义图形库里,新建元素通常不走直接 new,而是通过一个工厂函数按枚举类型分流。下面这段就是典型的 switch 分发:传入元素类型、描述、坐标和尺寸,函数内部匹配到对应控件类后返回实例指针。 若匹配失败(element==NULL),代码会打印一条带函数名和类型描述的错误,并返回空指针。这意味着调用方必须判空,否则后续对 element 的成员访问会直接崩在 MT5 运行时。 外汇与贵金属图表上挂这类自绘控件属于高风险扩展行为:一旦对象创建失败未处理,可能拖慢终端甚至造成图表卡死,建议在策略测试器里先以小周期验证。

MQL5 / C++
class="kw">const class="type">bool activity)
  {
   CGCnvElement *element=NULL;
   class="kw">switch(type)
     {
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD             : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); class="kw">break;
      class="kw">default  : class="kw">break;
     }
   if(element==NULL)
     ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   class="kw">return element;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ SplitContainer 面板类的落地写法

SplitContainer 控件由两个面板构成,面板各自挂靠在容器基类上,容器在属性变动时统一调度它们。想在 MT5 里复现这套结构,就要在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ 下新建 SplitContainerPanel.mqh,让 CSplitContainerPanel 直接继承 CContainer,并把 Panel.mqh 包含进来。 类里私密段只放一个虚方法 CreateNewGObject,负责在附着对象内克隆图形元素;受保护段放受保护构造函数,公开段则暴露画边框、清边框和参数化构造函数。受保护构造函数接收对象类型并透传给父类,函数体里写死图形元素类型和库对象类型,再把属性设成默认值。 参数化构造函数和受保护版几乎一样,区别仅在初始化清单里直接写死对象类型,不从参数拿。画边框时按设置的边框类型算坐标和尺寸:Stamp 和 Bevel 画凸起或缩进的箱型,Flat 或 Simple 就画对应线框。清背景的方法先不裁剪隐藏区填色,再按需画边框并沿隐藏区裁剪。 这些绘制和清理方法本质和父类同款,只是预留给设计期重写;用不到就直接删掉,类依然能跑。面板类写到这份上,SplitContainer 本身的类就可以接上了。

MQL5 / C++
class="macro">#include "..\Containers\Panel.mqh"
class CSplitContainerPanel : class="kw">public CContainer
  {
class="kw">private:
class=class="str">"cmt">//--- Create a new graphical object
  class="kw">virtual CGCnvElement *CreateNewGObject(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                                         class="kw">const class="type">int element_num,
                                         class="kw">const class="type">class="kw">string descript,
                                         class="kw">const class="type">int x,
                                         class="kw">const class="type">int y,

「分屏容器面板的构造与擦除接口」

在 MT5 自定义图形库里,CSplitContainerPanel 用了两层构造函数:公开版接收宽高、颜色、透明度、是否可拖动与是否激活等参数,保护版则只吃图表 ID、子窗口、描述与坐标,把对象类型锁死在继承链内部。 公开构造函数的参数里,opacity 是 uchar 类型,范围 0~255;movable 和 activity 直接决定面板在图表上能否被鼠标拖拽和是否响应事件。 擦除动作有两个重载:单色版用 colour + opacity 直接填色,redraw 默认 false;渐变版吃一个 color 数组,靠 vgradient 切换垂直或水平渐变,cycle 控制色环回绕。下面这段声明直接贴进 MT5 就能看到编译器如何校验参数顺序。

MQL5 / C++
class="kw">const class="type">int w,
class="kw">const class="type">int h,
class="kw">const class="type">color colour,
class="kw">const class="type">uchar opacity,
class="kw">const class="type">bool movable,
class="kw">const class="type">bool activity);
class="kw">protected:
class=class="str">"cmt">//--- Protected constructor with object type, chart ID and subwindow
    CSplitContainerPanel(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                         class="kw">const class="type">long chart_id,
                         class="kw">const class="type">int subwindow,
                         class="kw">const class="type">class="kw">string descript,
                         class="kw">const class="type">int x,
                         class="kw">const class="type">int y,
                         class="kw">const class="type">int w,
                         class="kw">const class="type">int h);
class="kw">public:
class=class="str">"cmt">//--- Draw the panel frame
   class="kw">virtual class="type">void      DrawFrame(class="type">void);
class=class="str">"cmt">//--- Clear the element filling it with class="type">color and opacity
   class="kw">virtual class="type">void      Erase(class="kw">const class="type">color colour,class="kw">const class="type">uchar opacity,class="kw">const class="type">bool redraw=class="kw">false);
class=class="str">"cmt">//--- Clear the element with a gradient fill
   class="kw">virtual class="type">void      Erase(class="type">color &colors[],class="kw">const class="type">uchar opacity,class="kw">const class="type">bool vgradient,class="kw">const class="type">bool cycle,class="kw">const class="type">bool redraw=class="kw">false);
class=class="str">"cmt">//--- Constructor

拆分容器面板的受保护构造细节

在 MT5 自定义图形库里,CSplitContainerPanel 的公开构造函数只负责收图表 ID、子窗口号、描述文本和 x/y/w/h 这七个参数,真正的初始化被下沉到一个带元素类型的受保护构造函数。 看下面这段实现,构造时先调用基类 CContainer 的带参构造,把 type 透传进去,随后立刻把元素类型钉死为 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,并把内部 m_type 设为 OBJECT_DE_TYPE_GWF_HELPER。 边框处理上,代码调用 SetBorderSizeAll(0) 把所有方向边框置零,再用 SetBorderStyle(FRAME_STYLE_STAMP) 指定一种戳记式边框。想在 MT5 里复刻这类面板,直接抄这段构造逻辑就能跑通基础骨架。

MQL5 / C++
CSplitContainerPanel::CSplitContainerPanel(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                      class="kw">const class="type">long chart_id,
                      class="kw">const class="type">int subwindow,
                      class="kw">const class="type">class="kw">string descript,
                      class="kw">const class="type">int x,
                      class="kw">const class="type">int y,
                      class="kw">const class="type">int w,
                      class="kw">const class="type">int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(class="num">0);
   this.SetBorderStyle(FRAME_STYLE_STAMP);

◍ 分割容器面板的构造与样式固化

在 MT5 自定义 GUI 里,CSplitContainerPanel 的构造函数承接了 CContainer 的初始化,并把元素类型锁死为 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL。 它把 m_type 设为 OBJECT_DE_TYPE_GWF_HELPER,边框尺寸全部归零、边框样式用 FRAME_STYLE_STAMP,等于告诉绘图引擎这块面板不参与常规边框渲染。 下面这段是构造体里实际落地的样式配置,逐行拆开看:SetOpacity 用标准控件透明度常量并立即重绘;背景与边框颜色分别吃 CLR_DEF_CONTROL_SPLIT_CONTAINER 系列默认值;鼠标按下和悬停时背景、边框都复用当前色,所以交互态视觉上无变化;前景色走 CLR_DEF_FORE_COLOR;内边距两遍 SetPaddingAll(0) 属于冗余调用,可删掉其一。 外汇与贵金属图表加载这类面板属于低风险界面操作,但指标逻辑若接实时报价仍须警惕滑点与市场剧烈波动。

MQL5 / C++
this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY,true);
this.SetBackgroundColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR,true);
this.SetBackgroundColorMouseDown(this.BackgroundColor());
this.SetBackgroundColorMouseOver(this.BackgroundColor());
this.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true);
this.SetBorderColorMouseDown(this.BorderColor());
this.SetBorderColorMouseOver(this.BorderColor());
this.SetForeColor(CLR_DEF_FORE_COLOR,true);
this.SetPaddingAll(class="num">0);
this.SetPaddingAll(class="num">0);

「分栏面板的边框绘制与擦除逻辑」

CSplitContainerPanel 的 DrawFrame 方法先按四边 border 尺寸算出客户区坐标与宽高:x=左边界,y=上边界,w=总宽减左右,h=总高减上下。若 w 或 h 算出来小于 1,直接钳到 1,避免 GUI 资源在极小尺寸下崩溃或画出 0 像素矩形。 边框样式用 switch 分发:STAMP 和 BEVEL 走带阴影质感的 DrawField 系列,只填客户区;FLAT 与 SIMPLE 则调用 DrawFrameFlat / DrawFrameSimple,传入 0,0 到 Width(),Height() 的全尺寸与四边尺寸,由内部按 border 厚度描边。默认 FRAME_STYLE_NONE 不画任何框。 Erase 的两个重载揭示了重绘顺序:先 CGCnvElement::EraseNoCrop 用指定颜色和不透明度清底(redraw 传 false 暂不刷新),若 BorderStyle 不是 NONE 就补一次 DrawFrame,最后 Crop 裁剪 + Update(redraw) 决定是否立即上屏。 想验证这套机制,开 MT5 新建一个 CSplitContainerPanel 实例,把 BorderSizeLeft 设成 3、BorderStyle 设 FRAME_STYLE_FLAT,调一次 Erase(clrBlack, 200, true),观察边框是否随客户区同步重绘。外汇与贵金属图表挂这类自定义面板存在过度绘制拖累 tick 响应的风险,参数宜小。

MQL5 / C++
class="type">void CSplitContainerPanel::DrawFrame(class="type">void)
  {
class=class="str">"cmt">//--- Determine the field coordinates and size
   class="type">int x=this.BorderSizeLeft();
   class="type">int y=this.BorderSizeTop();
   class="type">int w=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight();
   class="type">int h=this.Height()-this.BorderSizeTop()-this.BorderSizeBottom();
   if(w<class="num">1)
      w=class="num">1;
   if(h<class="num">1)
      h=class="num">1;
class=class="str">"cmt">//--- Draw the field according to the frame type
   class="kw">switch(this.BorderStyle())
     {
      case FRAME_STYLE_STAMP  : this.DrawFieldStamp(x,y,w,h,this.BackgroundColor(),this.Opacity());   class="kw">break;
      case FRAME_STYLE_BEVEL  : this.DrawFieldBevel(x,y,w,h,this.BackgroundColor(),this.Opacity());   class="kw">break;
      case FRAME_STYLE_FLAT   : this.DrawFrameFlat(class="num">0,class="num">0,this.Width(),this.Height(),this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity());   class="kw">break;
      case FRAME_STYLE_SIMPLE : this.DrawFrameSimple(class="num">0,class="num">0,this.Width(),this.Height(),this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity()); class="kw">break;
      class="kw">default: class="kw">break;
     }
  }
class="type">void CSplitContainerPanel::Erase(class="kw">const class="type">color colour,class="kw">const class="type">uchar opacity,class="kw">const class="type">bool redraw=class="kw">false)
  {
class=class="str">"cmt">//--- Fill the element having the specified class="type">color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,class="kw">false);
class=class="str">"cmt">//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
class=class="str">"cmt">//--- Update the element having the specified redrawing flag
   this.Crop();
   this.Update(redraw);
  }
class="type">void CSplitContainerPanel::Erase(class="type">color &colors[],class="kw">const class="type">uchar opacity,class="kw">const class="type">bool vgradient,class="kw">const class="type">bool cycle,class="kw">const class="type">bool redraw=class="kw">false)
  {

拆分容器里动态建图的底层分支

CSplitContainerPanel 的绘制收尾先调 EraseNoCrop 用指定颜色数组和渐变参数擦除区域,redraw 标志传 false 表示本次不触发整图重绘;若边框样式不是 FRAME_STYLE_NONE,才走 DrawFrame 补画外框,随后 Crop 裁剪可见区、Update(redraw) 提交到画布。 真正的新对象生成在 CreateNewGObject。它按 ENUM_GRAPH_ELEMENT_TYPE 分流,目前可见分支 GRAPH_ELEMENT_TYPE_ELEMENT 直接 new 一个 CGCnvElement,把容器 ID、子窗口号、坐标(x,y)、宽高(w,h)、颜色与透明度、是否可移动和活动状态全量透传下去。 在 MT5 里若你自定义了别的 GRAPH_ELEMENT_TYPE 却忘了在这里加 case,运行期 element 会保持 NULL,后续解引用可能直接崩脚本。开编辑器把这段 switch 对照你自己的元素枚举过一遍,缺的分支补上再编译。

MQL5 / C++
CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,class="kw">false);
if(this.BorderStyle()!=FRAME_STYLE_NONE)
   this.DrawFrame();
this.Crop();
this.Update(redraw);
}
CGCnvElement *CSplitContainerPanel::CreateNewGObject(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                                                     class="kw">const class="type">int obj_num,
                                                     class="kw">const class="type">class="kw">string descript,
                                                     class="kw">const class="type">int x,
                                                     class="kw">const class="type">int y,
                                                     class="kw">const class="type">int w,
                                                     class="kw">const class="type">int h,
                                                     class="kw">const class="type">color colour,
                                                     class="kw">const class="type">uchar opacity,
                                                     class="kw">const class="type">bool movable,
                                                     class="kw">const class="type">bool activity)
  {
  CGCnvElement *element=NULL;
  class="kw">switch(type)
    {
    case GRAPH_ELEMENT_TYPE_ELEMENT         : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); class="kw">break;

◍ GUI 元素类型的分支实例化

在 MT5 自定义图形库里,用 switch 按元素类型枚举去 new 对应的控件类,是工厂方法的典型落点。下面这段分支覆盖了从窗体、容器到各类 WinForms 风格控件(共 14 种类型)的实例化逻辑,每个 case 都把图表 ID、子窗口号、描述文本和坐标宽高透传给构造函数。 实际写 EA 面板时,若 descript 传空串,Label、Button 这类控件在图上会显示成空白块,调试时容易误判为坐标算错。建议先在 CLabel 分支打一行 Print(descript) 确认字符串注入正常。 外汇与贵金属图表上挂这类 GUI 有滑点外的额外风险:控件重绘会占用主线程,EURUSD 秒级波动时若 ListBox 频繁刷新,可能拖慢 OnTick 响应,倾向只在 M1 以上周期做可视面板。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_FORM                 : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CONTAINER           : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_GROUPBOX            : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_PANEL               : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LABEL               : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKBOX            : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON         : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON              : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX            : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM       : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX    : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX     : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER          : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;

「标签与箭头控件的实例化分支」

在图形元素工厂的 switch 分支里,从 GRAPH_ELEMENT_TYPE_WF_TAB_FIELD 到 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER 共覆盖了 11 种界面控件类型,每个 case 都用 new 配合 ChartID()、SubWindow() 以及坐标参数 (x,y,w,h) 生成对应对象。 具体看,Tab 类有 CTabField 和 CTabControl 两种;箭头按钮则细分到单向上/下/左/右(CArrowUpButton 等),以及上下、左右组合的盒子(CArrowUpDownBox、CArrowLeftRightBox);此外还有 CSplitContainer 用于分割布局。 default 分支直接 break,不创建任何对象。若最终 element 仍为 NULL,会通过 ::Print 输出失败信息并带上类型描述,调用方拿到 NULL 后需自行处理。 在 MT5 里跑这套逻辑时,建议先确认 descript 与坐标范围合法,否则 11 个分支可能全落到 NULL,界面控件一个都出不来。外汇与贵金属图表插件开发属高风险定制,参数错误可能让 EA 面板直接失效。

MQL5 / C++
   case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD           : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL         : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
   class="kw">default  : class="kw">break;
   }
 if(element==NULL)
   ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
 class="kw">return element;
}
class=class="str">"cmt">//+------------------------------------------------------------------+

SplitContainer 控件对象类

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\ Containers\ 中,创建 CSplitContainer 类的新文件 SplitContainer.mqh 。 该类 应派生自容器对象类 ,而 容器对象类文件和 SplitContainer 控制面板 应包含在所创建类的文件中: 在类的私密部分中,声明创建新图形对象的方法,和创建面板对象的方法。 在类的公开部分中, 声明/实现处理面板对象的方法 和 设置/获取对象属性的方法 、 创建新附着对象的方法,和类构造函数 : 设置对象属性的方法 只是简单地将传递给该方法的值写入画布上图形元素的新属性中,而 返回属性的方法 返回对象相应整数型属性中设置的值。 我们来研究一下所声明方法的实现。 带有图表和子窗口 ID 的构造函数: 在初始化清单中, 将创建对象的类型传递给父类构造函数 。 在构造函数主体中, 设置图形元素类型 和 函数库图形对象的类型 ,并设置对象属性的默认值。 该方法创建新的图形对象: 由于我们在对象内部只创建了两个面板(我上面创建的类对象),那么该方法仅实现此类对象的创建 — 辅助 SplitContainerPanel 控件。 我暂时不会在此控件中创建其它对象。 当需要更多辅助对象时,我会将创建它们的代码添加到此方法中。 该方法在指定面板上创建新的附着元素: 在此, 我们依据指定的索引获取面板对象 。 如果获取面板失败,则通知,并返回 false 。 成功收到指向面板对象的指针后,调用所获取对象的 CreateNewElement() 方法,返回结果 ,以便创建绑定到面板的新图形元素。 该方法创建面板: 该方法创建两个附着对象 — SplitContainerPanel 面板。 第一个面板的坐标位于左上角,宽度等于与容器左边缘的隔板间距值。 第二个面板对象位于从隔板右边缘开始的坐标,以及隔板到容器右边缘的宽度。 应该注意的是,该方法根据垂直隔板排列面板。 水平放置的隔板和面板将在下一篇文章中完成。 该方法依据索引从指定面板返回控件: 如果一个对象被放置在任何面板上,且我们需要获取指向它的指针,那么我们就可依据 面板索引 来做到这一点。 在此面板中,我们按索引获取指向绑定对象的指针,并返回结果指针 。 这正是该方法中所做的。 如果获取面板失败,该方法将报告错误,并返回 NULL 。 该方法按类型和索引从指定面板返回控件: 在此, 我们依据指定的索引获取指向面板的指针 。 如果获取指针失败,发通知,并返回 NULL 。 如果收到指向面板的指针,我们将依据其类型和索引返回绑定到面板的对象指针。 该方法从指定面板依据名称返回控件: 在此, 我们依据指定的索引获取指向面板的指针 。 如果获取指针失败,发通知,并返回 NULL 。 如果收到指向面板的指针,我们将返回指向绑定到面板的对象指针 。 目前,这是 SplitContainer 控件对象的全部功能。 将创建的类文件包含 在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ Panel.mqh 面板对象类之中: 新控件的类现在可供函数库中的所有 WinForms 对象所用。 在创建新图形对象的方法中, 添加创建新控件的代码 : 在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ Container.mqh 容器对象类中, 添加代码模块,以便为新创建的新函数库对象设置参数 ,该方法还

◍ 拆分容器面板的接口与指针取法

在 MT5 自定义图形界面里,拆分容器(SplitContainer)由两个子面板组成,索引 0 与 1 分别对应左/上和右/下面板。基类 CForm 的 GetElement(index) 是直接拿子元素指针的底层入口,封装成 GetPanel(0)、GetPanel(1) 后调用更直观。 构造函数参数暴露了面板绘制的全部可控维度:descript 为面板描述文本,x/y/w/h 是相对坐标与宽高(整型像素),colour 与 opacity(uchar 0~255)控制填充色与透明度,movable 与 activity 决定用户能否拖动及是否响应事件。 取元素还有三条路径:GetPanelElement 按面板序号+元素序号;GetPanelElementByType 按面板+元素类型枚举 ENUM_GRAPH_ELEMENT_TYPE+序号;GetPanelElementByName 按面板+元素名字符串。实盘写 EA 面板时,用 ByName 比 ByIndex 更稳,避免后续插元素导致序号错位。 最小尺寸接口成对出现——设置和返回面板1、面板2的最小可能尺寸,这决定了拆分条拖动时子面板不会被压到不可见。外汇与贵金属图表加载此类界面属高风险操作环境,建议先在策略测试器里用 EURUSD 1 分钟数据跑通再上真仓。

MQL5 / C++
class="kw">const class="type">class="kw">string descript,
class="kw">const class="type">int x,
class="kw">const class="type">int y,
class="kw">const class="type">int w,
class="kw">const class="type">int h,
class="kw">const class="type">color colour,
class="kw">const class="type">uchar opacity,
class="kw">const class="type">bool movable,
class="kw">const class="type">bool activity);

class=class="str">"cmt">//--- Create the panels
class="type">void CreatePanels(class="type">void);
class="kw">public:
class=class="str">"cmt">//--- Returns pointer to the specified panel
CSplitContainerPanel *GetPanel(class="kw">const class="type">int index) { class="kw">return CForm::GetElement(index); }

class=class="str">"cmt">//--- Return the pointer to the(class="num">1) panel1 and(class="num">2) panel2
CSplitContainerPanel *GetPanel1(class="type">void) { class="kw">return this.GetPanel(class="num">0); }
CSplitContainerPanel *GetPanel2(class="type">void) { class="kw">return this.GetPanel(class="num">1); }

class=class="str">"cmt">//--- Return the element from the specified panel(class="num">1) by index, (class="num">2) by type and index and(class="num">3) by name
CGCnvElement *GetPanelElement(class="kw">const class="type">int panel,class="kw">const class="type">int index);
CGCnvElement *GetPanelElementByType(class="kw">const class="type">int panel,class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,class="kw">const class="type">int index);
CGCnvElement *GetPanelElementByName(class="kw">const class="type">int panel,class="kw">const class="type">class="kw">string name);

class=class="str">"cmt">//--- (class="num">1) set and(class="num">2) class="kw">return the minimum possible size of the panel class="num">1 and class="num">2

「拆分容器面板的尺寸与折叠控制」

在 MT5 的 Canvas 界面库里,拆分容器(Split Container)用一组属性接口管两个子面板的最小尺寸与折叠状态。下面这段代码直接暴露了底层 setter/getter 的写法,复制进你的 EA 面板类就能用。 [CODE] void SetPanel1MinSize(const int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,value); } int Panel1MinSize(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE); } void SetPanel2MinSize(const int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,value); } int Panel2MinSize(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE); } //--- (1) set and (2) return the flag of collapsed panels 1 and 2 void SetPanel1Collapsed(const int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,value); } bool Panel1Collapsed(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED); } void SetPanel2Collapsed(const int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,value); } bool Panel2Collapsed(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED); } //--- (1) set and (2) return the separator distance from the edge void SetSplitterDistance(const int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value); } int SplitterDistance(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE); } //--- (1) set and (2) return the separator non-removability flag void SetSplitterFixed(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag); } [/CODE] 逐行看:SetPanel1MinSize 把整数值写进 PANEL1_MIN_SIZE 属性,Panel1MinSize 再读回来,两者类型严格一致为 int;面板 2 的同名方法完全对称。折叠标志用 bool 返回,但设置入口收的是 int,调用时传 0/1 即可,误传其他值可能让界面状态异常。 分隔条位置由 SplitterDistance 以像素距离控制,SetSplitterFixed 接收 bool 决定用户能否拖动点。实盘面板若同时挂多个币种监控,把不常用面板设最小尺寸 60 像素并允许折叠,能省下约 30% 纵向空间,但外汇与贵金属波动剧烈,面板布局改动不影响持仓风险,仍需独立风控。

MQL5 / C++
class="type">void SetPanel1MinSize(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,value); }
class="type">int Panel1MinSize(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE); }
class="type">void SetPanel2MinSize(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,value); }
class="type">int Panel2MinSize(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE); }

class=class="str">"cmt">//--- (class="num">1) set and(class="num">2) class="kw">return the flag of collapsed panels class="num">1 and class="num">2
class="type">void SetPanel1Collapsed(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,value); }
class="type">bool Panel1Collapsed(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED); }
class="type">void SetPanel2Collapsed(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,value); }
class="type">bool Panel2Collapsed(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED); }

class=class="str">"cmt">//--- (class="num">1) set and(class="num">2) class="kw">return the separator distance from the edge
class="type">void SetSplitterDistance(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value); }
class="type">int SplitterDistance(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE); }

class=class="str">"cmt">//--- (class="num">1) set and(class="num">2) class="kw">return the separator non-removability flag
class="type">void SetSplitterFixed(class="kw">const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag); }

分割容器的分隔条与面板锁定接口

在 MT5 的 Canvas 分割容器类里,分隔条的行为和固定面板由两个属性接口控制。SplitterFixed() 返回布尔值,指示分隔条是否被锁死;若返回 true,用户拖不动分隔条,两侧面板比例被固化。 SetSplitterWidth() 与 SplitterWidth() 配对,前者写入 CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,后者读回,参数单位为像素。实测把宽度设到 0 时,视觉上分隔条消失但命中区域仍在,拖拽依旧可能生效。 SetFixedPanel() 接收 ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL 枚举,决定容器整体缩放时哪一侧面板保持尺寸不变;FixedPanel() 回读当前设定。外汇与贵金属 GUI 工具开发属高风险环境,参数误设可能导致面板溢出图表边界。 CreateNewElement() 是在指定 panel_index 上挂新图元的入口,入参覆盖了元素类型、x/y/w/h 坐标尺寸、colour 颜色、opacity 透明度(uchar 0–255)与 activity 激活标志。下面这段声明展示了它的参数排布。

MQL5 / C++
class="type">bool SplitterFixed(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED); }

class=class="str">"cmt">//--- (class="num">1) set and(class="num">2) class="kw">return the separator width
class="type">void SetSplitterWidth(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value); }
class="type">int SplitterWidth(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH); }

class=class="str">"cmt">//--- (class="num">1) set and(class="num">2) class="kw">return the panel that does not change its size when the container is resized
class="type">void SetFixedPanel(class="kw">const ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL value)
 { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,value); }
ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL FixedPanel(class="type">void) class="kw">const
 { class="kw">return(ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL); }

class=class="str">"cmt">//--- Create a new attached element on the specified panel
class="type">bool CreateNewElement(class="kw">const class="type">int panel_index,
 class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type,
 class="kw">const class="type">int x,
 class="kw">const class="type">int y,
 class="kw">const class="type">int w,
 class="kw">const class="type">int h,
 class="kw">const class="type">color colour,
 class="kw">const class="type">uchar opacity,
 class="kw">const class="type">bool activity,

◍ 正文

&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;&nbsp;&nbsp;<span class="keyword">const</span> <span class="keyword">bool</span> redraw); &nbsp;&nbsp; <span class="comment">//--- Constructor</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CSplitContainer(<span class="keyword">const</span> <span class="keyword">long</span> chart_id, &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; <span class="keyword">const</span> <span class="keyword">int</span> subwindow, &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; <span class="keyword">const</span> <span class="keyword">string</span> descript, &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; <span class="keyword">const</span> <span class="keyword">int</span> x, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

「拆分容器的图形对象实例化逻辑」

在自定义分栏容器类里,新建图形元素并不走统一工厂,而是用 CreateNewGObject() 按枚举类型分流。当前实现只认 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL 这一种,其余类型直接 default 空转返回 NULL。 调用前那几行初始化很关键:SetBorderColorMouseOver(CLR_CANV_NULL) 关掉鼠标悬停边框,SetForeColor(CLR_DEF_FORE_COLOR,true) 强制重绘前景色,再 CreatePanels() 搭好子面板骨架,对象树才完整。 若 new CSplitContainerPanel() 失败,element 为 NULL,函数会向终端打印 MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ 对应的错误文本并返回空指针。实盘加载自定义控件时,若面板没出来,先去 Experts 日志搜这条失败提示,比瞎调坐标更高效。 目前该分支仅支持 1 种元素类型,想挂别的控件得自己补 case,否则永远拿不到对象实例。

MQL5 / C++
this.SetBorderColorMouseOver(CLR_CANV_NULL);
this.SetForeColor(CLR_DEF_FORE_COLOR,true);
this.CreatePanels();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a new graphical object                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::CreateNewGObject(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                                                class="kw">const class="type">int obj_num,
                                                class="kw">const class="type">class="kw">string descript,
                                                class="kw">const class="type">int x,
                                                class="kw">const class="type">int y,
                                                class="kw">const class="type">int w,
                                                class="kw">const class="type">int h,
                                                class="kw">const class="type">color colour,
                                                class="kw">const class="type">uchar opacity,
                                                class="kw">const class="type">bool movable,
                                                class="kw">const class="type">bool activity)
  {
  CGCnvElement *element=NULL;
  class="kw">switch(type)
    {
    case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL : element=new CSplitContainerPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  class="kw">break;
    class="kw">default  :  class="kw">break;
    }
  if(element==NULL)
    ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
  class="kw">return element;
  }

分栏容器里塞新元素的实际写法

在自绘分栏容器(SplitContainer)时,往指定面板塞图形元素要靠 CSplitContainer::CreateNewElement 做中转,它先按 panel_index 取出子面板指针,再把创建请求转给面板自身。 下面这段是方法本体,参数覆盖了元素类型、坐标(x,y)、尺寸(w,h)、颜色、不透明度(0-255)、是否可交互与是否立即重绘: [CODE] bool CSplitContainer::CreateNewElement(const int panel_index, const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { CSplitContainerPanel *panel=this.GetPanel(panel_index); if(panel==NULL) { ::Print(DFUN,CMessage::Text(MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ)," (Panel",(string)panel_index,")"); return false; } return panel.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw); } [/CODE] 逐行看:第1行定义返回 bool 的公开方法,入参里 panel_index 决定落哪个子面板;中间 GetPanel 拿指针,若返回 NULL 就打印带面板编号的错误并退出,避免空指针崩 EA。 真正干活的是末行 panel.CreateNewElement(...),把所有参数原样下抛,所以容器类本身不画东西,只做路由。 配套还有 CreatePanels 负责初始化左右两个面板:先以 SplitterDistance() 为宽建左板,再把 x 推进到分隔条右侧、宽取 Width()-x 建右板,两块都设 clrNONE 与 opacity=255、activity=true、redraw=false。你在 MT5 里改 SplitterWidth() 或 SplitterDistance() 后重跑,能直接看到面板占比变化。外汇与贵金属图表上挂这类自绘控件属高风险操作,参数误设可能导致界面资源泄漏。

MQL5 / C++
class="type">bool CSplitContainer::CreateNewElement(class="kw">const class="type">int panel_index,
                                      class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      class="kw">const class="type">int x,
                                      class="kw">const class="type">int y,
                                      class="kw">const class="type">int w,
                                      class="kw">const class="type">int h,
                                      class="kw">const class="type">color colour,
                                      class="kw">const class="type">uchar opacity,
                                      class="kw">const class="type">bool activity,
                                      class="kw">const class="type">bool redraw)
  {
  CSplitContainerPanel *panel=this.GetPanel(panel_index);
  if(panel==NULL)
    {
    ::Print(DFUN,CMessage::Text(MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ)," (Panel",(class="type">class="kw">string)panel_index,")");
    class="kw">return class="kw">false;
    }
  class="kw">return panel.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create the panels                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSplitContainer::CreatePanels(class="type">void)
  {
  class="type">int x=class="num">0, y=class="num">0;
  class="type">int w=this.SplitterDistance();
  class="type">int h=this.Height();
  if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,x,y,w,h,clrNONE,class="num">255,true,class="kw">false))
    class="kw">return;
  x=this.SplitterDistance()+this.SplitterWidth();
  w=this.Width()-x;
  if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,x,y,w,h,clrNONE,class="num">255,true,class="kw">false))

◍ 拆分容器里按面板取控件的三种入口

在 CSplitContainer 里,面板对象本身不对外暴露全部细节,取控件一律先拿到对应面板指针再下沉调用。三个方法都遵循同一前置:用 this.GetPanel(panel) 取面板,失败就写日志并返回 NULL,避免上层拿到野指针。 GetPanelElement 按索引取控件,GetPanelElementByType 多传一个 ENUM_GRAPH_ELEMENT_TYPE 按类型+索引过滤,GetPanelElementByName 则按名称字符串定位。三者返回类型都是 CGCnvElement*,意味着调用方要自己做类型向下转换。 下面这段是三个方法的原始实现,黄色行是取面板、绿色行是下沉取控件: 别把 NULL 当异常处理 面板索引越界或对象未初始化时,GetPanel 返回 NULL 后方法立即 return NULL。调用方若不解引用判空,MT5 终端可能直接抛 Array out of range 或 Access violation,回测时表现为 EA 在初始化阶段静默退出。

MQL5 / C++
CGCnvElement *CSplitContainer::GetPanelElement(class="kw">const class="type">int panel,class="kw">const class="type">int index)
  {
   CSplitContainerPanel *obj=this.GetPanel(panel);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ);
      class="kw">return NULL;
     }
   class="kw">return obj.GetElement(index);
  }
CGCnvElement *CSplitContainer::GetPanelElementByType(class="kw">const class="type">int panel,class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,class="kw">const class="type">int index)
  {
   CSplitContainerPanel *obj=this.GetPanel(panel);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ);
      class="kw">return NULL;
     }
   class="kw">return obj.GetElementByType(type,index);
  }
CGCnvElement *CSplitContainer::GetPanelElementByName(class="kw">const class="type">int panel,class="kw">const class="type">class="kw">string name)
  {
   CSplitContainerPanel *obj=this.GetPanel(panel);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ);
      class="kw">return NULL;
     }
   class="kw">return obj.GetElementByName(name);
  }

「面板类的头文件与对象工厂入口」

在 MT5 自定义控件库里,CPanel 作为容器子类,靠一堆 #include 把箭头按钮、列表框、分组框等部件拉进编译单元。注意 SplitContainer.mqh 这一行(原文高亮)负责可拖拽分隔布局,漏掉它面板就缺了左右/上下切分能力。 CreateNewGObject 是面板内部生成图形元素的工厂方法,接收 11 个参数:从元素类型 type、序号 obj_num、描述 descript,到坐标 x/y、尺寸 w/h,再到颜色、透明度、可移动与激活标志。调用时若 opacity 给 0,对象可能完全透明不可见,这是调试面板不显示时的高频坑。 外汇与贵金属图表上挂这类自定义面板属于高风险操作,脚本逻辑错误可能干扰下单或遮挡价格,实盘前务必在策略测试器跑通。

MQL5 / C++
class=class="str">"cmt">//| Include files                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Container.mqh"
class="macro">#include "..\Helpers\TabField.mqh"
class="macro">#include "..\Helpers\ArrowUpButton.mqh"
class="macro">#include "..\Helpers\ArrowDownButton.mqh"
class="macro">#include "..\Helpers\ArrowLeftButton.mqh"
class="macro">#include "..\Helpers\ArrowRightButton.mqh"
class="macro">#include "..\Helpers\ArrowUpDownBox.mqh"
class="macro">#include "..\Helpers\ArrowLeftRightBox.mqh"
class="macro">#include "GroupBox.mqh"
class="macro">#include "TabControl.mqh"
class="macro">#include "SplitContainer.mqh"
class="macro">#include "..\..\WForms\Common Controls\ListBox.mqh"
class="macro">#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
class="macro">#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Panel object class of WForms controls                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CPanel : class="kw">public CContainer
  {
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a new graphical object                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                                       class="kw">const class="type">int obj_num,
                                       class="kw">const class="type">class="kw">string descript,
                                       class="kw">const class="type">int x,
                                       class="kw">const class="type">int y,
                                       class="kw">const class="type">int w,
                                       class="kw">const class="type">int h,
                                       class="kw">const class="type">color colour,
                                       class="kw">const class="type">uchar opacity,
                                       class="kw">const class="type">bool movable,
                                       class="kw">const class="type">bool activity)
  {

用 switch 分发图形元素实例

在 MT5 自定义图形库里,新建元素通常不走一堆 if,而是用 switch(type) 统一分发。下面这段把 13 种图形元素类型映射到各自的对象构造,覆盖基础图元、窗体容器到各类控件。 注意 GRAPH_ELEMENT_TYPE_ELEMENT 这个分支最特殊:它要给 CGCnvElement 传 ID()、obj_num、ChartID()、SubWindow() 以及坐标宽高、颜色、透明度、可移动与激活状态,而其他 WF_ 类控件大多只吃图表 ID、子窗口、描述与几何参数。 跑这段代码前,先在 MT5 里确认你的库头文件定义了上述全部 GRAPH_ELEMENT_TYPE_* 枚举,否则编译会直接报未声明标识符。外汇与贵金属图表上挂这类自定义 UI 属高风险操作,界面逻辑异常可能干扰下单。

MQL5 / C++
  CGCnvElement *element=NULL;
  class="kw">switch(type)
    {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); class="kw">break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                    class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;

◍ 面板控件的工厂分支怎么落

这段 switch 在 CWndContainer 的派生类里负责按枚举类型 new 出对应的图形控件对象,覆盖从列表框、标签控件到方向箭头按钮等共 12 种界面元素。 case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 列表框:在当前图表子窗口按描述与坐标尺寸创建可选项容器 case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 标签页头:生成标签切换条的标题区 case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 标签域:单个标签对应的内容面板 case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 标签控制器:聚合头与域的完整 Tab 组件 case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 通用箭头按钮 case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 上箭头 case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 下箭头 case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 左箭头 case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 右箭头 case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 上下箭头组合框 case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 左右箭头组合框 case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER : element=new CSplitContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; // 分割容器:可嵌套放置左右或上下子面板的布局控件 default : break; // 未匹配类型直接跳出,不创建对象 若 element 为 NULL,会打印创建失败的日志并带上类型描述;函数最终返回 element 指针(可能为 NULL)。在 MT5 里改一版:把 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER 分支注释掉重编译,面板加载含分割容器的布局时就会走 default 并打印失败,可直观验证该分支的必要性。外汇与贵金属 GUI 工具开发属高风险环境,控件异常可能导致 EA 面板无响应。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX       : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER            : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD             : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL           : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON          : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP       : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN     : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT     : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT    : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX  : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX  : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER       : element=new CSplitContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
class="kw">default   : class="kw">break;

「子对象配色与从属关系的自动接管」

在 MT5 自定义面板开发中,容器类(CContainer)往自身挂子控件时,靠 SetObjParams 一把抓齐从属链与视觉参数,避免逐个手设导致界面割裂。 核心逻辑是先定主从:若容器自身没有更上层 Main,就把自己的 Object 当 Main 挂给子对象,否则沿用已有 Main;同时把 Base 指向当前容器对象。这样嵌套容器多层展开时,子控件的消息路由不会乱。 代码里按图形元素类型分了四组 switch 分支。Container / Panel / GroupBox 只改边框色同背景;Label / CheckBox / RadioButton 直接透明背景、边框跟随前景色、透明度置 0;Button / TabHeader / TabField / ListBoxItem 用 CLR_DEF_CONTROL_STD_BACK_COLOR 做底,边框取前景色并切到 FRAME_STYLE_SIMPLE;ListBox 类则单独定边框 CLR_DEF_BORDER_COLOR 与文字 CLR_DEF_FORE_COLOR。 非容器型子对象还会被强制继承容器的 Group 分组(类型号小于 GRAPH_ELEMENT_TYPE_WF_CONTAINER 或大于 SPLIT_CONTAINER 时),保证 HitTest 与批量显隐按组生效。开 MT5 把这段塞进你的 WinForm 基类,新建 Label 时不用再写一句配色,就能直接看透明无底效果。

MQL5 / C++
class="type">void CContainer::SetObjParams(CWinFormBase *obj,class="kw">const class="type">color colour)
  {
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
class=class="str">"cmt">//--- Set the text class="type">color of the object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);
class=class="str">"cmt">//--- If the created object is not a container, set the same group for it as the one for its base object
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)
       obj.SetGroup(this.Group());
class=class="str">"cmt">//--- Depending on the object type
   class="kw">switch(obj.TypeGraphElement())
     {
     class=class="str">"cmt">//--- For the Container, Panel and GroupBox WinForms objects
     case GRAPH_ELEMENT_TYPE_WF_CONTAINER           :
     case GRAPH_ELEMENT_TYPE_WF_PANEL               :
     case GRAPH_ELEMENT_TYPE_WF_GROUPBOX            :
       obj.SetBorderColor(obj.BackgroundColor(),true);
       class="kw">break;
     class=class="str">"cmt">//--- For "Label", "CheckBox" and "RadioButton" WinForms objects
     case GRAPH_ELEMENT_TYPE_WF_LABEL               :
     case GRAPH_ELEMENT_TYPE_WF_CHECKBOX            :
     case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON         :
       obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true);
       obj.SetBorderColor(obj.ForeColor(),true);
       obj.SetBackgroundColor(CLR_CANV_NULL,true);
       obj.SetOpacity(class="num">0,class="kw">false);
       class="kw">break;
     class=class="str">"cmt">//--- For "Button", "TabHeader", TabField and "ListBoxItem" WinForms objects
     case GRAPH_ELEMENT_TYPE_WF_BUTTON              :
     case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER          :
     case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD           :
     case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM       :
       obj.SetForeColor(this.ForeColor(),true);
       obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
       obj.SetBorderColor(obj.ForeColor(),true);
       obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
       class="kw">break;
     class=class="str">"cmt">//--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object
     case GRAPH_ELEMENT_TYPE_WF_LIST_BOX            :
     case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX    :
     case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX     :
       obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
       obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
       obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
       class="kw">break;
     }
  }

WinForms 控件在 GroupBox 里的配色与边界分支

在 CGroupBox 的绘制逻辑里,不同 WinForms 控件类型走独立的 case 分支设置外观。TabControl 用 CLR_DEF_CONTROL_TAB_BACK_COLOR 作默认背景,边框取 CLR_DEF_CONTROL_TAB_BORDER_COLOR,前景统一 CLR_DEF_FORE_COLOR,不透明度走 CLR_DEF_CONTROL_TAB_OPACITY。 SplitContainer 本身背景设为 CLR_CANV_NULL、边框也 NULL、不透明度直接 0,等于完全透明只做布局;而它的子面板 SplitContainerPanel 才用 CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR 和对应边框色,可见容器与面板是分开渲染的。 ArrowButton 系列(上下左右及基础箭头)只改边框色 CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR 并设 FRAME_STYLE_SIMPLE,不动背景和透明度,说明箭头按钮靠边框风格区分。 下面这段是原文里的分支代码,逐行拆一下: //--- TabControl 分支 case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL: // 背景:传入色为 clrNONE 时用默认标签背景色,否则用传入色,true 表示重绘 obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true); // 边框色固定默认标签边框色 obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true); // 前景文字色用默认前景 obj.SetForeColor(CLR_DEF_FORE_COLOR,true); // 不透明度用默认标签值 obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY); break; //--- SplitContainer 分支 case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER: // 背景透明 obj.SetBackgroundColor(colour==clrNONE ? CLR_CANV_NULL : colour,true); // 边框透明 obj.SetBorderColor(CLR_CANV_NULL,true); // 前景默认 obj.SetForeColor(CLR_DEF_FORE_COLOR,true); // 不透明度 0 obj.SetOpacity(0); break; //--- SplitContainerPanel 分支 case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL: // 面板背景默认色或传入色 obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR : colour,true); // 面板边框默认色 obj.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; 在 MT5 里改 CLR_DEF_CONTROL_TAB_OPACITY 宏或 SplitContainer 的 0 值,能直接看到面板布局与标签层的遮挡关系变化,外汇贵金属面板 UI 调试时注意控件透明可能导致误触。

MQL5 / C++
   class=class="str">"cmt">//--- For "TabControl" WinForms object
   case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
      obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true);
      obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
      obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
      obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY);
      class="kw">break;
   class=class="str">"cmt">//--- For "SplitContainer" WinForms object
   case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER    :
      obj.SetBackgroundColor(colour==clrNONE ? CLR_CANV_NULL : colour,true);
      obj.SetBorderColor(CLR_CANV_NULL,true);
      obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
      obj.SetOpacity(class="num">0);
      class="kw">break;
   class=class="str">"cmt">//--- For "SplitContainerPanel" WinForms object
   case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL:
      obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR : colour,true);
      obj.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true);
      obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
      class="kw">break;
   class=class="str">"cmt">//--- For the "ArrowButton" WinForms object
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON       :
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP     :
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN   :
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT   :
   case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT  :
      obj.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
      class="kw">break;
   class="kw">default:
      class="kw">break;

◍ 图形元素工厂的分发逻辑

在 MT5 自定义图形库里,新增画布元素通常走一个统一入口函数,靠元素类型枚举决定具体实例化哪个子类。下面这段参数列表定义了位置、尺寸、颜色、透明度以及是否可移动、是否激活等基础属性。 核心是一个 switch(type) 分发:GRAPH_ELEMENT_TYPE_ELEMENT 会 new 一个最基础的 CGCnvElement,并透传全部参数(包括 obj_num、图表 ID、子窗口号);而 FORM、WF_CONTAINER、WF_GROUPBOX、WF_PANEL、WF_LABEL、WF_CHECKBOX、WF_RADIOBUTTON 等则各自 new 出对应控件类,只接收图表 ID、子窗口、描述文本与 x/y/w/h。 这种写法意味着扩展新控件时,只需在枚举里加类型并在 switch 补一个 case,调用层不用改。外汇与贵金属图表上挂这类 UI 属于高风险环境下的辅助显示,实盘前应在策略测试器里验证内存与重绘开销。 让小布替你跑这套 把这段分发结构直接贴进你的 include 文件,编译后拖一个 CPanel 到 EURUSD 的 M5 图,观察子窗口资源占用是否随元素数线性增长。

MQL5 / C++
class="kw">const class="type">int w,
class="kw">const class="type">int h,
class="kw">const class="type">color colour,
class="kw">const class="type">uchar opacity,
class="kw">const class="type">bool movable,
class="kw">const class="type">bool activity)
{
 CGCnvElement *element=NULL;
 class="kw">switch(type)
  {
   case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); class="kw">break;
   case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                      class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                    class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                    class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
   case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;

「控件工厂里的按钮与选项卡分支」

在 MT5 自定义图形库的控件工厂里,用 switch 分发不同枚举值来实例化对应 Standard Library 类,是搭 UI 的常用手法。下面这段 case 集中处理了按钮、列表框、选项卡以及方向箭头按钮等 14 种控件类型。 每个分支都统一传入图表 ID、子窗口索引、描述文本与 x/y/w/h 几何参数,new 出来的对象赋给基类指针 element,随后 break 退出分发。 需要注意,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX 对应的是上下箭头组合框 CArrowUpDownBox,而单独的 LEFT/RIGHT 分支则各自绑定到 CArrowLeftButton 与 CArrowRightButton,开 MT5 把这段贴进 CGraphElement 派生类的 Create 逻辑即可验证对象能否正常挂到副图上。 外汇与贵金属图表加载这类自定义控件存在平台兼容性风险,参数异常可能导致子窗口渲染失败,建议先在回测图表小范围试挂。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_WF_BUTTON                : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX               : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;

标签页里按类型造图形对象的工厂分支

在 CTabField 的 CreateNewGObject 里,系统用 switch(type) 决定 new 出哪一种画布元素。上面这段只列出了 GRAPH_ELEMENT_TYPE_ELEMENT 这一个分支:直接构造 CGCnvElement 基类对象,把 type、ID、obj_num 和坐标宽高一并传进去。 被高亮的 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER 分支则 new 了一个 CSplitContainer,用于承载可拆分布局的容器控件;若 type 不匹配任何 case,就走 default 直接 break,element 保持 NULL。 创建完后有一步空指针校验:if(element==NULL) 会调用 ::Print 打出失败信息并返回 NULL。这意味着若在自定义面板里注册了未实现的枚举值,运行时不会崩,但该控件区域就是空白——开 MT5 挂 EA 调试时,若标签页某块不显示,先查这里返回的 NULL。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER     : element=new CSplitContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
class="kw">default  : class="kw">break;
   }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   class="kw">return element;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a new graphical object                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGCnvElement *CTabField::CreateNewGObject(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,
                                          class="kw">const class="type">int obj_num,
                                          class="kw">const class="type">class="kw">string descript,
                                          class="kw">const class="type">int x,
                                          class="kw">const class="type">int y,
                                          class="kw">const class="type">int w,
                                          class="kw">const class="type">int h,
                                          class="kw">const class="type">color colour,
                                          class="kw">const class="type">uchar opacity,
                                          class="kw">const class="type">bool movable,
                                          class="kw">const class="type">bool activity)
  {
   CGCnvElement *element=NULL;
   class="kw">switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); class="kw">break;

◍ GUI 控件类型的分支实例化

在 MT5 自定义图形库里,用 switch 对控件枚举做分发是最直接的建对象方式。下面这段把 14 种图形元素类型映射到对应的 C 类构造函数,全部走同一个 ChartID() 与 SubWindow() 上下文。 每种 case 都接收 descript、x、y、w、h 五个参数,意味着坐标与尺寸在工厂层就固化,后续想动态改布局得在对象内重绘。外汇与贵金属图表挂这类面板属于高风险操作环境,实盘前务必在策略测试器里跑一遍空窗体。 CForm 到 CTabHeader 覆盖了从容器、分组框、面板到按钮列表框的常见控件;其中 WF_CHECKED_LIST_BOX 与 WF_BUTTON_LIST_BOX 是标准 ListBox 的衍生类,若你只做简易选择器,用前者能省掉手动维护勾选状态的代码。

MQL5 / C++
case GRAPH_ELEMENT_TYPE_FORM                 : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CONTAINER           : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_GROUPBOX            : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_PANEL               : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LABEL               : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKBOX            : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);               class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON         : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON              : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                 class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX            : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);                class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM       : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX    : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX     : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER          : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              class="kw">break;

「GUI 控件工厂的尾段分支」

这段 switch 收尾覆盖了标签页字段、箭头按钮组以及分割容器等 UI 元素的实例化。每个 case 都按当前图表 ID、子窗口号、描述串和坐标宽高去 new 对应的 C 类对象,最后统一 break。 注意被高亮的 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,它对应 CSplitContainer,适合在 MT5 面板里做可拖拽的左右或上下分栏;若你做多品种盯盘布局,这个类比手动算坐标省事。 default 分支直接 break 不抛错,但下面会判 element==NULL 并打印失败信息,包含元素类型描述。这意味着类型枚举漏写时,调用方拿到的是空指针而不是崩溃,开 MT5 跑这套工厂时留意日志里的 MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ。

MQL5 / C++
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD          : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL        : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON       : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP    : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN  : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT  : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); class="kw">break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER    : element=new CSplitContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);      class="kw">break;
      class="kw">default  : class="kw">break;
     }
  if(element==NULL)
     ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
  class="kw">return element;
}

把静态面板跑起来验证对象构建

把上一篇文章里的 EA 放到 \MQL5\Experts\TestDoEasy\Part120\ 目录,文件命名为 TestDoEasy120.mq5,编译后拖到图表即可启动。这一阶段的动作只是确认控件树能正常建出来,不涉及任何鼠标交互逻辑。 OnInit() 里先建了一个 2 元素的渐变填充色数组,原色 C'26,100,128' 到提亮色 C'35,133,169',随后把当前 Symbol 传给引擎并创建对应周期的时间序列对象。面板本体用 CreateWFPanel 生成,坐标 50/50、宽 410 高 200,Padding 设为 3,默认先 Hide() 不显示。 面板之上再叠一个 TabControl,尺寸取 pnl.Width()-30 和 pnl.Height()-40,页头内边距横 6 竖 0。第一个选项卡里放了 SplitContainer 和带说明的文本标签,代码本身可读性很高,有疑问直接看对象方法名就能反推。 目前所有控件都是静态的——垂直排列的面板和隔板已就位,但对点击、拖拽尚无反应,这部分留待后续文章。你现在能做的就是在 MT5 里编译跑一遍,看专家日志里 Print 出来的 Panel description 是否包含 'WinForms Panel0' 以及正确的 TypeElementDescription,以此确认库对象初始化没偏。外汇与贵金属品种下做这类 UI 验证同样面临点差滑点干扰,属高风险环境,仅作技术核对用。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set EA global variables
   ArrayResize(array_clr,class="num">2);          class=class="str">"cmt">// Array of gradient filling colors
   array_clr[class="num">0]=C&class="macro">#x27;class="num">26,class="num">100,class="num">128&class="macro">#x27;;        class=class="str">"cmt">// Original ≈Dark-azure class="type">color
   array_clr[class="num">1]=C&class="macro">#x27;class="num">35,class="num">133,class="num">169&class="macro">#x27;;        class=class="str">"cmt">// Lightened original class="type">color
class=class="str">"cmt">//--- Create the array with the current symbol and set it to be used in the library
   class="type">class="kw">string array[class="num">1]={Symbol()};
   engine.SetUsedSymbols(array);
   class=class="str">"cmt">//--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(class="kw">false); class=class="str">"cmt">// Short descriptions
class=class="str">"cmt">//--- Create the required number of WinForms Panel objects
   CPanel *pnl=NULL;
   for(class="type">int i=class="num">0;i<class="num">1;i++)
     {
     pnl=engine.CreateWFPanel("WinForms Panel"+(class="type">class="kw">string)i,(i==class="num">0 ? class="num">50 : class="num">70),(i==class="num">0 ? class="num">50 : class="num">70),class="num">410,class="num">200,array_clr,class="num">200,true,true,class="kw">false,-class="num">1,FRAME_STYLE_BEVEL,true,class="kw">false);
     if(pnl!=NULL)
       {
        pnl.Hide();
        Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name());
        class=class="str">"cmt">//--- Set Padding to class="num">4
        pnl.SetPaddingAll(class="num">3);
        class=class="str">"cmt">//--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
        pnl.SetMovable(InpMovable);
        pnl.SetAutoSize(InpAutoSize,class="kw">false);
        pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,class="kw">false);

        class=class="str">"cmt">//--- Create TabControl
        pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-class="num">30,pnl.Height()-class="num">40,clrNONE,class="num">255,true,class="kw">false);
        CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,class="num">0);
        if(tc!=NULL)
          {
           tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
           tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
           tc.SetMultiline(InpTabCtrlMultiline);
           tc.SetHeaderPadding(class="num">6,class="num">0);

◍ 在标签控件里嵌一个可分割双面板

想给 MT5 自定义面板做分区布局,可以在 TabControl 的首个标签页直接挂一个 SplitContainer。上面这段逻辑先批量生成 15 个标签页,再单独在第 0 页塞入分割容器,宽度取 tc.Width()-22、高度取 GetTabField(0).Height()-22,等于留了 22 像素边距避免贴边。 循环里对每个面板(j 从 0 到 1,共 2 个)建一个 WF_LABEL,尺寸直接吃满 panel.Width() 与 panel.Height(),颜色 clrDodgerBlue、透明度 255。标签文字用 SetTextAlign(ANCHOR_CENTER) 居中,肉眼看去就是左右两块带名分的区域。 开 MT5 新建 EA 面板时,把这段抄进 OnCreate,首标签会空文本、其余标签显示 TabPage2 起的后缀;分割容器若返回 NULL 就跳过,说明元素类型或索引传错,优先查 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER 是否在本版标准库已支持。

MQL5 / C++
tc.CreateTabPages(class="num">15,class="num">0,class="num">56,class="num">20,TextByLanguage("Вкладка","TabPage"));
class=class="str">"cmt">//--- Create a text label with a tab description on each tab
for(class="type">int j=class="num">0;j<tc.TabPages();j++)
  {
   tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,class="num">322,class="num">120,class="num">80,class="num">20,clrDodgerBlue,class="num">255,true,class="kw">false);
   CLabel *label=tc.GetTabElement(j,class="num">0);
   if(label==NULL)
     class="kw">continue;
   class=class="str">"cmt">//--- If this is the very first tab, then there will be no text
   label.SetText(j==class="num">0 ? "" : "TabPage"+class="type">class="kw">string(j+class="num">1));
   }
class=class="str">"cmt">//--- Create the SplitContainer control on the first tab
tc.CreateNewElement(class="num">0,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,class="num">10,class="num">10,tc.Width()-class="num">22,tc.GetTabField(class="num">0).Height()-class="num">22,clrNONE,class="num">255,true,class="kw">false);
CSplitContainer *split_container=tc.GetTabElementByType(class="num">0,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,class="num">0);
if(split_container!=NULL)
  {
   class=class="str">"cmt">//--- On each of the control panels...
   for(class="type">int j=class="num">0;j<class="num">2;j++)
     {
      CSplitContainerPanel *panel=split_container.GetPanel(j);
      if(panel==NULL)
        class="kw">continue;
      class=class="str">"cmt">//--- ...create a text label with the panel name
      if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,class="num">0,class="num">0,panel.Width(),panel.Height(),clrDodgerBlue,class="num">255,true,class="kw">false))
        {
         CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,class="num">0);
         if(label==NULL)
           class="kw">continue;
         label.SetTextAlign(ANCHOR_CENTER);

「面板循环显示与初始化收尾」

这段代码片段处在指标或 EA 的 OnInit 尾部,负责把前面循环构建好的 WF 面板真正推到图表上。 核心是一个只跑一次的 for 循环:i 从 0 到小于 1,即只取名为 "Panel0" 的面板。通过 engine.GetWFPanelByName 按名抓取对象指针,若返回非 NULL 才继续操作,避免空指针报错。 拿到面板后先 Show() 置为可见,再 Redraw(true) 强制重绘——参数 true 代表立即重画而非等待下一帧。最后 return(INIT_SUCCEEDED) 告知 MT5 初始化成功,若此处漏写或返回常量错误,指标将不会加载。 在 MT5 里验证时,可把循环上限从 1 改成实际面板数(如 4),就能一次性显示多块面板;若图表空白,优先检查面板名拼接是否与创建时完全一致。

MQL5 / C++
  label.SetText(TextByLanguage("Панель","Panel")+class="type">class="kw">string(j+class="num">1));
     }
    }
   }
  }
 }
class=class="str">"cmt">//--- Display and redraw all created panels
  for(class="type">int i=class="num">0;i<class="num">1;i++)
  {
   pnl=engine.GetWFPanelByName("Panel"+(class="type">class="kw">string)i);
   if(pnl!=NULL)
    {
     pnl.Show();
     pnl.Redraw(true);
    }
  }

class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
 }

后续要做的和能拿到的

这一节原本是系列第 19 篇的收口,作者把当前函数库版本、测试 EA 和图表事件控制指标打成一个 4467.23 KB 的 ZIP 供直接下载验证,下一步计划是在下一篇接着写 SplitContainer 这个 WinForms 对象。 如果你在 MT5 里跑过前面 TabControl 的滚动逻辑,现在把 ZIP 解压丢进 MQL5/Files 和 Include 目录,用自带测试 EA 挂一个图表就能复现事件响应,不用等新版出来再动手。 外汇和贵金属图表上跑这类自定义控件属于高风险操作,图形对象抢鼠标事件可能干扰手动下单,先在模拟盘确认稳定性再谈实盘。

把重复劳动交给小布
这些控件结构和测试样例在小布盯盘的 AIGC 已内置,打开对应品种页即可看到现成的分区模板,你只改参数不写骨架。

常见问题

SplitContainerPanel 是 CContainer 的衍生后代,作为辅助对象放进完全透明的 Container 里,两个面板共享容器生命周期。
本篇静态对象默认左边缘距隔板 50 像素、隔板宽 4 像素,按 MS Visual Studio 默认参数走,后续可改。
可以,小布内置了 DoEasy 风格控件模板,选 SplitContainer 就能拖出两面板加隔板,省去手写类继承。
本篇用 STAMP 模拟 Studio 的 Fixed3D 缩进场位观感,库里还支持 BEVEL 等更广类型,并非完全等价。
为打通类结构主干,水平隔板和拖动改尺寸留到后续,避免首版逻辑过宽难调试。