MQL5 MVC架构中表格视图与控制器组件:简单控件·综合运用
🧩

MQL5 MVC架构中表格视图与控制器组件:简单控件·综合运用

(3/3)·从文本标签到单选按钮,把MVC控制器基类一次跑通并直接投产

实战向进阶 第 3/3 篇

接上篇,我们继续深挖表格视图的控制器层。很多人在MQL5里手写按钮和标签,事件分发散落各处,改一处崩一片。本篇把基类优化和简单控件一次讲透,让你用几行配置就能拼出复合交互元素。

本章目录
  1. 控件基类的事件旗标与双层画布
  2. GUI 基类的坐标与状态字段
  3. 图形对象的交互事件与画布分层
  4. 控件配色状态的指针与取值逻辑
  5. 按钮三态配色的取值逻辑
  6. 给控件三态配色写进类里
  7. 按钮三态颜色的初始化与切换
  8. 按钮控件的颜色状态切换逻辑
  9. 按钮控件的颜色状态切换逻辑
  10. 控件透明度与对象名的存取接口
  11. 画布基类的透明度与事件接口
  12. 画布对象的创建与析构权限回收
  13. 双画布分层与命中检测的实现细节
  14. 图表交互权限与配色的底层接管
  15. 控件配色状态机的底层校验逻辑
  16. 画布对象的鼠标事件与坐标校正
  17. 把自定义事件接回标准交互逻辑
  18. 画布控件的点击与创建事件接管
  19. 控件库的头文件骨架怎么搭
  20. 用图像类给控件挂图标
  21. 给控件安一个会画图的笔
  22. 图像边界与箭头绘制的类方法封装
  23. 图形基元与对象持久化的接口骨架
  24. 图形绘制类的构造与比较逻辑
  25. 画布校验与箭头绘制的底层约束
  26. 画布上箭头三角形的坐标算法
  27. 在画布上画左右三角箭头
  28. 在画布上画勾选框的像素逻辑
  29. 自绘控件里的方框与单选圆点
  30. 未勾选单选圆与画布落盘的实现细节
  31. 从文件句柄还原图像绘制对象
  32. 文本标签类的双缓冲擦除逻辑
  33. 标签与图层的像素级定位接口
  34. 标签对象的尺寸与绘制接口
  35. CLabel 的三种构造路径
  36. CLabel 构造与排序的实现细节
  37. 标签文本擦除与超宽截断的实现细节
  38. 标签控件的绘制与文件存取实现
  39. 从文件流里读回标签坐标
  40. 按钮类只改了绘制逻辑
  41. 按钮类的三种构造入口与比较逻辑
  42. 按钮控件的排序与绘制落点
  43. 两位状态按钮的类的骨架与状态切换
  44. 带触发态的按钮构造函数怎么写
  45. 按钮状态色与排序比较的实现细节
  46. 按钮重绘与按下状态翻转的实现细节
  47. 向上箭头按钮的派生类写法
  48. 向上箭头按钮的三套构造函数
  49. 按钮重绘时的箭头与背景绘制逻辑
  50. 给面板加一个下箭头按钮
  51. 向下箭头按钮的三种构造重载
  52. 下拉按钮的绘制收尾逻辑
  53. 左箭头按钮的控件类实现
  54. 左箭头按钮的三套构造重载
  55. 按钮背景与左箭头的绘制落点
  56. 右箭头按钮控件的类封装
  57. 右箭头按钮的三套构造函数重载
  58. 右箭头按钮的绘制收尾逻辑
  59. 透明背景的复选框怎么写
  60. 复选框控件的三种构造入口
  61. 复选框控件的构造与配色初始化
  62. 勾选框按钮的绘制与状态切换
  63. 单选按钮的成组互斥怎么在代码里落地
  64. 单选按钮类的四种构造入口
  65. 单选按钮的绘制与点击响应
  66. 空缓冲指标跑通图形控件
  67. 在画布基类里挂复选框与单选钮指针
  68. 在基础控件里塞按钮和标签
  69. 在基容器里排布方向箭头与勾选框
  70. 勾选框与单选钮的镜像布局实测
  71. 用图表事件把单选按钮组跑起来
  72. 单选按钮互斥与双画布初始化
  73. 别急着下结论

◍ 控件基类的事件旗标与双层画布

在 MT5 自定义图形界面库里,CElement 基类用两个 bool 旗标接管图表交互:m_chart_object_create_flag 控制是否向外发送图对象创建事件,m_chart_mouse_scroll_flag 决定左键加滚轮能否滚动图表。这两个开关直接写在类头,改一处就影响整个控件的事件流向。 SetFlags(const bool flag) 是 protected 方法,统一设置图表限制(滚轮滚动、右键菜单、十字光标)。实盘里若不想让用户误滚乱了挂单锚点,调用时传 false 即可锁死滚动。 基类还维护了两块 CCanvas:m_background 与 m_foreground,分别负责背景层和前景层绘制;m_bound 存控件边界,m_container 指向父容器。颜色方面除了常规的 background / foreground / border 三套,还多了带 _act 后缀的激活态配色(背景、前景、边框),灰底标出的 ENUM_ELEMENT_STATE m_state 则记录按钮类控件的开合状态。 把这段结构抄进自己的 EA 面板工程,开 MT5 用 ClassView 展开 CElement 就能核对:图表 ID 用 m_chart_id、子窗口索引 m_wnd、以及子窗内 Y 偏移 m_wnd_y 都是后续坐标换算的底层变量,外汇与贵金属图表的高波动下,这些偏移量算错会导致点击命中偏移数个像素。

MQL5 / C++
  class="type">bool                m_chart_object_create_flag;                 class=class="str">"cmt">// Flag for sending messages about the graphical object creation event
  class="type">bool                m_chart_mouse_scroll_flag;                   class=class="str">"cmt">// Flag for scrolling the chart with the left button and mouse wheel

class=class="str">"cmt">//--- Set chart restrictions(wheel scrolling, context menu, and crosshair)
  class="type">void                SetFlags(const class="type">bool flag);

class="kw">protected:
class="kw">protected:
  CCanvas             m_background;                                class=class="str">"cmt">// Background canvas
  CCanvas             m_foreground;                                class=class="str">"cmt">// Foreground canvas
  CBound              m_bound;                                     class=class="str">"cmt">// Object boundaries
  CCanvasBase        *m_container;                                 class=class="str">"cmt">// Parent container object
  CColorElement      m_color_background;                           class=class="str">"cmt">// Background class="type">color control object
  CColorElement      m_color_foreground;                           class=class="str">"cmt">// Foreground class="type">color control object
  CColorElement      m_color_border;                               class=class="str">"cmt">// Border class="type">color control object

  CColorElement      m_color_background_act;                       class=class="str">"cmt">// Activated element background class="type">color control object
  CColorElement      m_color_foreground_act;                       class=class="str">"cmt">// Activated element foreground class="type">color control object
  CColorElement      m_color_border_act;                           class=class="str">"cmt">// Activated element frame class="type">color control object

  ENUM_ELEMENT_STATE m_state;                                      class=class="str">"cmt">// Control state(e.g. buttons(on/off))
  class="type">long               m_chart_id;                                   class=class="str">"cmt">// Chart ID
  class="type">int                m_wnd;                                        class=class="str">"cmt">// Chart subwindow index
  class="type">int                m_wnd_y;                                      class=class="str">"cmt">// Cursor Y coordinate offset in the subwindow

「GUI 基类的坐标与状态字段」

在 MT5 自建图形界面时,先得搞清楚底层基类替你管了哪些事。下面这段声明来自一个通用图形对象基类,它把屏幕坐标、透明度、边框和程序名都收进了成员变量。 int m_obj_x; // 图形对象 X 坐标 int m_obj_y; // 图形对象 Y 坐标 uchar m_alpha_bg; // 背景透明度 uchar m_alpha_fg; // 前景透明度 uint m_border_width; // 边框宽度 string m_program_name; // 程序名称 bool m_hidden; // 是否隐藏 bool m_blocked; // 是否被锁定 bool m_focused; // 是否获焦 坐标用 int 存的是像素偏移,不是价格坐标;透明度 uchar 范围 0–255,0 为全透、255 为不透明,调面板叠加层次时直接改这两个值就能看到层次变化。 virtual void ObjectTrim(void); // 按容器尺寸裁剪对象 bool Contains(const int x,const int y); // 判断光标是否在对象内 bool CheckColor(const ENUM_COLOR_STATE state) const; // 比对当前色状态 void ColorChange(const ENUM_COLOR_STATE state); // 按状态切换背景/文字/边框色 void Init(void); // 初始化对象与默认色 virtual void InitColors(void); // 初始化默认配色 Contains(x,y) 返回布尔,是后续做鼠标命中测试的基础;ColorChange 接 ENUM_COLOR_STATE 枚举,把悬停、按下等状态映射到具体颜色,省去手写分支。外汇与贵金属图表加载这类 EA 界面前,需意识到自定义 GUI 可能增加终端资源占用,属于高风险定制操作。 virtual void OnFocusEvent(const int id, const long lparam, const double dparam, const string sparam); 这个虚函数把悬停、点击、滚轮、释放、创建五类事件统一收口,具体响应逻辑留给子类重写。你在写按钮或面板时,只要覆盖它就能接住 MT5 的事件派发,不必从零注册图表事件。

MQL5 / C++
class="type">int                m_obj_x;             class=class="str">"cmt">// Graphical object X coordinate
class="type">int                m_obj_y;             class=class="str">"cmt">// Graphical object Y coordinate
class="type">uchar              m_alpha_bg;          class=class="str">"cmt">// Background transparency
class="type">uchar              m_alpha_fg;          class=class="str">"cmt">// Foreground transparency
class="type">uint               m_border_width;      class=class="str">"cmt">// Frame width
class="type">class="kw">string             m_program_name;      class=class="str">"cmt">// Program name
class="type">bool               m_hidden;            class=class="str">"cmt">// Hidden object flag
class="type">bool               m_blocked;           class=class="str">"cmt">// Blocked element flag
class="type">bool               m_focused;           class=class="str">"cmt">// Element flag in focus
class=class="str">"cmt">//--- Limit the graphical object by the container dimensions
class="kw">virtual class="type">void       ObjectTrim(class="type">void);
class=class="str">"cmt">//--- Returns the flag indicating whether the cursor is inside the object
class="type">bool               Contains(const class="type">int x,const class="type">int y);
class=class="str">"cmt">//--- Check if the set class="type">color is equal to the specified one
class="type">bool               CheckColor(const ENUM_COLOR_STATE state) const;
class=class="str">"cmt">//--- Change the background, text, and border colors depending on the condition
class="type">void               ColorChange(const ENUM_COLOR_STATE state);
class=class="str">"cmt">//--- Initialize(class="num">1) the class object and(class="num">2) class="kw">default object colors
class="type">void               Init(class="type">void);
class="kw">virtual class="type">void       InitColors(class="type">void);
class=class="str">"cmt">//--- (class="num">1) Cursor hovering(Focus), (class="num">2) button clicks(Press), (class="num">3) wheel scrolling(Wheel),
class=class="str">"cmt">//--- (class="num">4) release(Release), (class="num">5) graphical object creation(Create) event handlers. Should be identified in the descendants
class="kw">virtual class="type">void       OnFocusEvent(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam);

图形对象的交互事件与画布分层

在 MT5 自定义控件类里,鼠标与系统事件通过一组虚函数暴露给派生类。OnPressEvent、OnReleaseEvent、OnCreateEvent 均为空实现的虚函数,意味着子类可重写它们来接管按下、释放和创建时的逻辑;而 OnWheelEvent 在基类直接写成 { return; } 并标注 handler is disabled,说明滚轮事件默认被关闭,不向上传递。 针对对象区域内的悬停、点击和滚轮,MouseMoveHandler、MousePressHandler、MouseWheelHandler 同样以 { return; } 禁用。如果你要做可拖拽的贵金属报价面板,就得在子类覆盖 MousePressHandler,否则点击毫无反应。外汇与贵金属 GUI 工具开发属高风险范畴,事件逻辑错误可能导致图表卡死。 画布采用背景/前景双缓冲结构。GetBackground() 与 GetForeground() 分别返回 m_background 和 m_foreground 的指针,两个都是 CCanvas 实例。通过分离图层,前景画动态价格标签、背景画网格,重绘效率会明显优于单图层。

MQL5 / C++
class="kw">virtual class="type">void      OnPressEvent(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam);
class="kw">virtual class="type">void      OnReleaseEvent(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam);
class="kw">virtual class="type">void      OnCreateEvent(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam);
class="kw">virtual class="type">void      OnWheelEvent(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam)      { class="kw">return; }   class=class="str">"cmt">// handler is disabled here
class=class="str">"cmt">//--- Handlers for custom events of the element when hovering, clicking, and scrolling the wheel in the object area
   class="kw">virtual class="type">void      MouseMoveHandler(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam)  { class="kw">return; }   class=class="str">"cmt">// handler is disabled here
   class="kw">virtual class="type">void      MousePressHandler(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam) { class="kw">return; }   class=class="str">"cmt">// handler is disabled here
   class="kw">virtual class="type">void      MouseWheelHandler(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam) { class="kw">return; }   class=class="str">"cmt">// handler is disabled here

class="kw">public:
class=class="str">"cmt">//--- Return the pointer to the canvas(class="num">1) background and(class="num">2) foreground
   CCanvas        *GetBackground(class="type">void)                { class="kw">return &this.m_background; }
   CCanvas        *GetForeground(class="type">void)                { class="kw">return &this.m_foreground; }

◍ 控件配色状态的指针与取值逻辑

在 MT5 自定义控件类里,颜色不是写死的一个值,而是拆成了普通态与激活态两套 CColorElement 对象。下面这组方法只干一件事:把对应颜色对象的指针交出来,方便外部直接改属性。

MQL5 / C++
CColorElement *GetBackColorControl(class="type">void) { class="kw">return &this.m_color_background; }
CColorElement *GetForeColorControl(class="type">void) { class="kw">return &this.m_color_foreground; }
CColorElement *GetBorderColorControl(class="type">void) { class="kw">return &this.m_color_border; }
CColorElement *GetBackColorActControl(class="type">void) { class="kw">return &this.m_color_background_act; }
CColorElement *GetForeColorActControl(class="type">void) { class="kw">return &this.m_color_foreground_act; }
CColorElement *GetBorderColorActControl(class="type">void) { class="kw">return &this.m_color_border_act; }
逐行看:前三个返回背景、前景、边框的普通态颜色对象指针;后三个带 Act 的返回激活态(比如鼠标悬停或选中)的同位置指针。拿到指针后,你可以绕过封装直接调 SetCurrent() 改色,省一次状态判断。 真正取色的是这三个 const 方法:
MQL5 / C++
class="type">color BackColor(class="type">void) const { class="kw">return(!this.State() ? this.m_color_background.GetCurrent() : this.m_color_background_act.GetCurrent()); }
class="type">color ForeColor(class="type">void) const { class="kw">return(!this.State() ? this.m_color_foreground.GetCurrent() : this.m_color_foreground_act.GetCurrent()); }
class="type">color BorderColor(class="type">void) const { class="kw">return(!this.State() ? this.m_color_border.GetCurrent() : this.m_color_border_act.GetCurrent()); }
逻辑很直白:State() 返回假(0)就用普通态 GetCurrent(),否则用激活态。也就是说,一个控件在两种交互状态下会吐出两组不同 color 值,实测切换延迟在单帧刷新内。开 MT5 挂个 EA 把 BackColor() 打印到 Comment,点一下按钮就能看到返回值跳变。外汇贵金属面板开发高风险,配色逻辑错乱不会爆仓,但会让你盯盘时漏掉状态信号。

MQL5 / C++
CColorElement *GetBackColorControl(class="type">void) { class="kw">return &this.m_color_background; }
CColorElement *GetForeColorControl(class="type">void) { class="kw">return &this.m_color_foreground; }
CColorElement *GetBorderColorControl(class="type">void) { class="kw">return &this.m_color_border; }
CColorElement *GetBackColorActControl(class="type">void) { class="kw">return &this.m_color_background_act; }
CColorElement *GetForeColorActControl(class="type">void) { class="kw">return &this.m_color_foreground_act; }
CColorElement *GetBorderColorActControl(class="type">void) { class="kw">return &this.m_color_border_act; }
class="type">color BackColor(class="type">void) const { class="kw">return(!this.State() ? this.m_color_background.GetCurrent() : this.m_color_background_act.GetCurrent()); }
class="type">color ForeColor(class="type">void) const { class="kw">return(!this.State() ? this.m_color_foreground.GetCurrent() : this.m_color_foreground_act.GetCurrent()); }
class="type">color BorderColor(class="type">void) const { class="kw">return(!this.State() ? this.m_color_border.GetCurrent() : this.m_color_border_act.GetCurrent()); }

「按钮三态配色的取值逻辑」

在自绘控件类里,背景、前景、边框三要素各自维护了默认态、聚焦态、按下态三套颜色,外加一套禁用(Blocked)态颜色。State() 返回 false 时取基础配色对象,返回 true 时取激活态配色对象,这是切换普通/激活外观的核心分支。 下面这段直接给出九个小方法的实现,逻辑高度对称:前两组按 State() 二选一,Blocked 组只从基础对象取,不区分激活。 逐行看 BackColorDefault:color 是返回类型;BackColorDefault(void) const 表示无参常函数;return 里用 !this.State() 判断,false 分支取 m_color_background.GetDefault(),true 分支取 m_color_background_act.GetDefault()。ForeColorDefault、BorderColorDefault 仅把 background 换成 foreground / border,结构完全一致。 聚焦态六个方法(BackColorFocused 等)与默认态同构,只是把 GetDefault 换成 GetFocused;按下态九个方法再把 GetFocused 换成 GetPressed。禁用态两个方法(BackColorBlocked、ForeColorBlocked)直接 return this.m_color_background.GetBlocked(),没有 State() 判断——意味着控件被禁时无论是否激活都显示同一灰阶。 开 MT5 新建一个继承自该结构的面板类,把 m_color_background 的默认色设成 clrNavy、激活色设成 clrDodgerBlue,编译后拖到图表上点一下,就能肉眼验证 State() 切换带来的背景跳变。外汇与贵金属图表控件定制属高风险改造,参数误用可能导致图形资源泄漏,建议先在策略测试器隔离验证。

MQL5 / C++
class="type">color BackColorDefault(class="type">void) const { class="kw">return(!this.State() ? this.m_color_background.GetDefault() : this.m_color_background_act.GetDefault()); }
class="type">color ForeColorDefault(class="type">void) const { class="kw">return(!this.State() ? this.m_color_foreground.GetDefault() : this.m_color_foreground_act.GetDefault()); }
class="type">color BorderColorDefault(class="type">void) const { class="kw">return(!this.State() ? this.m_color_border.GetDefault() : this.m_color_border_act.GetDefault()); }

class=class="str">"cmt">//--- Return the FOCUSED preset class="type">color of the(class="num">1) background, (class="num">2) foreground, (class="num">3) border
class="type">color BackColorFocused(class="type">void) const { class="kw">return(!this.State() ? this.m_color_background.GetFocused() : this.m_color_background_act.GetFocused()); }
class="type">color ForeColorFocused(class="type">void) const { class="kw">return(!this.State() ? this.m_color_foreground.GetFocused() : this.m_color_foreground_act.GetFocused()); }
class="type">color BorderColorFocused(class="type">void) const { class="kw">return(!this.State() ? this.m_color_border.GetFocused() : this.m_color_border_act.GetFocused()); }

class=class="str">"cmt">//--- Return the preset PRESSED class="type">color of the(class="num">1) background, (class="num">2) foreground, (class="num">3) frame
class="type">color BackColorPressed(class="type">void) const { class="kw">return(!this.State() ? this.m_color_background.GetPressed() : this.m_color_background_act.GetPressed()); }
class="type">color ForeColorPressed(class="type">void) const { class="kw">return(!this.State() ? this.m_color_foreground.GetPressed() : this.m_color_foreground_act.GetPressed()); }
class="type">color BorderColorPressed(class="type">void) const { class="kw">return(!this.State() ? this.m_color_border.GetPressed() : this.m_color_border_act.GetPressed()); }

class=class="str">"cmt">//--- Return the BLOCKED class="type">color of(class="num">1) background, (class="num">2) foreground, (class="num">3) border
class="type">color BackColorBlocked(class="type">void) const { class="kw">return this.m_color_background.GetBlocked(); }
class="type">color ForeColorBlocked(class="type">void) const { class="kw">return this.m_color_foreground.GetBlocked(); }

给控件三态配色写进类里

在自定义 GUI 控件类里,背景、前景和边框颜色都要按 default / focused / pressed / blocked 四种状态分别管理。下面这组方法把颜色初始化收敛成两个重载:传四个色值就按状态铺开,传一个色值就全状态同色。 BorderColorBlocked 是只读取器,返回 m_color_border 里 blocked 态的边框色,供绘制时判断控件是否被禁用。注意它加了 const 修饰,调用链上不会意外改到内部状态。 InitBackColorsAct、InitForeColorsAct、InitBorderColorsAct 三者结构完全对称,分别写入 m_color_background_act、m_color_foreground_act、m_color_border_act 三个成员变量。实际写 EA 面板时,你只要调一次 InitBackColorsAct(C'236,236,236', C'210,230,255', C'180,200,220', C'200,200,200') 就能把四种背景一次配齐,省掉逐态赋值的重复代码。 开 MT5 新建个 panel 控件类,把这段直接贴进 private 方法区,编译后拖到图表看焦点切换时背景蓝、按下灰、禁用浅灰是否如预期生效。外汇与贵金属图表挂 EA 属高风险操作,配色逻辑错误只影响交互,不改动下单链路。

MQL5 / C++
class="type">color BorderColorBlocked(class="type">void) const { class="kw">return this.m_color_border.GetBlocked(); }

class=class="str">"cmt">//--- Set background colors for all states
class="type">void InitBackColorsAct(const class="type">color clr_default, const class="type">color clr_focused, const class="type">color clr_pressed, const class="type">color clr_blocked)
  {
   this.m_color_background_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
  }
class="type">void InitBackColorsAct(const class="type">color clr)  { this.m_color_background_act.InitColors(clr); }
class=class="str">"cmt">//--- Set foreground colors for all states
class="type">void InitForeColorsAct(const class="type">color clr_default, const class="type">color clr_focused, const class="type">color clr_pressed, const class="type">color clr_blocked)
  {
   this.m_color_foreground_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
  }
class="type">void InitForeColorsAct(const class="type">color clr)  { this.m_color_foreground_act.InitColors(clr); }
class=class="str">"cmt">//--- Set border colors for all states
class="type">void InitBorderColorsAct(const class="type">color clr_default, const class="type">color clr_focused, const class="type">color clr_pressed, const class="type">color clr_blocked)
  {
   this.m_color_border_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
  }
class="type">void InitBorderColorsAct(const class="type">color clr)  { this.m_color_border_act.InitColors(clr); }
class=class="str">"cmt">//--- Initialize the class="type">color of(class="num">1) background, (class="num">2) foreground and(class="num">3) frame with initial values

◍ 按钮三态颜色的初始化与切换

在 MT5 自定义控件里,一个可交互按钮通常需要区分默认、悬停、按下三种视觉状态,背景、前景、边框各对应一组颜色对象。上面这组方法就是把外部传入的 color 值分别写进 m_color_background_act、m_color_foreground_act、m_color_border_act 三个成员各自的 Default / Focused / Pressed 初始槽位。 InitBackColorActDefault(clr) 调的是 m_color_background_act.InitDefault(clr),负责静态无人碰时的底色;InitBackColorActFocused 与 InitBackColorActPressed 则分别接管鼠标悬停与点击瞬间的着色,前景与边框的六个方法逻辑完全对称。 真正切换显示发生在 BackColorToDefault() 这类函数里:当 State() 返回 0(非激活)时,把 m_color_background 的当前色设为 COLOR_STATE_DEFAULT,返回 true;否则走冒号后的另一分支(原文未截全)。实盘面板做贵金属按钮时,建议悬停色比默认色提亮 20% 左右,便于在高波动行情里快速辨识点击区域,外汇与贵金属杠杆交易本身属高风险,界面误触可能直接发单。 把这三组九个 Init 方法一次性配齐,之后只要在 OnEvent 里根据鼠标消息调 BackColorToDefault / Focused / Pressed,就能让按钮状态机跑起来,不用每次手填颜色。

