DoEasy. 控件 (第 2 部分): 操控 CPanel 类·进阶篇
🧩

DoEasy. 控件 (第 2 部分): 操控 CPanel 类·进阶篇

(2/3)· 从时间帧切换丢对象到面板无鼠标响应,逐一堵上库里的旧坑

进阶 第 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 方法分别清空文本、矩形、几何动画帧数组,调用时机应在表单重绘前,否则旧帧残留会叠影。

MQL5 / C++
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 看左侧留白变化,就能验证封装是否生效。

MQL5 / C++
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 作为全面板文字默认色,意味着子控件若不单独指定就沿用此值——改一处就能整体换肤。

MQL5 / C++
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 体会锚定差异。

MQL5 / C++
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 派生类上,就能精确控制面板描边粗细。

MQL5 / C++
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) 编译后拖到图表,能直接看到面板贴合度变化。外汇与贵金属图表加载自定义面板属高风险操作,参数误设可能导致界面渲染异常。

MQL5 / C++
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();
};
让小布替你跑这套
这些缺陷排查与面板渲染验证,小布盯盘的 AIGC 已内置诊断逻辑,打开对应品种页即可看到对象状态与交互异常提示,把重复劳动交给小布,你专注决策。

常见问题

早期库未处理时间帧切换时的对象重绘逻辑,对象创建消息发出却未实际渲染。本篇修复了该缺陷,确保面板在各时间帧正确显示。
面板对象设置默认字体参数后,附着其上的含文本控件自动继承,也可在绘制前显式覆盖特定参数。
可以,小布盯盘的 AIGC 诊断模块能识别面板控件未绑定鼠标处理等异常,并在品种页给出提示,减少手动逐行排查。
能,本篇实现了采用默认值或显式指定参数绘制边框的能力,不传参则走默认框架配置。
它集中管理图形元素的配色方案、类型与显示样式参数,便于快速设置和后续扩展自定义样式。