在MQL5中创建交易管理员面板(第三部分):扩展内置类以进行主题管理(II)·综合运用
- 按钮控件的生命周期钩子怎么写
- 让输入框跟着主题走
- CEdit 类的只读与对齐封装
- 编辑框控件的事件与构造骨架
- CEdit 控件的创建与可见性封装
- 可编辑文本框的事件与尺寸联动
- 封装可编辑文本框的 CEdit 类
- 可编辑文本框的封装与事件钩子
- 自定义编辑框控件的创建与外观参数
- CEdit 控件的生命周期钩子怎么落地
- 编辑框点击事件的两种分派逻辑
- 管理员面板主题切换的落地逻辑
- 把面板和推送认证跑通
- 控制面板的控件落位与坐标参数
- 面板按钮的创建与销毁逻辑
- 图表控件点击事件如何分流到各按钮逻辑
- 面板交互背后的消息与主题逻辑
- 面板控件的主题与显隐切换逻辑
- 把信号推到 Telegram 的 POST 封装
- 动头文件前先备好退路
按钮控件的生命周期钩子怎么写
在 MT5 的自定义界面库里,CButton 把图表对象的创建、显隐、位移和鼠标响应都拆成了独立虚函数。读懂这几个 On 开头的方法,你就能在不碰底层 OBJ_RECTANGLE_LABEL 的情况下,直接接管按钮行为。 下面这段是 CButton 类里最核心的几个事件回调。OnCreate 负责按预设矩形区生成对象;OnShow / OnHide 分别用 OBJ_ALL_PERIODS 和 OBJ_NO_PERIODS 控制跨周期可见性;OnMove 与 OnResize 把 m_rect 里的坐标和尺寸同步给底层对象。 鼠标部分值得注意:OnMouseDown 里若 IS_CAN_LOCK 为假,就翻转 Pressed 状态;OnMouseUp 则在按钮处于按下态且不可锁定时复位 State。这意味着默认按钮是‘点按即弹’,要做法Toggle开关必须改 IS_CAN_LOCK 逻辑。 开 MT5 新建 EA 继承 CButton 覆盖这几个方法,把 IS_CAN_LOCK 定义为 true,按钮就能保持按下高亮,适合做‘开启自动盯盘’这类状态切换。外汇与贵金属界面脚本同样受此机制约束,实盘加载前请在策略测试器跑一遍避免对象泄漏。
class="type">bool CButton::OnCreate(class="type">void) { class=class="str">"cmt">//--- create the chart object by previously set parameters class="kw">return(m_button.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top,m_rect.Width(),m_rect.Height())); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display object on chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButton::OnShow(class="type">void) { class="kw">return(m_button.Timeframes(OBJ_ALL_PERIODS)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Hide object from chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButton::OnHide(class="type">void) { class="kw">return(m_button.Timeframes(OBJ_NO_PERIODS)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Absolute movement of the chart object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButton::OnMove(class="type">void) { class=class="str">"cmt">//--- position the chart object class="kw">return(m_button.X_Distance(m_rect.left) && m_button.Y_Distance(m_rect.top)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Resize the chart object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButton::OnResize(class="type">void) { class=class="str">"cmt">//--- resize the chart object class="kw">return(m_button.X_Size(m_rect.Width()) && m_button.Y_Size(m_rect.Height())); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handler of click on the left mouse button | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButton::OnMouseDown(class="type">void) { if(!IS_CAN_LOCK) Pressed(!Pressed()); class=class="str">"cmt">//--- call of the method of the parent class class="kw">return(CWnd::OnMouseDown()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handler of click on the left mouse button | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButton::OnMouseUp(class="type">void) { class=class="str">"cmt">//--- depress the button if it is not fixed if(m_button.State() && !IS_CAN_LOCK) m_button.State(false); class=class="str">"cmt">//--- call of the method of the parent class class="kw">return(CWnd::OnMouseUp()); } class=class="str">"cmt">//+------------------------------------------------------------------+
「让输入框跟着主题走」
做管理员面板时,面板和控件默认是白底黑字,点主题切换按钮后前景转白。但原生 CEdit 输入框的背景和边框颜色不会自动变,切到暗色主题时文本可能和底融在一起,可读性直接掉。 标准库里的 CEdit 已经带了 OnSetColor、OnSetColorBackground、OnSetColorBorder 三个内部方法,只是没有对外暴露修改接口。我们在派生类里补三个公开方法,写进去就同步刷新图表上的编辑框对象。 下面这段是整合进 CEdit 的主题控制代码,三个方法各自改成员变量再调原内部方法生效。 别只改文字色 很多人在切换主题时只调了 SetTextColor,结果边框还是系统灰、背景还是白,暗色面板里输入框像块补丁。三个颜色最好一起设,否则视觉断层很明显。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set text class="type">color | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::SetTextColor(const class="type">color clr) { m_color = clr; class="kw">return(OnSetColor()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set background class="type">color | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::SetBackgroundColor(const class="type">color clr) { m_color_background = clr; class="kw">return(OnSetColorBackground()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set border class="type">color | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::SetBorderColor(const class="type">color clr) { m_color_border = clr; class="kw">return(OnSetColorBorder()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Edit.mqh | class=class="str">"cmt">//| Copyright class="num">2000-class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "WndObj.mqh" class="macro">#include <ChartObjects\ChartObjectsTxtControls.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CEdit | class=class="str">"cmt">//| Usage: control that is displayed by | class=class="str">"cmt">//| the CChartObjectEdit object | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEdit : class="kw">public CWndObj { class="kw">private: CChartObjectEdit m_edit; class=class="str">"cmt">// chart object class=class="str">"cmt">//--- parameters of the chart object
◍ CEdit 类的只读与对齐封装
在 MT5 标准库 GUI 体系里,CEdit 负责封装图表上的可编辑文本框对象。它内部用 m_read_only 标记是否进入只读模式,用 m_align_mode 控制文本对齐方式,这两个私有成员直接决定了控件的交互边界与视觉排布。 对外暴露的接口很有规律:ReadOnly() 和 TextAlign() 都提供了 const 取值版本与带参设置版本。例如只读标记的取值只是一行 return(m_read_only),而设置版本会触发重绘逻辑,这种双版本写法在自定义面板里能避免误改状态。 数据访问上,Text() 的读取直接转发到 m_edit.Description(),写入则走 CWndObj::Text(value)。如果你在 EA 面板里动态刷新提示语,调 Text(const string) 就能实时改框内文字,外汇与贵金属图表挂此类控件时需注意高频刷新可能拖慢主图响应。 受保护区的事件钩子更贴近底层:OnObjectEndEdit 在用户结束输入时触发,OnSetText、OnSetColor 等则把父类颜色与文字设置映射到 m_edit 对象。想拦截用户输入做校验,重写 OnObjectEndEdit 是最直接的落点。
class="type">bool m_read_only; class=class="str">"cmt">// "read-only" mode flag ENUM_ALIGN_MODE m_align_mode; class=class="str">"cmt">// align mode class="kw">public: CEdit(class="type">void); ~CEdit(class="type">void); class=class="str">"cmt">//--- create class="kw">virtual class="type">bool Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2); class=class="str">"cmt">//--- chart event handler class="kw">virtual class="type">bool OnEvent(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">//--- parameters of the chart object class="type">bool ReadOnly(class="type">void) const { class="kw">return(m_read_only); } class="type">bool ReadOnly(const class="type">bool flag); ENUM_ALIGN_MODE TextAlign(class="type">void) const { class="kw">return(m_align_mode); } class="type">bool TextAlign(const ENUM_ALIGN_MODE align); class=class="str">"cmt">//--- data access class="type">class="kw">string Text(class="type">void) const { class="kw">return(m_edit.Description()); } class="type">bool Text(const class="type">class="kw">string value) { class="kw">return(CWndObj::Text(value)); } class="kw">protected: class=class="str">"cmt">//--- handlers of object events class="kw">virtual class="type">bool OnObjectEndEdit(class="type">void); class=class="str">"cmt">//--- handlers of object settings class="kw">virtual class="type">bool OnSetText(class="type">void) { class="kw">return(m_edit.Description(m_text)); } class="kw">virtual class="type">bool OnSetColor(class="type">void) { class="kw">return(m_edit.Color(m_color)); } class="kw">virtual class="type">bool OnSetColorBackground(class="type">void) { class="kw">return(m_edit.BackColor(m_color_background)); } class="kw">virtual class="type">bool OnSetColorBorder(class="type">void) { class="kw">return(m_edit.BorderColor(m_color_border)); }
编辑框控件的事件与构造骨架
在 MT5 自定义控件库里,CEdit 类把字体、字号、层级这些外观属性做成虚函数回调:OnSetFont 直接把 m_font 塞进 m_edit.Font(),OnSetFontSize 同理走 m_edit.FontSize(),OnSetZOrder 管图层前后。改一处成员就能触发重绘,不用动外部调用逻辑。 事件分发靠 OnEvent 拦截 CHARTEVENT_OBJECT_ENDEDIT,且必须用 m_name==sparam 做对象名比对,否则同图表多个编辑框会串味。没命中就丢回 CWndObj::OnEvent 让基类处理,这套链路保证了控件只认自己的输入焦点。 构造函数把只读标志 m_read_only 默认 false、对齐 m_align_mode 默认 ALIGN_LEFT,颜色 trio(前景/背景/边框)分别吃 CONTROLS_EDIT_COLOR、CONTROLS_EDIT_COLOR_BG、CONTROLS_EDIT_COLOR_BORDER 三个宏。析构函数留空,说明资源回收交给 MT5 图形对象自身。 开 MT5 新建 EA 把这段类声明抄进 include,断点打在 OnEvent 的 if 行,拖两个 CEdit 到图表分别改名,就能看到只有名字匹配的那个会进 OnObjectEndEdit。
class="kw">virtual class="type">bool OnSetFont(class="type">void) { class="kw">return(m_edit.Font(m_font)); } class="kw">virtual class="type">bool OnSetFontSize(class="type">void) { class="kw">return(m_edit.FontSize(m_font_size)); } class="kw">virtual class="type">bool OnSetZOrder(class="type">void) { class="kw">return(m_edit.Z_Order(m_zorder)); } class=class="str">"cmt">//--- internal event handlers class="kw">virtual class="type">bool OnCreate(class="type">void); class="kw">virtual class="type">bool OnShow(class="type">void); class="kw">virtual class="type">bool OnHide(class="type">void); class="kw">virtual class="type">bool OnMove(class="type">void); class="kw">virtual class="type">bool OnResize(class="type">void); class="kw">virtual class="type">bool OnChange(class="type">void); class="kw">virtual class="type">bool OnClick(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Common handler of chart events | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam) { if(m_name==sparam && id==CHARTEVENT_OBJECT_ENDEDIT) class="kw">return(OnObjectEndEdit()); class=class="str">"cmt">//--- event was not handled class="kw">return(CWndObj::OnEvent(id,lparam,dparam,sparam)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEdit::CEdit(class="type">void) : m_read_only(false), m_align_mode(ALIGN_LEFT) { m_color =CONTROLS_EDIT_COLOR; m_color_background=CONTROLS_EDIT_COLOR_BG; m_color_border =CONTROLS_EDIT_COLOR_BORDER; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEdit::~CEdit(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a control | class=class="str">"cmt">//+------------------------------------------------------------------+
「CEdit 控件的创建与可见性封装」
在 MT5 自定义界面库里,CEdit 作为可编辑文本框控件,其生命周期由一组重载方法接管。Create() 先调用父类 CWndObj::Create() 确立窗口锚点,再用 m_edit.Create() 生成底层图表对象,最后靠 OnChange() 回写样式参数;任意一步返回 false 都会中断创建。 只读与对齐这类属性被拆成了独立 setter:ReadOnly() 把布尔值存进 m_read_only 后直接透传给 m_edit.ReadOnly(flag),TextAlign() 则把 ENUM_ALIGN_MODE 枚举同步给底层对象,调用方无需碰图表 API。 控件的显隐不走 Visible 属性,而是借时间框架掩码切换:OnShow() 设 OBJ_ALL_PERIODS,OnHide() 设 OBJ_NO_PERIODS。这意味着隐藏本质是所有周期都不绘制,而非从对象树移除。 OnCreate() 用 m_rect 的 left/top/Width/Height 重建对象,验证时可故意改 m_rect 数值看重绘偏移;外汇与贵金属图表挂这类控件需注意,高频 OnMove 可能拖慢报价刷新,属高风险界面操作。
class="type">bool CEdit::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2) { class=class="str">"cmt">//--- call method of the parent class if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) class="kw">return(false); class=class="str">"cmt">//--- create the chart object if(!m_edit.Create(chart,name,subwin,x1,y1,Width(),Height())) class="kw">return(false); class=class="str">"cmt">//--- call the settings handler class="kw">return(OnChange()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set parameter | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::ReadOnly(const class="type">bool flag) { class=class="str">"cmt">//--- save new value of parameter m_read_only=flag; class=class="str">"cmt">//--- set up the chart object class="kw">return(m_edit.ReadOnly(flag)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set parameter | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::TextAlign(const ENUM_ALIGN_MODE align) { class=class="str">"cmt">//--- save new value of parameter m_align_mode=align; class=class="str">"cmt">//--- set up the chart object class="kw">return(m_edit.TextAlign(align)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create object on chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnCreate(class="type">void) { class=class="str">"cmt">//--- create the chart object by previously set parameters class="kw">return(m_edit.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top,m_rect.Width(),m_rect.Height())); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display object on chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnShow(class="type">void) { class="kw">return(m_edit.Timeframes(OBJ_ALL_PERIODS)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Hide object from chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnHide(class="type">void) { class="kw">return(m_edit.Timeframes(OBJ_NO_PERIODS)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Absolute movement of the chart object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnMove(class="type">void) {
◍ 可编辑文本框的事件与尺寸联动
在 MT5 自定义控件库里,CEdit 类把图表上的可输入文本框封装成了带生命周期的方法群。位置、尺寸、属性变更各自独立成函数,避免在主循环里写一堆坐标判断。 OnResize 里直接拿矩形区域的宽高喂给 X_Size / Y_Size,意味着你拖拽父容器时编辑框会同步拉伸,不用手动重算像素。
class="type">bool CEdit::OnResize(class="type">void) { class=class="str">"cmt">//--- resize the chart object class="kw">return(m_edit.X_Size(m_rect.Width()) && m_edit.Y_Size(m_rect.Height())); }
class="type">bool CEdit::OnResize(class="type">void) { class=class="str">"cmt">//--- resize the chart object class="kw">return(m_edit.X_Size(m_rect.Width()) && m_edit.Y_Size(m_rect.Height())); } class="type">bool CEdit::OnChange(class="type">void) { class=class="str">"cmt">//--- set up the chart object class="kw">return(CWndObj::OnChange() && ReadOnly(m_read_only) && TextAlign(m_align_mode)); } class="type">bool CEdit::OnObjectEndEdit(class="type">void) { class=class="str">"cmt">//--- send the ON_END_EDIT notification EventChartCustom(CONTROLS_SELF_MESSAGE,ON_END_EDIT,m_id,class="num">0.0,m_name); class=class="str">"cmt">//--- handled class="kw">return(true); } class="type">bool CEdit::OnClick(class="type">void) { class=class="str">"cmt">//--- if editing is enabled, send the ON_START_EDIT notification if(!m_read_only) { EventChartCustom(CONTROLS_SELF_MESSAGE,ON_START_EDIT,m_id,class="num">0.0,m_name); class=class="str">"cmt">//--- handled class="kw">return(true); } class=class="str">"cmt">//--- else send the ON_CLICK notification class="kw">return(CWnd::OnClick()); }
封装可编辑文本框的 CEdit 类
在 MT5 自定义控件库里,CEdit 把图表上的 CChartObjectEdit 对象包成了一层 C++ 类,继承关系来自 CWndObj。它把底层图表对象、只读标志和文本对齐方式收进私有成员,外部只暴露必要的读写接口。 下面这段声明里,m_edit 是真正的图表对象,m_read_only 控制是否禁止用户输入,m_align_mode 决定文本在框内的对齐基准(ENUM_ALIGN_MODE 枚举)。
class CEdit : class="kw">public CWndObj { class="kw">private: CChartObjectEdit m_edit; class=class="str">"cmt">// chart object class="type">bool m_read_only; class=class="str">"cmt">// "read-only" mode flag ENUM_ALIGN_MODE m_align_mode; class=class="str">"cmt">// align mode class="kw">public: CEdit(class="type">void); ~CEdit(class="type">void); class="kw">virtual class="type">bool Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2); class="kw">virtual class="type">bool OnEvent(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="type">bool ReadOnly(class="type">void) const { class="kw">return(m_read_only); } class="type">bool ReadOnly(const class="type">bool flag); ENUM_ALIGN_MODE TextAlign(class="type">void) const { class="kw">return(m_align_mode); } class="type">bool TextAlign(const ENUM_ALIGN_MODE align); class="type">class="kw">string Text(class="type">void) const { class="kw">return(m_edit.Description()); } class="type">bool Text(const class="type">class="kw">string value) { class="kw">return(CWndObj::Text(value)); } class="type">bool SetTextColor(const class="type">color clr); class="type">bool SetBackgroundColor(const class="type">color clr); };
class CEdit : class="kw">public CWndObj { class="kw">private: CChartObjectEdit m_edit; class=class="str">"cmt">// chart object class="type">bool m_read_only; class=class="str">"cmt">// "read-only" mode flag ENUM_ALIGN_MODE m_align_mode; class=class="str">"cmt">// align mode class="kw">public: CEdit(class="type">void); ~CEdit(class="type">void); class="kw">virtual class="type">bool Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2); class="kw">virtual class="type">bool OnEvent(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="type">bool ReadOnly(class="type">void) const { class="kw">return(m_read_only); } class="type">bool ReadOnly(const class="type">bool flag); ENUM_ALIGN_MODE TextAlign(class="type">void) const { class="kw">return(m_align_mode); } class="type">bool TextAlign(const ENUM_ALIGN_MODE align); class="type">class="kw">string Text(class="type">void) const { class="kw">return(m_edit.Description()); } class="type">bool Text(const class="type">class="kw">string value) { class="kw">return(CWndObj::Text(value)); } class="type">bool SetTextColor(const class="type">color clr); class="type">bool SetBackgroundColor(const class="type">color clr); };
「可编辑文本框的封装与事件钩子」
在 MT5 自定义控件库里,CEdit 把输入框的样式与行为收进一个类。公开方法 SetBorderColor 负责改边框色,protected 区则挂了一串虚函数,专门响应对象事件与属性变更。 看 OnSetText 到 OnSetZOrder 这组,本质是把上层设置的 m_text、m_color 等成员变量,逐一同步给底层 m_edit 实例。比如 OnSetColorBackground 返回 m_edit.BackColor(m_color_background),说明背景色改动会直接落到原生对象,调用方无需关心绘制细节。 构造函数把只读标志 m_read_only 默认置 false,对齐方式 m_align_mode 设成 ALIGN_LEFT,三行颜色赋值引用了 CONTROLS_EDIT_COLOR 系列宏。若你在面板里发现输入框底色不对,优先核对这些宏在 controls 头文件里的定义值。 析构函数为空,意味着资源释放依赖 m_edit 自身生命周期。开 MT5 新建个 CEdit 实例,改一句 CONTROLS_EDIT_COLOR_BG 宏,就能直观看到背景色偏移效果。
class="type">bool SetBorderColor(const class="type">color clr); class="kw">protected: class=class="str">"cmt">//--- handlers of object events class="kw">virtual class="type">bool OnObjectEndEdit(class="type">void); class=class="str">"cmt">//--- handlers of object settings class="kw">virtual class="type">bool OnSetText(class="type">void) { class="kw">return(m_edit.Description(m_text)); } class="kw">virtual class="type">bool OnSetColor(class="type">void) { class="kw">return(m_edit.Color(m_color)); } class="kw">virtual class="type">bool OnSetColorBackground(class="type">void) { class="kw">return(m_edit.BackColor(m_color_background)); } class="kw">virtual class="type">bool OnSetColorBorder(class="type">void) { class="kw">return(m_edit.BorderColor(m_color_border)); } class="kw">virtual class="type">bool OnSetFont(class="type">void) { class="kw">return(m_edit.Font(m_font)); } class="kw">virtual class="type">bool OnSetFontSize(class="type">void) { class="kw">return(m_edit.FontSize(m_font_size)); } class="kw">virtual class="type">bool OnSetZOrder(class="type">void) { class="kw">return(m_edit.Z_Order(m_zorder)); } class=class="str">"cmt">//--- internal event handlers class="kw">virtual class="type">bool OnCreate(class="type">void); class="kw">virtual class="type">bool OnShow(class="type">void); class="kw">virtual class="type">bool OnHide(class="type">void); class="kw">virtual class="type">bool OnMove(class="type">void); class="kw">virtual class="type">bool OnResize(class="type">void); class="kw">virtual class="type">bool OnChange(class="type">void); class="kw">virtual class="type">bool OnClick(class="type">void); }; CEdit::CEdit(class="type">void) : m_read_only(false), m_align_mode(ALIGN_LEFT) { m_color =CONTROLS_EDIT_COLOR; m_color_background=CONTROLS_EDIT_COLOR_BG; m_color_border =CONTROLS_EDIT_COLOR_BORDER; } CEdit::~CEdit(class="type">void) { }
◍ 自定义编辑框控件的创建与外观参数
在 MT5 自定义图形界面里,CEdit 类封装了图表上的可输入文本框。它的 Create 方法先调父类 CWndObj::Create 确立坐标与子窗口归属,再用内部 m_edit 对象按 Width()、Height() 生成实际 OBJ_EDIT 图表对象,最后走 OnChange() 完成初始化。 只读与对齐这类开关,代码里分了两个薄封装:ReadOnly(bool) 把状态存进 m_read_only 后转交 m_edit.ReadOnly;TextAlign(ENUM_ALIGN_MODE) 同理存 m_align_mode 再下发。注意它们都返回底层调用结果,父层要根据返回值判断控件是否真的生效。 配色部分有三个独立入口:SetTextColor 改文字色并触发 OnSetColor,SetBackgroundColor 改背景色走 OnSetColorBackground,边框色也另有独立设置函数。三者互不覆盖,意味着你可以单独把黄金报价框的文字设成金色、背景设成深灰而不动边框。 实盘面板开发中,若发现改了颜色界面没反应,优先查对应 OnSet* 是否被重写或返回了 false——这是 MT5 标准库控件最常见的静默失效点。外汇与贵金属界面涉及实时报价,高杠杆环境下误触输入可能引发错单,建议测试阶段就给编辑框默认 ReadOnly(true)。
class="type">bool CEdit::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2) { class=class="str">"cmt">//--- call method of the parent class if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) class="kw">return(false); class=class="str">"cmt">//--- create the chart object if(!m_edit.Create(chart,name,subwin,x1,y1,Width(),Height())) class="kw">return(false); class=class="str">"cmt">//--- call the settings handler class="kw">return(OnChange()); } class="type">bool CEdit::ReadOnly(const class="type">bool flag) { class=class="str">"cmt">//--- save new value of parameter m_read_only=flag; class=class="str">"cmt">//--- set up the chart object class="kw">return(m_edit.ReadOnly(flag)); } class="type">bool CEdit::TextAlign(const ENUM_ALIGN_MODE align) { class=class="str">"cmt">//--- save new value of parameter m_align_mode=align; class=class="str">"cmt">//--- set up the chart object class="kw">return(m_edit.TextAlign(align)); } class="type">bool CEdit::SetTextColor(const class="type">color clr) { m_color = clr; class="kw">return(OnSetColor()); } class="type">bool CEdit::SetBackgroundColor(const class="type">color clr) { m_color_background = clr; class="kw">return(OnSetColorBackground()); }
CEdit 控件的生命周期钩子怎么落地
在 MT5 自定义面板里,CEdit 不是画完就完事,它有一组 On 开头的内部方法,分别接管创建、显隐、位移、缩放和属性变更。看懂这几个返回布尔值的钩子,你才能知道为什么有时候改了颜色界面没反应,或者拖了窗口文字框跑偏。 下面这段是 CEdit 类里几个关键方法的原生实现,注意每个方法都返回 bool,且大多依赖底层 m_edit 对象的实际执行结果,不是无条件 true。 SetBorderColor 先存颜色再调 OnSetColorBorder,边框色修改是否生效完全看后者;OnCreate 用 m_rect 的left/top和宽高去调 m_edit.Create,坐标和尺寸若越界就会返回 false。 显隐靠 Timeframes 宏切换:OBJ_ALL_PERIODS 全周期显示,OBJ_NO_PERIODS 全周期隐藏,这意味着隐藏不是删对象,只是不在任何周期渲染。OnMove 和 OnResize 分别用 X/Y_Distance 和 X/Y_Size 同步矩形参数,若你外部改了 m_rect 却没触发这两个,控件位置就会和逻辑矩形脱节。 OnChange 里除了父类处理,还链式调了 ReadOnly 和 TextAlign,说明只读模式和对齐方式是在变更事件里强制刷新的。OnObjectEndEdit 则在编辑结束时用 EventChartCustom 抛出 ON_END_EDIT 自定义事件,m_id 和 m_name 会带进事件参数,EA 里可以据此捕获用户输入完毕的时机。 开 MT5 把这段塞进你的 CEdit 派生类,故意让 OnResize 返回一个 false,能看到控件画出来但尺寸不跟手——这就是钩子返回值在拦你。
class="type">bool CEdit::SetBorderColor(const class="type">color clr) { m_color_border = clr; class="kw">return(OnSetColorBorder()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create object on chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnCreate(class="type">void) { class=class="str">"cmt">//--- create the chart object by previously set parameters class="kw">return(m_edit.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top,m_rect.Width(),m_rect.Height())); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display object on chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnShow(class="type">void) { class="kw">return(m_edit.Timeframes(OBJ_ALL_PERIODS)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Hide object from chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnHide(class="type">void) { class="kw">return(m_edit.Timeframes(OBJ_NO_PERIODS)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Absolute movement of the chart object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnMove(class="type">void) { class=class="str">"cmt">//--- position the chart object class="kw">return(m_edit.X_Distance(m_rect.left) && m_edit.Y_Distance(m_rect.top)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Resize the chart object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnResize(class="type">void) { class=class="str">"cmt">//--- resize the chart object class="kw">return(m_edit.X_Size(m_rect.Width()) && m_edit.Y_Size(m_rect.Height())); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set up the chart object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnChange(class="type">void) { class=class="str">"cmt">//--- set up the chart object class="kw">return(CWndObj::OnChange() && ReadOnly(m_read_only) && TextAlign(m_align_mode)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handler of the "End of editing" event | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnObjectEndEdit(class="type">void) { class=class="str">"cmt">//--- send the ON_END_EDIT notification EventChartCustom(CONTROLS_SELF_MESSAGE,ON_END_EDIT,m_id,class="num">0.0,m_name); class=class="str">"cmt">//--- handled class="kw">return(true); }
「编辑框点击事件的两种分派逻辑」
在 MT5 自定义控件里,CEdit 的 OnClick 决定了用户点输入框时系统走哪条路。核心判断就一句:m_read_only 是否为 false。 若控件没被设为只读,函数直接发 ON_START_EDIT 自定义事件,把控件 ID 和名称通过 EventChartCustom 抛给图表消息队列,并返回 true 表示事件已消化。此时父类不再介入。 一旦处于只读状态,代码不自己处理,而是 return CWnd::OnClick(),把点击交给基类走标准 ON_CLICK 通知。这种分流让你在 EA 面板里能区分“可编辑框激活”和“只读框被点”两种交互意图。 把下面这段塞进你的控件类验证:非只读编辑框点击会在 Experts 日志里触发你绑定的 ON_START_EDIT 分支;改成 m_read_only=true 后,同样点击只会走通用点击回调。外汇与贵金属面板开发涉及实时交互,回测无法覆盖鼠标事件,需在真实行情环境手动点测,杠杆品种误操作风险偏高。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handler of the "click" event | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CEdit::OnClick(class="type">void) { class=class="str">"cmt">//--- if editing is enabled, send the ON_START_EDIT notification if(!m_read_only) { EventChartCustom(CONTROLS_SELF_MESSAGE,ON_START_EDIT,m_id,class="num">0.0,m_name); class=class="str">"cmt">//--- handled class="kw">return(true); } class=class="str">"cmt">//--- else send the ON_CLICK notification class="kw">return(CWnd::OnClick()); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 管理员面板主题切换的落地逻辑
面板里的明暗主题靠一个 bool 变量 darkTheme 和 UpdateThemeColors() 函数驱动,点击 toggleThemeButton 就翻转标志并重绘界面。 颜色映射写死在 UpdateThemeColors() 内:深色下文本用 clrWhite、按钮底用 clrDarkSlateGray、边框 clrSlateGray、背景 clrDarkBlue;浅色则对应 clrBlack、clrGainsboro、clrGray、clrWhite。 文本框走 SetTextColor / SetBackgroundColor / SetBorderColor 三个方法;七个主按钮加一组快捷消息按钮统一调 UpdateButtonTheme() 辅助函数,字符计数器直接改 Color 属性,最后 ChartRedraw() 生效。把重复设色收进一个函数,面板里 10+ 个控件换肤不再散落代码。 下面这段是可直接塞进 EA 面板类的核心片段,注意 OnToggleThemeButtonClick 里用 Print 打出当前主题状态便于调试。
<span class="keyword">class="type">bool</span> darkTheme = <span class="macro">false</span>; <span class="keyword">class="type">void</span> OnToggleThemeButtonClick() { darkTheme = !darkTheme; UpdateThemeColors(); <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Theme toggled: "</span>, darkTheme ? <span class="class="type">class="kw">string">"Dark"</span> : <span class="class="type">class="kw">string">"Light"</span>); } <span class="keyword">class="type">void</span> UpdateThemeColors() { <span class="comment">class=class="str">"cmt">// <span style="background-class="type">color:rgb(class="num">255, class="num">235, class="num">85);">Determine colors based on the current theme</span></span> <span class="keyword">class="type">color</span> textColor = darkTheme ? <span class="macro">clrWhite</span> : <span class="macro">clrBlack</span>; <span class="keyword">class="type">color</span> buttonBgColor = darkTheme ? <span class="macro">clrDarkSlateGray</span> : <span class="macro">clrGainsboro</span>; <span class="keyword">class="type">color</span> borderColor = darkTheme ? <span class="macro">clrSlateGray</span> : <span class="macro">clrGray</span>; <span class="keyword">class="type">color</span> bgColor = darkTheme ? <span class="macro">clrDarkBlue</span> : <span class="macro">clrWhite</span>; <span class="comment">class=class="str">"cmt">// Set text box colors</span> inputBox.SetTextColor(textColor); inputBox.SetBackgroundColor(bgColor); inputBox.SetBorderColor(borderColor); <span class="comment" style="background-class="type">color:rgb(class="num">255, class="num">235, class="num">85);">class=class="str">"cmt">// Update button colors</span> UpdateButtonTheme(clearButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(sendButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(toggleThemeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(changeFontButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(minimizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(maximizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor); <span class="comment" style="background-class="type">color:rgb(class="num">255, class="num">235, class="num">85);">class=class="str">"cmt">// Update quick message buttons</span> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < <span class="functions">ArraySize</span>(quickMessageButtons); i++) { UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor); } <span class="comment">class=class="str">"cmt">// <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">Update character counter class="type">color</span></span> charCounter.Color(textColor); <span class="comment">class=class="str">"cmt">// Redraw chart to apply changes</span> <span class="functions">ChartRedraw</span>(); } <span class="comment" style="background-class="type">color:rgb(class="num">255, class="num">221, class="num">0);">class=class="str">"cmt">//Theme button application</span> <span class="keyword">class="type">void</span> UpdateButtonTheme(CButton &button, <span class="keyword">class="type">color</span> textColor, <span class="keyword">class="type">color</span> bgColor, <span class="keyword">class="type">color</span> borderColor) { button.SetTextColor(textColor); button.SetBackgroundColor(bgColor); button.SetBorderColor(borderColor); }
把面板和推送认证跑通
编译通过后,EA 会在图表左上角 (30,30) 坐标拉出一个 500×500 的悬浮管理面板,主题切换按钮直接改背景与控件色,视觉层不依赖外部资源。颜色值写在代码里,想换配色就改全局变量与画刷赋值,不必动逻辑。 Telegram 推送依赖两个 input 字段:InputChatId 与 InputBotToken。日志里常见的「无法发送自定义消息」基本就是这两项留空或抄错——机器人令牌和聊天 ID 任一个不对,WebRequest 就不会返回 200,消息发不出去。 认证信息属于高敏感字段,外汇与贵金属账户接这套面板时,本地端泄露令牌可能导致策略被远程操控,务必只在自己机器上填,别进公共 VPS 的共享配置。 下面这段是面板骨架与输入参数的核心声明,逐行拆一下便于你直接抄去改: //+------------------------------------------------------------------+
| // | Admin Panel.mq5 |
|---|---|
| // | Copyright 2024, Clemence Benjamin |
| // | [MQL5官方文档] |
//+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "[MQL5官方文档] #property version "1.12" #include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> // Input parameters input string QuickMessage1 = "Updates"; input string QuickMessage2 = "Close all"; input string QuickMessage3 = "In deep profits"; input string QuickMessage4 = "Hold position"; input string QuickMessage5 = "Swing Entry"; input string QuickMessage6 = "Scalp Entry"; input string QuickMessage7 = "Book profit"; input string QuickMessage8 = "Invalid Signal"; input string InputChatId = "Enter Chat ID from Telegram bot API"; input string InputBotToken = "Enter BOT TOKEN from your Telegram bot"; // Global variables CDialog adminPanel; CButton sendButton, clearButton, changeFontButton, toggleThemeButton; CButton quickMessageButtons[8], minimizeButton, maximizeButton, closeButton; CEdit inputBox; CLabel charCounter; bool minimized = false; bool darkTheme = false; int MAX_MESSAGE_LENGTH = 4096; string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman" }; int currentFontIndex = 0; //+------------------------------------------------------------------+
| // | Expert initialization function |
|---|
//+------------------------------------------------------------------+ int OnInit() { // Initialize the Dialog if (!adminPanel.Create(ChartID(), "Admin Panel", 0, 30, 30, 500, 500)) { Print("Failed to create dialog"); return INIT_FAILED; } // Create controls if (!CreateControls()) { Print("Control creation failed"); return INIT_FAILED; } 逐行看:前 3 行 #property 是元信息,version 1.12 代表当前面板迭代;4 个 include 拉入交易类与控件库。input 段里 8 个 QuickMessage 是快捷文本按钮的默认文案,发单时一键带出;InputChatId / InputBotToken 就是前面说的推送凭证。全局里 MAX_MESSAGE_LENGTH=4096 对应 Telegram 单条消息上限,availableFonts 给了 4 种字体循环切换。OnInit 里先 Create 对话框,失败就 INIT_FAILED 退出;再 CreateControls 建按钮编辑框,任一步崩了 EA 都起不来。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Admin Panel.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, Clemence Benjamin | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, Clemence Benjamin" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.12" class="macro">#include <Trade\Trade.mqh> class="macro">#include <Controls\Dialog.mqh> class="macro">#include <Controls\Button.mqh> class="macro">#include <Controls\Edit.mqh> class="macro">#include <Controls\Label.mqh> class=class="str">"cmt">// Input parameters input class="type">class="kw">string QuickMessage1 = "Updates"; input class="type">class="kw">string QuickMessage2 = "Close all"; input class="type">class="kw">string QuickMessage3 = "In deep profits"; input class="type">class="kw">string QuickMessage4 = "Hold position"; input class="type">class="kw">string QuickMessage5 = "Swing Entry"; input class="type">class="kw">string QuickMessage6 = "Scalp Entry"; input class="type">class="kw">string QuickMessage7 = "Book profit"; input class="type">class="kw">string QuickMessage8 = "Invalid Signal"; input class="type">class="kw">string InputChatId = "Enter Chat ID from Telegram bot API"; input class="type">class="kw">string InputBotToken = "Enter BOT TOKEN from your Telegram bot"; class=class="str">"cmt">// Global variables CDialog adminPanel; CButton sendButton, clearButton, changeFontButton, toggleThemeButton; CButton quickMessageButtons[class="num">8], minimizeButton, maximizeButton, closeButton; CEdit inputBox; CLabel charCounter; class="type">bool minimized = false; class="type">bool darkTheme = false; class="type">int MAX_MESSAGE_LENGTH = class="num">4096; class="type">class="kw">string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman" }; class="type">int currentFontIndex = class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Initialize the Dialog if (!adminPanel.Create(ChartID(), "Admin Panel", class="num">0, class="num">30, class="num">30, class="num">500, class="num">500)) { Print("Failed to create dialog"); class="kw">return INIT_FAILED; } class=class="str">"cmt">// Create controls if (!CreateControls()) { Print("Control creation failed"); class="kw">return INIT_FAILED; }
「控制面板的控件落位与坐标参数」
这段逻辑集中在 CreateControls() 里,把输入框、字符计数、清除、发送、字体切换、主题切换等控件逐个挂到 adminPanel 容器上。每个 Create() 调用都带 4 个坐标参数(左、上、右、下),直接决定 MT5 图表上的像素排布。 以输入框为例:inputBox.Create(chart_id, "InputBox", 0, 5, 25, 460, 95) 中 5 和 25 是左上角坐标,460 和 95 是右下角,意味着它从图表左侧 5 像素、顶部 25 像素起,宽约 455、高约 70。字符计数器 charCounter 放在 380,5 到 460,25,紧贴右上角,初始文本被写成 "0/" 加 MAX_MESSAGE_LENGTH,说明上限由宏控制而非写死。 按钮排布有重叠风险点:clearButton 占 235,95–345,125,changeFontButton 占 95,95–230,115,两者水平区间 95–345 连续但字体钮右沿 230 与清除钮左沿 235 留了 5 像素缝。sendButton 在 350,95–460,125,和清除钮右沿 345 同样留 5 像素。这种 5 像素间隔是手调出来的,改布局时若动一个坐标,可能让相邻控件贴边。 任何一次 Create() 返回 false 都会 Print 具体失败控件名并整体返回 false,初始化链会断。开 MT5 加载 EA 后,若面板缺某个钮,先看日志里 Failed to create 后接的名字,对照下面代码里的坐标改数值即可。
adminPanel.Show(); UpdateThemeColors(); Print("Initialization complete"); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create necessary UI controls | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CreateControls() { class="type">long chart_id = ChartID(); class=class="str">"cmt">// Create the input box if (!inputBox.Create(chart_id, "InputBox", class="num">0, class="num">5, class="num">25, class="num">460, class="num">95)) { Print("Failed to create input box"); class="kw">return false; } adminPanel.Add(inputBox); class=class="str">"cmt">// Character counter if (!charCounter.Create(chart_id, "CharCounter", class="num">0, class="num">380, class="num">5, class="num">460, class="num">25)) { Print("Failed to create character counter"); class="kw">return false; } charCounter.Text("class="num">0/" + IntegerToString(MAX_MESSAGE_LENGTH)); adminPanel.Add(charCounter); class=class="str">"cmt">// Clear button if (!clearButton.Create(chart_id, "ClearButton", class="num">0, class="num">235, class="num">95, class="num">345, class="num">125)) { Print("Failed to create clear button"); class="kw">return false; } clearButton.Text("Clear"); adminPanel.Add(clearButton); class=class="str">"cmt">// Send button if (!sendButton.Create(chart_id, "SendButton", class="num">0, class="num">350, class="num">95, class="num">460, class="num">125)) { Print("Failed to create send button"); class="kw">return false; } sendButton.Text("Send"); adminPanel.Add(sendButton); class=class="str">"cmt">// Change font button if (!changeFontButton.Create(chart_id, "ChangeFontButton", class="num">0, class="num">95, class="num">95, class="num">230, class="num">115)) { Print("Failed to create change font button"); class="kw">return false; } changeFontButton.Text("Font<>"); adminPanel.Add(changeFontButton); class=class="str">"cmt">// Toggle theme button if (!toggleThemeButton.Create(chart_id, "ToggleThemeButton", class="num">0, class="num">5, class="num">95, class="num">90, class="num">115)) { Print("Failed to create toggle theme button"); class="kw">return false; } toggleThemeButton.Text("Theme<>"); adminPanel.Add(toggleThemeButton); class=class="str">"cmt">// Minimize button
◍ 面板按钮的创建与销毁逻辑
这段代码演示了在 MT5 图表上用 CButton 类拼出一个管理面板(adminPanel)的收尾部分:先挂最小化、最大化、关闭三个控制钮,再批量生成 8 个快捷消息按钮,最后在 EA 卸载时整体销毁。 最小化钮建在 X=375、Y=-22 到 X=405、Y=0 的区间内,文本设为下划线「_」;最大化钮紧随其后占 X=405~435,文本「[ ]」;关闭钮在 X=435~465,文本「X」。任一处 Create 失败就 Print 报错并返回 false,面板不会半残着留图上。 快捷消息按钮由 CreateQuickMessageButtons() 统一生成:起始坐标 (5,160),单钮宽 222、高 65、间距 5,按 2 列排布(i%2 控横移、i/2 控纵移),文本取自 quickMessages[8] 字符串数组。循环里任意一号建不出来同样返回 false。 OnDeinit 里只调了 adminPanel.Destroy() 并打印「Deinitialization complete」,说明面板对象生命周期绑定 EA:外汇与贵金属图表挂 EA 有断线重连导致重复初始化风险,多次挂卸若 Destroy 不干净可能留僵尸控件,建议你在策略测试器里反复加载/移除 EA 验证是否零残留。
if (!minimizeButton.Create(chart_id, "MinimizeButton", class="num">0, class="num">375, -class="num">22, class="num">405, class="num">0)) { Print("Failed to create minimize button"); class="kw">return false; } minimizeButton.Text("_"); adminPanel.Add(minimizeButton); class=class="str">"cmt">// Maximize button if (!maximizeButton.Create(chart_id, "MaximizeButton", class="num">0, class="num">405, -class="num">22, class="num">435, class="num">0)) { Print("Failed to create maximize button"); class="kw">return false; } maximizeButton.Text("[ ]"); adminPanel.Add(maximizeButton); class=class="str">"cmt">// Close button if (!closeButton.Create(chart_id, "CloseButton", class="num">0, class="num">435, -class="num">22, class="num">465, class="num">0)) { Print("Failed to create close button"); class="kw">return false; } closeButton.Text("X"); adminPanel.Add(closeButton); class=class="str">"cmt">// Quick messages class="kw">return CreateQuickMessageButtons(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create quick message buttons | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CreateQuickMessageButtons() { class="type">class="kw">string quickMessages[class="num">8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; class="type">int startX = class="num">5, startY = class="num">160, width = class="num">222, height = class="num">65, spacing = class="num">5; for (class="type">int i = class="num">0; i < class="num">8; i++) { if (!quickMessageButtons[i].Create(ChartID(), "QuickMessageButton" + IntegerToString(i + class="num">1), class="num">0, startX + (i % class="num">2) * (width + spacing), startY + (i / class="num">2) * (height + spacing), startX + (i % class="num">2) * (width + spacing) + width, startY + (i / class="num">2) * (height + spacing) + height)) { Print("Failed to create quick message button ", i + class="num">1); class="kw">return false; } quickMessageButtons[i].Text(quickMessages[i]); adminPanel.Add(quickMessageButtons[i]); } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { adminPanel.Destroy(); Print("Deinitialization complete"); } class=class="str">"cmt">//+------------------------------------------------------------------+
图表控件点击事件如何分流到各按钮逻辑
在 MT5 自定义面板里,所有图形对象的交互都汇聚到 OnChartEvent 这个系统回调。它拿到事件 id 和 sparam(被点击对象的名称),靠 switch 把不同动作派发给对应函数,这是面板响应鼠标操作的主入口。 下面这段是事件分发的核心骨架:图表点击(CHARTEVENT_OBJECT_CLICK)时,sparam 等于哪个按钮名就调哪个处理函数;编辑框结束输入(CHARTEVENT_OBJECT_ENDEDIT)且 sparam 为 InputBox 时,触发输入变更回调。注意 QuickMessageButton 用 StringFind 做前缀匹配,再截取下标数字减 1 得到索引,这样可以批量挂快捷消息按钮而不用逐个写 case。
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="kw">switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") OnSendButtonClick(); else if (sparam == "ClearButton") OnClearButtonClick(); else if (sparam == "ChangeFontButton") OnChangeFontButtonClick(); else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick(); else if (sparam == "MinimizeButton") OnMinimizeButtonClick(); else if (sparam == "MaximizeButton") OnMaximizeButtonClick(); else if (sparam == "CloseButton") OnCloseButtonClick(); else if (StringFind(sparam, "QuickMessageButton") != -class="num">1) { class="type">long index = StringToInteger(StringSubstr(sparam, class="num">18)); OnQuickMessageButtonClick(index - class="num">1); } break; case CHARTEVENT_OBJECT_ENDEDIT: if (sparam == "InputBox") OnInputChange(); break; } }
class="type">void OnSendButtonClick() { class="type">class="kw">string message = inputBox.Text(); if (message != "") { if (SendMessageToTelegram(message)) Print("Custom message sent: ", message); else Print("Failed to send custom message."); } else { Print("No message entered."); } }
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="kw">switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") OnSendButtonClick(); else if (sparam == "ClearButton") OnClearButtonClick(); else if (sparam == "ChangeFontButton") OnChangeFontButtonClick(); else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick(); else if (sparam == "MinimizeButton") OnMinimizeButtonClick(); else if (sparam == "MaximizeButton") OnMaximizeButtonClick(); else if (sparam == "CloseButton") OnCloseButtonClick(); else if (StringFind(sparam, "QuickMessageButton") != -class="num">1) { class="type">long index = StringToInteger(StringSubstr(sparam, class="num">18)); OnQuickMessageButtonClick(index - class="num">1); } break; case CHARTEVENT_OBJECT_ENDEDIT: if (sparam == "InputBox") OnInputChange(); break; } } class="type">void OnSendButtonClick() { class="type">class="kw">string message = inputBox.Text(); if (message != "") { if (SendMessageToTelegram(message)) Print("Custom message sent: ", message); else Print("Failed to send custom message."); } else { Print("No message entered."); } } class="type">void OnClearButtonClick() {
「面板交互背后的消息与主题逻辑」
这段代码片段把 MT5 自定义面板的几个关键回调拆开了:清输入框、发快捷消息、字数统计和明暗主题切换,都是实盘喊单面板最常用的底层动作。
快捷消息用了固定 8 槽位数组 string quickMessages[8],由外部输入参数 QuickMessage1~8 注入,点击对应按钮时按 index 取文案并调 SendMessageToTelegram()。若返回 true 仅打印发送成功,失败则打印失败——这里没有任何重试或异常上报,实盘跑的时候断网会静默丢消息,外汇与贵金属喊单场景属高风险,建议自己加个重试计数。
字数计数器在 OnInputChange() 里实时算 StringLen(inputBox.Text()) 并拼出「当前/上限」文本,上限由 MAX_MESSAGE_LENGTH 宏控制,每次都 ChartRedraw() 强制重绘,字符数一多可能拖慢图表。
主题切换就是翻转 darkTheme 布尔后批量改色:暗色用 clrWhite 文字配 clrDarkBlue 底,亮色反过来用 clrBlack 配 clrWhite,八个按钮统一走 UpdateButtonTheme() 刷色。想验证就直接把这段挂到自己的 EA 面板工程里,改 MAX_MESSAGE_LENGTH 看计数器越界表现。
inputBox.Text(""); OnInputChange(); Print("Input box cleared."); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handle quick message button click | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnQuickMessageButtonClick(class="type">int index) { class="type">class="kw">string quickMessages[class="num">8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; class="type">class="kw">string message = quickMessages[index]; if (SendMessageToTelegram(message)) Print("Quick message sent: ", message); else Print("Failed to send quick message."); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update character counter | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnInputChange() { class="type">int currentLength = StringLen(inputBox.Text()); charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH)); ChartRedraw(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handle toggle theme button click | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnToggleThemeButtonClick() { darkTheme = !darkTheme; UpdateThemeColors(); Print("Theme toggled: ", darkTheme ? "Dark" : "Light"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update theme colors for the panel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void UpdateThemeColors() { class=class="str">"cmt">// Use the dialog&class="macro">#x27;s theme update method as a placeholder. adminPanel.UpdateThemeColors(darkTheme); class="type">color textColor = darkTheme ? clrWhite : clrBlack; class="type">color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro; class="type">color borderColor = darkTheme ? clrSlateGray : clrGray; class="type">color bgColor = darkTheme? clrDarkBlue : clrWhite; inputBox.SetTextColor(textColor); inputBox.SetBackgroundColor(bgColor); inputBox.SetBorderColor(borderColor); UpdateButtonTheme(clearButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(sendButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(toggleThemeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(changeFontButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(minimizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(maximizeButton, textColor, buttonBgColor,borderColor); UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor); for (class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++) {
◍ 面板控件的主题与显隐切换逻辑
这段 MT5 面板代码把按钮主题、字体轮换和最小化/最大化做成了独立函数,方便在 EA 里热切换界面状态。 UpdateButtonTheme 用引用方式接住 CButton 对象,一次性设文字色、背景色和边框色,循环里对快捷消息按钮批量调用,最后 ChartRedraw 才刷新。 OnChangeFontButtonClick 靠 currentFontIndex 对 availableFonts 数组长度取模实现字体轮播,遍历所有控件包括快捷按钮设 Font,并在日志打出当前字体名。 最小化只置 minimized=true 并隐藏主面板与最小化键,最大化则在 minimized 为真时复原显示并隐藏自身;关闭键直接 ExpertRemove 卸载 EA。外汇与贵金属图表挂这类面板属高风险操作,误触关闭会中断自动逻辑。
class="type">void UpdateButtonTheme(CButton &button, class="type">color textColor, class="type">color bgColor, class="type">color borderColor) { button.SetTextColor(textColor); button.SetBackgroundColor(bgColor); button.SetBorderColor(borderColor); } class="type">void OnChangeFontButtonClick() { currentFontIndex = (currentFontIndex + class="num">1) % ArraySize(availableFonts); inputBox.Font(availableFonts[currentFontIndex]); clearButton.Font(availableFonts[currentFontIndex]); sendButton.Font(availableFonts[currentFontIndex]); toggleThemeButton.Font(availableFonts[currentFontIndex]); changeFontButton.Font(availableFonts[currentFontIndex]); for (class="type">int i = class="num">0; i < ArraySize(quickMessageButtons); i++) { quickMessageButtons[i].Font(availableFonts[currentFontIndex]); } Print("Font changed to: ", availableFonts[currentFontIndex]); ChartRedraw(); } class="type">void OnMinimizeButtonClick() { minimized = true; adminPanel.Hide(); minimizeButton.Hide(); maximizeButton.Show(); closeButton.Show(); Print("Panel minimized."); } class="type">void OnMaximizeButtonClick() { if (minimized) { adminPanel.Show(); minimizeButton.Show(); maximizeButton.Hide(); closeButton.Hide(); Print("Panel maximized."); } } class="type">void OnCloseButtonClick() { ExpertRemove(); Print("Admin Panel closed."); }
把信号推到 Telegram 的 POST 封装
想在 MT5 上把盯盘信号实时丢进 Telegram,核心就是封装一个发送函数,用 WebRequest 走 bot API。下面这段实现把 token 和 chat_id 作为参数拼进 URL 与 JSON,超时设 5000 毫秒,返回是否发送成功。
代码里先拼出 https://api.telegram.org/bot<token>/sendMessage,再把消息体写成 {"chat_id":"...", "text":"..."} 的 JSON 字符串。注意 StringToCharArray 转换后末尾带 0 结束符,所以用 ArrayResize 减 1 去掉,避免把空字符也发出去。
WebRequest 的返回值直接判断:等于 200 才算送达,否则打印 HTTP 码和 GetLastError(),方便排查是网络问题还是 token 错配。实盘接这段时,外汇与贵金属波动剧烈、滑点频繁,建议只在关键价位突破或持仓异动时触发,别每根 tick 都推,否则可能刷屏被 TG 限流。
调用前记得在 MT5 的「选项-Expert Advisors」里把 https://api.telegram.org 加进允许 URL 列表,否则 WebRequest 会直接返回 0。开 MT5 新建脚本粘进去,填好 InputBotToken 和 InputChatId 就能验证。
class="type">bool SendMessageToTelegram(class="type">class="kw">string message) { class="type">class="kw">string url = "https:class=class="str">"cmt">//api.telegram.org/bot" + InputBotToken + "/sendMessage"; class="type">class="kw">string jsonMessage = "{\"chat_id\":\"" + InputChatId + "\", \"text\":\"" + message + "\"}"; class="type">char post_data[]; ArrayResize(post_data, StringToCharArray(jsonMessage, post_data, class="num">0, WHOLE_ARRAY) - class="num">1); class="type">int timeout = class="num">5000; class="type">char result[]; class="type">class="kw">string responseHeaders; class="type">int res = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, post_data, result, responseHeaders); if (res == class="num">200) { Print("Message sent successfully: ", message); class="kw">return true; } else { Print("Failed to send message. HTTP code: ", res, " Error code: ", GetLastError()); Print("Response: ", CharArrayToString(result)); class="kw">return false; } }
「动头文件前先备好退路」
这套管理面板把主题切换塞进了原有 MQL5 库类里,实测没拖慢依赖同一批库的其他平台功能,但项目已经叠了 Telegram 推送和高级绘图,复杂度明显上来了。 改头文件能出好看的界面,也可能让交易程序莫名其妙崩、不兼容。作者把 Admin_Panel.mq5(14.69 KB)和 Extended_MQL5_Header_Files.zip(10.06 KB)都给了,主题切换就靠这些类在,缺一个就白搭。 真要动手,先留原始文件备份,在模拟环境跑稳了再碰实盘。外汇和贵金属杠杆高、波动猛,任何改完没验过的组件直接上线都等于裸奔。 要是改出问题,重装 MT5 能把系统文件复位、类重置回默认,这是最省事的回滚手段。