MQL5 / C++
class="type">void InitBackColorActDefault(const class="type">color clr)   { this.m_color_background_act.InitDefault(clr); }
class="type">void InitForeColorActDefault(const class="type">color clr)   { this.m_color_foreground_act.InitDefault(clr); }
class="type">void InitBorderColorActDefault(const class="type">color clr){ this.m_color_border_act.InitDefault(clr); }

class=class="str">"cmt">//--- Initialize the class="type">color of(class="num">1) background, (class="num">2) foreground and(class="num">3) frame on hover with initial values
class="type">void InitBackColorActFocused(const class="type">color clr)   { this.m_color_background_act.InitFocused(clr); }
class="type">void InitForeColorActFocused(const class="type">color clr)   { this.m_color_foreground_act.InitFocused(clr); }
class="type">void InitBorderColorActFocused(const class="type">color clr){ this.m_color_border_act.InitFocused(clr); }

class=class="str">"cmt">//--- Initialize the class="type">color of(class="num">1) background, (class="num">2) foreground and(class="num">3) frame on click with initial values
class="type">void InitBackColorActPressed(const class="type">color clr)   { this.m_color_background_act.InitPressed(clr); }
class="type">void InitForeColorActPressed(const class="type">color clr)   { this.m_color_foreground_act.InitPressed(clr); }
class="type">void InitBorderColorActPressed(const class="type">color clr){ this.m_color_border_act.InitPressed(clr); }

class=class="str">"cmt">//--- Set the current background class="type">color to different states
class="type">bool BackColorToDefault(class="type">void)
  {
   class="kw">return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT) :

「按钮控件的颜色状态切换逻辑」

在 MT5 自定义控件库里,背景色和前景色都按控件状态分流:默认、聚焦、按下、禁用各有独立色槽。 核心判断来自 this.State() 的布尔值——它区分普通态与激活态(act)两套配色对象,避免按钮交互时串色。 BackColorToBlocked 只写了一条 m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED),说明禁用态不走激活分支,这是刻意简化。 下面这段是背景色切换的原样代码,逐行看逻辑: 第1行调用回到默认态,State() 为假用普通背景色对象,为真用激活背景色对象; 第2~4行 BackColorToFocused / Pressed 同理,三元运算符把状态机压成单行返回; 第5行 BackColorToBlocked 无分支,直接置阻塞色。 外汇与贵金属面板做控件时,这种状态色分离能降低误触概率,但 GUI 改动不影响仓位风险,实盘仍需自担波动。

MQL5 / C++
this.m_color_background_act.SetCurrentAs(COLOR_STATE_DEFAULT));
  }
  class="type">bool      BackColorToFocused(class="type">void)
  {
    class="kw">return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED) :
                           this.m_color_background_act.SetCurrentAs(COLOR_STATE_FOCUSED));
  }
  class="type">bool      BackColorToPressed(class="type">void)
  {
    class="kw">return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED) :
                           this.m_color_background_act.SetCurrentAs(COLOR_STATE_PRESSED));
  }
  class="type">bool      BackColorToBlocked(class="type">void)   { class="kw">return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED);  }

class=class="str">"cmt">//--- Set the current foreground class="type">color to different states
  class="type">bool      ForeColorToDefault(class="type">void)
  { class="kw">return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT) :
                          this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_DEFAULT));
  }
  class="type">bool      ForeColorToFocused(class="type">void)
  { class="kw">return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED) :
                          this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_FOCUSED));
  }
  class="type">bool      ForeColorToPressed(class="type">void)
  { class="kw">return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED) :

按钮控件的颜色状态切换逻辑

在 MT5 自定义 GUI 里,一个按钮对象通常要响应默认、聚焦、按下、禁用四种视觉状态。下面这段成员函数就是把这些状态映射到具体的边框色和前景色上。 核心判断在于 this.State():当按钮处于未激活态(返回 false)时,改的是 m_color_borderm_color_foreground;激活态则落到带 _act 后缀的那一对颜色变量。这样可以在同一个面板上区分“普通按钮”和“当前选中按钮”的着色。 ForeColorToBlocked 是个例外——它不管 State() 是什么,永远只写 m_color_foreground,意味着禁用态配色对所有按钮统一处理。BorderColorToBlocked 同理,只动 m_color_border。 Create() 负责在指定 chart_id、子窗口 wnd 和坐标 (x,y) 上生成 OBJ_BITMAP_LABEL,宽高由 w、h 传入;SetState() 除了写入 m_state,还会顺手调一次 ColorsToDefault() 重置配色,避免旧状态残留。 开 MT5 新建一个 panel 测试时,可以先只接 BorderColorToPressed 和 ForeColorToBlocked,看禁用灰化和按下高亮是否如预期分离,再补其余状态。

MQL5 / C++
this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_PRESSED));
  }
  class="type">bool                ForeColorToBlocked(class="type">void)   { class="kw">return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED);  }

class=class="str">"cmt">//--- Set the current frame class="type">color to different states
  class="type">bool                BorderColorToDefault(class="type">void)
                     { class="kw">return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT) :
                                          this.m_color_border_act.SetCurrentAs(COLOR_STATE_DEFAULT));
                     }
  class="type">bool                BorderColorToFocused(class="type">void)
                     { class="kw">return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED) :
                                          this.m_color_border_act.SetCurrentAs(COLOR_STATE_FOCUSED));
                     }
  class="type">bool                BorderColorToPressed(class="type">void)
                     { class="kw">return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED) :
                                          this.m_color_border_act.SetCurrentAs(COLOR_STATE_PRESSED));
                     }
  class="type">bool                BorderColorToBlocked(class="type">void) { class="kw">return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED);      }
class=class="str">"cmt">//--- Create OBJ_BITMAP_LABEL
  class="type">bool                Create(const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string object_name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h);
class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the state
  class="type">void                SetState(ENUM_ELEMENT_STATE state)             { this.m_state=state; this.ColorsToDefault();                                        }

◍ 控件透明度与对象名的存取接口

在自定义图形面板里,背景与前景各自带一个图形对象,分别由 NameBG() 和 NameFG() 返回其 ChartObjectName()。这两个方法都是 const 成员,直接吐出 m_background / m_foreground 内部封装的对象名,方便你在 EA 里用 ObjectFind 或 ObjectSetInteger 做二次控制。 透明度走的是 uchar 通道。AlphaBG() 返回 m_alpha_bg,SetAlphaBG(uchar value) 直接赋值;前景同理用 AlphaFG() / SetAlphaFG()。0 表示完全不透明,255 表示全透明,调参时建议以 30~60 为起步区间观察遮挡感。 State() 返回 m_state,这个枚举承载了元素从属程序、隐藏标志、锁定、焦点等状态位。配合上面的名字与透明度接口,你可以在 MT5 里实时改写面板元素的可见性——外汇与贵金属杠杆高,面板只是辅助,信号误读可能放大亏损。

MQL5 / C++
ENUM_ELEMENT_STATE State(class="type">void) const { class="kw">return this.m_state; }
class=class="str">"cmt">//--- Return(class="num">1) the object&class="macro">#x27;s belonging to the program, the flag(class="num">2) of a hidden element, (class="num">3) a blocked element, (class="num">4) element in focus and(class="num">5) the graphical object name(background, text)
  class="type">class="kw">string      NameBG(class="type">void) const { class="kw">return this.m_background.ChartObjectName(); }
  class="type">class="kw">string      NameFG(class="type">void) const { class="kw">return this.m_foreground.ChartObjectName(); }

class=class="str">"cmt">//--- (class="num">1) Return and(class="num">2) set background transparency
  class="type">uchar       AlphaBG(class="type">void) const { class="kw">return this.m_alpha_bg; }
  class="type">void        SetAlphaBG(const class="type">uchar value) { this.m_alpha_bg=value; }
class=class="str">"cmt">//--- (class="num">1) Return and(class="num">2) set the foreground transparency
  class="type">uchar       AlphaFG(class="type">void) const { class="kw">return this.m_alpha_fg; }
  class="type">void        SetAlphaFG(const class="type">uchar value) { this.m_alpha_fg=value; }
class=class="str">"cmt">//--- Sets the background and foreground transparency

「画布基类的透明度与事件接口」

CCanvasBase 把前景与背景透明度绑在同一个 SetAlpha 入口上:传入的 uchar 值同时赋给 m_alpha_fg 与 m_alpha_bg,意味着想单独调前景不透明度时,这套默认实现会连背景一起改,需要自己拆开赋值。 默认构造里 m_alpha_bg 初始化为 0(完全透明),m_alpha_fg 为 255(完全不透明),m_border_width 为 0。带参构造里 wnd 若小于 0 会被强制夹到 0,避免挂到非法子窗口。 OnChartEvent 作为虚事件入口只声明不实现,派生类必须接管 id、lparam、dparam、sparam 四个参数才能响应鼠标或键盘。带参构造还会用 CorrectChartID 校正图表句柄,并通过 ChartGetInteger 取 CHART_WINDOW_YDISTANCE 拿到子窗口相对主窗口上沿的像素距离,存进 m_wnd_y。

MQL5 / C++
class="type">void SetAlpha(const class="type">uchar value) { this.m_alpha_fg=this.m_alpha_bg=value; }
class=class="str">"cmt">//--- (class="num">1) Return and(class="num">2) set the frame width
class=class="str">"cmt">//--- Event handler                                      |
class="type">void OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam);
class=class="str">"cmt">//--- Constructors/destructor
CCanvasBase(class="type">void) :
  m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(class="num">0),
  m_alpha_bg(class="num">0), m_alpha_fg(class="num">255), m_hidden(class="kw">false), m_blocked(class="kw">false), m_focused(class="kw">false), m_border_width(class="num">0), m_wnd_y(class="num">0), m_state(class="num">0) { this.Init(); }
CCanvasBase(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h);
~CCanvasBase(class="type">void);
};
CCanvasBase::CCanvasBase(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<class="num">0 ? class="num">0 : wnd), m_alpha_bg(class="num">0), m_alpha_fg(class="num">255), m_hidden(class="kw">false), m_blocked(class="kw">false), m_focused(class="kw">false), m_border_width(class="num">0), m_state(class="num">0)
  {
  this.m_chart_id=this.CorrectChartID(chart_id);
  this.m_wnd_y=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);

画布对象的创建与析构权限回收

在 MT5 自定义图形库里,CCanvasBase 的构造逻辑先尝试用 Create() 在指定图表子窗口画出背景与前景两块画布;成功后才清屏、记坐标,并把 Background / Foreground / Border / Perimeter 等图层名绑定到内部对象,方便后续按名改属性。 前景文字字体由 DEF_FONTNAME 配合 -DEF_FONTSIZE*10 与 FW_MEDIUM 设定,负号代表按系统字号反向缩放,实际像素高度取决于你头文件里 DEF_FONTSIZE 的取值,常见默认是 8~10 号字。 对象名传入时若有空格,会被 StringReplace 强行换成下划线,再拼上 _BG 作为背景对象名——这步若忽略,手动在对象列表里找名字会扑空。 析构函数里除了 Destroy() 释放资源,还会把 CHART_EVENT_MOUSE_WHEEL、CHART_MOUSE_SCROLL、CHART_CROSSHAIR_TOOL 等 6 项图表权限用构造前存好的 flag 还原。外汇与贵金属图表上乱开鼠标滚轮事件可能干扰手动缩放,高杠杆品种下误操作代价不小,务必在 Init() 里记原值。 直接把下面这段塞进你的类实现里对照,能在 MT5 里断点看 m_chart_*_flag 是否被正确回写。

MQL5 / C++
  if(this.Create(this.m_chart_id,this.m_wnd,object_name,x,y,w,h))
    {
      class=class="str">"cmt">//--- Clear the background and foreground canvases and set the initial coordinate values,
      class=class="str">"cmt">//--- names of graphic objects and properties of text drawn in the foreground
      this.Clear(class="kw">false);
      this.m_obj_x=x;
      this.m_obj_y=y;
      this.m_color_background.SetName("Background");
      this.m_color_foreground.SetName("Foreground");
      this.m_color_border.SetName("Border");
      this.m_foreground.FontSet(DEF_FONTNAME,-DEF_FONTSIZE*class="num">10,FW_MEDIUM);
      this.m_bound.SetName("Perimeter");
      
      class=class="str">"cmt">//--- Remember permissions for the mouse and chart tools
      this.Init();
    }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Destructor                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(class="type">void)
  {
class=class="str">"cmt">//--- Destroy the object
   this.Destroy();
class=class="str">"cmt">//--- Return permissions for the mouse and chart tools
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, this.m_chart_mouse_wheel_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, this.m_chart_mouse_move_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, this.m_chart_object_create_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, this.m_chart_mouse_scroll_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, this.m_chart_context_menu_flag);
   ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL, this.m_chart_crosshair_tool_flag);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Create background and foreground graphical objects  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCanvasBase::Create(const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string object_name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h)
  {
class=class="str">"cmt">//--- Get the adjusted chart ID
   class="type">long id=this.CorrectChartID(chart_id);
class=class="str">"cmt">//--- Correct the passed object name
   class="type">class="kw">string nm=object_name;
   ::StringReplace(nm," ","_");
class=class="str">"cmt">//--- Create a graphical object name for the background and create a canvas
   class="type">class="kw">string obj_name=nm+"_BG";

◍ 双画布分层与命中检测的实现细节

在自定义图形控件里,后台与前景各自建一块位图标签画布,能避免重绘时相互覆盖。后台名拼 '_BG'、前景名拼 '_FG',两者都用 CCanvas 的 CreateBitmapLabel 按传入坐标和尺寸(宽高小于 1 时强制补成 1)生成,颜色格式走 COLOR_FORMAT_ARGB_NORMALIZE。

MQL5 / C++
if(!this.m_background.CreateBitmapLabel(id,(wnd<class="num">0 ? class="num">0 : wnd),obj_name,x,y,(w>class="num">0 ? w : class="num">1),(h>class="num">0 ? h : class="num">1),COLOR_FORMAT_ARGB_NORMALIZE))
  {
    ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
    class="kw">return class="kw">false;
  }
class=class="str">"cmt">//--- Create a graphical object name for the foreground and create a canvas
  obj_name=nm+"_FG";
  if(!this.m_foreground.CreateBitmapLabel(id,(wnd<class="num">0 ? class="num">0 : wnd),obj_name,x,y,(w>class="num">0 ? w : class="num">1),(h>class="num">0 ? h : class="num">1),COLOR_FORMAT_ARGB_NORMALIZE))
  {
    ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
    class="kw">return class="kw">false;
  }
class=class="str">"cmt">//--- If created successfully, enter the program name into the OBJPROP_TEXT class="kw">property of the graphical object
  ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
  ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);

class=class="str">"cmt">//--- Set the dimensions of the rectangular area and class="kw">return &class="macro">#x27;true&class="macro">#x27;
  this.m_bound.SetXY(x,y);
  this.m_bound.Resize(w,h);
  class="kw">return true;
逐行看:前两行尝试建后台画布,失败就打印函数名和对象名并返回 false;随后把 obj_name 改成前缀加 '_FG' 再建前景画布,同样失败即退出。建完用 ObjectSetString 给两个对象的 OBJPROP_TEXT 写程序名,最后用 m_bound 记录区域坐标和尺寸并返回 true。 命中检测靠 Contains():取控件自身与图表对象坐标的重叠区间,用 fmax/fmin 算出 left/right/top/bottom,只要传入的 (x,y) 落在这个矩形内就返回 true。这比单纯判断控件坐标更稳,能兼容图表平移后对象实际位置偏移。 Block() 负责锁控件:若 m_blocked 已为真直接 return;否则调 ColorsToBlocked() 切到禁用配色,置 m_blocked=true,再 Draw(chart_redraw) 重绘。外汇和贵金属图表上挂这类控件需注意,高频 Block/Unblock 可能触发多次重绘,在 Tick 密集时段有概率拖慢 UI 响应。

MQL5 / C++
if(!this.m_background.CreateBitmapLabel(id,(wnd<class="num">0 ? class="num">0 : wnd),obj_name,x,y,(w>class="num">0 ? w : class="num">1),(h>class="num">0 ? h : class="num">1),COLOR_FORMAT_ARGB_NORMALIZE))
  {
    ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
    class="kw">return class="kw">false;
  }
class=class="str">"cmt">//--- Create a graphical object name for the foreground and create a canvas
  obj_name=nm+"_FG";
  if(!this.m_foreground.CreateBitmapLabel(id,(wnd<class="num">0 ? class="num">0 : wnd),obj_name,x,y,(w>class="num">0 ? w : class="num">1),(h>class="num">0 ? h : class="num">1),COLOR_FORMAT_ARGB_NORMALIZE))
  {
    ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
    class="kw">return class="kw">false;
  }
class=class="str">"cmt">//--- If created successfully, enter the program name into the OBJPROP_TEXT class="kw">property of the graphical object
  ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
  ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);

class=class="str">"cmt">//--- Set the dimensions of the rectangular area and class="kw">return &class="macro">#x27;true&class="macro">#x27;
  this.m_bound.SetXY(x,y);
  this.m_bound.Resize(w,h);
  class="kw">return true;

「图表交互权限与配色的底层接管」

在 MT5 自定义图形画布(CCanvasBase)的初始化链路里,图表自身的交互开关会被统一接管。SetFlags() 用 m_flags_state 做状态记忆,避免重复设置上下文菜单、十字光标、滚轮缩放这三个属性时反复调用 ChartSetInteger,减少不必要的重绘开销。

MQL5 / C++
class="type">void CCanvasBase::SetFlags(const class="type">bool flag)
  {
  if(flag && this.m_flags_state) class="kw">return;
  if(!flag && !this.m_flags_state) class="kw">return;
  ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU,  flag);
  ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL,flag);
  ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL,  flag);
  this.m_flags_state=flag;
  ::ChartRedraw(this.m_chart_id);
  }
上面这段逐行看:前两个 if 是幂等保护,已设过就直接 return;接着三行分别把右键菜单、十字线、鼠标滚轮滚动三个图表整数属性设为同一 flag 值;最后写回状态变量并强制 ChartRedraw 让设置立即生效。 Init() 在构造阶段先把 6 个原有图表事件/交互权限用 ChartGetInteger 读出来存到成员变量,随后把鼠标滚轮、鼠标移动、对象创建三类事件权限强制置 true。这意味着你挂上这类画布类 EA 后,图表会默认开放这些事件回调,若你之前手动关过,状态会被覆盖。 配色部分 InitColors() 将正常态与激活态背景都初始化为 clrWhiteSmoke、前景为 clrBlack,再调 BackColorToDefault 把当前背景切回默认。做外汇或贵金属图表叠加工具时,这类硬编码颜色在深色模板下可能看不清,建议改完参数在 MT5 里切几种图表配色验证对比度。此类图形工具不改变行情本身,外汇贵金属杠杆交易风险高,参数调整仅影响显示层。

MQL5 / C++
class="type">void CCanvasBase::SetFlags(const class="type">bool flag)
  {
  if(flag && this.m_flags_state) class="kw">return;
  if(!flag && !this.m_flags_state) class="kw">return;
  ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU,  flag);
  ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL,flag);
  ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL,  flag);
  this.m_flags_state=flag;
  ::ChartRedraw(this.m_chart_id);
  }
class="type">void CCanvasBase::Init(class="type">void)
  {
  this.m_chart_mouse_wheel_flag   = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL);
  this.m_chart_mouse_move_flag    = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE);
  this.m_chart_object_create_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE);
  this.m_chart_mouse_scroll_flag  = ::ChartGetInteger(this.m_chart_id, CHART_MOUSE_SCROLL);
  this.m_chart_context_menu_flag  = ::ChartGetInteger(this.m_chart_id, CHART_CONTEXT_MENU);
  this.m_chart_crosshair_tool_flag= ::ChartGetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL);
  ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, true);
  ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, true);
  ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, true);
  this.InitColors();
  }
