MQL5 MVC架构中表格视图与控制器组件:简单控件·综合运用
(3/3)·从文本标签到单选按钮,把MVC控制器基类一次跑通并直接投产
接上篇,我们继续深挖表格视图的控制器层。很多人在MQL5里手写按钮和标签,事件分发散落各处,改一处崩一片。本篇把基类优化和简单控件一次讲透,让你用几行配置就能拼出复合交互元素。
- 控件基类的事件旗标与双层画布
- GUI 基类的坐标与状态字段
- 图形对象的交互事件与画布分层
- 控件配色状态的指针与取值逻辑
- 按钮三态配色的取值逻辑
- 给控件三态配色写进类里
- 按钮三态颜色的初始化与切换
- 按钮控件的颜色状态切换逻辑
- 按钮控件的颜色状态切换逻辑
- 控件透明度与对象名的存取接口
- 画布基类的透明度与事件接口
- 画布对象的创建与析构权限回收
- 双画布分层与命中检测的实现细节
- 图表交互权限与配色的底层接管
- 控件配色状态机的底层校验逻辑
- 画布对象的鼠标事件与坐标校正
- 把自定义事件接回标准交互逻辑
- 画布控件的点击与创建事件接管
- 控件库的头文件骨架怎么搭
- 用图像类给控件挂图标
- 给控件安一个会画图的笔
- 图像边界与箭头绘制的类方法封装
- 图形基元与对象持久化的接口骨架
- 图形绘制类的构造与比较逻辑
- 画布校验与箭头绘制的底层约束
- 画布上箭头三角形的坐标算法
- 在画布上画左右三角箭头
- 在画布上画勾选框的像素逻辑
- 自绘控件里的方框与单选圆点
- 未勾选单选圆与画布落盘的实现细节
- 从文件句柄还原图像绘制对象
- 文本标签类的双缓冲擦除逻辑
- 标签与图层的像素级定位接口
- 标签对象的尺寸与绘制接口
- CLabel 的三种构造路径
- CLabel 构造与排序的实现细节
- 标签文本擦除与超宽截断的实现细节
- 标签控件的绘制与文件存取实现
- 从文件流里读回标签坐标
- 按钮类只改了绘制逻辑
- 按钮类的三种构造入口与比较逻辑
- 按钮控件的排序与绘制落点
- 两位状态按钮的类的骨架与状态切换
- 带触发态的按钮构造函数怎么写
- 按钮状态色与排序比较的实现细节
- 按钮重绘与按下状态翻转的实现细节
- 向上箭头按钮的派生类写法
- 向上箭头按钮的三套构造函数
- 按钮重绘时的箭头与背景绘制逻辑
- 给面板加一个下箭头按钮
- 向下箭头按钮的三种构造重载
- 下拉按钮的绘制收尾逻辑
- 左箭头按钮的控件类实现
- 左箭头按钮的三套构造重载
- 按钮背景与左箭头的绘制落点
- 右箭头按钮控件的类封装
- 右箭头按钮的三套构造函数重载
- 右箭头按钮的绘制收尾逻辑
- 透明背景的复选框怎么写
- 复选框控件的三种构造入口
- 复选框控件的构造与配色初始化
- 勾选框按钮的绘制与状态切换
- 单选按钮的成组互斥怎么在代码里落地
- 单选按钮类的四种构造入口
- 单选按钮的绘制与点击响应
- 空缓冲指标跑通图形控件
- 在画布基类里挂复选框与单选钮指针
- 在基础控件里塞按钮和标签
- 在基容器里排布方向箭头与勾选框
- 勾选框与单选钮的镜像布局实测
- 用图表事件把单选按钮组跑起来
- 单选按钮互斥与双画布初始化
- 别急着下结论
◍ 控件基类的事件旗标与双层画布
在 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 都是后续坐标换算的底层变量,外汇与贵金属图表的高波动下,这些偏移量算错会导致点击命中偏移数个像素。
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 的事件派发,不必从零注册图表事件。
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 实例。通过分离图层,前景画动态价格标签、背景画网格,重绘效率会明显优于单图层。
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 对象。下面这组方法只干一件事:把对应颜色对象的指针交出来,方便外部直接改属性。
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()); }
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() 切换带来的背景跳变。外汇与贵金属图表控件定制属高风险改造,参数误用可能导致图形资源泄漏,建议先在策略测试器隔离验证。
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 属高风险操作,配色逻辑错误只影响交互,不改动下单链路。
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,就能让按钮状态机跑起来,不用每次手填颜色。
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 改动不影响仓位风险,实盘仍需自担波动。
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_border 和 m_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,看禁用灰化和按下高亮是否如预期分离,再补其余状态。
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 里实时改写面板元素的可见性——外汇与贵金属杠杆高,面板只是辅助,信号误读可能放大亏损。
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。
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 是否被正确回写。
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。
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;
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,减少不必要的重绘开销。
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::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) 看返回值,大概率是当前色被别处覆盖。 外汇与贵金属图表挂这类控件属高风险环境,实盘前务必在策略测试器用模拟品种跑一遍状态切换。
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 里做按钮、热图时,不用自己重写坐标换算,直接继承改三个虚函数即可验证。
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 在悬停前后的布尔值,验证这套状态机是否按预期翻转。外汇与贵金属图表上挂此类控件需注意,交互逻辑异常可能误导下单操作,属高风险场景。
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) 把本控件提到最前,避免被新对象盖住。外汇与贵金属图表上挂这类控件时,注意自定义事件可能和别的脚本冲突,属高风险调试场景。
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 里能直接过编译。
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 对象层级多、重绘频繁,可能引发布局抖动,建议在策略测试器外的实盘图表先验证渲染开销。
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()) 一次,比事后猜坐标强。
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) 试画。
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 仅辅助判读,实盘仍属高风险。
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 里编译验证。
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。外汇与贵金属图表叠加自定义绘制属高风险操作,参数错乱可能遮盖价格本身。
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) 一次,能少触发几次画布重绘。 外汇与贵金属图表叠加这类自绘信号,需注意不同品种点距与画布分辨率差异可能导致箭头视觉大小漂移,实盘前应在对应品种手动验证。
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,就能看到方向箭头以概率极高的清晰度渲染在画布上。外汇与贵金属图表叠加此类元素时,注意高风险品种波动快,箭头信号仅作辅助、不构成方向承诺。
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 可能拖慢刷帧,属于高风险界面的性能坑。
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 可能拖慢主图帧率。
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 就能看圆框留白变化。
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 里打印返回值排查。
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 数组,正是双缓冲擦除能算准旧文本宽高的前提。
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 分钟图这种高密度界面尤其明显。外汇与贵金属杠杆高,界面逻辑错误不会直接亏钱,但可能让你误读信号。 别把坐标当全局 文字偏移是相对控件的局部量,图像坐标是绘制缓冲区的绝对量,混用会在多图表平铺时把标签甩到屏幕外。
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 头文件,就能直接看清楚接口骨架。
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,能直接看到主图左上角浮出透明底标签。
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,屏幕可能什么都不显示——这印证了构造里尺寸归零的设计意图。
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 驱动,高频行情下有误触高风险。
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 缩放影响。
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 读写封装里,改两个成员变量名即可直接验证。
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 基类的时间。外汇与贵金属界面自动化涉及实时报价交互,属于高风险操作环境,参数改动前建议在策略测试器先验证。
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 编译运行,若副图没加载指标可能看不到——这步能帮你确认窗口坐标系是否按预期生效。
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 策略工具开发属高风险范畴,参数误用可能导致图表资源泄漏。
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 的全部增量。
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() 本身负责把常态与激活态的背景色设好并置为当前背景色,这样按钮被点下去时视觉反馈直接由类内部接管,不用在外层再写状态判断。外汇与贵金属图表挂这类按钮做手动触发下单,仍需注意滑点和高波动风险。
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() 返回值有没有被外部逻辑改写过。
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 接好这套代码后,点按钮能看到状态色交替且只重绘一次。
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 参数决定是否需要重绘整图。
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 参数填上对应索引。外汇与贵金属图表上挂这类控件属于低风险界面操作,但自动化点击若联动下单仍须警惕滑点。
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% 的界面卡顿。外汇与贵金属图表挂多控件时属高风险操作,建议先在模拟账户验证重绘频率。
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 前先在策略测试器里建几个实例确认不报错。
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 表示暂不重绘图表。实盘挂这类按钮做下单面板时,外汇与贵金属波动剧烈、点错方向风险极高,建议先在策略测试器里把控件坐标和尺寸跑一遍。
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 响应。
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 分支,永远命中不了,调试时只会看到按钮静默不响应。
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 留白是否够用,而不是去改基类坐标。
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 的偏移基准设错了。
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 图表,可能看到左上角出现可交互箭头;外汇与贵金属杠杆高,控件只是交互壳,下单逻辑需自写风控。
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 看箭头是否出血。
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() 参数就能验证。
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 这个类型标识得在枚举里先定义,否则编译会报未声明。
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 值就能直观比对子窗口落点差异。
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 面板属高风险操作,改控件代码前建议在策略测试器里先用模拟品种跑一遍。
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 倍。
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后可直接编译验证。
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 管理多组单选,排序行为可直接参考父类实现。
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 工具开发属高风险调试,参数错乱可能让面板卡死。
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 里给前景和背景对象统一加两行设置,一劳永逸。 外汇与贵金属交易高风险,这类界面实验仅用于提升盯盘工具效率,不代表任何收益预期。
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 里跑通再上实盘。
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 前先开模拟盘验证。
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,给文字留了余量。外汇与贵金属界面自动化改这些参数时波动风险高,先在策略测试器里看坐标再上真仓。
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 仍属高风险环境,参数偏移量若算错会导致控件重叠不可点。
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 自己点两下验证互斥是否顺手。
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 处理过,否则子窗口下标错乱时画布会跑到主图外。
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 到此就完整了。