DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象·综合运用
(3/3)· 从辅助面板到主控件,把 WinForms 风格的分隔容器跑通并测起来
- 画布对象的初始化与分栏属性落地
- 拆分容器与图形元素的底层构造
- 可分割画布容器的初始化落点
- 拆分容器属性落进结构体
- 图形对象结构的双向序列化
- 分隔容器与标签头的结构落点
- 标签控件里分页对象的循环构建细节
- 左右与上下箭头盒子的指针接驳
- GUI 控件类型的实例化分支
- 标签控件里怎么 new 出箭头按钮
- 按类型实例化图形控件的分发逻辑
- SplitContainer 面板类的落地写法
- 分屏容器面板的构造与擦除接口
- 拆分容器面板的受保护构造细节
- 分割容器面板的构造与样式固化
- 分栏面板的边框绘制与擦除逻辑
- 拆分容器里动态建图的底层分支
- GUI 元素类型的分支实例化
- 标签与箭头控件的实例化分支
- SplitContainer 控件对象类
- 拆分容器面板的接口与指针取法
- 拆分容器面板的尺寸与折叠控制
- 分割容器的分隔条与面板锁定接口
- 正文
- 拆分容器的图形对象实例化逻辑
- 分栏容器里塞新元素的实际写法
- 拆分容器里按面板取控件的三种入口
- 面板类的头文件与对象工厂入口
- 用 switch 分发图形元素实例
- 面板控件的工厂分支怎么落
- 子对象配色与从属关系的自动接管
- WinForms 控件在 GroupBox 里的配色与边界分支
- 图形元素工厂的分发逻辑
- 控件工厂里的按钮与选项卡分支
- 标签页里按类型造图形对象的工厂分支
- GUI 控件类型的分支实例化
- GUI 控件工厂的尾段分支
- 把静态面板跑起来验证对象构建
- 在标签控件里嵌一个可分割双面板
- 面板循环显示与初始化收尾
- 后续要做的和能拿到的
「画布对象的初始化与分栏属性落地」
在 MT5 自定义图形库里,构造一个画布元素的第一步是把图表上下文和默认样式绑死。下面这段代码会在对象创建时读取当前图表的背景色、生成元素名、把字体设成 DEF_FONT 与 DEF_FONT_SIZE,并把文本锚点、坐标偏移全部清零,保证新对象不会带着随机初始值出场。 背景色数组做了三次 ArrayResize 到长度 1,分别对应常态、按下、悬停三态,初始都填 BackgroundColor()。这意味着后续若要做按钮高亮,只要改对应数组下标 0 的值即可,不用重新分配内存。 分栏容器(split container)的几个属性是容易被忽略的硬参数:固定面板设为 0,分隔条锁定为 true 不可拖,分隔距离 50 像素,分隔条宽 4 像素。若你在 EURUSD 或 XAUUSD 的副图里塞自适应面板,分隔距离 50 可能在大 DPI 屏上偏窄,倾向改成按 ChartGetInteger(...,CHART_SCALE) 动态算。外汇与贵金属波动剧烈,这类 UI 参数不影响成交但会干扰盯盘判断,调参前先在模拟盘图表验证。
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 分钟周期压一遍创建/销毁循环。
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 像素且拽不动。外汇与贵金属图表叠加这类自绘控件时波动剧烈,务必先在模拟盘验证渲染开销。
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,确认读写链路没断。
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,重载后分隔条位置大概率偏移,据此验证序列化链路是否完整。
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,能直观看到拖拽条变粗。外汇与贵金属图表挂这类自定义控件时波动行情下重绘可能卡顿,属于高风险界面的可控项。
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,避免和已有元素冲突。外汇与贵金属图表加载这类自定义控件存在刷新开销与滑点风险,参数不当可能造成界面卡顿。
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、子窗口与描述。
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 个分支都能编译通过。
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 的副图就能看到箭头按钮渲染。
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 运行时。 外汇与贵金属图表上挂这类自绘控件属于高风险扩展行为:一旦对象创建失败未处理,可能拖慢终端甚至造成图表卡死,建议在策略测试器里先以小周期验证。
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 本身的类就可以接上了。
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 就能看到编译器如何校验参数顺序。
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 里复刻这类面板,直接抄这段构造逻辑就能跑通基础骨架。
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) 属于冗余调用,可删掉其一。 外汇与贵金属图表加载这类面板属于低风险界面操作,但指标逻辑若接实时报价仍须警惕滑点与市场剧烈波动。
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 响应的风险,参数宜小。
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 对照你自己的元素枚举过一遍,缺的分支补上再编译。
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 以上周期做可视面板。
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 面板直接失效。
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 分钟数据跑通再上真仓。
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% 纵向空间,但外汇与贵金属波动剧烈,面板布局改动不影响持仓风险,仍需独立风控。
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 激活标志。下面这段声明展示了它的参数排布。
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,
◍ 正文
<span class="keyword">const</span> <span class="keyword">bool</span> redraw); <span class="comment">//--- Constructor</span> CSplitContainer(<span class="keyword">const</span> <span class="keyword">long</span> chart_id, <span class="keyword">const</span> <span class="keyword">int</span> subwindow, <span class="keyword">const</span> <span class="keyword">string</span> descript, <span class="keyword">const</span> <span class="keyword">int</span> x,
「拆分容器的图形对象实例化逻辑」
在自定义分栏容器类里,新建图形元素并不走统一工厂,而是用 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,否则永远拿不到对象实例。
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() 后重跑,能直接看到面板占比变化。外汇与贵金属图表上挂这类自绘控件属高风险操作,参数误设可能导致界面资源泄漏。
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 在初始化阶段静默退出。
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,对象可能完全透明不可见,这是调试面板不显示时的高频坑。 外汇与贵金属图表上挂这类自定义面板属于高风险操作,脚本逻辑错误可能干扰下单或遮挡价格,实盘前务必在策略测试器跑通。
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 属高风险操作,界面逻辑异常可能干扰下单。
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 面板无响应。
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 时不用再写一句配色,就能直接看透明无底效果。
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 调试时注意控件透明可能导致误触。
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 图,观察子窗口资源占用是否随元素数线性增长。
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 逻辑即可验证对象能否正常挂到副图上。 外汇与贵金属图表加载这类自定义控件存在平台兼容性风险,参数异常可能导致子窗口渲染失败,建议先在回测图表小范围试挂。
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。
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 的衍生类,若你只做简易选择器,用前者能省掉手动维护勾选状态的代码。
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。
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 验证同样面临点差滑点干扰,属高风险环境,仅作技术核对用。
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 是否在本版标准库已支持。
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),就能一次性显示多块面板;若图表空白,优先检查面板名拼接是否与创建时完全一致。
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 挂一个图表就能复现事件响应,不用等新版出来再动手。 外汇和贵金属图表上跑这类自定义控件属于高风险操作,图形对象抢鼠标事件可能干扰手动下单,先在模拟盘确认稳定性再谈实盘。