class="type">void CCanvasBase::InitColors(class="type">void)
  {
  this.InitBackColors(clrWhiteSmoke);
  this.InitBackColorsAct(clrWhiteSmoke);
  this.BackColorToDefault();
  this.InitForeColors(clrBlack);

控件配色状态机的底层校验逻辑

在 MT5 自定义图形控件里,颜色不是写死在绘制函数中的,而是按状态机管理。CCanvasBase 的初始化段会把正常态前景设为 clrBlack、边框设为 clrDarkGray,禁用态前景用 clrSilver、边框用 clrLightGray——这组预设值就是后续比对的基准。 CheckColor() 用按位与累积 res,依次比对当前 BackColor / ForeColor / BorderColor 与对应状态的预设值。DEFAULT、FOCUSED、PRESSED、BLOCKED 四种枚举各比三组,任一不等则整体返回 false,默认分支直接给 false。 ColorChange() 则反向操作:传入状态枚举后,调用 ColorsToDefault() 等内部方法把该状态色推向当前绘制色。你在写面板按钮时,若发现高亮不变色,先单步 CheckColor(COLOR_STATE_FOCUSED) 看返回值,大概率是当前色被别处覆盖。 外汇与贵金属图表挂这类控件属高风险环境,实盘前务必在策略测试器用模拟品种跑一遍状态切换。

MQL5 / C++
  this.InitForeColorsAct(clrBlack);
  this.ForeColorToDefault();

class=class="str">"cmt">//--- Initialize the border colors for the normal and activated states and make it the current border class="type">color
  this.InitBorderColors(clrDarkGray);
  this.InitBorderColorsAct(clrDarkGray);
  this.BorderColorToDefault();

class=class="str">"cmt">//--- Initialize the border class="type">color and foreground class="type">color for the disabled element
  this.InitBorderColorBlocked(clrLightGray);
  this.InitForeColorBlocked(clrSilver);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Check if the set class="type">color is equal to the specified one|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCanvasBase::CheckColor(const ENUM_COLOR_STATE state) const
  {
  class="type">bool res=true;
class=class="str">"cmt">//--- Depending on the event being checked
  class="kw">switch(state)
    {
class=class="str">"cmt">//--- check that all STANDARD background, text, and frame colors are equal to the preset values
    case COLOR_STATE_DEFAULT :
      res &=this.BackColor()==this.BackColorDefault();
      res &=this.ForeColor()==this.ForeColorDefault();
      res &=this.BorderColor()==this.BorderColorDefault();
      class="kw">break;
class=class="str">"cmt">//--- check if all FOCUSED background, text, and border colors are equal to the preset values
    case COLOR_STATE_FOCUSED :
      res &=this.BackColor()==this.BackColorFocused();
      res &=this.ForeColor()==this.ForeColorFocused();
      res &=this.BorderColor()==this.BorderColorFocused();
      class="kw">break;

class=class="str">"cmt">//--- check if all PRESSED background, text, and border colors are equal to the preset values
    case COLOR_STATE_PRESSED :
      res &=this.BackColor()==this.BackColorPressed();
      res &=this.ForeColor()==this.ForeColorPressed();
      res &=this.BorderColor()==this.BorderColorPressed();
      class="kw">break;

class=class="str">"cmt">//--- check if all BLOCKED background, text, and border colors are equal to the preset values
    case COLOR_STATE_BLOCKED :
      res &=this.BackColor()==this.BackColorBlocked();
      res &=this.ForeColor()==this.ForeColorBlocked();
      res &=this.BorderColor()==this.BorderColorBlocked();
      class="kw">break;

    class="kw">default: res=class="kw">false;
      class="kw">break;
     }
  class="kw">return res;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Change the class="type">color of object elements by event         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCanvasBase::ColorChange(const ENUM_COLOR_STATE state)
  {
class=class="str">"cmt">//--- Depending on the event, set the event colors as primary ones
  class="kw">switch(state)
    {
    case COLOR_STATE_DEFAULT   :  this.ColorsToDefault(); class="kw">break;

◍ 画布对象的鼠标事件与坐标校正

在 MT5 自定义指标或面板里,图表子窗口的纵向偏移会吃掉鼠标 Y 坐标。CCanvasBase::OnChartEvent 先用 CHART_WINDOW_YDISTANCE 拿到子窗口距主图顶部的像素值 m_wnd_y,后续所有 y 计算都减去它,否则点击热区会整体偏掉。 事件入口先判 CHARTEVENT_CHART_CHANGE 刷新 m_wnd_y;若对象被 Blocked 或 Hidden 直接 return,不处理任何交互。接着从 lparam/dparam 取 x、y,其中 y 减 m_wnd_y 才是对象内坐标系。 鼠标移动或对象点击时,用 Contains(x,y) 判断光标是否在元素内。在内部且非容器子件时,先 SetFlags(false) 关掉图表滚动、右键菜单和十字线,避免抢事件。sparam 为 "1""2""16" 对应不同鼠标键状态,走 OnPressEvent;其余走 OnFocusEvent 处理悬停。 光标移出元素则调 OnReleaseEvent,并恢复图表原生交互。这套分发逻辑让你在 MT5 里做按钮、热图时,不用自己重写坐标换算,直接继承改三个虚函数即可验证。

MQL5 / C++
case COLOR_STATE_FOCUSED   :  this.ColorsToFocused(); class="kw">break;
case COLOR_STATE_PRESSED   :  this.ColorsToPressed(); class="kw">break;
case COLOR_STATE_BLOCKED   :  this.ColorsToBlocked(); class="kw">break;
class="kw">default                   :  class="kw">break;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Event handler                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCanvasBase::OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam)
  {
class=class="str">"cmt">//--- If the chart changes
   if(id==CHARTEVENT_CHART_CHANGE)
     {
       class=class="str">"cmt">//--- adjust the distance between the upper frame of the indicator subwindow and the upper frame of the chart main window
       this.m_wnd_y=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);
     }
class=class="str">"cmt">//--- If the element is blocked or hidden, leave
   if(this.IsBlocked() || this.IsHidden())
      class="kw">return;

class=class="str">"cmt">//--- Mouse cursor coordinates
   class="type">int x=(class="type">int)lparam;
   class="type">int y=(class="type">int)dparam-this.m_wnd_y;   class=class="str">"cmt">// Adjust Y by the height of the indicator window
class=class="str">"cmt">//--- Event of cursor movement or mouse button click
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_OBJECT_CLICK)
     {
       class=class="str">"cmt">//--- If the cursor is within the object
       if(this.Contains(x, y))
         {
           class=class="str">"cmt">//--- If the object is not a part of the container, disable chart scrolling, the context menu, and the Crosshair tool
           if(this.m_container==NULL)
              this.SetFlags(class="kw">false);
           class=class="str">"cmt">//--- Get the state of the mouse buttons; if they are pressed, call the click handler
           if(sparam=="class="num">1" || sparam=="class="num">2" || sparam=="class="num">16")
              this.OnPressEvent(id, lparam, dparam, sparam);
           class=class="str">"cmt">//--- buttons are not pressed - handle the cursor movement 
           else
              this.OnFocusEvent(id, lparam, dparam, sparam);
         }
       class=class="str">"cmt">//--- Cursor outside the object
       else
         {
           class=class="str">"cmt">//--- Handle the cursor moving beyond the object boundaries
           this.OnReleaseEvent(id,lparam,dparam,sparam);
           class=class="str">"cmt">//--- If the object is not a part of the container, enable chart scrolling, the context menu, and the Crosshair tool
           if(this.m_container==NULL)

「把自定义事件接回标准交互逻辑」

在 MT5 的 CCanvasBase 类里,图表事件被统一收口到一个分发函数中。鼠标滚轮、对象创建这类系统事件直接用 id 比对后转给对应处理方法,例如 CHARTEVENT_MOUSE_WHEEL 交给 OnWheelEvent,CHARTEVENT_OBJECT_CREATE 交给 OnCreateEvent。 自定义事件(id 大于 CHARTEVENT_CUSTOM)需要先减掉偏移量还原成标准枚举。代码里用 ENUM_CHART_EVENT(chart_event)=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM) 做映射,再把 OBJECT_CLICK、MOUSE_MOVE、MOUSE_WHEEL 分别路由到 MousePressHandler、MouseMoveHandler、MouseWheelHandler。注意 sparam==this.NameBG() 时直接 return,避免自己发出的事件被自己重复处理。 焦点状态由 OnReleaseEvent 和 OnFocusEvent 维护。光标移开时 m_focused 置 false,若当前不是 COLOR_STATE_DEFAULT 就切回默认色并重绘;进入焦点时置 true,非 COLOR_STATE_FOCUSED 才换色。你可以在 MT5 里建个派生类,打印 m_focused 在悬停前后的布尔值,验证这套状态机是否按预期翻转。外汇与贵金属图表上挂此类控件需注意,交互逻辑异常可能误导下单操作,属高风险场景。

MQL5 / C++
this.SetFlags(true);
  }
  }

class=class="str">"cmt">//--- Mouse wheel scroll event
  if(id==CHARTEVENT_MOUSE_WHEEL)
  {
    this.OnWheelEvent(id,lparam,dparam,sparam);
  }
class=class="str">"cmt">//--- Graphical object creation event
  if(id==CHARTEVENT_OBJECT_CREATE)
  {
    this.OnCreateEvent(id,lparam,dparam,sparam);
  }

class=class="str">"cmt">//--- If a custom chart event has arrived
  if(id>CHARTEVENT_CUSTOM)
  {
    class=class="str">"cmt">//--- do not handle its own events 
    if(sparam==this.NameBG())
      class="kw">return;
    class=class="str">"cmt">//--- bring the custom event in line with the standard ones
    ENUM_CHART_EVENT chart_event=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM);
    class=class="str">"cmt">//--- If clicking an object
    if(chart_event==CHARTEVENT_OBJECT_CLICK)
      {
       this.MousePressHandler(chart_event, lparam, dparam, sparam);
      }
    class=class="str">"cmt">//--- If the mouse cursor is moving
    if(chart_event==CHARTEVENT_MOUSE_MOVE)
      {
       this.MouseMoveHandler(chart_event, lparam, dparam, sparam);
      }
    class=class="str">"cmt">//--- If the mouse wheel is scrolling
    if(chart_event==CHARTEVENT_MOUSE_WHEEL)
      {
       this.MouseWheelHandler(chart_event, lparam, dparam, sparam);
      }
  }
}

class="type">void CCanvasBase::OnReleaseEvent(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
  {
class=class="str">"cmt">//--- The element is not in focus when the cursor is moved away
  this.m_focused=class="kw">false;
class=class="str">"cmt">//--- restore the original colors, reset the Focused flag and redraw the object
  if(!this.CheckColor(COLOR_STATE_DEFAULT))
    {
      this.ColorChange(COLOR_STATE_DEFAULT);
      this.Draw(true);
    }
  }

class="type">void CCanvasBase::OnFocusEvent(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
  {
class=class="str">"cmt">//--- Element in focus
  this.m_focused=true;
class=class="str">"cmt">//--- If the object colors are not for Focused mode
  if(!this.CheckColor(COLOR_STATE_FOCUSED))
    {

画布控件的点击与创建事件接管

在 MT5 自定义画布类里,鼠标按下和对象创建这两个事件决定了控件的交互观感与层级。OnPressEvent 先无条件把 m_focused 置为 true,让后续逻辑知道当前控件已获得焦点。 若当前配色还不是按下态(CheckColor(COLOR_STATE_PRESSED) 返回 false),就调用 ColorChange 切到 COLOR_STATE_PRESSED 并立即 Draw(true) 重绘,视觉上给出被按下的反馈。 紧接着用 EventChartCustom 向图表抛自定义事件,事件号写死为 CHARTEVENT_OBJECT_CLICK,lparam 与 dparam 透传,sparam 填 NameBG() 返回的控件名,EA 或脚本端就能按名字识别是谁被点了。 OnCreateEvent 则处理别处生成的对象:先用 IsBelongsToThis(sparam) 判断归属,是自己的就直接 return;不是就 BringToTop(true) 把本控件提到最前,避免被新对象盖住。外汇与贵金属图表上挂这类控件时,注意自定义事件可能和别的脚本冲突,属高风险调试场景。

MQL5 / C++
class=class="str">"cmt">//--- set the colors and the Focused flag and redraw the object
   this.ColorChange(COLOR_STATE_FOCUSED);
   this.Draw(true);
   }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Object click handler                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCanvasBase::OnPressEvent(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
  {
class=class="str">"cmt">//--- The element is in focus when clicked on
   this.m_focused=true;
class=class="str">"cmt">//--- If the object colors are not for Pressed mode
   if(!this.CheckColor(COLOR_STATE_PRESSED))
     {
     class=class="str">"cmt">//--- set the Pressed colors and redraw the object
     this.ColorChange(COLOR_STATE_PRESSED);
     this.Draw(true);
     }
   class=class="str">"cmt">//--- send a custom event to the chart with the passed values in lparam, dparam, and the object name in sparam
   ::EventChartCustom(this.m_chart_id, (class="type">class="kw">ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameBG());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Graphical object creation event handler            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCanvasBase::OnCreateEvent(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
  {
class=class="str">"cmt">//--- if this is an object belonging to this program, leave
   if(this.IsBelongsToThis(sparam))
      class="kw">return;
class=class="str">"cmt">//--- bring the object to the front
   this.BringToTop(true);
  }

◍ 控件库的头文件骨架怎么搭

在 MT5 的 MQL5\Indicators\tables\controls\ 目录下新建 Controls.mqh,第一步就是引入图形元素基类 Base.mqh。没有这行包含,后面所有标签、按钮的继承关系都编译不过。 宏替换里给了四个默认尺寸:文本标签宽 40 高 16,按钮宽 50 高 16(单位均为像素)。这四个值直接决定你在面板上拖出来的原生控件初版大小,想统一改 UI 比例,调这里比逐个对象设属性快得多。 枚举 ENUM_ELEMENT_COMPARE_BY 列了 6 个可比对属性:ID、名称、文本、颜色、透明度、状态。后面做对象检索或排序时,传这个枚举常量就行;你自己加新属性(比如绑定一个自定义数据字段),记得往枚举里补常量,否则比较函数会漏掉它。 下面这段代码就是最小可编译的控件头文件起点,复制到 Controls.mqh 里能直接过编译。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Controls.mqh |
class=class="str">"cmt">//|                                         Copyright class="num">2025, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include libraries                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Base.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Macro substitutions                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define   DEF_LABEL_W          class="num">40            class=class="str">"cmt">// Text label class="kw">default width
class="macro">#define   DEF_LABEL_H          class="num">16            class=class="str">"cmt">// Text label class="kw">default height
class="macro">#define   DEF_BUTTON_W         class="num">50            class=class="str">"cmt">// Default button width
class="macro">#define   DEF_BUTTON_H         class="num">16            class=class="str">"cmt">// Default button height
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Enumerations                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_ELEMENT_COMPARE_BY                  class=class="str">"cmt">// Compared properties
  {
   ELEMENT_SORT_BY_ID   =  class="num">0,                class=class="str">"cmt">// Comparison by element ID
   ELEMENT_SORT_BY_NAME,                     class=class="str">"cmt">// Comparison by element name
   ELEMENT_SORT_BY_TEXT,                     class=class="str">"cmt">// Comparison by element text
   ELEMENT_SORT_BY_COLOR,                    class=class="str">"cmt">// Comparison by element class="type">color
   ELEMENT_SORT_BY_ALPHA,                    class=class="str">"cmt">// Comparison by element transparency
   ELEMENT_SORT_BY_STATE,                    class=class="str">"cmt">// Comparison by element state
   };

「用图像类给控件挂图标」

MQL5 里每个图形元素都能把图像作为自身组成部分,这意味着按钮、文本行这类简单控件都可以直接画出图标,不用额外叠一层对象。 做法是单独写一个专门绘制图像的类,再把它作为简单控件的成员来用。这样按钮上贴箭头、文本前加状态点都可以在控件内部完成,避免用 OBJ_BITMAP_LABEL 在外面硬凑坐标。 下面这段是源码文件里划分函数区与类区的注释框架,实际图像类就写在 Classes 注释块之后: //+------------------------------------------------------------------+

//Functions

//+------------------------------------------------------------------+ //+------------------------------------------------------------------+

//Classes

//+------------------------------------------------------------------+ 外汇与贵金属图表上做这类自定义控件时,MT5 对象层级多、重绘频繁,可能引发布局抖动,建议在策略测试器外的实盘图表先验证渲染开销。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Functions                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Classes                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+

给控件安一个会画图的笔

在 MT5 自定义控件体系里,先得有一个专门负责「在指定矩形里作画」的对象。它不关心按钮逻辑,只认两件事:画布指针(CCanvas*)和一块带坐标的边界(CBound)。把这两者交给它,后续所有箭头、勾选框、单选点都能在这块区域里落笔。 类内部用 m_canvas 存画布地址,m_bound 存 x/y/宽/高,m_alpha 控制透明度。对外暴露 CanvasAssign() 挂画布、SetXY() 设左上角、SetSize() 改尺寸、SetAlpha() 调透明——这四个调用基本就是控件初始化时的标配三行。 CheckBound() 是兜底方法:没传画布指针,或宽高任一项为 0,直接返回 false,避免后面 FillRect 越界崩脚本。ClearArea() 则用全透明色铺满边界,相当于把这块 UI 擦干净重画。 箭头类方法(上/下/左/右阴影箭头)都遵循同一规则:在边界矩形各内缩 1 像素作为箭头区,再画阴影。复选框和单选按钮的选中/未选中态也是独立方法,调用方无需自己算像素。若要持久化,还可把这块区域存文件再读回。 别把正态当圣经 CheckBound() 返回 false 时很多初学者会漏判,结果图形不显示却查不出原因。开 MT5 跑自定义控件前,先 Print(CheckBound()) 一次,比事后猜坐标强。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Classes                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Image drawing class                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CImagePainter : class="kw">public CBaseObj
  {
class="kw">protected:
   CCanvas             *m_canvas;             class=class="str">"cmt">// Pointer to the canvas where we draw
   CBound              m_bound;               class=class="str">"cmt">// Image coordinates and boundaries
   class="type">uchar               m_alpha;               class=class="str">"cmt">// Transparency

class=class="str">"cmt">//--- Check the canvas validity and correct dimensions
   class="type">bool                CheckBound(class="type">void);
class="kw">public:
class=class="str">"cmt">//--- (class="num">1) Assigns the canvas to draw on, (class="num">2) sets and(class="num">3) returns transparency
   class="type">void                CanvasAssign(CCanvas *canvas)                  { this.m_canvas=canvas;                    }
   class="type">void                SetAlpha(const class="type">uchar value)                    { this.m_alpha=value;                      }
   class="type">uchar               Alpha(class="type">void)                       const        { class="kw">return this.m_alpha;                      }

class=class="str">"cmt">//--- (class="num">1) Set the coordinates and(class="num">2) change the area size
   class="type">void                SetXY(const class="type">int x,const class="type">int y)                 { this.m_bound.SetXY(x,y);                 }
   class="type">void                SetSize(const class="type">int w,const class="type">int h)               { this.m_bound.Resize(w,h);                }
class=class="str">"cmt">//--- Set the area coordinates and dimensions

◍ 图像边界与箭头绘制的类方法封装

在 MT5 自定义图形库中,边界管理通常交给一个内部 m_bound 对象统一处理。下面这段类方法把坐标与尺寸的设置、读取全部分发到该对象,避免散落各处的硬编码。 SetBound(x,y,w,h) 先调 SetXY 再调 SetSize,一次性框定图像区域;X()、Y()、Right()、Bottom()、Width()、Height() 则直接返回 m_bound 对应值,调用方无需关心底层存储。 Clear(x,y,w,h,update) 负责清空指定矩形区域,update 默认 true 表示立即重绘。ArrowUp(x,y,w,h,clr,alpha,update) 在该区域内画一个向上实心箭头,alpha 控制透明度(0–255),适合在 K 线图上叠加信号标记。外汇与贵金属波动剧烈,此类图形仅作辅助参考,实盘信号需结合多周期验证。 把下面代码贴进你的类定义,编译后可在 EA 里直接 this.ArrowUp(10,10,20,20,clrGreen,200) 试画。

MQL5 / C++
class="type">void SetBound(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h)
              {
               this.SetXY(x,y);
               this.SetSize(w,h);
              }
class=class="str">"cmt">//--- Returns the image boundaries and dimensions
   class="type">int X(class="type">void)                const { class="kw">return this.m_bound.X();       }
   class="type">int Y(class="type">void)                const { class="kw">return this.m_bound.Y();       }
   class="type">int Right(class="type">void)            const { class="kw">return this.m_bound.Right();    }
   class="type">int Bottom(class="type">void)           const { class="kw">return this.m_bound.Bottom();   }
   class="type">int Width(class="type">void)            const { class="kw">return this.m_bound.Width();    }
   class="type">int Height(class="type">void)           const { class="kw">return this.m_bound.Height();   }

class=class="str">"cmt">//--- Clear the area
   class="type">bool Clear(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">bool update=true);
class=class="str">"cmt">//--- Draw a filled(class="num">1) up, (class="num">2) down, (class="num">3) left and(class="num">4) right arrow
   class="type">bool ArrowUp(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);

「图形基元与对象持久化的接口骨架」

在 MT5 自定义控件库里,箭头、勾选框、单选钮这类基础图元都走同一套参数约定:x/y 定坐标,w/h 定尺寸,clr 与 alpha(0~255)控制颜色与透明度,update 默认 true 表示画完立即重绘画布。 ArrowDown、ArrowLeft、ArrowRight 三个函数只差朝向,适合在价格行为信号上标方向——比如向下箭头压在吞没阴线最高点,视觉上比文字标签更直接。 CheckedBox / UncheckedBox 与 CheckedRadioButton / UncheckedRadioButton 成对出现,前者用于多选项面板,后者用于互斥周期切换;alpha 调低到 120 左右能在黄金报价浮动窗上减少遮挡感。 Compare、Save、Load 是虚方法:Compare 默认 mode=0 做指针比较,重写后可按坐标或类型排序;Save/Load 接收文件句柄,意味着这些图元对象能脱离图表存到 bin 文件,下次加载重建。外汇与贵金属波动剧烈,这类自绘 UI 仅辅助判读,实盘仍属高风险。

MQL5 / C++
class="type">bool ArrowDown(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);
class="type">bool ArrowLeft(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);
class="type">bool ArrowRight(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);
class=class="str">"cmt">//--- Draw(class="num">1) checked and(class="num">2) unchecked CheckBox
class="type">bool CheckedBox(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);
class="type">bool UncheckedBox(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);
class=class="str">"cmt">//--- Draw(class="num">1) checked and(class="num">2) unchecked RadioButton
class="type">bool CheckedRadioButton(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);
class="type">bool UncheckedRadioButton(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
class="kw">virtual class="type">int Compare(const CObject *node,const class="type">int mode=class="num">0) const;
class="kw">virtual class="type">bool Save(const class="type">int file_handle);
class="kw">virtual class="type">bool Load(const class="type">int file_handle);

图形绘制类的构造与比较逻辑

CImagePainter 是 MT5 图形界面里负责在画布上贴图的元素类,它的类型标识固定返回 ELEMENT_TYPE_IMAGE_PAINTER,用于在对象树里区分其他控件。 类提供了多组构造函数:无参版本把绘制边界设为 (1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2) 并命名 "Image Painter";带 CCanvas* 的版本把外部画布指针接进来;还能用 id+name 或 id+坐标宽高+name 直接定制边界。析构函数为空,资源靠画布自身管理。 比较方法 Compare 按模式排序,目前只实现 ELEMENT_SORT_BY_NAME:用 Name() 做大于、小于、等于的三态返回 1 / -1 / 0。想在自有面板里挂图片按钮,直接抄下面构造参数就能在 MT5 里编译验证。

MQL5 / C++
class="kw">virtual class="type">int Type(class="type">void) const { class="kw">return(ELEMENT_TYPE_IMAGE_PAINTER); }

class=class="str">"cmt">//--- Constructors/destructor
CImagePainter(class="type">void) : m_canvas(NULL) { this.SetBound(class="num">1,class="num">1,DEF_BUTTON_H-class="num">2,DEF_BUTTON_H-class="num">2); this.SetName("Image Painter"); }
CImagePainter(CCanvas *canvas) : m_canvas(canvas) { this.SetBound(class="num">1,class="num">1,DEF_BUTTON_H-class="num">2,DEF_BUTTON_H-class="num">2); this.SetName("Image Painter"); }
CImagePainter(CCanvas *canvas,const class="type">int id,const class="type">class="kw">string name) : m_canvas(canvas)
  {
   this.m_id=id;
   this.SetName(name);
   this.SetBound(class="num">1,class="num">1,DEF_BUTTON_H-class="num">2,DEF_BUTTON_H-class="num">2);
  }
CImagePainter(CCanvas *canvas,const class="type">int id,const class="type">int dx,const class="type">int dy,const class="type">int w,const class="type">int h,const class="type">class="kw">string name) : m_canvas(canvas)
  {
   this.m_id=id;
   this.SetName(name);
   this.SetBound(dx,dy,w,h);
  }
~CImagePainter(class="type">void) {} };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CImagePainter::Compare two objects                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CImagePainter::Compare(const CObject *node,const class="type">int mode=class="num">0) const
  {
   const CImagePainter *obj=node;
   class="kw">switch(mode)
     {
      case ELEMENT_SORT_BY_NAME : class="kw">return(this.Name() >obj.Name() ? class="num">1 : this.Name() <obj.Name() ? -class="num">1 : class="num">0);

◍ 画布校验与箭头绘制的底层约束

在 MT5 自定义图形类里,任何绘制动作前都必须先过 CheckBound()。它干两件事:确认画布指针 m_canvas 不是 NULL,确认宽高均不为 0。任一项不满足就打印具体错误并返回 false,避免后续 FillRectangle 在空对象上崩掉。 Clear() 方法用 FillRectangle(x,y,x+w-1,y+h-1,clrNULL) 以透明色覆盖指定矩形。注意宽度 w 和高度 h 在坐标计算时都减了 1,因为像素坐标从 0 起算,传 10 像素宽实际落点是 x 到 x+9。update 参数默认 true,会调用 m_canvas.Update(false) 把离屏缓冲刷到图表。 ArrowUp() 开头同样先 CheckBound(),通不过直接返回 false。只有校验通过后,才进入箭头角点坐标的计算逻辑——这保证所有图形函数共用同一道闸门,减少重复判空代码。 实盘加载这类指标时,若图表一片空白且日志报 'First you need to assign the canvas',九成是 CanvasAssign() 漏调或 SetSize() 传了 0。外汇与贵金属图表叠加自定义绘制属高风险操作,参数错乱可能遮盖价格本身。

MQL5 / C++
class="type">bool CImagePainter::CheckBound(class="type">void)
  {
   if(this.m_canvas==NULL)
     {
      ::PrintFormat("%s: Error. First you need to assign the canvas using the CanvasAssign() method",__FUNCTION__);
      class="kw">return class="kw">false;
     }
   if(this.Width()==class="num">0 || this.Height()==class="num">0)
     {
      ::PrintFormat("%s: Error. First you need to set the area size using the SetSize() or SetBound() methods",__FUNCTION__);
      class="kw">return class="kw">false;
     }
   class="kw">return true;
  }

class="type">bool CImagePainter::Clear(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">bool update=true)
  {
   if(!this.CheckBound())
      class="kw">return class="kw">false;
   this.m_canvas.FillRectangle(x,y,x+w-class="num">1,y+h-class="num">1,clrNULL);
   if(update)
      this.m_canvas.Update(class="kw">false);
   class="kw">return true;  
  }

class="type">bool CImagePainter::ArrowUp(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
   if(!this.CheckBound())
      class="kw">return class="kw">false;

「画布上箭头三角形的坐标算法」

在 MT5 的 CImagePainter 类里,向上箭头本质是一个填充三角形。顶点取区域水平中线上方 3 像素处,底边两点落在区域下沿往上 4 像素,这种偏移是为了在有限画布高度里留出 1 像素描边余量。 半宽 hw 用 floor(w/2) 算,若结果为 0 则强制置 1,避免宽度为 1 时三角形退化成线段。x1 从区域左边 +1 起步,x3 到 x1+w-1 收尾,整型坐标直接喂给 FillTriangle,不依赖浮点抗锯齿。 向下箭头只是把 y 轴镜像:左底点 y1=y+4,顶点 y2=y+h-3,其余 x 逻辑完全相同。两者都带 update 开关,批量绘制时设 false,最后统一 Update(false) 一次,能少触发几次画布重绘。 外汇与贵金属图表叠加这类自绘信号,需注意不同品种点距与画布分辨率差异可能导致箭头视觉大小漂移,实盘前应在对应品种手动验证。

MQL5 / C++
class="type">int hw=(class="type">int)::floor(w/class="num">2);      class=class="str">"cmt">// Half width
if(hw==class="num">0)
   hw=class="num">1;
class="type">int x1 = x + class="num">1;                class=class="str">"cmt">// X. Base(left point)
class="type">int y1 = y + h - class="num">4;            class=class="str">"cmt">// Y. Left base point
class="type">int x2 = x1 + hw;              class=class="str">"cmt">// X. Vertex(central top point)
class="type">int y2 = y + class="num">3;                class=class="str">"cmt">// Y. Vertex(highest point)
class="type">int x3 = x1 + w - class="num">1;           class=class="str">"cmt">// X. Base(right point)
class="type">int y3 = y1;                   class=class="str">"cmt">// Y. Base(right point)
class=class="str">"cmt">//--- Draw a triangle
this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));
if(update)
   this.m_canvas.Update(class="kw">false);
class="kw">return true;   
}
class="type">bool CImagePainter::ArrowDown(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
  if(!this.CheckBound())
     class="kw">return class="kw">false;
  class="type">int hw=(class="type">int)::floor(w/class="num">2);      class=class="str">"cmt">// Half width
  if(hw==class="num">0)
     hw=class="num">1;
  class="type">int x1=x+class="num">1;                    class=class="str">"cmt">// X. Base(left point)
  class="type">int y1=y+class="num">4;                    class=class="str">"cmt">// Y. Left base point
  class="type">int x2=x1+hw;                  class=class="str">"cmt">// X. Vertex(central bottom point)
  class="type">int y2=y+h-class="num">3;                  class=class="str">"cmt">// Y. Vertex(lowest point)
  class="type">int x3=x1+w-class="num">1;                 class=class="str">"cmt">// X. Base(right point)
  class="type">int y3=y1;                     class=class="str">"cmt">// Y. Base(right point)
  this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));
  if(update)
     this.m_canvas.Update(class="kw">false);
  class="kw">return true;   
  }

在画布上画左右三角箭头

做自定义指标或面板时,常需要在图表角落标出信号方向。CImagePainter 里两个方法 ArrowLeft 与 ArrowRight 就是干这个的:用三角形填充来画左指或右指箭头,参数里 x、y 是区域左上角,w、h 是包围盒尺寸,clr 和 alpha 控制颜色与透明度。 两个函数都先调用 CheckBound() 校验画布区域是否有效,无效直接返回 false,避免越界写像素。半高 hh 用 floor(h/2) 算,若结果为 0 则强制置 1,保证箭头至少有 1 像素高度。 ArrowLeft 把顶点放在左侧(x+3, y1+hh),底边在右侧(x+w-4);ArrowRight 反过来,底边在左(x+4)、顶点在右(x+w-3)。注意后者代码片段在 y3 行被截断,完整版应类似 ArrowLeft 补出 y3=y1+h-1 并调用 FillTriangle 与 Update。 若你要在 MT5 里验证,把这两段塞进自己的 CImagePainter 类,传一组 w=20、h=16 的包围盒,alpha 设 200,就能看到方向箭头以概率极高的清晰度渲染在画布上。外汇与贵金属图表叠加此类元素时,注意高风险品种波动快,箭头信号仅作辅助、不构成方向承诺。

MQL5 / C++
class="type">bool CImagePainter::ArrowLeft(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
class=class="str">"cmt">//--- If the image area is not valid, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!this.CheckBound())
        class="kw">return class="kw">false;
class=class="str">"cmt">//--- Calculate the coordinates of the arrow corners inside the image area
   class="type">int hh=(class="type">int)::floor(h/class="num">2);   class=class="str">"cmt">// Half height
   if(hh==class="num">0)
        hh=class="num">1;
   class="type">int x1=x+w-class="num">4;                class=class="str">"cmt">// X. Base(right side)
   class="type">int y1=y+class="num">1;                  class=class="str">"cmt">// Y. Base upper corner
   class="type">int x2=x+class="num">3;                  class=class="str">"cmt">// X. Vertex(left center point)
   class="type">int y2=y1+hh;                class=class="str">"cmt">// Y. Central point(vertex)
   class="type">int x3=x1;                   class=class="str">"cmt">// X. Bottom base corner
   class="type">int y3=y1+h-class="num">1;               class=class="str">"cmt">// Y. Bottom base corner
class=class="str">"cmt">//--- Draw a triangle
   this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));
   if(update)
       this.m_canvas.Update(class="kw">false);
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CImagePainter::Draw a filled right arrow                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CImagePainter::ArrowRight(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
class=class="str">"cmt">//--- If the image area is not valid, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!this.CheckBound())
        class="kw">return class="kw">false;
class=class="str">"cmt">//--- Calculate the coordinates of the arrow corners inside the image area
   class="type">int hh=(class="type">int)::floor(h/class="num">2);   class=class="str">"cmt">// Half height
   if(hh==class="num">0)
        hh=class="num">1;
   class="type">int x1=x+class="num">4;                  class=class="str">"cmt">// X. Triangle base(left side)
   class="type">int y1=y+class="num">1;                  class=class="str">"cmt">// Y. Base upper corner
   class="type">int x2=x+w-class="num">3;                class=class="str">"cmt">// X. Vertex(right center point)
   class="type">int y2=y1+hh;                class=class="str">"cmt">// Y. Central point(vertex)
   class="type">int x3=x1;                   class=class="str">"cmt">// X. Bottom base corner

◍ 在画布上画勾选框的像素逻辑

给 MT5 自定义指标做图形界面时,勾选框(CheckBox)不一定非要用系统控件,直接用 Canvas 画出来更可控。下面这段 CImagePainter 的成员函数就演示了如何纯手绘一个带对勾的方框。 函数开头先调 CheckBound() 确认画布区域有效,无效直接返回 false,避免越界写像素。接着以传入的 x、y 为左上基准,内缩 1 像素得到 x1、y1,外缩 2 像素得到 x2、y2,也就是方框实际绘制范围。 对勾的几何用三个点描述:左点在 x1+(x2-x1)/4,中点偏右到 x1+w/3,右点在 x2-(x2-x1)/4;纵坐标上左点在中线略下,中点靠近底部,右点靠近顶部,整体构成一个斜向上的折角。 勾线用了两次 Polyline:第一次按原坐标画,第二次把三个 X 都加 1 再画一遍,相当于线宽翻倍,在普通分辨率屏上勾号更清楚。最后若 update 为 true 就调 Update(false) 把缓冲刷到图面,返回 true 表示绘制成功。 想验证的话,把 w 和 h 设成 16 丢进你自己的 Canvas 面板,看勾号比例是否顺眼;外汇和贵金属图表叠加这类自绘控件时波动快,注意高频 Update 可能拖慢刷帧,属于高风险界面的性能坑。

MQL5 / C++
class="type">int y3=y1+h-class="num">1;                  class=class="str">"cmt">// Y. Bottom base corner
class=class="str">"cmt">//--- Draw a triangle
   this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha));
   if(update)
      this.m_canvas.Update(class="kw">false);
   class="kw">return true;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CImagePainter::Draw a checked CheckBox                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CImagePainter::CheckedBox(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
class=class="str">"cmt">//--- If the image area is not valid, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!this.CheckBound())
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Rectangle coordinates
   class="type">int x1=x+class="num">1;                 class=class="str">"cmt">// Upper left corner, X
   class="type">int y1=y+class="num">1;                 class=class="str">"cmt">// Upper left corner, Y
   class="type">int x2=x+w-class="num">2;               class=class="str">"cmt">// Bottom right corner, X
   class="type">int y2=y+h-class="num">2;               class=class="str">"cmt">// Bottom right corner, Y
class=class="str">"cmt">//--- Draw a rectangle
   this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha));

class=class="str">"cmt">//--- Checkmark coordinates
   class="type">int arrx[class="num">3], arry[class="num">3];

   arrx[class="num">0]=x1+(x2-x1)/class="num">4;       class=class="str">"cmt">// X. Left point
   arrx[class="num">1]=x1+w/class="num">3;             class=class="str">"cmt">// X. Central point
   arrx[class="num">2]=x2-(x2-x1)/class="num">4;       class=class="str">"cmt">// X. Right point

   arry[class="num">0]=y1+class="num">1+(y2-y1)/class="num">2;     class=class="str">"cmt">// Y. Left point
   arry[class="num">1]=y2-(y2-y1)/class="num">3;       class=class="str">"cmt">// Y. Central point
   arry[class="num">2]=y1+(y2-y1)/class="num">3;       class=class="str">"cmt">// Y. Right point

class=class="str">"cmt">//--- Draw a "tick" with a class="type">class="kw">double-thickness line
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha));
   arrx[class="num">0]++;
   arrx[class="num">1]++;
   arrx[class="num">2]++;
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha));

   if(update)
      this.m_canvas.Update(class="kw">false);
   class="kw">return true;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CImagePainter::Draw unchecked CheckBox                              |
class=class="str">"cmt">//+------------------------------------------------------------------+

「自绘控件里的方框与单选圆点」

在 MT5 的 CImagePainter 类里,UncheckedBox 负责画一个未选中状态的方框。它先调用 CheckBound 确认画布区域有效,无效直接返回 false,避免越界写屏。 方框实际绘制区域比传入的 (x,y,w,h) 各向内缩 1 像素:左上角变成 (x+1, y+1),右下角变成 (x+w-2, y+h-2),相当于留 1 像素描边空隙。Rectangle 用 ColorToARGB(clr, alpha) 上色,alpha 控制透明度,update 为 true 时立即 m_canvas.Update(false) 刷新。 CheckedRadioButton 画的是单选圆点。它同样先校验边界,再用较短边 d = fmin(x2-x1, y2-y1) 算直径,半径 r = d/2,圆心落在 (x1+r, y1+r)。CircleWu 做抗锯齿画圆,之后把 r 减半作为内部选中标记的半径,若小于 1 则强制为 1,保证小尺寸下也能看见点。 开 MT5 新建脚本把这两段塞进自己的绘图画布类,传 w=h=20、clr=clrDodgerBlue、alpha=200,能直接看到 18×18 方框和直径 18 的圆点。外汇与贵金属图表叠加自绘控件时波动刷新频繁,注意高并发 Update 可能拖慢主图帧率。

MQL5 / C++
class="type">bool CImagePainter::UncheckedBox(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
class=class="str">"cmt">//--- If the image area is not valid, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!this.CheckBound())
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Rectangle coordinates
   class="type">int x1=x+class="num">1;                    class=class="str">"cmt">// Upper left corner, X
   class="type">int y1=y+class="num">1;                    class=class="str">"cmt">// Upper left corner, Y
   class="type">int x2=x+w-class="num">2;                  class=class="str">"cmt">// Bottom right corner, X
   class="type">int y2=y+h-class="num">2;                  class=class="str">"cmt">// Bottom right corner, Y
class=class="str">"cmt">//--- Draw a rectangle
   this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha));
   
   if(update)
      this.m_canvas.Update(class="kw">false);
   class="kw">return true;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CImagePainter::Draw checked RadioButton                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CImagePainter::CheckedRadioButton(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
class=class="str">"cmt">//--- If the image area is not valid, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!this.CheckBound())
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Circle coordinates and radius
   class="type">int x1=x+class="num">1;                    class=class="str">"cmt">// Circle region upper left corner, X
   class="type">int y1=y+class="num">1;                    class=class="str">"cmt">// Circle region upper left corner, Y
   class="type">int x2=x+w-class="num">2;                  class=class="str">"cmt">// Circle region lower right corner, X
   class="type">int y2=y+h-class="num">2;                  class=class="str">"cmt">// Circle region lower right corner, Y

class=class="str">"cmt">//--- Circle coordinates and radius
   class="type">int d=::fmin(x2-x1,y2-y1); class=class="str">"cmt">// Shorter side diameter(width or height)
   class="type">int r=d/class="num">2;                      class=class="str">"cmt">// Radius
   class="type">int cx=x1+r;                    class=class="str">"cmt">// Center X coordinate
   class="type">int cy=y1+r;                    class=class="str">"cmt">// Center Y coordinate
class=class="str">"cmt">//--- Draw a circle
   this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha));

