在MQL5中创建交易管理员面板(第三部分):扩展内置类以进行主题管理(II)·综合运用
📘

在MQL5中创建交易管理员面板(第三部分):扩展内置类以进行主题管理(II)·综合运用

第 3/3 篇

按钮控件的生命周期钩子怎么写

在 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,按钮就能保持按下高亮,适合做‘开启自动盯盘’这类状态切换。外汇与贵金属界面脚本同样受此机制约束,实盘加载前请在策略测试器跑一遍避免对象泄漏。

MQL5 / C++
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,结果边框还是系统灰、背景还是白,暗色面板里输入框像块补丁。三个颜色最好一起设,否则视觉断层很明显。

MQL5 / C++
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 是最直接的落点。

MQL5 / C++
  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。

MQL5 / C++
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 可能拖慢报价刷新,属高风险界面操作。

MQL5 / C++
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,意味着你拖拽父容器时编辑框会同步拉伸,不用手动重算像素。

MQL5 / C++
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()));
  }
上面这段逐行看:函数返回 bool;m_rect.Width() 取当前绑定的矩形宽度;X_Size 设水平像素;Y_Size 设垂直像素;两个调用用 && 短路,任一失败整体返回失败。 OnChange 在属性重设时触发,除了走基类 CWndObj::OnChange(),还顺手把只读标志和文本对齐模式刷进对象。只读状态下 OnClick 不会发 ON_START_EDIT,只走普通点击通知,这个分支决定了输入框能不能被用户激活。 编辑结束靠 EventChartCustom 发自定义事件,参数里 m_id 和 m_name 用来在 EA 里定位是哪个框退出了编辑。外汇与贵金属图表挂这类控件需注意:自定义事件只在当前图表上下文有效,多图表同步有概率漏事件,属于高风险交互设计。

MQL5 / C++
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 枚举)。

MQL5 / C++
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 : public CWndObj 表示它复用窗口对象基类的生命周期;m_edit 是实际画在图表上的编辑框;ReadOnly() 有两个重载,无参版本直接返回私有 flag,带参版本用于运行时切换只读;Text() 无参时转调 m_edit.Description() 拿当前内容,带参时走基类赋值。 SetTextColor 和 SetBackgroundColor 负责换主题色,做多品种盯盘面板时,可以用这两个接口把黄金和欧美品种的输入框底色区分开。外汇与贵金属波动剧烈、杠杆风险高,这类 UI 封装只解决交互效率,不改变任何交易胜率。

MQL5 / C++
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 宏,就能直观看到背景色偏移效果。

MQL5 / C++
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)。

MQL5 / C++
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,能看到控件画出来但尺寸不跟手——这就是钩子返回值在拦你。

MQL5 / C++
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 后,同样点击只会走通用点击回调。外汇与贵金属面板开发涉及实时交互,回测无法覆盖鼠标事件,需在真实行情环境手动点测,杠杆品种误操作风险偏高。

MQL5 / C++
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 打出当前主题状态便于调试。

MQL5 / C++
<span class="keyword">class="type">bool</span> darkTheme = <span class="macro">false</span>;
<span class="keyword">class="type">void</span> OnToggleThemeButtonClick()
{
&nbsp;&nbsp;&nbsp;&nbsp;darkTheme = !darkTheme;
&nbsp;&nbsp;&nbsp;&nbsp;UpdateThemeColors();
&nbsp;&nbsp;&nbsp;&nbsp;<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()
{
&nbsp;&nbsp;&nbsp;&nbsp;<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>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">color</span> textColor = darkTheme ? <span class="macro">clrWhite</span> : <span class="macro">clrBlack</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">color</span> buttonBgColor = darkTheme ? <span class="macro">clrDarkSlateGray</span> : <span class="macro">clrGainsboro</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">color</span> borderColor = darkTheme ? <span class="macro">clrSlateGray</span> : <span class="macro">clrGray</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">color</span> bgColor&nbsp;&nbsp;&nbsp;&nbsp; = darkTheme ? <span class="macro">clrDarkBlue</span> : <span class="macro">clrWhite</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Set text box colors</span>
&nbsp;&nbsp;&nbsp;&nbsp;inputBox.SetTextColor(textColor);
&nbsp;&nbsp;&nbsp;&nbsp;inputBox.SetBackgroundColor(bgColor);
&nbsp;&nbsp;&nbsp;&nbsp;inputBox.SetBorderColor(borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;<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>
&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(clearButton, textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(sendButton, textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(toggleThemeButton, textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(changeFontButton, textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(minimizeButton, textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(maximizeButton, textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;<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>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; <span class="functions">ArraySize</span>(quickMessageButtons); i++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;<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>
&nbsp;&nbsp;&nbsp;&nbsp;charCounter.Color(textColor);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Redraw chart to apply changes</span>
&nbsp;&nbsp;&nbsp;&nbsp;<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 &amp;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)
{
&nbsp;&nbsp;&nbsp;&nbsp;button.SetTextColor(textColor);
&nbsp;&nbsp;&nbsp;&nbsp;button.SetBackgroundColor(bgColor);
&nbsp;&nbsp;&nbsp;&nbsp;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 都起不来。

MQL5 / C++
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 后接的名字,对照下面代码里的坐标改数值即可。

MQL5 / C++
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 验证是否零残留。

MQL5 / C++
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。

MQL5 / C++
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;
  }
}
发送按钮的逻辑很直白:从输入框取文本,空串就 Print 提示没内容;非空则调 SendMessageToTelegram,成功或失败都只打日志,不弹窗也不阻塞。外汇与贵金属行情波动剧烈,这类自动推送只作辅助提醒,任何信号都可能因滑点而失效。
MQL5 / C++
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.");
  }
}
清屏函数只写了函数头,里面尚未填充。若你要做自己的面板,直接在 OnClearButtonClick 里清 inputBox 和消息日志对象即可,MT5 对象名要和上面 case 里的字符串严格一致,否则点击无反应。

MQL5 / C++
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 底,亮色反过来用 clrBlackclrWhite,八个按钮统一走 UpdateButtonTheme() 刷色。想验证就直接把这段挂到自己的 EA 面板工程里,改 MAX_MESSAGE_LENGTH 看计数器越界表现。

MQL5 / C++
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。外汇与贵金属图表挂这类面板属高风险操作,误触关闭会中断自动逻辑。

MQL5 / C++
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 就能验证。

MQL5 / C++
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 能把系统文件复位、类重置回默认,这是最省事的回滚手段。

常见问题

多数情况是只重写了构造没接 OnEvent,需在派生类里覆盖点击事件并调用父级刷新,否则按钮状态卡死。
在主题切换函数里遍历所有 CEdit 实例重设背景与字体色,并强制 Redraw,别只改面板背景。
可以,把面板代码贴给小布,它能标出未绑定主题色的控件和缺失的生命周期钩子,省去逐行查。
正常,只读不挡选中;封装时继承 CEdit 重写 SetReadOnly 同步灰化背景并屏蔽键盘消息更稳妥。
在容器 Resize 里按锚点重算 CEdit 的宽高与坐标再 Show,不能只在构造时定死尺寸。