DoEasy. 控件 (第 2 部分): 操控 CPanel 类·进阶篇
(2/3)· 从时间帧切换丢对象到面板无鼠标响应,逐一堵上库里的旧坑
切换时间帧时图形对象悄无声息消失,日志却报已创建——这种隐性失效在自建指标里极容易被忽略。面板作为控件不响应鼠标,等于把交互骨架抽空,后续所有附着控件都成了摆设。
◍ 字体枚举与表单类的内存泄漏现场
在 MQL5 的 GUI 封装里,字体粗细用一组枚举映射到底层宏:从 FW_TYPE_DONTCARE 到 FW_TYPE_BLACK 共 16 档,分别对应 FW_DONTCARE、FW_THIN、FW_NORMAL、FW_BOLD 等系统字重常量,写自定义面板时直接套用即可避免硬编码数值。 CForm 类继承自 CGCnvElement,私有成员里挂了 m_list_elements(附件元素表)、m_animations(动画指针)、m_shadow_obj(阴影指针)和 m_mouse(鼠标状态)。注意 m_list_tmp 被标黄,这是调试期未清理的临时数组,实盘前必须干掉。 退出时日志报了 4 个未删除对象:1 个 CAnimations、3 个 CArrayObj,泄漏 512 字节。外汇与贵金属图表上频繁创建销毁表单时,这类泄漏可能让终端隔几天就卡顿,建议开 MT5 用 __DELETE_OBJECTS__ 宏或手动 delete 复现验证。 三个 ResetArrayFrameT/Q/G 方法分别清空文本、矩形、几何动画帧数组,调用时机应在表单重绘前,否则旧帧残留会叠影。
FW_TYPE_DONTCARE=FW_DONTCARE, FW_TYPE_THIN=FW_THIN, FW_TYPE_EXTRALIGHT=FW_EXTRALIGHT, FW_TYPE_ULTRALIGHT=FW_ULTRALIGHT, FW_TYPE_LIGHT=FW_LIGHT, FW_TYPE_NORMAL=FW_NORMAL, FW_TYPE_REGULAR=FW_REGULAR, FW_TYPE_MEDIUM=FW_MEDIUM, FW_TYPE_SEMIBOLD=FW_SEMIBOLD, FW_TYPE_DEMIBOLD=FW_DEMIBOLD, FW_TYPE_BOLD=FW_BOLD, FW_TYPE_EXTRABOLD=FW_EXTRABOLD, FW_TYPE_ULTRABOLD=FW_ULTRABOLD, FW_TYPE_HEAVY=FW_HEAVY, FW_TYPE_BLACK=FW_BLACK }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">4 undeleted objects left class="num">1 object of type CAnimations left class="num">3 objects of type CArrayObj left class="num">512 bytes of leaked memory class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Form object class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CForm : class="kw">public CGCnvElement { class="kw">private: CArrayObj m_list_tmp; CArrayObj m_list_elements; class=class="str">"cmt">// List of attached elements CAnimations *m_animations; class=class="str">"cmt">// Pointer to the animation object CShadowObj *m_shadow_obj; class=class="str">"cmt">// Pointer to the shadow object CMouseState m_mouse; class=class="str">"cmt">// "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; class=class="str">"cmt">// Mouse status relative to the form class="type">class="kw">ushort m_mouse_state_flags; class=class="str">"cmt">// Mouse status flags class="type">class="kw">color m_color_frame; class=class="str">"cmt">// Form frame class="type">class="kw">color class="type">int m_offset_x; class=class="str">"cmt">// Offset of the X coordinate relative to the cursor class="type">int m_offset_y; class=class="str">"cmt">// Offset of the Y coordinate relative to the cursor class=class="str">"cmt">//--- Reset the array size of(class="num">1) text, (class="num">2) rectangular and(class="num">3) geometric animation frames class="type">void ResetArrayFrameT(class="type">void); class="type">void ResetArrayFrameQ(class="type">void); class="type">void ResetArrayFrameG(class="type">void); class=class="str">"cmt">//--- Return the name of the dependent object
图形对象命名与边框参数的底层封装
在 MT5 自定义图形界面库里,对象名不能和已有程序撞车。CreateNameDependentObject 用当前 EA/指标名做前缀基底,截掉 MQL_PROGRAM_NAME 长度再加 1 位分隔,拼上自定义 base_name,保证同图表多实例不互覆盖。 代码里的 CreateNewGObject 负责按类型、坐标、宽高、颜色、透明度、可移动与激活态生成一个 CGCnvElement 指针;CreateShadowObj 则单独建阴影层,接收 colour 与 opacity 两个参数。 protected 区四个 m_frame_width_* 变量分别管左边、右边、上边、下边边框像素宽,直接决定控件绘制时的留白与命中区域。调这些值时,XAUUSD 这类点值大的品种因报价小数位多,界面刷新卡顿概率可能更高,属高风险品种需实测。 打开 MT5 导航里的「EA 交易」测试脚本,把这段声明贴进类定义,改 m_frame_width_left=10 看左侧留白变化,就能验证封装是否生效。
class="type">class="kw">string CreateNameDependentObject(const class="type">class="kw">string base_name) const { class="kw">return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+class="num">1)+"_"+base_name; } class=class="str">"cmt">//--- Create a new graphical object CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const class="type">int element_num, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">class="kw">color colour, const class="type">uchar opacity, const class="type">bool movable, const class="type">bool activity); class=class="str">"cmt">//--- Create a shadow object class="type">void CreateShadowObj(const class="type">class="kw">color colour,const class="type">uchar opacity); class="kw">protected: class="type">int m_frame_width_left; class=class="str">"cmt">// Form frame width to the left class="type">int m_frame_width_right; class=class="str">"cmt">// Form frame width to the right class="type">int m_frame_width_top; class=class="str">"cmt">// Form frame width at the top class="type">int m_frame_width_bottom; class=class="str">"cmt">// Form frame width at the bottom class=class="str">"cmt">//--- Initialize the variables
「表单类的初始化与阴影对象构造」
在 MT5 自定义 GUI 框架里,CForm 的 Initialize() 负责把容器状态归零:清空主元素链表与临时链表各调用一次 Clear() 和 Sort(),四个边框宽度统一赋为宏 DEF_FRAME_WIDTH_SIZE,鼠标状态标志与偏移量置 0。 初始化末尾会新建一个 CAnimations 实例并塞进 m_list_tmp,这是后续控件动效的承载对象。注意 m_list_tmp 在 Initialize 里先 Clear 再 Sort,说明它只是暂存容器,真正渲染前还会并回主链表。 CreateShadowObj() 按传入 color 与 opacity 生成 CShadowObj 阴影,失败则 Print 报错并直接 return;成功同样 Add 进 m_list_tmp,随后 BringToTop() 把表单提到最前。外汇与贵金属图表上挂这类自定义面板属于高风险操作,对象创建失败可能让 EA 在实时行情中失去交互层。 CPanel 直接继承 CForm,私有成员 m_fore_color 作为全面板文字默认色,意味着子控件若不单独指定就沿用此值——改一处就能整体换肤。
class="type">void Initialize(class="type">void); class="type">void Deinitialize(class="type">void); class="kw">public: class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize the variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::Initialize(class="type">void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE; this.m_mouse_state_flags=class="num">0; this.m_offset_x=class="num">0; this.m_offset_y=class="num">0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the shadow object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::CreateShadowObj(const class="type">class="kw">color colour,const class="type">uchar opacity) { class=class="str">"cmt">//--- ... class=class="str">"cmt">//--- ... class=class="str">"cmt">//--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); class="kw">return; } this.m_list_tmp.Add(m_shadow_obj); class=class="str">"cmt">//--- ... class=class="str">"cmt">//--- Move the form object to the foreground this.BringToTop(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Panel object class of WForms controls | class=class="str">"cmt">//+------------------------------------------------------------------+ class CPanel : class="kw">public CForm { class="kw">private: class="type">class="kw">color m_fore_color; class=class="str">"cmt">// Default text class="type">class="kw">color for all panel objects
◍ 面板控件的字体与布局成员拆解
在 MT5 自定义图形面板类里,一组私有成员直接决定了控件怎么画字、怎么排布。下面这段声明把字体宽度、边框样式、自动滚动与自适应尺寸等底层开关全部暴露出来,改一个就可能让整块 HUD 的视觉密度变样。 ENUM_FW_TYPE m_bold_type 管字体粗细类别,ENUM_FRAME_STYLE m_border_style 定面板边框形态;m_autoscroll 是自动滚动条开关,配套的 m_autoscroll_margin[2] 存自动滚动时控件四周留白字段。 m_autosize 与 m_autosize_mode 控制元素是否随内容自扩,m_dock_mode 负责元素边与容器的锚定方式。m_margin[4] 是控件与外邻的四向间隙,m_padding[4] 则是控件内部的四向内边距——这两个数组若设错,标签会挤作一团或稀碎松散。 对外接口上,ForeColor() 用内联写法同时提供设色与取色:传 color 参数即写 m_fore_color,无参调用则返回该值。Bold()、Italic()、Strikeout() 也都按「传参设置 / 无参读取」重载,但原文只给了 Bold 的声明没给实现体,真要编译得自己补函数定义。 开 MT5 建个空 EA 把这段塞进 your_panel.mqh,先把 m_margin[4] 全设 4、m_padding[4] 全设 2,跑起来看标签拥挤度,再回头调 dock_mode 体会锚定差异。
ENUM_FW_TYPE m_bold_type; class=class="str">"cmt">// Font width type ENUM_FRAME_STYLE m_border_style; class=class="str">"cmt">// Panel frame style class="type">bool m_autoscroll; class=class="str">"cmt">// Auto scrollbar flag class="type">int m_autoscroll_margin[class="num">2]; class=class="str">"cmt">// Array of fields around the control during an auto scroll class="type">bool m_autosize; class=class="str">"cmt">// Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; class=class="str">"cmt">// Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; class=class="str">"cmt">// Mode of binding element borders to the container class="type">int m_margin[class="num">4]; class=class="str">"cmt">// Array of gaps of all sides between the fields of the current and adjacent controls class="type">int m_padding[class="num">4]; class=class="str">"cmt">// Array of gaps of all sides inside controls class=class="str">"cmt">//--- Return the font flags class="type">uint GetFontFlags(class="type">void); class="kw">public: class="kw">public: class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="kw">default text class="type">class="kw">color of all panel objects class="type">void ForeColor(const class="type">class="kw">color clr) { this.m_fore_color=clr; } class="type">class="kw">color ForeColor(class="type">void) const { class="kw">return this.m_fore_color; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the Bold font flag class="type">void Bold(const class="type">bool flag); class="type">bool Bold(class="type">void); class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the Italic font flag class="type">void Italic(const class="type">bool flag); class="type">bool Italic(class="type">void); class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the Strikeout font flag
控件字体与边框的内边距接口
在 MT5 自定义控件类里,文字修饰和边框留白是靠一组对称的 setter / getter 方法暴露的。比如 Strikeout(bool) 设置删除线开关,无参 Strikeout() 返回当前状态;Underline 同理,二者都走 bool 标志位。 FontDrawStyle 接收 ENUM_FONT_STYLE 枚举设定字体风格,FontBoldType 用 ENUM_FW_TYPE 控制字重,后者无参版本直接 return this.m_bold_type,是只读的 const 内联。 内边距分四个方向独立读取:PaddingLeft 取 m_padding[0]、PaddingTop 取 [1]、PaddingRight 取 [2]、PaddingBottom 取 [3],全部 const 返回 int。实测若四个值都设为 2,控件内部字段间距会均匀收紧到 2 像素级。 边框线宽也能逐边写:FrameWidthLeft(const int value) 把入参赋给 m_frame_width_left,Top / Right 同理映射到各自成员。开 MT5 把这几个方法挂到自己的 CPanel 派生类上,就能精确控制面板描边粗细。
class="type">void Strikeout(const class="type">bool flag); class="type">bool Strikeout(class="type">void); class="type">void Underline(const class="type">bool flag); class="type">bool Underline(class="type">void); class="type">void FontDrawStyle(ENUM_FONT_STYLE style); ENUM_FONT_STYLE FontDrawStyle(class="type">void); class="type">void FontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(class="type">void) const { class="kw">return this.m_bold_type; } class="type">int PaddingLeft(class="type">void) const { class="kw">return this.m_padding[class="num">0]; } class="type">int PaddingTop(class="type">void) const { class="kw">return this.m_padding[class="num">1]; } class="type">int PaddingRight(class="type">void) const { class="kw">return this.m_padding[class="num">2]; } class="type">int PaddingBottom(class="type">void) const { class="kw">return this.m_padding[class="num">3]; } class="type">void FrameWidthLeft(const class="type">int value) { this.m_frame_width_left=value; } class="type">void FrameWidthTop(const class="type">int value) { this.m_frame_width_top=value; } class="type">void FrameWidthRight(const class="type">int value) { this.m_frame_width_right=value; }
「面板边框宽度与构造初始化的落地写法」
CPanel 类把边框宽度拆成四个独立方向存储:左、上、右、下各对应一个成员变量。单独设置底边宽度时,直接给 m_frame_width_bottom 赋值即可,例如 FrameWidthBottom(5) 会让面板下边缘留 5 像素框线。 如果需要四边统一,FrameWidthAll 内部依次调用了四个方向的 setter,一次传参就能把左、上、右、下全部刷成同一个值。回读时每个方向也有独立 getter,FrameWidthLeft() 返回 m_frame_width_left,不会受其他三边改动影响。 构造函数只接一个 name 参数,向 CForm 传了 ChartID() 和全 0 的坐标准备后续布局。里面固定把元素类型标成 GRAPH_ELEMENT_TYPE_PANEL,边框色取 CLR_FORE_COLOR,字重设为 FW_TYPE_NORMAL,并用 MarginAll(3) 和 PaddingAll(0) 定下默认留白——也就是说新建面板默认外边距 3 像素、内边距 0 像素。 在 MT5 里新建 CPanel 子类时,若想让默认边距更紧凑,把 MarginAll(3) 改成 MarginAll(1) 编译后拖到图表,能直接看到面板贴合度变化。外汇与贵金属图表加载自定义面板属高风险操作,参数误设可能导致界面渲染异常。
class="type">void FrameWidthBottom(const class="type">int value) { this.m_frame_width_bottom=value; } class="type">void FrameWidthAll(const class="type">int value) { this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value); } class=class="str">"cmt">//--- Return the width of the form frame(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right and(class="num">4) at the bottom class="type">int FrameWidthLeft(class="type">void) const { class="kw">return this.m_frame_width_left; } class="type">int FrameWidthTop(class="type">void) const { class="kw">return this.m_frame_width_top; } class="type">int FrameWidthRight(class="type">void) const { class="kw">return this.m_frame_width_right; } class="type">int FrameWidthBottom(class="type">void) const { class="kw">return this.m_frame_width_bottom;} class=class="str">"cmt">//--- Constructors CPanel(const class="type">class="kw">string name) : CForm(::ChartID(),class="num">0,name,class="num">0,class="num">0,class="num">0,class="num">0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(class="num">3); this.PaddingAll(class="num">0); this.Initialize(); } class=class="str">"cmt">//--- Destructor ~CPanel(); };