class=class="str">"cmt">//--- Label radius
   r/=class="num">2;
   if(r<class="num">1)
      r=class="num">1;
class=class="str">"cmt">//--- Draw a label

未勾选单选圆与画布落盘的实现细节

在自定义图形库里,未勾选状态的单选按钮靠 CImagePainter::UncheckedRadioButton 画出。函数先以 CheckBound 确认画布区域有效,无效直接返 false,避免越界写像素。 坐标处理上,圆区左上角取 (x+1, y+1),右下角取 (x+w-2, y+h-2),直径 d 用 fmin 取宽高较短边,半径 r=d/2,圆心落在 (x1+r, y1+r)。这样在 20×20 的控件里实际圆直径就是 16,留了 2 像素描边余量。 绘制本身调用 CircleWu 做抗锯齿圆,而不是 FillCircle,所以未勾选态只有空心轮廓。update 为真时调 m_canvas.Update(false) 把缓冲刷到图表,返回 true 表示成功。 保存环节由 CImagePainter::Save 负责,先写父类 CBaseObj 数据,再 FileWriteInteger 落 m_alpha 透明度(INT_VALUE 长度校验),最后 m_bound.Save 存区域。三者任一写失败即返 false,全部成功才返 true。开 MT5 把这段塞进你的面板类,改 w、h 就能看圆框留白变化。

MQL5 / C++
class="type">bool CImagePainter::UncheckedRadioButton(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,const class="type">color clr,const class="type">uchar alpha,const class="type">bool update=true)
  {
class=class="str">"cmt">//--- If the image area is not valid, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!this.CheckBound())
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Circle coordinates and radius
   class="type">int x1=x+class="num">1;                 class=class="str">"cmt">// Circle region upper left corner, X
   class="type">int y1=y+class="num">1;                 class=class="str">"cmt">// Circle region upper left corner, Y
   class="type">int x2=x+w-class="num">2;               class=class="str">"cmt">// Circle region lower right corner, X
   class="type">int y2=y+h-class="num">2;               class=class="str">"cmt">// Circle region lower right corner, Y

class=class="str">"cmt">//--- Circle coordinates and radius
   class="type">int d=::fmin(x2-x1,y2-y1); class=class="str">"cmt">// Shorter side diameter(width or height)
   class="type">int r=d/class="num">2;                 class=class="str">"cmt">// Radius
   class="type">int cx=x1+r;               class=class="str">"cmt">// Center X coordinate
   class="type">int cy=y1+r;               class=class="str">"cmt">// Center Y coordinate
class=class="str">"cmt">//--- Draw a circle
   this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha));

   if(update)
      this.m_canvas.Update(class="kw">false);
   class="kw">return true;
  }

class="type">bool CImagePainter::Save(const class="type">int file_handle)
  {
class=class="str">"cmt">//--- Save the parent object data
   if(!CBaseObj::Save(file_handle))
      class="kw">return class="kw">false;

class=class="str">"cmt">//--- Save transparency
   if(::FileWriteInteger(file_handle,this.m_alpha,INT_VALUE)!=INT_VALUE)
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save area data
   if(!this.m_bound.Save(file_handle))
      class="kw">return class="kw">false;
     
class=class="str">"cmt">//--- All is successful
   class="kw">return true;
  }

◍ 从文件句柄还原图像绘制对象

在 MT5 自定义图形库里,CImagePainter 的 Load 方法负责把已序列化的图像绘制对象从文件流里读回来。它先调用父类 CBaseObj::Load 恢复基类字段,任何一步失败直接返回 false,保证对象不会处于半初始化状态。 随后用 FileReadInteger 以 INT_VALUE 方式读出一个整数并强转 uchar,写入 m_alpha 透明度成员;这说明透明度在文件里只占一个字节,取值范围 0–255,对应完全透明到不透明。 最后加载包围区域 m_bound,同样失败即返回。整段没有额外校验 alpha 合法性,若你打算复用这套序列化,建议在读取后加一句范围判断,避免脏数据让图表渲染异常。外汇与贵金属图表叠加自绘层时,这类对象加载失败可能无声吞掉图形,需自行在 EA 里打印返回值排查。

MQL5 / C++
class=class="str">"cmt">//| CImagePainter::Load from file                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CImagePainter::Load(const class="type">int file_handle)
  {
class=class="str">"cmt">//--- Load parent object data
   if(!CBaseObj::Load(file_handle))
      class="kw">return class="kw">false;
     
class=class="str">"cmt">//--- Load transparency
   this.m_alpha=(class="type">uchar)::FileReadInteger(file_handle,INT_VALUE);
class=class="str">"cmt">//--- Load area data
   if(!this.m_bound.Load(file_handle))
      class="kw">return class="kw">false;

class=class="str">"cmt">//--- All is successful
   class="kw">return true;
  }

「文本标签类的双缓冲擦除逻辑」

在 MT5 自定义控件里,CLabel 用两个 ushort 数组 m_text 和 m_text_prev 分别存当前与上一帧的标签文本。绘制新文本前,先按旧文本尺寸用全透明矩形覆盖,才能干净擦掉残留笔画,避免重绘时字符叠影。 四个构造函数都把绘图区尺寸设成 0,即该元素不带图标;背景全透明、前景全不透明,文本直接浮在画布上。对象比较默认走 ID,同链表内按 ID 取对象比按名称或颜色快,适合高频刷新面板。 文本超出边界时,右侧会补一个冒号提示截断,例如“这段文本超界...”。这种细节在盯盘 EA 的状态栏里很实用,能一眼看出参数没显示全。 下面这段是类的保护成员与文本存取方法的核心声明,留意 StringToShortArray 把 string 压成 ushort 数组,正是双缓冲擦除能算准旧文本宽高的前提。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Text label class                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CLabel : class="kw">public CCanvasBase
  {
class="kw">protected:
   CImagePainter       m_painter;                                        class=class="str">"cmt">// Drawing class
   class="type">class="kw">ushort              m_text[];                                         class=class="str">"cmt">// Text
   class="type">class="kw">ushort              m_text_prev[];                                    class=class="str">"cmt">// Previous text
   class="type">int                 m_text_x;                                         class=class="str">"cmt">// Text X coordinate(offset relative to the object left border)
   class="type">int                 m_text_y;                                         class=class="str">"cmt">// Text Y coordinate(offset relative to the object upper border)

class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the previous text
   class="type">void                SetTextPrev(const class="type">class="kw">string text)                    { ::StringToShortArray(text,this.m_text_prev);  }
   class="type">class="kw">string              TextPrev(class="type">void)                              const { class="kw">return ::ShortArrayToString(this.m_text_prev);}

class=class="str">"cmt">//--- Delete the text
   class="type">void                ClearText(class="type">void);
class="kw">public:
class=class="str">"cmt">//--- Return the pointer to the drawing class
   CImagePainter      *Painter(class="type">void)                                     { class="kw">return &this.m_painter;      }

class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the text
   class="type">void                SetText(const class="type">class="kw">string text)                        { ::StringToShortArray(text,this.m_text);       }
   class="type">class="kw">string              Text(class="type">void)                              const     { class="kw">return ::ShortArrayToString(this.m_text);     }

class=class="str">"cmt">//--- Return the text(class="num">1) X and(class="num">2) Y coordinate

标签与图层的像素级定位接口

在 MT5 自定义控件类里,文字偏移和图像区域是两套独立坐标。文字用 TextX / TextY 读、用 SetTextShiftH / SetTextShiftV 写,单位是像素,不影响底层图像缓冲区。 图像区域则由 m_painter 托管:SetImageXY 改左上角起点,SetImageSize 改宽高,SetImageBound 一口气把四个参数都设了,内部就是连续调前两个方法。 ImageX / ImageY 直接透传 painter 的 X() / Y(),返回的是图形区当前左上角坐标。实盘里若发现 HUD 文字和背景图错位,先打印这两个值:X 偏差超过 2 像素肉眼就能看出来,贵金属 1 分钟图这种高密度界面尤其明显。外汇与贵金属杠杆高,界面逻辑错误不会直接亏钱,但可能让你误读信号。 别把坐标当全局 文字偏移是相对控件的局部量,图像坐标是绘制缓冲区的绝对量,混用会在多图表平铺时把标签甩到屏幕外。

MQL5 / C++
class="type">int TextX(class="type">void) const { class="kw">return this.m_text_x; }
class="type">int TextY(class="type">void) const { class="kw">return this.m_text_y; }
class=class="str">"cmt">//--- Set the text(class="num">1) X and(class="num">2) Y coordinate
class="type">void SetTextShiftH(const class="type">int x) { this.m_text_x=x; }
class="type">void SetTextShiftV(const class="type">int y) { this.m_text_y=y; }

class=class="str">"cmt">//--- (class="num">1) Set the coordinates and(class="num">2) change the image area size
class="type">void SetImageXY(const class="type">int x,const class="type">int y) { this.m_painter.SetXY(x,y); }
class="type">void SetImageSize(const class="type">int w,const class="type">int h) { this.m_painter.SetSize(w,h); }
class=class="str">"cmt">//--- Set the area coordinates and image area dimensions
class="type">void SetImageBound(const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h)
  {
   this.SetImageXY(x,y);
   this.SetImageSize(w,h);
  }
class=class="str">"cmt">//--- Return the(class="num">1) X, (class="num">2) Y coordinate, (class="num">3) width, (class="num">4) height, (class="num">5) right, (class="num">6) bottom image area border
class="type">int ImageX(class="type">void) const { class="kw">return this.m_painter.X(); }
class="type">int ImageY(class="type">void) const { class="kw">return this.m_painter.Y(); }

◍ 标签对象的尺寸与绘制接口

在 MT5 自定义图形库里,CLabel 类把标签的画布尺寸直接映射到底层 painter 对象的属性。ImageWidth 与 ImageHeight 返回 m_painter 的宽高,ImageRight 和 ImageBottom 则返回右边界与下边界坐标,这四个方法都是 const 修饰,调用时不改变对象状态。 DrawText 接收相对坐标 dx、dy 和字符串 text,以及是否触发图表重绘的布尔开关 chart_redraw;Draw 作为虚函数,负责把外观真正落到图上,同样由 chart_redraw 控制重绘时机。 比较、存盘、读盘和类型识别由四个虚方法承接:Compare 支持按 mode 比较节点,Save/Load 依赖文件句柄做序列化,Type 固定返回 ELEMENT_TYPE_LABEL 以区分元素类别。 构造函数提供了三种重载:无参初始化、指定名称与文本并给坐标宽高的二维版本,以及额外带窗口索引 wnd 的子窗口版本。复制下面这段代码到 MT5 头文件,就能直接看清楚接口骨架。

MQL5 / C++
  class="type">int               ImageWidth(class="type">void)                const { class="kw">return this.m_painter.Width();        }
  class="type">int               ImageHeight(class="type">void)               const { class="kw">return this.m_painter.Height();       }
  class="type">int               ImageRight(class="type">void)                const { class="kw">return this.m_painter.Right();        }
  class="type">int               ImageBottom(class="type">void)               const { class="kw">return this.m_painter.Bottom();       }
class=class="str">"cmt">//--- Display the text
  class="type">void              DrawText(const class="type">int dx, const class="type">int dy, const class="type">class="kw">string text, const class="type">bool chart_redraw);

class=class="str">"cmt">//--- Draw the appearance
  class="kw">virtual class="type">void      Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
  class="kw">virtual class="type">int       Compare(const CObject *node,const class="type">int mode=class="num">0) const;
  class="kw">virtual class="type">bool      Save(const class="type">int file_handle);
  class="kw">virtual class="type">bool      Load(const class="type">int file_handle);
  class="kw">virtual class="type">int       Type(class="type">void)                          const { class="kw">return(ELEMENT_TYPE_LABEL);          }

class=class="str">"cmt">//--- Constructors/destructor
                     CLabel(class="type">void);
                     CLabel(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CLabel(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);

「CLabel 的三种构造路径」

在 MT5 的 Canvas 标签类里,CLabel 提供了无参、双参简化、全参三种构造入口,分别对应不同挂载需求。无参构造会把标签建在当前图表主窗口的 (0,0),宽高走 DEF_LABEL_W / DEF_LABEL_H 默认值,文本先填 "Label"。 细看两个有参构造,它们都把背景透明度设成 0、前景设成 255,也就是背景透、字不透。m_painter 在构造时先绑定前景画布,再把自身坐标和尺寸归零,让标签处于未激活绘制状态,等后续 Text 更新才重画。 如果你要在子窗口(比如独立指标窗)挂标签,就得用带 wnd 参数的第三个构造;chart_id 默认取 ChartID() 表示当前图。开 MT5 新建 EA,把下面代码贴进 OnInit,能直接看到主图左上角浮出透明底标签。

MQL5 / C++
CLabel::CLabel(class="type">void) : CCanvasBase("Label",::ChartID(),class="num">0,class="num">0,class="num">0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(class="num">0), m_text_y(class="num">0)
  {
  this.m_painter.CanvasAssign(this.GetForeground());
  this.m_painter.SetXY(class="num">0,class="num">0);
  this.m_painter.SetSize(class="num">0,class="num">0);
  this.SetText("Label");
  this.SetTextPrev("");
  this.SetAlphaBG(class="num">0);
  this.SetAlphaFG(class="num">255);
  }
CLabel::CLabel(const class="type">class="kw">string object_name, const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CCanvasBase(object_name,::ChartID(),class="num">0,x,y,w,h), m_text_x(class="num">0), m_text_y(class="num">0)
  {
  this.m_painter.CanvasAssign(this.GetForeground());
  this.m_painter.SetXY(class="num">0,class="num">0);
  this.m_painter.SetSize(class="num">0,class="num">0);
  this.SetText(text);
  this.SetTextPrev("");
  this.SetAlphaBG(class="num">0);
  this.SetAlphaFG(class="num">255);
  }

CLabel 构造与排序的实现细节

在 MT5 的图表对象封装里,CLabel 提供了两个构造函数:一个默认把画布绑到当前图表(ChartID()),另一个允许显式传入 chart_id 与子窗口序号 wnd。两者都把 m_text_x、m_text_y 初始化为 0,说明文本锚点默认落在对象左上角。 构造函数体内先调用 CanvasAssign 把前景画布挂到绘制对象,紧接 SetXY(0,0) 与 SetSize(0,0) 把前景画布置为无效尺寸——这意味着标签在未被显式重设前不会主动刷出像素。随后 SetText 写入当前文本,SetTextPrev("") 清空上一帧,背景透明度 SetAlphaBG(0) 全透,前景 SetAlphaFG(255) 不透明。 对比排序用 Compare 方法,按 mode 切换:ELEMENT_SORT_BY_NAME 比 Name(),ELEMENT_SORT_BY_TEXT 比 Text(),ELEMENT_SORT_BY_COLOR 比 ForeColor()。返回值遵守 > 返 1、< 返 -1、相等返 0 的惯例,可直接塞进容器排序。 开 MT5 在 EA 里 new 一个 CLabel,传 chart_id=0、wnd=0、w=100、h=20,若不改 SetSize 就 Draw,屏幕可能什么都不显示——这印证了构造里尺寸归零的设计意图。

MQL5 / C++
  CCanvasBase(object_name,::ChartID(),wnd,x,y,w,h), m_text_x(class="num">0), m_text_y(class="num">0)
  {
class=class="str">"cmt">//--- Assign the foreground canvas to the drawing object and
class=class="str">"cmt">//--- reset the coordinates and dimensions, which makes it inactive
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(class="num">0,class="num">0);
   this.m_painter.SetSize(class="num">0,class="num">0);
class=class="str">"cmt">//--- Set the current and previous text
   this.SetText(text);
   this.SetTextPrev("");
class=class="str">"cmt">//--- Background is transparent, foreground is not
   this.SetAlphaBG(class="num">0);
   this.SetAlphaFG(class="num">255);
  }
class=class="str">"cmt">//+------------------------------------------------------------------------------+
class=class="str">"cmt">//| CLabel::Parametric constructor. Builds a label in the specified window       |
class=class="str">"cmt">//| of the specified chart with the specified text, coordinates and dimensions    |
class=class="str">"cmt">//+------------------------------------------------------------------------------+
CLabel::CLabel(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
   CCanvasBase(object_name,chart_id,wnd,x,y,w,h), m_text_x(class="num">0), m_text_y(class="num">0)
  {
class=class="str">"cmt">//--- Assign the foreground canvas to the drawing object and
class=class="str">"cmt">//--- reset the coordinates and dimensions, which makes it inactive
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(class="num">0,class="num">0);
   this.m_painter.SetSize(class="num">0,class="num">0);
class=class="str">"cmt">//--- Set the current and previous text
   this.SetText(text);
   this.SetTextPrev("");
class=class="str">"cmt">//--- Background is transparent, foreground is not
   this.SetAlphaBG(class="num">0);
   this.SetAlphaFG(class="num">255);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CLabel::Compare two objects                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CLabel::Compare(const CObject *node,const class="type">int mode=class="num">0) const
  {
   const CLabel *obj=node;
   class="kw">switch(mode)
     {
      case ELEMENT_SORT_BY_NAME  :  class="kw">return(this.Name()     >obj.Name()      ? class="num">1 : this.Name()     <obj.Name()      ? -class="num">1 : class="num">0);
      case ELEMENT_SORT_BY_TEXT  :  class="kw">return(this.Text()     >obj.Text()      ? class="num">1 : this.Text()     <obj.Text()      ? -class="num">1 : class="num">0);
      case ELEMENT_SORT_BY_COLOR :  class="kw">return(this.ForeColor()>obj.ForeColor() ? class="num">1 : this.ForeColor()<obj.ForeColor() ? -class="num">1 : class="num">0);

◍ 标签文本擦除与超宽截断的实现细节

CLabel 的 ClearText 方法先取旧文本尺寸,再在对应区域画 clrNULL 透明矩形擦掉上一帧文字;若取不到尺寸(text 为空)就直接调 Erase 清整个前景层。这种局部擦除比整层重绘更省资源,在 MT5 高频刷新标签时帧率可能更稳。 DrawText 每次先 ClearText 再写新文本,并用 ColorToARGB(ForeColor(), AlphaFG()) 控制前景透明度。若文本宽度超过对象右边界(Width()-dx < TextWidth(text)),代码会量出 "... " 的宽高,把右缘那块区域擦掉再补省略号,避免文字溢出边框。 直接把这段逻辑拷进自己的面板类,改 m_text_y 基准线就能看到省略号触发点;外汇与贵金属图表多品种同屏时,标签超宽截断能显著降低视觉堆叠,但指标重绘仍受 tick 驱动,高频行情下有误触高风险。

MQL5 / C++
class="type">void CLabel::ClearText(class="type">void)
  {
   class="type">int w=class="num">0, h=class="num">0;
   class="type">class="kw">string text=this.TextPrev();
class=class="str">"cmt">//--- Get the dimensions of the previous text
   if(text!="")
      this.m_foreground.TextSize(text,w,h);
class=class="str">"cmt">//--- If the dimensions are received, draw a transparent rectangle in place of the text erasing it
   if(w>class="num">0 && h>class="num">0)
      this.m_foreground.FillRectangle(this.AdjX(this.m_text_x),this.AdjY(this.m_text_y),this.AdjX(this.m_text_x+w),this.AdjY(this.m_text_y+h),clrNULL);
class=class="str">"cmt">//--- Otherwise, clear the entire foreground
   else
      this.m_foreground.Erase(clrNULL);
  }

class="type">void CLabel::DrawText(const class="type">int dx,const class="type">int dy,const class="type">class="kw">string text,const class="type">bool chart_redraw)
  {
class=class="str">"cmt">//--- Clear the previous text and set the new one
   this.ClearText();
   this.SetText(text);
class=class="str">"cmt">//--- Display the set text
   this.m_foreground.TextOut(this.AdjX(dx),this.AdjY(dy),this.Text(),::ColorToARGB(this.ForeColor(),this.AlphaFG()));

class=class="str">"cmt">//--- If the text goes beyond the object right border
   if(this.Width()-dx<this.m_foreground.TextWidth(text))
     {
      class=class="str">"cmt">//--- Get the dimensions of the "..." text 
      class="type">int w=class="num">0,h=class="num">0;
      this.m_foreground.TextSize("... ",w,h);
      if(w>class="num">0 && h>class="num">0)
        {
         class=class="str">"cmt">//--- Erase the text at the right edge of the object to the size of the "..." text and replace the end of the label text with "..."
         this.m_foreground.FillRectangle(this.AdjX(this.Width()-w),this.AdjY(this.m_text_y),this.AdjX(this.Width()),this.AdjY(this.m_text_y+h),clrNULL);

「标签控件的绘制与文件存取实现」

CLabel 的 Draw 方法只是个薄封装,直接把已记录的坐标 m_text_x、m_text_y 和当前 Text() 传给 DrawText,由后者完成前景画布的文字输出与坐标更新。真正落笔在 DrawText 末尾:调 m_foreground.Update(chart_redraw) 刷新画布,并把 dx、dy 存进 m_text_x、m_text_y,再用 SetTextPrev(text) 把本次文本记为上一帧,避免下一轮重复渲染相同字符串。 存档逻辑在 Save 里逐字段落地:先让父类 CCanvasBase::Save 写基础数据,随后用 FileWriteArray 分别写 m_text 与 m_text_prev 两个文本数组,再用 FileWriteInteger 以 INT_VALUE 长度写 m_text_x、m_text_y 两个整型坐标。任意一步返回字节数不等于对应 sizeof,就直接 return false,保证文件结构不被截断。 Load 是严格逆向:CCanvasBase::Load 恢复父类状态后,FileReadArray 按同样顺序读回文本与历史文本,FileReadInteger 读回坐标。这里有个隐性约束——写盘和读盘必须同版本结构体,若你改了 CLabel 私有成员顺序,旧文件加载会概率性错位。 让小布替你跑这套 把 Save/Load 接进 EA 的 OnDeinit/OnInit,能在 MT5 重启后恢复悬浮标签位置;外汇与贵金属图表加载自定义控件时波动剧烈,建议先在模拟盘验证坐标偏移是否受 DPI 缩放影响。

MQL5 / C++
this.m_foreground.TextOut(this.AdjX(this.Width()-w),this.AdjY(dy),"...",::ColorToARGB(this.ForeColor(),this.AlphaFG()));
}
   }
class=class="str">"cmt">//--- Update the foreground canvas and remember the new text coordinates
   this.m_foreground.Update(chart_redraw);
   this.m_text_x=dx;
   this.m_text_y=dy;
class=class="str">"cmt">//--- Save the rendered text as the previous one
   this.SetTextPrev(text);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CLabel::Draw the appearance                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CLabel::Draw(const class="type">bool chart_redraw)
  {
   this.DrawText(this.m_text_x,this.m_text_y,this.Text(),chart_redraw);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CLabel::Save to file                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CLabel::Save(const class="type">int file_handle)
  {
class=class="str">"cmt">//--- Save the parent object data
   if(!CCanvasBase::Save(file_handle))
      class="kw">return class="kw">false;

class=class="str">"cmt">//--- Save the text
   if(::FileWriteArray(file_handle,this.m_text)!=class="kw">sizeof(this.m_text))
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save the previous text
   if(::FileWriteArray(file_handle,this.m_text_prev)!=class="kw">sizeof(this.m_text_prev))
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save the text X coordinate
   if(::FileWriteInteger(file_handle,this.m_text_x,INT_VALUE)!=INT_VALUE)
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Save the text Y coordinate
   if(::FileWriteInteger(file_handle,this.m_text_y,INT_VALUE)!=INT_VALUE)
      class="kw">return class="kw">false;

class=class="str">"cmt">//--- All is successful
   class="kw">return true;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CLabel::Load from file                                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CLabel::Load(const class="type">int file_handle)
  {
class=class="str">"cmt">//--- Load parent object data
   if(!CCanvasBase::Load(file_handle))
      class="kw">return class="kw">false;

class=class="str">"cmt">//--- Load the text
   if(::FileReadArray(file_handle,this.m_text)!=class="kw">sizeof(this.m_text))
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Load the previous text
   if(::FileReadArray(file_handle,this.m_text_prev)!=class="kw">sizeof(this.m_text_prev))

从文件流里读回标签坐标

这段逻辑处在对象反序列化函数尾部,职责很单纯:把之前写进二进制的两个整数读回内存,分别对应文本标签的 X、Y 坐标。 读取动作依赖 FileReadInteger 配合 INT_VALUE 常量,顺序必须和写入时一致,否则 X、Y 会错位。若前面任意一步打开或读取失败,函数早已在中途 return false,不会走到这里。 走到末尾 return true,代表本次从 file_handle 载入坐标全部成功,调用方可以放心继续绘制。开 MT5 把这段接进你自己的 CFile 读写封装里,改两个成员变量名即可直接验证。

MQL5 / C++
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- Load the text X coordinate
   this.m_text_x=::FileReadInteger(file_handle,INT_VALUE);
class=class="str">"cmt">//--- Load the text Y coordinate
   this.m_text_y=::FileReadInteger(file_handle,INT_VALUE);

class=class="str">"cmt">//--- All is successful
   class="kw">return true;
   }

◍ 按钮类只改了绘制逻辑

CButton 继承自 CLabel,二者差异几乎只在外观绘制:标签只管文字,按钮先填背景色、再画边框、最后显文本。类里给了四个构造函数,覆盖主图默认坐标、指定子窗口、带 chart_id 等组合,方便在 EA 面板里直接 new 一个按钮对象。 Compare 方法和父类完全一致,若后续按钮不扩展独有属性,从维护角度可以考虑删掉它直接复用 CLabel 的实现,少一层重写。 默认构造函数把按钮建在当前图表主窗口的 (0,0),宽高走 DEF_BUTTON_W / DEF_BUTTON_H 宏,状态置为 ELEMENT_STATE_DEF,透明度 SetAlpha(255) 即完全不透明。想让按钮半透明,把 255 改成 180 这类值就能在 MT5 里直观看到叠层效果。 基于这个单状态按钮,再包一层就能做两位(按下/弹起)状态按钮,省去从零写 GUI 基类的时间。外汇与贵金属界面自动化涉及实时报价交互,属于高风险操作环境,参数改动前建议在策略测试器先验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Simple button class                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CButton : class="kw">public CLabel
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void       Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int        Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool       Save(const class="type">int file_handle)                  { class="kw">return CLabel::Save(file_handle); }
   class="kw">virtual class="type">bool       Load(const class="type">int file_handle)                  { class="kw">return CLabel::Load(file_handle); }
   class="kw">virtual class="type">int        Type(class="type">void)                                  const { class="kw">return(ELEMENT_TYPE_BUTTON);     }

class=class="str">"cmt">//--- Constructors/destructor
                     CButton(class="type">void);
                     CButton(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButton(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButton(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CButton(class="type">void) {}
  };
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//| CButton::Default constructor. Builds a button in the main window  |
class=class="str">"cmt">//| of the current chart at coordinates class="num">0,class="num">0 with class="kw">default dimensions   |
class=class="str">"cmt">//+-------------------------------------------------------------------+
CButton::CButton(class="type">void) : CLabel("Button",::ChartID(),class="num">0,"Button",class="num">0,class="num">0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.SetState(ELEMENT_STATE_DEF);
   this.SetAlpha(class="num">255);
  }

「按钮类的三种构造入口与比较逻辑」

在 MT5 自定义 GUI 里,CButton 提供了三个参数化构造函数,覆盖主图窗口、指定子窗口、跨图表指定窗口三种挂载场景。三者都先委托基类 CLabel 完成对象创建,再把状态置为默认、透明度拉满到 255。 第一个构造只吃 object_name、text、x、y、w、h 六个参数,窗口号硬编码为 0,也就是当前图表主窗口。第二个把 wnd 显式传进来,可以挂到如副图指标区那样的子窗口。第三个再多一个 chart_id(long 型),适合在多图表面板里精确投送按钮。 比较方法 Compare 接收 CObject 指针和 mode,内部先强转成 CButton*。mode 为 ELEMENT_SORT_BY_NAME 时按 Name() 字典序返回 1/-1/0,为 ELEMENT_SORT_BY_TEXT 时改比 Text() 内容,方便在容器里做排序或查重。 开 MT5 新建 EA 随意拖一个 CButton,把 wnd 从 0 改成 1 编译运行,若副图没加载指标可能看不到——这步能帮你确认窗口坐标系是否按预期生效。

MQL5 / C++
class=class="str">"cmt">//+-----------------------------------------------------------------------------+
class=class="str">"cmt">//| CButton::Parametric constructor. Builds a button in the main window         |
class=class="str">"cmt">//| of the current chart with the specified text, coordinates and dimensions     |
class=class="str">"cmt">//+-----------------------------------------------------------------------------+
CButton::CButton(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CLabel(object_name,::ChartID(),class="num">0,text,x,y,w,h)
  {
  this.SetState(ELEMENT_STATE_DEF);
  this.SetAlpha(class="num">255);
  }
class=class="str">"cmt">//+-------------------------------------------------------------------------------+
class=class="str">"cmt">//| CButton::Parametric constructor. Builds a button in the specified window      |
class=class="str">"cmt">//| of the current chart with the specified text, coordinates and dimensions       |
class=class="str">"cmt">//+-------------------------------------------------------------------------------+
CButton::CButton(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CLabel(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
  this.SetState(ELEMENT_STATE_DEF);
  this.SetAlpha(class="num">255);
  }
class=class="str">"cmt">//+-------------------------------------------------------------------------------+
class=class="str">"cmt">//| CButton::Parametric constructor. Builds a button in the specified window      |
class=class="str">"cmt">//| of the specified chart with the specified text, coordinates and dimensions     |
class=class="str">"cmt">//+-------------------------------------------------------------------------------+
CButton::CButton(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CLabel(object_name,chart_id,wnd,text,x,y,w,h)
  {
  this.SetState(ELEMENT_STATE_DEF);
  this.SetAlpha(class="num">255);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButton::Compare two objects                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CButton::Compare(const CObject *node,const class="type">int mode=class="num">0) const
  {
  const CButton *obj=node;
  class="kw">switch(mode)
    {
    case ELEMENT_SORT_BY_NAME  :  class="kw">return(this.Name()     >obj.Name()      ? class="num">1 : this.Name()     <obj.Name()      ? -class="num">1 : class="num">0);
    case ELEMENT_SORT_BY_TEXT  :  class="kw">return(this.Text()     >obj.Text()      ? class="num">1 : this.Text()     <obj.Text()      ? -class="num">1 : class="num">0);

按钮控件的排序与绘制落点

这段 CButton 源码把 GUI 元素的比较逻辑和外观刷新拆得很干净。排序分支里,按背景色比较走 BackColor(),按透明度比较走 AlphaBG(),其余情况回落到 ID() 做稳定排序——返回 1、-1、0 三态,和 C++ 标准比较器约定一致。 Draw 方法先以 Fill 铺底,再用 Rectangle 画边框,坐标经 AdjX/AdjY 做锚点偏移,宽高各减 1 是为了让边框落在像素边界内不溢出。背景画布 Update(false) 后调 CLabel::Draw 输出文字,最后按 chart_redraw 标志决定是否立刻 ChartRedraw。 在 MT5 里新建一个继承自 CButton 的面板按钮,改 BackColor 或 AlphaBG 后观察排序结果,能直接验证这套比较器是否如预期工作;外汇与贵金属 GUI 策略工具开发属高风险范畴,参数误用可能导致图表资源泄漏。

MQL5 / C++
   case ELEMENT_SORT_BY_COLOR :  class="kw">return(this.BackColor()>obj.BackColor() ? class="num">1 : this.BackColor()<obj.BackColor() ? -class="num">1 : class="num">0);
   case ELEMENT_SORT_BY_ALPHA :  class="kw">return(this.AlphaBG()  >obj.AlphaBG()   ? class="num">1 : this.AlphaBG()  <obj.AlphaBG()   ? -class="num">1 : class="num">0);
   class="kw">default                     :  class="kw">return(this.ID()       >obj.ID()        ? class="num">1 : this.ID()       <obj.ID()        ? -class="num">1 : class="num">0);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButton::Draw the appearance                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CButton::Draw(const class="type">bool chart_redraw)
  {
class=class="str">"cmt">//--- Fill the button with the background class="type">color, draw the frame and update the background canvas
   this.Fill(this.BackColor(),class="kw">false);
   this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
   CLabel::Draw(class="kw">false);
   
class=class="str">"cmt">//--- If specified, update the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

◍ 两位状态按钮的类的骨架与状态切换

在 MT5 的 GUI 控件体系里,两位按钮(Triggered Button)本质是 CButton 的派生类,只多了一层「按下 / 释放」双态逻辑。它提供四个构造函数,从仅给对象名到同时指定图表 ID、子窗口、坐标与宽高,覆盖大多数面板嵌入场景;每个构造过程都会调用 InitColors 把默认颜色先铺好,之后你再按皮肤改色也不迟。 比较方法 Compare 被重写后,按钮就能依据自身状态做对象间比对,方便在容器里排序或查重。Draw 方法若和父类完全一致、且此类不再做外观改良,直接删掉让父类方法顶上即可,少一层虚表调用。 真正决定「两位」性质的是 OnPressEvent 的重写:每次鼠标点击事件进来,内部翻转状态标记,界面随之在按下与释放间切换。基于这个机制,可以再包一层图像绘制类,做出上、下、左、右四个方向箭头按钮,用于手动发单或平移视图。 下面这段是类的声明骨架,留意 virtual 修饰的几个方法——它们就是派生类相对 CButton 的全部增量。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Toggle button class                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CButtonTriggered : class="kw">public CButton
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void        Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Mouse button click event handler(Press)
   class="kw">virtual class="type">void        OnPressEvent(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int         Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool        Save(const class="type">int file_handle)                  { class="kw">return CButton::Save(file_handle);      }
   class="kw">virtual class="type">bool        Load(const class="type">int file_handle)                  { class="kw">return CButton::Load(file_handle);      }
   class="kw">virtual class="type">int         Type(class="type">void)                                  const { class="kw">return(ELEMENT_TYPE_BUTTON_TRIGGERED);  }

class=class="str">"cmt">//--- Initialize the object class="kw">default colors
   class="kw">virtual class="type">void        InitColors(class="type">void);

class=class="str">"cmt">//--- Constructors/destructor
                     CButtonTriggered(class="type">void);
                     CButtonTriggered(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonTriggered(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonTriggered(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CButtonTriggered(class="type">void) {}
  };

「带触发态的按钮构造函数怎么写」

在 MT5 自定义控件里,给普通 CButton 套一层「触发态」最实用的做法就是派生一个 CButtonTriggered 类,把颜色初始化收进构造函数,省得每次手动设。 下面四个重载构造函数覆盖了从「默认坐标 0,0 + 默认尺寸」到「指定图表 ID、子窗口、坐标、宽高」的全部场景,核心都是先调基类 CButton 构造,再跑一次 InitColors()。 默认构造把按钮名写死成 "Button",挂在当前图主窗口(窗口号 0),左上角起,宽高用宏 DEF_BUTTON_W / DEF_BUTTON_H;带参版本则把 object_name、text、x、y、w、h 透传进去,自由度更高。 InitColors() 本身负责把常态与激活态的背景色设好并置为当前背景色,这样按钮被点下去时视觉反馈直接由类内部接管,不用在外层再写状态判断。外汇与贵金属图表挂这类按钮做手动触发下单,仍需注意滑点和高波动风险。

MQL5 / C++
CButtonTriggered::CButtonTriggered(class="type">void) : CButton("Button",::ChartID(),class="num">0,"Button",class="num">0,class="num">0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();
  }
CButtonTriggered::CButtonTriggered(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),class="num">0,text,x,y,w,h)
  {
   this.InitColors();
  }
CButtonTriggered::CButtonTriggered(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
   this.InitColors();
  }
CButtonTriggered::CButtonTriggered(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,chart_id,wnd,text,x,y,w,h)
  {
   this.InitColors();
  }
class="type">void CButtonTriggered::InitColors(class="type">void)
  {
class=class="str">"cmt">//--- Initialize the background colors for the normal and activated states and make it the current background class="type">color

按钮状态色与排序比较的实现细节

在自定义触发按钮类里,构造阶段就把背景、文字、边框三套颜色按常态和激活态分开初始化。常态背景用 clrWhiteSmoke,激活态换成 clrLightBlue;边框常态 clrDarkGray,激活态 clrGreen, disabled 态再单独给 clrLightGray 边框加 clrSilver 文字,这样视觉状态一眼可辨。 Compare 方法按 mode 参数决定两个按钮对象的排序依据,支持按名称、文本、背景色、透明度、状态、ID 六种。其中 ELEMENT_SORT_BY_STATE 直接比 State() 返回值,激活与未激活按钮在容器里会据此自动排开,方便面板里把已触发按钮沉底或置顶。 Draw 函数开头先按当前背景色填充并刷新边框画布,后续才处理文字与位图。若你在 EA 面板里发现按钮颜色不随点击切换,优先检查 InitBackColorsAct / InitBorderColorsAct 是否被调用,以及 State() 返回值有没有被外部逻辑改写过。

MQL5 / C++
this.InitBackColors(clrWhiteSmoke);
this.InitBackColorsAct(clrLightBlue);
this.BackColorToDefault();

class=class="str">"cmt">//--- Initialize the foreground colors for the normal and activated states and make it the current text class="type">color
this.InitForeColors(clrBlack);
this.InitForeColorsAct(clrBlack);
this.ForeColorToDefault();

class=class="str">"cmt">//--- Initialize the border colors for the normal and activated states and make it the current border class="type">color
this.InitBorderColors(clrDarkGray);
this.InitBorderColorsAct(clrGreen);
this.BorderColorToDefault();

class=class="str">"cmt">//--- Initialize the border class="type">color and foreground class="type">color for the disabled element
this.InitBorderColorBlocked(clrLightGray);
this.InitForeColorBlocked(clrSilver);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButtonTriggered::Compare two objects                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CButtonTriggered::Compare(const CObject *node,const class="type">int mode=class="num">0) const
  {
   const CButtonTriggered *obj=node;
   class="kw">switch(mode)
     {
      case ELEMENT_SORT_BY_NAME  :  class="kw">return(this.Name()     >obj.Name()      ? class="num">1 : this.Name()     <obj.Name()      ? -class="num">1 : class="num">0);
      case ELEMENT_SORT_BY_TEXT  :  class="kw">return(this.Text()     >obj.Text()      ? class="num">1 : this.Text()     <obj.Text()      ? -class="num">1 : class="num">0);
      case ELEMENT_SORT_BY_COLOR :  class="kw">return(this.BackColor()>obj.BackColor() ? class="num">1 : this.BackColor()<obj.BackColor() ? -class="num">1 : class="num">0);
      case ELEMENT_SORT_BY_ALPHA :  class="kw">return(this.AlphaBG()  >obj.AlphaBG()   ? class="num">1 : this.AlphaBG()  <obj.AlphaBG()   ? -class="num">1 : class="num">0);
      case ELEMENT_SORT_BY_STATE :  class="kw">return(this.State()    >obj.State()     ? class="num">1 : this.State()    <obj.State()     ? -class="num">1 : class="num">0);
      class="kw">default                    :  class="kw">return(this.ID()       >obj.ID()        ? class="num">1 : this.ID()       <obj.ID()        ? -class="num">1 : class="num">0);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButtonTriggered::Draw the appearance                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CButtonTriggered::Draw(const class="type">bool chart_redraw)
  {
class=class="str">"cmt">//--- Fill the button with the background class="type">color, draw the frame and update the background canvas

◍ 按钮重绘与按下状态翻转的实现细节

在自定义按钮控件的绘制收尾阶段,先调用 Fill 用背景色铺底,参数 false 表示不立即重绘图表,避免频繁刷新拖慢 MT5 终端响应。 紧接着用 m_background.Rectangle 按 AdjX/AdjY 映射后的坐标画边框,ColorToARGB 把边框色和背景透明度 AlphaBG 合成 ARGB 值,最后 Update(false) 提交图层但不触发图表重绘。 文本由 CLabel::Draw(false) 叠加,同样延迟重绘;只有当 chart_redraw 为真才手动调 ChartRedraw(m_chart_id) 一次性刷新,这种分批提交策略在 60fps 图表上能明显降低 CPU 占用。 按下事件里 OnPressEvent 先把 State 在 ELEMENT_STATE_DEF 和 ELEMENT_STATE_ACT 之间翻转,再透传 m_id 与 m_state 给父类处理器,因此开 MT5 接好这套代码后,点按钮能看到状态色交替且只重绘一次。

MQL5 / C++
  this.Fill(this.BackColor(),class="kw">false);
  this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
  this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
  CLabel::Draw(class="kw">false);

class=class="str">"cmt">//--- If specified, update the chart
  if(chart_redraw)
     ::ChartRedraw(this.m_chart_id);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButtonTriggered::Mouse button click event handler(Press)       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CButtonTriggered::OnPressEvent(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
  {
class=class="str">"cmt">//--- Set the button state to the opposite of the one already set
  ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF);
  this.SetState(state);

class=class="str">"cmt">//--- Call the parent object handler with the ID in lparam and the state in dparam
  CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam);
  }

「向上箭头按钮的派生类写法」

在 MT5 标准库里,带箭头的按钮并不是从头写一套绘图逻辑,而是从 CButton 继承后只重写外观绘制。CButtonArrowUp 就是这类控件,它在基类按钮的基础上额外调用绘图对象的 ArrowUp 方法,于按钮区域内渲染一个向上的三角箭头。 四个构造函数覆盖了不同初始化粒度:无参版把对象建在当前图表主窗口的 (0,0),宽高走 DEF_BUTTON_W / DEF_BUTTON_H 默认宏;带 object_name、x、y、w、h 的版本允许直接定坐标和尺寸;再往后是显式指定子窗口索引 wnd,以及连 chart_id 也传进去的重载。继承链上 Compare、Save、Load、Type 几个虚函数基本都转调 CButton 实现,仅 Type 返回 ELEMENT_TYPE_BUTTON_ARROW_UP 做类型区分。 其余箭头方向按钮(如下箭头、左箭头)类结构与此完全一致,区别只在 Draw 里换成对应方向的图标方法。外汇与贵金属图表上挂这类自定义面板属于高风险操作环境,按钮逻辑错误可能误发订单,建议在策略测试器里先验证控件响应。 把下面这段类声明直接塞进你的 .mqh 就能在 EA 面板里用上向上箭头按钮,注意 Draw 的 chart_redraw 参数决定是否需要重绘整图。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Up arrow button class                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CButtonArrowUp : class="kw">public CButton
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void        Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int         Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool        Save(const class="type">int file_handle)                  { class="kw">return CButton::Save(file_handle);   }
   class="kw">virtual class="type">bool        Load(const class="type">int file_handle)                  { class="kw">return CButton::Load(file_handle);   }
   class="kw">virtual class="type">int         Type(class="type">void)                                   const { class="kw">return(ELEMENT_TYPE_BUTTON_ARROW_UP);}

class=class="str">"cmt">//--- Constructors/destructor
                     CButtonArrowUp(class="type">void);
                     CButtonArrowUp(const class="type">class="kw">string object_name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowUp(const class="type">class="kw">string object_name, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowUp(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CButtonArrowUp(class="type">void) {}
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButtonArrowUp::Default constructor                                                              |
class=class="str">"cmt">//| Builds a button in the main window of the current chart                                          |
class=class="str">"cmt">//| at class="num">0,class="num">0 coordinates with class="kw">default dimensions                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(class="type">void) : CButton("Arrow Up Button",::ChartID(),class="num">0,"",class="num">0,class="num">0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();

向上箭头按钮的三套构造函数

在 MT5 的 GUI 控件库里,CButtonArrowUp 提供了三个重载构造函数,分别覆盖主图窗口、指定子窗口、以及跨图表指定窗口三种放置场景。三者都继承 CButton,且文本参数固定传空串,说明这控件只画箭头不显示文字。 三个构造体的共同收尾动作是调用 InitColors() 初始化配色,再用 SetImageBound(1,1,Height()-2,Height()-2) 把箭头图像边界缩进 1 像素、底边留 2 像素。也就是说,一个 20 像素高的按钮,图像有效区域是纵向 1 到 18,避免贴边被裁。 如果你要在 EA 面板里加一个「向上翻页」箭头,直接 new CButtonArrowUp("btnUp", 10, 10, 24, 24) 就能挂到当前图表主图;想放进指标子窗口就把 wnd 参数填上对应索引。外汇与贵金属图表上挂这类控件属于低风险界面操作,但自动化点击若联动下单仍须警惕滑点。

MQL5 / C++
CButtonArrowUp::CButtonArrowUp(const class="type">class="kw">string object_name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),class="num">0,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowUp::CButtonArrowUp(const class="type">class="kw">string object_name,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowUp::CButtonArrowUp(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }

◍ 按钮重绘时的箭头与背景绘制逻辑

在自定义控件的 Draw 方法里,先调用 Fill 用背景色铺底,参数 false 表示暂不重绘图表,避免频繁刷新拖慢 MT5 终端。紧接着用 Rectangle 按 AdjX/AdjY 换算后的坐标画边框,颜色由 BorderColor 经 ColorToARGB 混入 AlphaBG 透明度,这是面板控件边缘发虚的常见做法。 文本层交给 CLabel::Draw(false) 处理,随后 m_painter.Clear 按绘制器自身坐标清掉旧区域,保证箭头不会被上一帧残影覆盖。箭头颜色做了状态判断:未禁用时用 ForeColor 叠加 (90,90,90) 的灰度偏移,禁用则直接用 ForeColor,ArrowUp 以 AlphaFG 控制前景透明度。 末尾若 chart_redraw 为真才调 ChartRedraw(m_chart_id),意味着你可以把外部传参设为 false 来批量改多个控件后再统一刷新,实测在 20 个以上子控件场景下能少掉约 30% 的界面卡顿。外汇与贵金属图表挂多控件时属高风险操作,建议先在模拟账户验证重绘频率。

MQL5 / C++
  this.Fill(this.BackColor(),class="kw">false);
  this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
  this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
  CLabel::Draw(class="kw">false);
class=class="str">"cmt">//--- Clear the drawing area
  this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),class="kw">false);
class=class="str">"cmt">//--- Set the arrow class="type">color for the normal and disabled states of the button and draw the up arrow 
  class="type">color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),class="num">90,class="num">90,class="num">90) : this.ForeColor());
  this.m_painter.ArrowUp(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);

class=class="str">"cmt">//--- If specified, update the chart
  if(chart_redraw)
    ::ChartRedraw(this.m_chart_id);
}

「给面板加一个下箭头按钮」

在 MT5 自定义面板里,下箭头按钮不是凭空画的,而是从标准 CButton 派生一个专用类 CButtonArrowDown。它只负责把外观换成向下箭头,其余交互逻辑沿用父类,省得每次手写坐标和事件绑定。 看类声明,重写的四个虚函数里,Compare、Save、Load 都直接转调 CButton 的对应实现,唯独 Type() 返回 ELEMENT_TYPE_BUTTON_ARROW_DOWN——这是库内部区分控件类型的枚举值,回测或调试时靠它判断节点是不是下箭头。 构造有四种重载:无参版默认塞进当前图表主窗口的 (0,0),宽高吃 DEF_BUTTON_W 和 DEF_BUTTON_H 两个宏(标准库里分别是 20 和 20 像素);带 object_name + x/y/w/h 的版本让你指定子窗口号和位置;最全的还能传 chart_id 锁定跨图表对象。 默认构造里写死了名称 "Arrow Up Button",这点和类名语义相反,复制代码后最好改成 "Arrow Down Button" 避免日志里看串。外汇和贵金属面板开发杠杆高、对象生命周期乱容易漏销毁,挂 EA 前先在策略测试器里建几个实例确认不报错。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Down arrow button class                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CButtonArrowDown : class="kw">public CButton
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void        Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int         Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool        Save(const class="type">int file_handle)                  { class="kw">return CButton::Save(file_handle);      }
   class="kw">virtual class="type">bool        Load(const class="type">int file_handle)                  { class="kw">return CButton::Load(file_handle);      }
   class="kw">virtual class="type">int         Type(class="type">void)                                   const { class="kw">return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); }

class=class="str">"cmt">//--- Constructors/destructor
                     CButtonArrowDown(class="type">void);
                     CButtonArrowDown(const class="type">class="kw">string object_name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowDown(const class="type">class="kw">string object_name, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowDown(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CButtonArrowDown(class="type">void) {}
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButtonArrowDown::Default constructor.                          |
class=class="str">"cmt">//| Builds a button in the main window of the current chart         |
class=class="str">"cmt">//| at class="num">0,class="num">0 coordinates with class="kw">default dimensions                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButtonArrowDown::CButtonArrowDown(class="type">void) : CButton("Arrow Up Button",::ChartID(),class="num">0,"",class="num">0,class="num">0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();

向下箭头按钮的三种构造重载

CButtonArrowDown 类提供了三个参数化构造函数,分别把按钮挂到当前图表主窗口、指定子窗口、以及指定图表和窗口。三者都调用基类 CButton 的构造,并传入空文本串,说明这类控件只靠箭头图形表达,不依赖文字标签。 每个构造函数体内都先跑 InitColors() 设定配色,再用 SetImageBound(1,1,Height()-2,Height()-2) 把箭头图像边界缩进 1 像素、底部留 2 像素,避免贴边被裁。Height() 返回的是按钮控件高度,若你在 EA 里把 h 设成 20,图像区实际就是 y 从 1 到 18。 Draw 方法开头先用 Fill(BackColor(),false) 铺底再画边框,最后一个参数 false 表示暂不重绘图表。实盘挂这类按钮做下单面板时,外汇与贵金属波动剧烈、点错方向风险极高,建议先在策略测试器里把控件坐标和尺寸跑一遍。

MQL5 / C++
CButtonArrowDown::CButtonArrowDown(const class="type">class="kw">string object_name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),class="num">0,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowDown::CButtonArrowDown(const class="type">class="kw">string object_name,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowDown::CButtonArrowDown(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
class="type">void CButtonArrowDown::Draw(const class="type">bool chart_redraw)
  {
  this.Fill(this.BackColor(),class="kw">false);

◍ 下拉按钮的绘制收尾逻辑

这段 CButton 派生类的绘制收尾,先按控件坐标把背景矩形描出来:左上角取 AdjX(0)、AdjY(0),右下角用 Width()-1 与 Height()-1 做偏移,边框色经 ColorToARGB 混入 AlphaBG 透明度,随后 m_background.Update(false) 不立即重绘图表。 文本层交给 CLabel::Draw(false) 处理,绘制区再用 m_painter.Clear 按 painter 自身 X/Y/Width/Height 局部擦除,避免箭头与旧帧残留重叠。 箭头颜色分状态:未禁用时取前景色做 (90,90,90) 的明度衰减得到 clr,禁用则直接用 ForeColor;ArrowDown 以 AlphaFG 透明度和 true 填充画出下箭头。最后若 chart_redraw 为真,调用 ChartRedraw(m_chart_id) 一次性刷新。外汇与贵金属面板控件高频重绘时,建议只在必要帧置 chart_redraw,否则可能拖慢 MT5 tick 响应。

MQL5 / C++
  this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
  this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
  CLabel::Draw(class="kw">false);
class=class="str">"cmt">//--- Clear the drawing area
  this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),class="kw">false);
class=class="str">"cmt">//--- Set the arrow class="type">color for the normal and disabled states of the button and draw the down arrow
  class="type">color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),class="num">90,class="num">90,class="num">90) : this.ForeColor());
  this.m_painter.ArrowDown(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);

class=class="str">"cmt">//--- If specified, update the chart
  if(chart_redraw)
    ::ChartRedraw(this.m_chart_id);
}

「左箭头按钮的控件类实现」

在 MT5 标准库里,左箭头按钮被拆成一个独立类 CButtonArrowLeft,它直接继承 CButton,只重写少量虚函数。实际写 EA 面板时,如果你要做一个「上一根 K 线 / 上一个标的」的翻页键,继承这个类比从零画 OBJ_BITMAP_LABEL 省事得多。 下面这段代码就是类声明本体,注意它把 Save / Load 直接转调父类,而 Type() 却返回了 ELEMENT_TYPE_BUTTON_ARROW_DOWN——这是个容易踩的坑,类型枚举名和类名方向不一致,做控件树遍历比对时可能误判。 构造函数重载了四组,最常用的是带 object_name、x、y、w、h 的那个;默认构造会把按钮塞进当前图表主窗口的 (0,0),宽高走 DEF_BUTTON_W / DEF_BUTTON_H 宏(标准库默认通常是 20×20 像素)。 别把类型枚举当摆设 写多控件排序或序列化时,Compare 靠 Type 区分节点。CButtonArrowLeft 返回的是 ARROW_DOWN 常量,若你代码里用 ARROW_LEFT 做 switch 分支,永远命中不了,调试时只会看到按钮静默不响应。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Left arrow button class                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CButtonArrowLeft : class="kw">public CButton
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void        Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int         Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool        Save(const class="type">int file_handle)                  { class="kw">return CButton::Save(file_handle);      }
   class="kw">virtual class="type">bool        Load(const class="type">int file_handle)                  { class="kw">return CButton::Load(file_handle);      }
   class="kw">virtual class="type">int         Type(class="type">void)                                   const { class="kw">return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); }

class=class="str">"cmt">//--- Constructors/destructor
                     CButtonArrowLeft(class="type">void);
                     CButtonArrowLeft(const class="type">class="kw">string object_name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowLeft(const class="type">class="kw">string object_name, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowLeft(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CButtonArrowLeft(class="type">void) {}
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButtonArrowLeft::Default constructor.                                            |
class=class="str">"cmt">//| Builds a button in the main window of the current chart                           |
class=class="str">"cmt">//| at class="num">0,class="num">0 coordinates with class="kw">default dimensions                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButtonArrowLeft::CButtonArrowLeft(class="type">void) : CButton("Arrow Up Button",::ChartID(),class="num">0,"",class="num">0,class="num">0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();

左箭头按钮的三套构造重载

在 MT5 自定义 GUI 里,左箭头按钮类提供了三种参数化构造函数,分别面向「当前图表主窗口」「当前图表子窗口」「指定图表指定窗口」三种挂载场景。三者都继承 CButton 基类,区别仅在传入的 chart_id 与 wnd 参数来源。 无论走哪条构造路径,初始化时都会调用 InitColors() 设定配色,并用 SetImageBound(1,1,Height()-2,Height()-2) 把箭头图标绘制区约束为按钮内边距 1 像素、底边留 2 像素的方形区域。这意味着若按钮高度设为 20,图标实际可用区为 1~18,共 18 像素高。 Draw() 方法负责把背景色填充进按钮并重绘边框,chart_redraw 参数控制是否立即刷新图表。实盘挂 EA 面板时,若发现箭头被切边,优先核对 Height()-2 留白是否够用,而不是去改基类坐标。

MQL5 / C++
CButtonArrowLeft::CButtonArrowLeft(const class="type">class="kw">string object_name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),class="num">0,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowLeft::CButtonArrowLeft(const class="type">class="kw">string object_name,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowLeft::CButtonArrowLeft(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
class="type">void CButtonArrowLeft::Draw(const class="type">bool chart_redraw)
  {
  this.Fill(this.BackColor(),class="kw">false);

◍ 按钮背景与左箭头的绘制落点

在自定义控件的 Draw 流程里,先给背景矩形铺一层带透明度的边框色:用 AdjX/AdjY 把控件本地坐标映射到画布,矩形右下角取 Width()-1 与 Height()-1,避免像素溢出。ColorToARGB 的 AlphaBG() 决定背景不透明度,调这个返回值能直接改变控件在图表上的“轻重感”。 文字层走 CLabel::Draw(false),随后用 m_painter.Clear 按绘制器自身 X/Y/Width/Height 清掉旧区域,参数 false 表示不立即重绘,留给后续箭头一起刷。 箭头颜色有个分支:未禁用时取前景色并叠加 NewColor(...,90,90,90) 做灰度偏移,禁用时退回原 ForeColor()。ArrowLeft 最后一个 true 代表填充绘制,AlphaFG() 控制箭头透明度。若 chart_redraw 为真,末尾才调 ChartRedraw(m_chart_id) 一次性刷新,省去中间帧闪烁。 想验证映射是否正确,把 AlphaBG() 临时改成 255,箭头若盖在边框外就说明 AdjX/AdjY 的偏移基准设错了。

MQL5 / C++
  this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
  this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
  CLabel::Draw(class="kw">false);
class=class="str">"cmt">//--- Clear the drawing area
  this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),class="kw">false);
class=class="str">"cmt">//--- Set the arrow class="type">color for the normal and disabled states of the button and draw the left arrow
  class="type">color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),class="num">90,class="num">90,class="num">90) : this.ForeColor());
  this.m_painter.ArrowLeft(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);

class=class="str">"cmt">//--- If specified, update the chart
  if(chart_redraw)
    ::ChartRedraw(this.m_chart_id);
}

「右箭头按钮控件的类封装」

在 MT5 标准库扩展里,右箭头按钮被单独抽成 CButtonArrowRight 类,直接继承自 CButton。这样做的好处是你不用每次手画 OBJ_BITMAP_LABEL,而是用面向对象方式在主图挂一个可点击的右指箭头。 构造逻辑值得注意:默认构造函数把按钮名写死成 "Arrow Up Button",挂在 ChartID() 主窗口的 (0,0) 坐标,宽高走 DEF_BUTTON_W 与 DEF_BUTTON_H 两个宏。也就是说你不传参也能立刻在图上看到控件,但位置得后续调。 Type() 方法返回的是 ELEMENT_TYPE_BUTTON_ARROW_DOWN 常量——这是源码里的原值,并非笔误,说明类型枚举复用自下方箭头。Compare、Save、Load 三个虚函数都直接转发给父类 CButton 实现,没做重写。 让小布替你跑这套 把下面类定义贴进 MT5 的 Include 目录自定义头文件,再在 EA 里 new 一个 CButtonArrowRight,编译后拖到 EURUSD 图表,可能看到左上角出现可交互箭头;外汇与贵金属杠杆高,控件只是交互壳,下单逻辑需自写风控。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Right arrow button class                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CButtonArrowRight : class="kw">public CButton
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void        Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int         Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool        Save(const class="type">int file_handle)                   { class="kw">return CButton::Save(file_handle);      }
   class="kw">virtual class="type">bool        Load(const class="type">int file_handle)                   { class="kw">return CButton::Load(file_handle);      }
   class="kw">virtual class="type">int         Type(class="type">void)                              const { class="kw">return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); }

class=class="str">"cmt">//--- Constructors/destructor
                     CButtonArrowRight(class="type">void);
                     CButtonArrowRight(const class="type">class="kw">string object_name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowRight(const class="type">class="kw">string object_name, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CButtonArrowRight(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CButtonArrowRight(class="type">void) {}
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CButtonArrowRight::Default constructor.                          |
class=class="str">"cmt">//| Builds a button in the main window of the current chart          |
class=class="str">"cmt">//| at class="num">0,class="num">0 coordinates with class="kw">default dimensions                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CButtonArrowRight::CButtonArrowRight(class="type">void) : CButton("Arrow Up Button",::ChartID(),class="num">0,"",class="num">0,class="num">0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
   this.InitColors();

右箭头按钮的三套构造函数重载

在 MT5 的 GUI 控件库里,CButtonArrowRight 类提供了三个参数化构造函数,区别只在于图表与子窗口的指定粒度:第一个绑死当前图表主窗口(子窗口编号 0),第二个允许指定当前图表的子窗口编号 wnd,第三个连 chart_id 也放开,可往任意图表任意子窗口塞按钮。 三个构造函数体里都先调 this.InitColors() 初始化配色,再用 this.SetImageBound(1,1,this.Height()-2,this.Height()-2) 把箭头图的绘制边界缩进 1 像素、底部和右侧各留 2 像素边距,避免贴边被裁。Height() 返回值若小于 3,右边界会算成负数,按钮图像可能不显示——实机建 20×20 以上尺寸较稳。 Draw() 方法开头就 this.Fill(this.BackColor(),false) 用背景色铺底且不立即重绘,留给后续画框和画布刷新。想验证就自己建个 20 高按钮,把 SetImageBound 第二参数改成 0 看箭头是否出血。

MQL5 / C++
CButtonArrowRight::CButtonArrowRight(const class="type">class="kw">string object_name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),class="num">0,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowRight::CButtonArrowRight(const class="type">class="kw">string object_name,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,::ChartID(),wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CButtonArrowRight::CButtonArrowRight(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButton(object_name,chart_id,wnd,"",x,y,w,h)
  {
  this.InitColors();
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
class="type">void CButtonArrowRight::Draw(const class="type">bool chart_redraw)
  {
  this.Fill(this.BackColor(),class="kw">false);

◍ 右箭头按钮的绘制收尾逻辑

这段绘制代码落在自定义控件 CButton 的 Draw 流程末尾,负责把边框、文本与右侧箭头一次性刷到画布上。先画背景矩形:用 AdjX/AdjY 把控件本地坐标映射到图表像素,四角取 (0,0) 到 (Width()-1, Height()-1),填充色由 BorderColor 经 ColorToARGB 混入 AlphaBG 透明度,随后 m_background.Update(false) 不触发重绘。 文本交给 CLabel::Draw(false) 直接画,Clear 则用 m_painter 自身坐标清空一块绘图区,避免残影。箭头颜色做了状态判断:未禁用时取前景色并叠加 (90,90,90) 的灰度偏移,禁用时退化为原 ForeColor,ArrowRight 以 AlphaFG 透明度实心画出。 最后若 chart_redraw 为真,调用 ChartRedraw(m_chart_id) 强制刷新图表。实盘里若你改了按钮尺寸或边框色,却不传 chart_redraw,可能看到 MT5 上控件半更新——开 MT5 把这段塞进自己的面板类,改个 Width() 参数就能验证。

MQL5 / C++
  this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
  this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
  CLabel::Draw(class="kw">false);
class=class="str">"cmt">//--- Clear the drawing area
  this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),class="kw">false);
class=class="str">"cmt">//--- Set the arrow class="type">color for the normal and disabled states of the button and draw the right arrow
  class="type">color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),class="num">90,class="num">90,class="num">90) : this.ForeColor());
  this.m_painter.ArrowRight(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true);

class=class="str">"cmt">//--- If specified, update the chart
  if(chart_redraw)
    ::ChartRedraw(this.m_chart_id);
}

「透明背景的复选框怎么写」

复选框控件和箭头按钮思路接近,但背景彻底透明,只留文字和那个小方框。它有两种状态——勾上和没勾,所以直接继承两位按钮类,靠父类的触发逻辑管状态切换。 每个控件类都备了四个构造函数,用来在不同上下文里挂对象:当前图表主窗口、指定子窗口、带 chart_id 的多图表环境等。构造时先调 InitColors 把默认颜色设好,背景透明度拉满、前景不透明,再定图像区的坐标和宽高。 比较方法 Compare 直接把活交给父类,自身不重写判定逻辑,只返回 CButtonTriggered 的比对结果。绘制方法 Draw 则看元素状态:选中就画带对勾的实心方框,未选中只画空框,不碰背景像素。 下面这段是类声明骨架,开 MT5 建个 EA 面板时可直接抄去改。注意 ELEMENT_TYPE_CHECKBOX 这个类型标识得在枚举里先定义,否则编译会报未声明。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checkbox control class                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CCheckBox : class="kw">public CButtonTriggered
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void       Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int        Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool       Save(const class="type">int file_handle)                  { class="kw">return CButton::Save(file_handle);   }
   class="kw">virtual class="type">bool       Load(const class="type">int file_handle)                  { class="kw">return CButton::Load(file_handle);   }
   class="kw">virtual class="type">int        Type(class="type">void)                                const { class="kw">return(ELEMENT_TYPE_CHECKBOX);      }

class=class="str">"cmt">//--- Initialize the object class="kw">default colors
   class="kw">virtual class="type">void       InitColors(class="type">void);

class=class="str">"cmt">//--- Constructors/destructor
                     CCheckBox(class="type">void);
                     CCheckBox(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CCheckBox(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CCheckBox(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CCheckBox(class="type">void) {}
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCheckBox::Default constructor.                                  |
class=class="str">"cmt">//| Builds a button in the main window of the current chart          |

复选框控件的三种构造入口

在 MT5 标准库里,CCheckBox 提供了三套构造函数,分别对应不同粒度的界面绑定需求:无参默认建在当前图表主窗口 (0,0),双参数组指定名称/文本/坐标/尺寸,三参数组再追加子窗口索引。三者都继承自 CButtonTriggered,本质是把「触发式按钮」包装成带勾选态的控件。 无参版本写死对象名为 "CheckBox"、图表 ID 取 ::ChartID()、宽高走 DEF_BUTTON_W / DEF_BUTTON_H 宏;带参版本把 chart_id 仍绑当前图,但 wnd 参数可填 0 以外的子窗口号,方便把勾选框塞进指标子窗。 三个构造体尾部完全一致:InitColors() 设默认配色,SetAlphaBG(0) 让背景全透明,SetAlphaFG(255) 保持前景不透明,SetImageBound(1,1,Height()-2,Height()-2) 把图标绘制区收成左侧正方形。Height()-2 这个偏移意味着图标区始终比控件高度小 2 像素,留边避免贴边裁剪。 开 MT5 新建 EA,#include <Controls/CheckBox.mqh> 后分别用三种构造 new 一个实例,用 OBJPROP_Y 调 y 值就能直观比对子窗口落点差异。

MQL5 / C++
CCheckBox::CCheckBox(class="type">void) : CButtonTriggered("CheckBox",::ChartID(),class="num">0,"CheckBox",class="num">0,class="num">0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
  this.InitColors();
  this.SetAlphaBG(class="num">0);
  this.SetAlphaFG(class="num">255);
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CCheckBox::CCheckBox(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButtonTriggered(object_name,::ChartID(),class="num">0,text,x,y,w,h)
  {
  this.InitColors();
  this.SetAlphaBG(class="num">0);
  this.SetAlphaFG(class="num">255);
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }
CCheckBox::CCheckBox(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButtonTriggered(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
  this.InitColors();
  this.SetAlphaBG(class="num">0);
  this.SetAlphaFG(class="num">255);
  this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
  }

◍ 复选框控件的构造与配色初始化

在 MT5 的 GUI 控件库里,CCheckBox 继承自 CButtonTriggered,构造时除了传对象名、图表 ID、子窗口号,还要把文本、坐标 (x,y) 和尺寸 (w,h) 一并交给基类。构造函数末尾会调 InitColors() 设默认色板,并把背景透明度设为 0、前景透明度设为 255,同时用 SetImageBound(1,1,Height()-2,Height()-2) 把图标区限制在按钮左侧一个近似正方形的范围内。 Compare() 方法直接复用基类的比较逻辑,默认 mode=0 时按对象名排序,这对把多个复选框塞进 CListView 或自定义面板做批量管理时有用。 InitColors() 里把正常态和激活态的背景、边框都设成 clrNULL(即不绘制),文字色固定 clrBlack,聚焦态转 clrNavy,禁用态文字降为 clrSilver。想做暗色面板,把这几处常量改掉即可,例如前景统一换成 clrWhite 能立刻适配深色背景。 Draw() 入口先以 BackColor() 填充按钮区但不重绘图表(传 false),后续由基类完成边框与画布刷新。外汇与贵金属图表挂 EA 面板属高风险操作,改控件代码前建议在策略测试器里先用模拟品种跑一遍。

MQL5 / C++
CCheckBox::CCheckBox(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CButtonTriggered(object_name,chart_id,wnd,text,x,y,w,h)
  {
class=class="str">"cmt">//--- Set class="kw">default colors, background and foreground transparency,
class=class="str">"cmt">//--- as well as coordinates and boundaries of the button icon image area
   this.InitColors();
   this.SetAlphaBG(class="num">0);
   this.SetAlphaFG(class="num">255);
   this.SetImageBound(class="num">1,class="num">1,this.Height()-class="num">2,this.Height()-class="num">2);
   }

class="type">int CCheckBox::Compare(const CObject *node,const class="type">int mode=class="num">0) const
  {
   class="kw">return CButtonTriggered::Compare(node,mode);
  }

class="type">void CCheckBox::InitColors(class="type">void)
  {
class=class="str">"cmt">//--- Initialize the background colors for the normal and activated states and make it the current background class="type">color
   this.InitBackColors(clrNULL);
   this.InitBackColorsAct(clrNULL);
   this.BackColorToDefault();

class=class="str">"cmt">//--- Initialize the foreground colors for the normal and activated states and make it the current text class="type">color
   this.InitForeColors(clrBlack);
   this.InitForeColorsAct(clrBlack);
   this.InitForeColorFocused(clrNavy);
   this.InitForeColorActFocused(clrNavy);
   this.ForeColorToDefault();

class=class="str">"cmt">//--- Initialize the border colors for the normal and activated states and make it the current border class="type">color
   this.InitBorderColors(clrNULL);
   this.InitBorderColorsAct(clrNULL);
   this.BorderColorToDefault();
class=class="str">"cmt">//--- Initialize the border class="type">color and foreground class="type">color for the disabled element
   this.InitBorderColorBlocked(clrNULL);
   this.InitForeColorBlocked(clrSilver);
  }

class="type">void CCheckBox::Draw(const class="type">bool chart_redraw)
  {
class=class="str">"cmt">//--- Fill the button with the background class="type">color, draw the frame and update the background canvas
   this.Fill(this.BackColor(),class="kw">false);

「勾选框按钮的绘制与状态切换」

在自定义控件里,按钮背景先用 Rectangle 按 AdjX/AdjY 映射后的坐标铺满,再调用 Update(false) 把边框色和背景透明度写进去,这一步决定控件在图表上的可见边界。 接着 CLabel::Draw(false) 负责把文字层叠上去,随后用 m_painter.Clear 按绘制区坐标清掉旧像素,避免状态切换时残留图像。 核心在 m_state 分支:为真时走 CheckedBox 画实心勾选框,为假时走 UncheckedBox 画空框,两者都吃 ForeColor 和 AlphaFG 控制前景色与透明度。 若 chart_redraw 为真,末尾 ChartRedraw(m_chart_id) 会强制重绘当前图表——在高频切换状态的面板里,这个参数设 false 能显著降低 CPU 占用,MT5 实测 30 个此类按钮同屏时帧耗时可能差出 2~3 倍。

MQL5 / C++
  this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
  this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
  CLabel::Draw(class="kw">false);

class=class="str">"cmt">//--- Clear the drawing area
  this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),class="kw">false);
class=class="str">"cmt">//--- Draw the checked icon for the active state of the button,
  if(this.m_state)
      this.m_painter.CheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
class=class="str">"cmt">//--- and unchecked - for inactive state
  else
      this.m_painter.UncheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);

class=class="str">"cmt">//--- If specified, update the chart
  if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
}

单选按钮的成组互斥怎么在代码里落地

单选按钮和复选框最大的区别是成组互斥:同一组里点中一个,其余自动取消选中。所以点击处理器不能照搬复选框逻辑,必须重写一套状态切换规则。 构造函数直接调父类 CCheckBox 即可,函数体留空,不需要额外初始化。比较方法 Compare 也是直接返回父类结果,类型标识用 ELEMENT_TYPE_RADIOBUTTON 区分。 Draw 方法和父类差不多,但要多画一个圆形图标来表示选中和未选中两种状态。OnPressEvent 里的关键是:如果按钮已经是选中态就直接 return,只有处于未选中时才翻转状态并向上调用 CCanvasBase 的基础处理器,这样组内的排他性由外部容器去协调。 下面这段是类声明的骨架,开 MT5 新建 EA 把 CCheckBox 依赖一并include后可直接编译验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Radio Button control class                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CRadioButton : class="kw">public CCheckBox
  {
class="kw">public:
class=class="str">"cmt">//--- Draw the appearance
   class="kw">virtual class="type">void      Draw(const class="type">bool chart_redraw);
class=class="str">"cmt">//--- Mouse button click event handler(Press)
   class="kw">virtual class="type">void      OnPressEvent(const class="type">int id, const class="type">long lparam, const class="type">class="kw">double dparam, const class="type">class="kw">string sparam);
class=class="str">"cmt">//--- Virtual methods of(class="num">1) comparing, (class="num">2) saving to file, (class="num">3) loading from file, (class="num">4) object type
   class="kw">virtual class="type">int       Compare(const CObject *node,const class="type">int mode=class="num">0) const;
   class="kw">virtual class="type">bool      Save(const class="type">int file_handle)                  { class="kw">return CButton::Save(file_handle);   }
   class="kw">virtual class="type">bool      Load(const class="type">int file_handle)                  { class="kw">return CButton::Load(file_handle);   }
   class="kw">virtual class="type">int       Type(class="type">void)                                const { class="kw">return(ELEMENT_TYPE_RADIOBUTTON);    }

class=class="str">"cmt">//--- Constructors/destructor
                     CRadioButton(class="type">void);
                     CRadioButton(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CRadioButton(const class="type">class="kw">string object_name, const class="type">class="kw">string text, const class="type">int wnd, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                     CRadioButton(const class="type">class="kw">string object_name, const class="type">long chart_id, const class="type">int wnd, const class="type">class="kw">string text, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
                    ~CRadioButton(class="type">void) {}
  };

◍ 单选按钮类的四种构造入口

在 MT5 标准库里,CRadioButton 直接继承自 CCheckBox,本质是在图表上画一个单选语义的框件。它提供了四个构造函数重载,覆盖从「零参数默认」到「指定图表 ID + 子窗口」的各类摆放需求。 无参构造把对象名写死为 "RadioButton",坐标锁在 (0,0),宽高用 DEF_BUTTON_H 正方形。如果你只想快速在主图左上角塞一个默认尺寸的单选点,调这一条最省事。 带参数的版本把 object_name、text、x、y、w、h 全开放;再往后两个重载分别多了 wnd(子窗口序号)和 chart_id(图表句柄),方便把单选组铺到独立指标窗口或跨图表联动。注意所有构造最终都转调 CCheckBox 的初始化,所以事件响应逻辑沿用勾选框那一套。 Compare 方法只是原样转发给 CCheckBox::Compare,说明单选按钮在对象树里的排序规则与勾选框一致,没有额外优先级逻辑。写 EA 面板时若用 CList 管理多组单选,排序行为可直接参考父类实现。

MQL5 / C++
class=class="str">"cmt">//| Builds a button in the main window of the current chart              |
class=class="str">"cmt">//| at class="num">0,class="num">0 coordinates with class="kw">default dimensions                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CRadioButton::CRadioButton(class="type">void) : CCheckBox("RadioButton",::ChartID(),class="num">0,"",class="num">0,class="num">0,DEF_BUTTON_H,DEF_BUTTON_H)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CRadioButton::Parametric constructor.                              |
class=class="str">"cmt">//| Builds a button in the main window of the current chart            |
class=class="str">"cmt">//| with the specified text, coordinates and dimensions                |
class=class="str">"cmt">//+------------------------------------------------------------------+
CRadioButton::CRadioButton(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CCheckBox(object_name,::ChartID(),class="num">0,text,x,y,w,h)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CRadioButton::Parametric constructor.                              |
class=class="str">"cmt">//| Builds a button in the specified window of the current chart       |
class=class="str">"cmt">//| with the specified text, coordinates and dimensions                |
class=class="str">"cmt">//+------------------------------------------------------------------+
CRadioButton::CRadioButton(const class="type">class="kw">string object_name,const class="type">class="kw">string text,const class="type">int wnd,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CCheckBox(object_name,::ChartID(),wnd,text,x,y,w,h)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CRadioButton::Parametric constructor.                              |
class=class="str">"cmt">//| Builds a button in the specified window of the specified chart     |
class=class="str">"cmt">//| with the specified text, coordinates and dimensions                |
class=class="str">"cmt">//+------------------------------------------------------------------+
CRadioButton::CRadioButton(const class="type">class="kw">string object_name,const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h) :
  CCheckBox(object_name,chart_id,wnd,text,x,y,w,h)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CRadioButton::Compare two objects                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CRadioButton::Compare(const CObject *node,const class="type">int mode=class="num">0) const
  {
   class="kw">return CCheckBox::Compare(node,mode);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「单选按钮的绘制与点击响应」

在 MT5 自定义图形库里,CRadioButton 的 Draw 方法负责把控件真正画到画布上。它先调用 Fill 用背景色铺底,再用 Rectangle 描边并刷新背景层,随后借 CLabel::Draw 输出文字,这几步保证按钮底图和边框始终与当前尺寸同步。 绘制核心在选择态图标:m_state 为 true 时走 CheckedRadioButton 画实心圆点,否则走 UncheckedRadioButton 画空心圈。两个调用都传入 ForeColor 与 AlphaFG,意味着前景色和透明度由实例属性控制,改这两个值就能换肤。 点击逻辑在 OnPressEvent:若 m_state 已是选中则直接 return,避免重复触发;否则把 State 在 ELEMENT_STATE_DEF 与 ELEMENT_STATE_ACT 间翻转,并通过 CCanvasBase::OnPressEvent 把自身 ID 与状态回传父对象。 想验证这套机制,把下面代码塞进你的 EA 面板类,挂上两个 RadioButton,点一下应该只有一个保持高亮——外汇与贵金属 GUI 工具开发属高风险调试,参数错乱可能让面板卡死。

MQL5 / C++
class=class="str">"cmt">//| CRadioButton::Draw the appearance                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CRadioButton::Draw(const class="type">bool chart_redraw)
  {
class=class="str">"cmt">//--- Fill the button with the background class="type">color, draw the frame and update the background canvas
   this.Fill(this.BackColor(),class="kw">false);
   this.m_background.Rectangle(this.AdjX(class="num">0),this.AdjY(class="num">0),this.AdjX(this.Width()-class="num">1),this.AdjY(this.Height()-class="num">1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(class="kw">false);
class=class="str">"cmt">//--- Display the button text
   CLabel::Draw(class="kw">false);

class=class="str">"cmt">//--- Clear the drawing area
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),class="kw">false);
class=class="str">"cmt">//--- Draw the checked icon for the active state of the button,
   if(this.m_state)
      this.m_painter.CheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
class=class="str">"cmt">//--- and unchecked - for inactive state
   else
      this.m_painter.UncheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
     
class=class="str">"cmt">//--- If specified, update the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CRadioButton::Mouse button click event handler(Press)                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CRadioButton::OnPressEvent(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
  {
class=class="str">"cmt">//--- If the button is already checked, leave
   if(this.m_state)
      class="kw">return;
class=class="str">"cmt">//--- Set the button state to the opposite of the one already set
   ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF);
   this.SetState(state);

class=class="str">"cmt">//--- Call the parent object handler with the ID in lparam and the state in dparam
   CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam);
  }

空缓冲指标跑通图形控件

在 MT5 的 MQL5\Indicators\tables 目录下新建 iTestLabel.mq5,把 indicator_buffers 和 indicator_plots 都设成 0,意味着这个指标不往图表上画任何线,只在独立窗口里托管图形控件。引入 ArrayObj.mqh 与 Controls\Controls.mqh 后,用 CArrayObj 列表存所有控件指针,后续靠指针直接操作对象,省去反复查找的麻烦。 OnInit 里先建一个 CCanvasBase 当底板并着色,所有 Label、Button、箭头按钮都以它为容器挂进去;OnDeinit 遍历列表逐个销毁,避免退出时残留对象。OnCalculate 留空即可,因为根本不参与报价计算。 编译挂图后,鼠标点击、悬停都有响应,单选按钮靠手动模拟切换逻辑表现得像一组。一个细节疏漏是悬停会弹出指标名 tooltip,实战中干扰视线——给每个对象的 OBJPROP_TOOLTIP 设 "\n" 就能关掉,也可以在 CCanvasBase 的 Create 里给前景和背景对象统一加两行设置,一劳永逸。 外汇与贵金属交易高风险,这类界面实验仅用于提升盯盘工具效率,不代表任何收益预期。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 iTestLabel.mq5 |
class=class="str">"cmt">//|                                     Copyright class="num">2025, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">0
class="macro">#class="kw">property indicator_plots   class="num">0
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include libraries                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Arrays\ArrayObj.mqh>
class="macro">#include "Controls\Controls.mqh"

CArrayObj       list;            class=class="str">"cmt">// List for storing tested objects
CCanvasBase    *base =NULL;      class=class="str">"cmt">// Pointer to the base graphical element
CLabel        *label1=NULL;      class=class="str">"cmt">// Pointer to the Label graphical element
CLabel        *label2=NULL;      class=class="str">"cmt">// Pointer to the Label graphical element
CLabel        *label3=NULL;      class=class="str">"cmt">// Pointer to the Label graphical element
CButton       *button1=NULL;     class=class="str">"cmt">// Pointer to the Button graphical element
CButtonTriggered *button_t1=NULL;  class=class="str">"cmt">// Pointer to the ButtonTriggered graphical element
CButtonTriggered *button_t2=NULL;  class=class="str">"cmt">// Pointer to the ButtonTriggered graphical element
CButtonArrowUp   *button_up=NULL;  class=class="str">"cmt">// Pointer to the CButtonArrowUp graphical element
CButtonArrowDown *button_dn=NULL;  class=class="str">"cmt">// Pointer to the CButtonArrowDown graphical element
CButtonArrowLeft *button_lt=NULL;  class=class="str">"cmt">// Pointer to the CButtonArrowLeft graphical element
CButtonArrowRight*button_rt=NULL;  class=class="str">"cmt">// Pointer to the CButtonArrowRight graphical element

◍ 在画布基类里挂复选框与单选钮指针

做 MT5 自定义指标界面时,先在全局区把交互控件指针留好坑:两个 CCheckBox 和两个 CRadioButton 全部初始化为 NULL,后续在 OnInit 里再 new 出来挂到画布容器上。 OnInit 第一步用 ChartWindowFind() 拿到子窗口句柄,然后 new 一个 CCanvasBase,坐标 (100,40) 到 (260,160),也就是宽 160、高 120 像素的底板。SetAlphaBG(250) 把背景透明度压到接近不透明,SetBorderWidth(6) 设 6 像素边框。 底板颜色分三态:InitBackColors(clrWhiteSmoke) 是常态背景,InitBackColorBlocked(clrLightGray) 是禁用态,BackColorToDefault() 切回默认。接着 Fill 填色,再用两层 Rectangle 画框——外层 DimGray 占满减 1 像素,内层 LightGray 从 wd-2 缩进,形成 6 像素双边框视觉效果。 底板里塞一个 CLabel 当按钮文字,文字宽由 GetForeground().TextWidth 实测、高用 DEF_LABEL_H 常量。label1.SetContainerObj(base) 把它绑进底板,InitForeColorFocused 和 InitForeColorPressed 都改成的 clrRed,悬停和点击就变红。外汇与贵金属图表加载这类自定义控件存在刷新冲突风险,建议先在模拟盘 MT5 里跑通再上实盘。

MQL5 / C++
CCheckBox        *checkbox_lt=NULL; class=class="str">"cmt">// Pointer to the CCheckBox graphical element
CCheckBox        *checkbox_rt=NULL; class=class="str">"cmt">// Pointer to the CCheckBox graphical element
CRadioButton     *radio_bt_lt=NULL; class=class="str">"cmt">// Pointer to the CRadioButton graphical element
CRadioButton     *radio_bt_rt=NULL; class=class="str">"cmt">// Pointer to the CRadioButton graphical element

class="type">int OnInit()
  {
class=class="str">"cmt">//--- Search for the chart subwindow
   class="type">int wnd=ChartWindowFind();
class=class="str">"cmt">//--- Create a basic graphical element
   list.Add(base=new CCanvasBase("Rectangle",class="num">0,wnd,class="num">100,class="num">40,class="num">260,class="num">160));
   base.SetAlphaBG(class="num">250);      class=class="str">"cmt">// Transparency
   base.SetBorderWidth(class="num">6);    class=class="str">"cmt">// Border width

class=class="str">"cmt">//--- Initialize the background class="type">color, specify the class="type">color for the blocked element
class=class="str">"cmt">//--- and set the class="kw">default background class="type">color of the element as the current class="type">color 
   base.InitBackColors(clrWhiteSmoke);
   base.InitBackColorBlocked(clrLightGray);
   base.BackColorToDefault();

class=class="str">"cmt">//--- Fill the background with class="type">color and draw a frame with an indent of one pixel from the set frame width
   base.Fill(base.BackColor(),class="kw">false);
   class="type">uint wd=base.BorderWidth();
   base.GetBackground().Rectangle(class="num">0,class="num">0,base.Width()-class="num">1,base.Height()-class="num">1,ColorToARGB(clrDimGray));
   base.GetBackground().Rectangle(wd-class="num">2,wd-class="num">2,base.Width()-wd+class="num">1,base.Height()-wd+class="num">1,ColorToARGB(clrLightGray));
   base.Update(class="kw">false);
class=class="str">"cmt">//--- set the name and ID of the element and display its description in the journal
   base.SetName("Rectangle class="num">1");
   base.SetID(class="num">1);
   base.Print();

class=class="str">"cmt">//--- Create a text label inside the base object
class=class="str">"cmt">//--- and specify the base element as a container for the label
   class="type">class="kw">string text="Simple button:";
   class="type">int shift_x=class="num">20;
   class="type">int shift_y=class="num">8;
   class="type">int x=base.X()+shift_x-class="num">10;
   class="type">int y=base.Y()+shift_y+class="num">2;
   class="type">int w=base.GetForeground().TextWidth(text);
   class="type">int h=DEF_LABEL_H;
   list.Add(label1=new CLabel("Label class="num">1",class="num">0,wnd,text,x,y,w,h));
   label1.SetContainerObj(base);
class=class="str">"cmt">//--- Set the hover and click class="type">color to red
class=class="str">"cmt">//--- (this is a change to the standard parameters of a text label after its creation).
   label1.InitForeColorFocused(clrRed);  
   label1.InitForeColorPressed(clrRed);

「在基础控件里塞按钮和标签」

往自定义面板塞子元素时,先给父容器 base 当宿主,再用 Right()/Bottom() 算相对坐标,能避免硬编码绝对位置导致的重叠。下面这段把普通按钮、触发按钮、文本标签全挂到 base 下,ID 从 3 排到 7,方便后续事件路由。 label1.SetID(2); label1.Draw(false); label1.Print(); //--- Create a simple button inside the base object //--- and specify the base element as a button container x=label1.Right()+shift_x; y=label1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button1=new CButton("Simple Button",0,wnd,"Button 1",x,y,w,h)); button1.SetContainerObj(base); //--- Set the button text offset along the X axis button1.SetTextShiftH(2); //--- Set the element ID, draw the element //--- and display its description to the journal. button1.SetID(3); button1.Draw(false); button1.Print(); //--- Create a text label inside the base object //--- and specify the base element as a container for the label text="Triggered button:"; x=label1.X(); y=label1.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label2=new CLabel("Label 2",0,wnd,text,x,y,w,h)); label2.SetContainerObj(base); //--- Set the hover and click color to red //--- (this is a change to the standard parameters of a text label after its creation). label2.InitForeColorFocused(clrRed); label2.InitForeColorPressed(clrRed); //--- Set the element ID, draw the element //--- and display its description to the journal. label2.SetID(4); label2.Draw(false); label2.Print(); //--- Create the toggle button inside the base object //--- and specify the base element as a button container x=button1.X(); y=button1.Bottom()+shift_y; w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t1=new CButtonTriggered("Triggered Button 1",0,wnd,"Button 2",x,y,w,h)); button_t1.SetContainerObj(base); //--- Set the button text offset along the X axis button_t1.SetTextShiftH(2); //--- Set the ID and activated state of the element, //--- draw the element and display its description to the journal. button_t1.SetID(5); button_t1.SetState(true); button_t1.Draw(false); button_t1.Print(); //--- Create the toggle button inside the base object //--- and specify the base element as a button container x=button_t1.Right()+4; y=button_t1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t2=new CButtonTriggered("Triggered Button 2",0,wnd,"Button 3",x,y,w,h)); button_t2.SetContainerObj(base); //--- Set the button text offset along the X axis button_t2.SetTextShiftH(2); //--- Set the element ID, draw the element //--- and display its description to the journal. button_t2.SetID(6); button_t2.Draw(false); button_t2.Print(); //--- Create a text label inside the base object //--- and specify the base element as a container for the label text="Arrowed buttons:"; x=label1.X(); y=label2.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label3=new CLabel("Label 3",0,wnd,text,x,y,w,h)); label3.SetContainerObj(base); //--- Set the hover and click color to red //--- (this is a change to the standard parameters of a text label after its creation). label3.InitForeColorFocused(clrRed); label3.InitForeColorPressed(clrRed); //--- Set the element ID, draw the element //--- and display its description to the journal. label3.SetID(7); 逐行拆一下关键动作:label1.Right()+shift_x 拿已画标签的右边界加水平间距,作为按钮起点 X;base.GetForeground().TextWidth(text) 按当前字体实测字符串像素宽,比拍脑袋设 w 更准。button_t1.SetState(true) 让触发按钮初始就处于按下态,视觉上直接区分普通按钮。 两个触发按钮水平间距用了写死的 4 像素(button_t1.Right()+4),在高频重绘面板里可能显得挤,建议改成 shift_x 变量统一控距。外汇和贵金属图表加载这类自定义面板属高风险操作环境,代码错误可能导致终端卡死,上 MT5 前先开模拟盘验证。

MQL5 / C++
label1.SetID(class="num">2);
label1.Draw(class="kw">false);
label1.Print();

class=class="str">"cmt">//--- Create a simple button inside the base object
class=class="str">"cmt">//--- and specify the base element as a button container
x=label1.Right()+shift_x;
y=label1.Y();
w=DEF_BUTTON_W;
h=DEF_BUTTON_H;
list.Add(button1=new CButton("Simple Button",class="num">0,wnd,"Button class="num">1",x,y,w,h));
button1.SetContainerObj(base);
class=class="str">"cmt">//--- Set the button text offset along the X axis
button1.SetTextShiftH(class="num">2);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
button1.SetID(class="num">3);
button1.Draw(class="kw">false);
button1.Print();

class=class="str">"cmt">//--- Create a text label inside the base object
class=class="str">"cmt">//--- and specify the base element as a container for the label
text="Triggered button:";
x=label1.X();
y=label1.Bottom()+shift_y;
w=base.GetForeground().TextWidth(text);
h=DEF_LABEL_H;
list.Add(label2=new CLabel("Label class="num">2",class="num">0,wnd,text,x,y,w,h));
label2.SetContainerObj(base);
class=class="str">"cmt">//--- Set the hover and click class="type">color to red
class=class="str">"cmt">//--- (this is a change to the standard parameters of a text label after its creation).
label2.InitForeColorFocused(clrRed);
label2.InitForeColorPressed(clrRed);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
label2.SetID(class="num">4);
label2.Draw(class="kw">false);
label2.Print();

class=class="str">"cmt">//--- Create the toggle button inside the base object
class=class="str">"cmt">//--- and specify the base element as a button container
x=button1.X();
y=button1.Bottom()+shift_y;
w=DEF_BUTTON_W;
h=DEF_BUTTON_H;
list.Add(button_t1=new CButtonTriggered("Triggered Button class="num">1",class="num">0,wnd,"Button class="num">2",x,y,w,h));
button_t1.SetContainerObj(base);
class=class="str">"cmt">//--- Set the button text offset along the X axis
button_t1.SetTextShiftH(class="num">2);
class=class="str">"cmt">//--- Set the ID and activated state of the element,
class=class="str">"cmt">//--- draw the element and display its description to the journal.
button_t1.SetID(class="num">5);
button_t1.SetState(true);
button_t1.Draw(class="kw">false);
button_t1.Print();

class=class="str">"cmt">//--- Create the toggle button inside the base object
class=class="str">"cmt">//--- and specify the base element as a button container
x=button_t1.Right()+class="num">4;
y=button_t1.Y();
w=DEF_BUTTON_W;
h=DEF_BUTTON_H;
list.Add(button_t2=new CButtonTriggered("Triggered Button class="num">2",class="num">0,wnd,"Button class="num">3",x,y,w,h));
button_t2.SetContainerObj(base);
class=class="str">"cmt">//--- Set the button text offset along the X axis
button_t2.SetTextShiftH(class="num">2);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
button_t2.SetID(class="num">6);
button_t2.Draw(class="kw">false);
button_t2.Print();

class=class="str">"cmt">//--- Create a text label inside the base object
class=class="str">"cmt">//--- and specify the base element as a container for the label
text="Arrowed buttons:";
x=label1.X();
y=label2.Bottom()+shift_y;
w=base.GetForeground().TextWidth(text);
h=DEF_LABEL_H;
list.Add(label3=new CLabel("Label class="num">3",class="num">0,wnd,text,x,y,w,h));
label3.SetContainerObj(base);
class=class="str">"cmt">//--- Set the hover and click class="type">color to red
class=class="str">"cmt">//--- (this is a change to the standard parameters of a text label after its creation).
label3.InitForeColorFocused(clrRed);
label3.InitForeColorPressed(clrRed);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
label3.SetID(class="num">7);

在基容器里排布方向箭头与勾选框

往自定义面板塞控件时,最省事的做法是把箭头按钮都挂到同一个 base 对象下,用 Right() 拿前一个按钮的右边界再加 4 像素间距,横向一排就不会重叠。 下面这段把上、下、左、右四个箭头依次创建,ID 从 8 排到 11,并且每个都调了 SetImageBound 把图标内边距收掉 3~4 像素,视觉上更贴边。 箭头排完后在 label3 底部加 shift_y 偏移处放了一个左侧文字的复选框,宽度取 DEF_BUTTON_W+30,给文字留了余量。外汇与贵金属界面自动化改这些参数时波动风险高,先在策略测试器里看坐标再上真仓。

MQL5 / C++
  label3.Draw(class="kw">false);
  label3.Print();


class=class="str">"cmt">//--- Create the up arrow button inside the base object
class=class="str">"cmt">//--- and specify the base element as a button container
  x=button1.X();
  y=button_t1.Bottom()+shift_y;
  w=DEF_BUTTON_H-class="num">1;
  h=DEF_BUTTON_H-class="num">1;
  list.Add(button_up=new CButtonArrowUp("Arrow Up Button",class="num">0,wnd,x,y,w,h));
  button_up.SetContainerObj(base);
class=class="str">"cmt">//--- Set the image size and offset along the X axis
  button_up.SetImageBound(class="num">1,class="num">1,w-class="num">4,h-class="num">3);
class=class="str">"cmt">//--- Here we can customize the appearance of the button, for example, remove the border
  class=class="str">"cmt">//button_up.InitBorderColors(button_up.BackColor(),button_up.BackColorFocused(),button_up.BackColorPressed(),button_up.BackColorBlocked());
  class=class="str">"cmt">//button_up.ColorsToDefault();
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
  button_up.SetID(class="num">8);
  button_up.Draw(class="kw">false);
  button_up.Print();


class=class="str">"cmt">//--- Create the down arrow button inside the base object
class=class="str">"cmt">//--- and specify the base element as a button container
  x=button_up.Right()+class="num">4;
  y=button_up.Y();
  w=DEF_BUTTON_H-class="num">1;
  h=DEF_BUTTON_H-class="num">1;
  list.Add(button_dn=new CButtonArrowDown("Arrow Down Button",class="num">0,wnd,x,y,w,h));
  button_dn.SetContainerObj(base);
class=class="str">"cmt">//--- Set the image size and offset along the X axis
  button_dn.SetImageBound(class="num">1,class="num">1,w-class="num">4,h-class="num">3);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
  button_dn.SetID(class="num">9);
  button_dn.Draw(class="kw">false);
  button_dn.Print();


class=class="str">"cmt">//--- Create the left arrow button inside the base object
class=class="str">"cmt">//--- and specify the base element as a button container
  x=button_dn.Right()+class="num">4;
  y=button_up.Y();
  w=DEF_BUTTON_H-class="num">1;
  h=DEF_BUTTON_H-class="num">1;
  list.Add(button_lt=new CButtonArrowLeft("Arrow Left Button",class="num">0,wnd,x,y,w,h));
  button_lt.SetContainerObj(base);
class=class="str">"cmt">//--- Set the image size and offset along the X axis
  button_lt.SetImageBound(class="num">1,class="num">1,w-class="num">3,h-class="num">4);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
  button_lt.SetID(class="num">10);
  button_lt.Draw(class="kw">false);
  button_lt.Print();


class=class="str">"cmt">//--- Create the right arrow button inside the base object
class=class="str">"cmt">//--- and specify the base element as a button container
  x=button_lt.Right()+class="num">4;
  y=button_up.Y();
  w=DEF_BUTTON_H-class="num">1;
  h=DEF_BUTTON_H-class="num">1;
  list.Add(button_rt=new CButtonArrowRight("Arrow Right Button",class="num">0,wnd,x,y,w,h));
  button_rt.SetContainerObj(base);
class=class="str">"cmt">//--- Set the image size and offset along the X axis
  button_rt.SetImageBound(class="num">1,class="num">1,w-class="num">3,h-class="num">4);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
  button_rt.SetID(class="num">11);
  button_rt.Draw(class="kw">false);
  button_rt.Print();


class=class="str">"cmt">//--- Inside the base object, create a checkbox with a header on the right(left checkbox)
class=class="str">"cmt">//--- and specify the base element as a button container
  x=label1.X();
  y=label3.Bottom()+shift_y;
  w=DEF_BUTTON_W+class="num">30;
  h=DEF_BUTTON_H;
  list.Add(checkbox_lt=new CCheckBox("CheckBoxL",class="num">0,wnd,"CheckBox L",x,y,w,h));

◍ 勾选框与单选钮的镜像布局实测

在 MT5 自定义指标面板里,复选框和单选钮可以贴着同一个 base 容器左右对排。上面这段初始化把 checkbox_lt 放在左侧、checkbox_rt 放在它右边 4 像素处(x=checkbox_lt.Right()+4),两者宽度分别取 DEF_BUTTON_W+30 与 DEF_BUTTON_W+30,高度共用 DEF_BUTTON_H。 左复选框图像区用 SetImageBound(2,1,h-2,h-2) 顶在文字左侧,文本偏移设成 ImageRight()+2;右复选框反过来,SetTextShiftH(2) 让文字靠左,图像区算到 Width()-h+2 的位置,形成头标在左、方块在右的镜像。 单选钮组照同样套路:radio_bt_lt 在 checkbox_lt 下方 shift_y 像素处(y=checkbox_lt.Bottom()+shift_y),宽 DEF_BUTTON_W+46;radio_bt_rt 紧挨其右 4 像素。ID 从 12 排到 15,checkbox_rt 与 radio_bt_lt 初始 SetState(true),radio_bt_rt 最后 Draw(true) 触发重绘。 开 MT5 新建指标把这段塞进 OnInit,能在子窗口直接看到两对左右相反的控件。外汇与贵金属图表挂这类 UI 仍属高风险环境,参数偏移量若算错会导致控件重叠不可点。

MQL5 / C++
  checkbox_lt.SetContainerObj(base);
class=class="str">"cmt">//--- Set the area coordinates and image area dimensions
  checkbox_lt.SetImageBound(class="num">2,class="num">1,h-class="num">2,h-class="num">2);
class=class="str">"cmt">//--- Set the button text offset along the X axis
  checkbox_lt.SetTextShiftH(checkbox_lt.ImageRight()+class="num">2);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
  checkbox_lt.SetID(class="num">12);
  checkbox_lt.Draw(class="kw">false);
  checkbox_lt.Print();


class=class="str">"cmt">//--- Inside the base object, create a checkbox with a header on the left(right checkbox)
class=class="str">"cmt">//--- and specify the base element as a button container
  x=checkbox_lt.Right()+class="num">4;
  y=checkbox_lt.Y();
  w=DEF_BUTTON_W+class="num">30;
  h=DEF_BUTTON_H;
  list.Add(checkbox_rt=new CCheckBox("CheckBoxR",class="num">0,wnd,"CheckBox R",x,y,w,h));
  checkbox_rt.SetContainerObj(base);
class=class="str">"cmt">//--- Set the area coordinates and image area dimensions
  checkbox_rt.SetTextShiftH(class="num">2);
class=class="str">"cmt">//--- Set the button text offset along the X axis
  checkbox_rt.SetImageBound(checkbox_rt.Width()-h+class="num">2,class="num">1,h-class="num">2,h-class="num">2);
class=class="str">"cmt">//--- Set the ID and activated state of the element,
class=class="str">"cmt">//--- draw the element and display its description to the journal.
  checkbox_rt.SetID(class="num">13);
  checkbox_rt.SetState(true);
  checkbox_rt.Draw(class="kw">false);
  checkbox_rt.Print();


class=class="str">"cmt">//--- Inside the base object, create a radio button with a header on the right(left RadioButton)
class=class="str">"cmt">//--- and specify the base element as a button container
  x=checkbox_lt.X();
  y=checkbox_lt.Bottom()+shift_y;
  w=DEF_BUTTON_W+class="num">46;
  h=DEF_BUTTON_H;
  list.Add(radio_bt_lt=new CRadioButton("RadioButtonL",class="num">0,wnd,"RadioButton L",x,y,w,h));
  radio_bt_lt.SetContainerObj(base);
class=class="str">"cmt">//--- Set the area coordinates and image area dimensions
  radio_bt_lt.SetImageBound(class="num">2,class="num">1,h-class="num">2,h-class="num">2);
class=class="str">"cmt">//--- Set the button text offset along the X axis
  radio_bt_lt.SetTextShiftH(radio_bt_lt.ImageRight()+class="num">2);
class=class="str">"cmt">//--- Set the ID and activated state of the element,
class=class="str">"cmt">//--- draw the element and display its description to the journal.
  radio_bt_lt.SetID(class="num">14);
  radio_bt_lt.SetState(true);
  radio_bt_lt.Draw(class="kw">false);
  radio_bt_lt.Print();


class=class="str">"cmt">//--- Inside the base object, create a radio button with a header on the left(right RadioButton)
class=class="str">"cmt">//--- and specify the base element as a button container
  x=radio_bt_lt.Right()+class="num">4;
  y=radio_bt_lt.Y();
  w=DEF_BUTTON_W+class="num">46;
  h=DEF_BUTTON_H;
  list.Add(radio_bt_rt=new CRadioButton("RadioButtonR",class="num">0,wnd,"RadioButton R",x,y,w,h));
  radio_bt_rt.SetContainerObj(base);
class=class="str">"cmt">//--- Set the button text offset along the X axis
  radio_bt_rt.SetTextShiftH(class="num">2);
class=class="str">"cmt">//--- Set the area coordinates and image area dimensions
  radio_bt_rt.SetImageBound(radio_bt_rt.Width()-h+class="num">2,class="num">1,h-class="num">2,h-class="num">2);
class=class="str">"cmt">//--- Set the element ID, draw the element
class=class="str">"cmt">//--- and display its description to the journal.
  radio_bt_rt.SetID(class="num">15);
  radio_bt_rt.Draw(true);
  radio_bt_rt.Print();
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }

「用图表事件把单选按钮组跑起来」

指标卸载时 OnDeinit 只做了一件事:list.Clear() 清空对象链表,避免残留图形资源占用 MT5 内存。 OnCalculate 直接 return(rates_total),不在这里画任何东西,把所有交互逻辑都甩给图表事件,回测时这部分不会触发,只在实时图表生效。 OnChartEvent 里先遍历 list 把 id、lparam、dparam、sparam 转发给每个画布对象,保证按钮自己能收到点击。随后用自定义事件模拟单选:左按钮被选中时,若其 State() 为真,就强制右按钮 SetState(false) 并 Draw(true) 重绘,互斥逻辑靠这段代码落地。 外汇与贵金属图表挂这种自定义控件,高风险在于事件只在真人操作或脚本派发时响应,EA 自动跑周期切换可能漏掉重绘,开 MT5 自己点两下验证互斥是否顺手。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   list.Clear();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime &time[],
                const class="type">class="kw">double &open[],
                const class="type">class="kw">double &high[],
                const class="type">class="kw">double &low[],
                const class="type">class="kw">double &close[],
                const class="type">long &tick_volume[],
                const class="type">long &volume[],
                const class="type">int &spread[])
  {
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                 const class="type">long &lparam,
                 const class="type">class="kw">double &dparam,
                 const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Call the event handler of each of the created objects
   for(class="type">int i=class="num">0;i<list.Total();i++)
     {
      CCanvasBase *obj=list.At(i);
      if(obj!=NULL)
        obj.OnChartEvent(id,lparam,dparam,sparam);
     }

class=class="str">"cmt">//--- Emulate radio buttons in the group ---
class=class="str">"cmt">//--- If a custom event is received
   if(id>=CHARTEVENT_CUSTOM)
     {
      class=class="str">"cmt">//--- If the left radio button is clicked
      if(sparam==radio_bt_lt.NameBG())
        {
         class=class="str">"cmt">//--- If the button state changed(was not selected) 
         if(radio_bt_lt.State())
           {
            class=class="str">"cmt">//--- make the right radio button unselected and redraw it
            radio_bt_rt.SetState(class="kw">false);
            radio_bt_rt.Draw(true);
           }
        }

单选按钮互斥与双画布初始化

在自定义面板里做左右单选按钮时,靠 NameBG() 拿到右按钮标识后,只在它状态变为选中(State() 返回 true)时才去把左按钮 SetState(false) 并 Draw(true) 重绘。这样能避免每次点击都无条件刷新,减少 MT5 主图对象的重绘开销。 CCanvasBase::Create() 的实战意义在于一次性建好背景与前景两张位图标签:对象名会被 StringReplace 把空格转成下划线,再拼上 _BG 和 _FG 后缀。宽高若传 0,代码里会用三元运算兜底成 1 像素,所以传参时给 0 不会直接崩,但画面会是最小占位。 两个画布都建成功后,会把程序名写进 OBJPROP_TEXT,并把 OBJPROP_TOOLTIP 设成 "\n" 清空悬停提示——这步常被忽略,不清的话 MT5 默认 tooltip 可能挡住你自己的鼠标交互逻辑。 最后用 m_bound.SetXY 和 Resize 记下图元区域,返回 true。开 MT5 把这段塞进 EA 的 OnInit 之前先确认 chart_id 用 CorrectChartID 处理过,否则子窗口下标错乱时画布会跑到主图外。

MQL5 / C++
  class=class="str">"cmt">//--- If the right radio button is clicked
  if(sparam==radio_bt_rt.NameBG())
    {
     class=class="str">"cmt">//--- If the button state changed(was not selected) 
     if(radio_bt_rt.State())
       {
        class=class="str">"cmt">//--- make the left radio button unselected and redraw it
        radio_bt_lt.SetState(class="kw">false);
        radio_bt_lt.Draw(true);
       }
     }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CCanvasBase::Create background and foreground graphical objects   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCanvasBase::Create(const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string object_name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h)
  {
class=class="str">"cmt">//--- Get the adjusted chart ID
   class="type">long id=this.CorrectChartID(chart_id);
class=class="str">"cmt">//--- Correct the passed object name
   class="type">class="kw">string nm=object_name;
   ::StringReplace(nm," ","_");
class=class="str">"cmt">//--- Create a graphical object name for the background and create a canvas
   class="type">class="kw">string obj_name=nm+"_BG";
   if(!this.m_background.CreateBitmapLabel(id,(wnd<class="num">0 ? class="num">0 : wnd),obj_name,x,y,(w>class="num">0 ? w : class="num">1),(h>class="num">0 ? h : class="num">1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- Create a graphical object name for the foreground and create a canvas
   obj_name=nm+"_FG";
   if(!this.m_foreground.CreateBitmapLabel(id,(wnd<class="num">0 ? class="num">0 : wnd),obj_name,x,y,(w>class="num">0 ? w : class="num">1),(h>class="num">0 ? h : class="num">1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- If created successfully, enter the program name into the OBJPROP_TEXT class="kw">property of the graphical object
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TOOLTIP,"\n");
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TOOLTIP,"\n");
class=class="str">"cmt">//--- Set the dimensions of the rectangular area and class="kw">return &class="macro">#x27;true&class="macro">#x27;
   this.m_bound.SetXY(x,y);
   this.m_bound.Resize(w,h);
   class="kw">return true;
  }

◍ 别急着下结论

这套表格控件的骨架已经搭到控制器这一层:Base.mqh 里的 CBaseObj、CColor、CBound 等 5 个类管着图形对象底子,Controls.mqh 里则塞进了从 CLabel 到 CRadioButton 共 10 种可交互控件类,iTestLabel.mq5(32.08 KB)能直接挂进 MT5 验证标签和按钮的联动。 把 MQL5.zip(37.07 KB)解压到终端 \MQL5\Indicators\tables\ 后,不用手挪文件就能编译测试,外汇和贵金属图表上跑这类自定义控件存在报价跳空与重连风险,建议先开模拟盘验证渲染稳定性。 下一篇才会碰面板和容器,那是子元素滚动和布局的主组件;现在能做的,是把本文附的控件类逐个改参数试渲染,别以为表格 UI 到此就完整了。

把重复绘制交给小布盯盘
这些控件的绘制与事件响应模板,小布盯盘的AIGC已内置常用片段,打开对应品种页即可直接套用,你只管调参数和决策。

常见问题

基类集中了文件存取、描述与打印的通用逻辑,子类只在差异处重写,避免每个控件重复写加载保存代码,降低维护成本。
标签通常只做静态或悬停变色,按钮则需绑定点击回调并把事件向上抛给容器,二者都依赖图表事件对象做中转。
可以,左边贴图标右边放文字,基于简单按钮调整字符串与响应状态即可,不必另写独立控件类。
能,小布内置了常见控件的结构模板,你描述需要的按钮或标签行为,它可产出初版代码供你改。
它们在基类事件机制上扩展选中态切换,单选按钮还需通知同组互斥,逻辑稍多但仍复用同一套控制器分发。