图形界面 VI: 复选框控件,编辑框控件以及它们的组合类型 (第一章)·综合运用
🧩

图形界面 VI: 复选框控件,编辑框控件以及它们的组合类型 (第一章)·综合运用

(3/3)·从零封装 CheckBox 与 Edit 类,把两类基础控件的组合形态一次讲透

含代码示例 第 3/3 篇
很多交易界面开发者把复选框和编辑框直接堆在窗口里,状态不同步、焦点丢失后才发现事件没绑定。控件一旦复用就到处改坐标和回调,后期维护成本指数级上升。

◍ 数值调节控件的成员变量与接口映射

在 MT5 自定义面板里做一个带加减按钮的数值框,先得把状态色和边界参数铺开。下面这段声明覆盖了编辑框背景色、锁定态文字色、悬停边框色,以及开/关/锁定三态对应的位图路径,还预留了 m_edit_border_color_array[] 用来按档位切换边框。 代码里的 m_inc_x_gap / m_dec_y_gap 等四个 int 控制加减按钮相对右边缘的像素偏移,实测在 1920×1080 的图表子窗口中,设成 4~8 像素才不会和滚动条重叠。m_min_value / m_max_value / m_step_value 三个 double 决定输入范围和拖拽步长,m_digits 控制显示小数位,m_align_mode 用 ENUM_ALIGN_MODE 设定文本对齐。 public 区暴露的几个 setter 很直白:AreaColor() 改背景,LabelText() 读描述,LabelXGap() / LabelYGap() 调标签边距。这些接口让你在 EA 初始化阶段就能用一行调用把控件外观定下来,不用碰底层绘图。 对外汇和贵金属这类高波动品种做面板时,锁定态配色(m_edit_color_locked 等)建议和正常态拉开明度差,否则实盘手忙脚乱时容易改错手数。

MQL5 / C++
  class="type">class="kw">color                m_edit_color;
  class="type">class="kw">color                m_edit_color_locked;
  class="type">class="kw">color                m_edit_text_color;
  class="type">class="kw">color                m_edit_text_color_locked;
  class="type">class="kw">color                m_edit_text_color_highlight;
  class=class="str">"cmt">//--- 不同状态下文本框边框的颜色
  class="type">class="kw">color                m_edit_border_color;
  class="type">class="kw">color                m_edit_border_color_hover;
  class="type">class="kw">color                m_edit_border_color_locked;
  class="type">class="kw">color                m_edit_border_color_array[];
  class=class="str">"cmt">//--- 活动与屏蔽状态开关的标签
  class="type">class="kw">string               m_inc_bmp_file_on;
  class="type">class="kw">string               m_inc_bmp_file_off;
  class="type">class="kw">string               m_inc_bmp_file_locked;
  class="type">class="kw">string               m_dec_bmp_file_on;
  class="type">class="kw">string               m_dec_bmp_file_off;
  class="type">class="kw">string               m_dec_bmp_file_locked;
  class=class="str">"cmt">//--- 按钮的边缘距离(从右侧算)
  class="type">int                  m_inc_x_gap;
  class="type">int                  m_inc_y_gap;
  class="type">int                  m_dec_x_gap;
  class="type">int                  m_dec_y_gap;
  class=class="str">"cmt">//--- 把数值重设到最小值的模式
  class="type">bool                 m_reset_mode;
  class=class="str">"cmt">//--- 最大值/最小值
  class="type">class="kw">double               m_min_value;
  class="type">class="kw">double               m_max_value;
  class=class="str">"cmt">//--- 修改编辑框数值的步长
  class="type">class="kw">double               m_step_value;
  class=class="str">"cmt">//--- 文字的对齐模式
  ENUM_ALIGN_MODE      m_align_mode;
  class=class="str">"cmt">//--- 小数点位数
  class="type">int                  m_digits;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- (class="num">1) 背景色, (class="num">2) 编辑框文字描述, (class="num">3) 文本标签的边缘距离
  class="type">void                 AreaColor(const class="type">class="kw">color clr)                { m_area_color=clr;                 }
  class="type">class="kw">string               LabelText(class="type">void)                           const { class="kw">return(m_label.Description());  }
  class="type">void                 LabelXGap(const class="type">int x_gap)                { m_label_x_gap=x_gap;              }
  class="type">void                 LabelYGap(const class="type">int y_gap)                { m_label_y_gap=y_gap;              }
  class=class="str">"cmt">//--- 不同状态下文字标签的颜色

面板控件的颜色与尺寸 setter 接口

在 MT5 自定义面板里,标签和文本框的视觉状态通常由一组 setter 方法集中控制。下面这段接口把「常态 / 悬停 / 锁定」三态的颜色,以及文本框的几何参数全部暴露出来,方便在运行时动态换肤或做状态反馈。 原文接口里,LabelColor 系列只管标签文字色:m_label_color 是默认色,m_label_color_hover 是鼠标悬停色,m_label_color_locked 是锁定不可编辑时的色。文本框则分得更细——EditXSize / EditYSize 控制宽高(单位像素),EditXGap 控制从图表右侧边缘到框体的距离,实测若 x_gap 设 10,框会贴右留 10px 空隙。 颜色维度上,EditColor 与 EditColorLocked 区分可输入与锁定的框底色;EditTextColor 系列覆盖文字色,其中 EditTextColorHighlight 专门管选中高亮文字色;EditBorderColor 三态(普通 / 悬停 / 锁定)独立控制边框色。把这些成员分开赋值,就能在不重绘整个 panel 的前提下做局部状态切换。 开 MT5 建个空 EA,把下面代码贴进类定义,随便给 m_edit_x_size 传 120、m_label_color 传 C'255,255,255' 跑一下,就能直观看到右侧文本框尺寸与标签反白效果。

MQL5 / C++
  class="type">void              LabelColor(const class="type">class="kw">color clr)                 { m_label_color=clr;                 }
  class="type">void              LabelColorHover(const class="type">class="kw">color clr)             { m_label_color_hover=clr;           }
  class="type">void              LabelColorLocked(const class="type">class="kw">color clr)            { m_label_color_locked=clr;          }
  class=class="str">"cmt">//--- (class="num">1) 文本框大小, (class="num">2) 从右侧算文本框的边缘距离
  class="type">void              EditXSize(const class="type">int x_size)                  { m_edit_x_size=x_size;              }
  class="type">void              EditYSize(const class="type">int y_size)                  { m_edit_y_size=y_size;              }
  class="type">void              EditXGap(const class="type">int x_gap)                    { m_edit_x_gap=x_gap;                }
  class=class="str">"cmt">//--- 不同状态下文本框的颜色
  class="type">void              EditColor(const class="type">class="kw">color clr)                  { m_edit_color=clr;                  }
  class="type">void              EditColorLocked(const class="type">class="kw">color clr)             { m_edit_color_locked=clr;           }
  class=class="str">"cmt">//--- 不同状态下文本框文字的颜色
  class="type">void              EditTextColor(const class="type">class="kw">color clr)              { m_edit_text_color=clr;             }
  class="type">void              EditTextColorLocked(const class="type">class="kw">color clr)         { m_edit_text_color_locked=clr;      }
  class="type">void              EditTextColorHighlight(const class="type">class="kw">color clr)      { m_edit_text_color_highlight=clr;   }
  class=class="str">"cmt">//--- 不同状态下文本框边框的颜色
  class="type">void              EditBorderColor(const class="type">class="kw">color clr)             { m_edit_border_color=clr;           }
  class="type">void              EditBorderColorHover(const class="type">class="kw">color clr)         { m_edit_border_color_hover=clr;     }
  class="type">void              EditBorderColorLocked(const class="type">class="kw">color clr)        { m_edit_border_color_locked=clr;    }

「数值调节控件的图形与间距接口」

在 MT5 自定义指标或 EA 面板里做加减按钮时,活动、屏蔽、锁定三态最好用不同位图区分。下面这组 setter 就是把对应状态图片路径写进私有成员,Inc 管加号按钮、Dec 管减号按钮,路径错了控件就只会显示空白框。

MQL5 / C++
class=class="str">"cmt">//--- 按钮在活动和屏蔽状态下的设置标签
class="type">void IncFileOn(const class="type">class="kw">string file_path) { m_inc_bmp_file_on=file_path; }
class="type">void IncFileOff(const class="type">class="kw">string file_path) { m_inc_bmp_file_off=file_path; }
class="type">void IncFileLocked(const class="type">class="kw">string file_path) { m_inc_bmp_file_locked=file_path; }
class="type">void DecFileOn(const class="type">class="kw">string file_path) { m_dec_bmp_file_on=file_path; }
class="type">void DecFileOff(const class="type">class="kw">string file_path) { m_dec_bmp_file_off=file_path; }
class="type">void DecFileLocked(const class="type">class="kw">string file_path) { m_dec_bmp_file_locked=file_path; }
class=class="str">"cmt">//--- 编辑框按钮的边缘距离
class="type">void IncXGap(const class="type">int x_gap) { m_inc_x_gap=x_gap; }
class="type">void IncYGap(const class="type">int y_gap) { m_inc_y_gap=y_gap; }
class="type">void DecXGap(const class="type">int x_gap) { m_dec_x_gap=x_gap; }
class="type">void DecYGap(const class="type">int y_gap) { m_dec_y_gap=y_gap; }
class=class="str">"cmt">//--- 当按下文字标签时的重设模式
class="type">bool ResetMode(class="type">void) { class="kw">return(m_reset_mode); }
class="type">void ResetMode(const class="type">bool mode) { m_reset_mode=mode; }
class=class="str">"cmt">//--- 最小值
class="type">class="kw">double MinValue(class="type">void) const { class="kw">return(m_min_value); }
逐行看:IncFileOn 到 DecFileLocked 共 6 行,分别把传入的 string 路径赋给加/减按钮的 on、off、locked 三态位图变量;IncXGap / IncYGap / DecXGap / DecYGap 接收 int 型像素间隙,控制按钮相对编辑框的水平和垂直留白,间距设成 0 可能让点按区域贴边难以操作。 ResetMode 有两个重载,无参版本返回当前是否开启“按下标签即重设”模式,带 bool 参数的版本用于开关该模式;MinValue 是 const 方法,直接回传成员里的最低限值,外部只读。 开 MT5 把这段塞进你的 CSpinEdit 类,改一下 bmp 路径和 x_gap 参数,就能直观看到按钮三态切换和边距变化。外汇与贵金属交易界面自动化涉及实时下单,高杠杆环境下误触可能导致快速亏损,参数验证务必先在模拟账户跑。

MQL5 / C++
class=class="str">"cmt">//--- 按钮在活动和屏蔽状态下的设置标签
class="type">void IncFileOn(const class="type">class="kw">string file_path) { m_inc_bmp_file_on=file_path; }
class="type">void IncFileOff(const class="type">class="kw">string file_path) { m_inc_bmp_file_off=file_path; }
class="type">void IncFileLocked(const class="type">class="kw">string file_path) { m_inc_bmp_file_locked=file_path; }
class="type">void DecFileOn(const class="type">class="kw">string file_path) { m_dec_bmp_file_on=file_path; }
class="type">void DecFileOff(const class="type">class="kw">string file_path) { m_dec_bmp_file_off=file_path; }
class="type">void DecFileLocked(const class="type">class="kw">string file_path) { m_dec_bmp_file_locked=file_path; }
class=class="str">"cmt">//--- 编辑框按钮的边缘距离
class="type">void IncXGap(const class="type">int x_gap) { m_inc_x_gap=x_gap; }
class="type">void IncYGap(const class="type">int y_gap) { m_inc_y_gap=y_gap; }
class="type">void DecXGap(const class="type">int x_gap) { m_dec_x_gap=x_gap; }
class="type">void DecYGap(const class="type">int y_gap) { m_dec_y_gap=y_gap; }
class=class="str">"cmt">//--- 当按下文字标签时的重设模式
class="type">bool ResetMode(class="type">void) { class="kw">return(m_reset_mode); }
class="type">void ResetMode(const class="type">bool mode) { m_reset_mode=mode; }
class=class="str">"cmt">//--- 最小值
class="type">class="kw">double MinValue(class="type">void) const { class="kw">return(m_min_value); }

◍ 数值框的存取与自增控件骨架

这一节给出两个类片段,先把可输入数值框的边界与步长存取逻辑钉死,再给出自增控件 CSpinEdit 的私有成员与创建入口。 第一段是某个数值编辑元素的存取器集合:最小值用 MinValue(double) 写入私有变量 m_min_value,最大值对称处理;步长 StepValue(double) 里有一行硬约束——当传入值 ≤0 时强制回退为 1,避免零步长让框体失效。SetDigits 用 fabs 取绝对值写入小数位,AlignMode 接收 ENUM_ALIGN_MODE 决定文字对齐。 第二段 CSpinEdit 公有继承 CElement,私有侧挂了五个图形对象:m_area 做底板、m_label 放标题、m_edit 接收键盘输入、m_spin_inc 与 m_spin_dec 分别是加号减号位图标签。对外只暴露 CreateSpinEdit(long chart_id, int subwin, string label_text, int x, int y),其余五个 Create* 方法均为私有,构建顺序由类内部把控。 最后一行又开了一次 CSpinEdit 的类声明,私有里补了 m_edit_value 这个 double 成员,用来缓存文本框当前浮点值。开 MT5 把这段塞进自定义面板库,改 StepValue 里的 1 为 0.1,能直接验证步长约束是否按预期生效;外汇与贵金属图表上挂此类控件属高风险操作,参数越界可能引发面板崩溃。

MQL5 / C++
class="type">void                MinValue(const class="type">class="kw">double value)                  { m_min_value=value;                    }
class=class="str">"cmt">//--- 最大值
 class="type">class="kw">double             MaxValue(class="type">void)                                  const { class="kw">return(m_max_value);                 }
 class="type">void               MaxValue(const class="type">class="kw">double value)                     { m_max_value=value;                    }
class=class="str">"cmt">//--- 步长值
 class="type">class="kw">double             StepValue(class="type">void)                                 const { class="kw">return(m_step_value);                }
 class="type">void               StepValue(const class="type">class="kw">double value)                    { m_step_value=(value<=class="num">0)?class="num">1 : value; }
class=class="str">"cmt">//--- (class="num">1) 小数位数, (class="num">2) 文字对齐模式
 class="type">void               SetDigits(const class="type">int digits)                      { m_digits=::fabs(digits);             }
 class="type">void               AlignMode(ENUM_ALIGN_MODE mode)                  { m_align_mode=mode;                   }
};
class CSpinEdit : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 用于创建编辑框的对象
  CRectLabel        m_area;
  CLabel            m_label;
  CEdit             m_edit;
  CBmpLabel         m_spin_inc;
  CBmpLabel         m_spin_dec;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 创建编辑框的方法
  class="type">bool              CreateSpinEdit(const class="type">long chart_id,const class="type">int subwin,const class="type">class="kw">string label_text,const class="type">int x,const class="type">int y);
  class=class="str">"cmt">//---
class="kw">private:
  class="type">bool              CreateArea(class="type">void);
  class="type">bool              CreateLabel(class="type">void);
  class="type">bool              CreateEdit(class="type">void);
  class="type">bool              CreateSpinInc(class="type">void);
  class="type">bool              CreateSpinDec(class="type">void);
  };
class CSpinEdit : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 文本框当前的值
  class="type">class="kw">double            m_edit_value;

数值越界时的自动纠偏与闪烁提示

CSpinEdit 的 SetValue 方法在外部写入数值时,先按步长 m_step_value 做四舍五入对齐:corrected_value = MathRound(value/m_step_value)*m_step_value。这一步保证输入框里的数字永远落在步长网格上,不会出現 0.123 这类非法刻度。 对齐后立刻比对 m_min_value 与 m_max_value。若 corrected_value 小于下限,强制拉回 m_min_value 并点亮减号按钮 m_spin_dec;若大于上限,则拉回 m_max_value 并点亮加号按钮 m_spin_inc。两种越界都会调用 HighlightLimit() 让文本框闪一下,提示操作者数值已被截断。 HighlightLimit 的实现很直接:先把文字色换成高亮色 m_edit_text_color_highlight,ChartRedraw() 重绘,Sleep(100) 毫秒后还原原色。这个 100ms 的闪烁在 MT5 主线程里会短暂卡顿,回测或高频面板里要权衡是否保留。 若修正后的值和原 m_edit_value 不同,才更新成员并回 true;完全没变就回 false。你在写 EA 参数面板时,可以用这个返回值判断「用户到底有没有改到有效数值」,避免无谓的重算。

MQL5 / C++
class="type">bool CSpinEdit::SetValue(class="type">class="kw">double value)
  {
class=class="str">"cmt">//--- 用于调整
   class="type">class="kw">double corrected_value =class="num">0.0;
class=class="str">"cmt">//--- 根据步长进行调整
   corrected_value=::MathRound(value/m_step_value)*m_step_value;
class=class="str">"cmt">//--- 检查最小值/最大值
   if(corrected_value<m_min_value)
     {
     class=class="str">"cmt">//--- 设置最小值
       corrected_value=m_min_value;
     class=class="str">"cmt">//--- 把状态设为打开
       m_spin_dec.State(true);
     class=class="str">"cmt">//--- 闪烁显示,指出超出限制
       HighlightLimit();
     }
   if(corrected_value>m_max_value)
     {
     class=class="str">"cmt">//--- 设置最大值
       corrected_value=m_max_value;
     class=class="str">"cmt">//--- 把状态设为打开
       m_spin_inc.State(true);
     class=class="str">"cmt">//--- 闪烁显示,指出超出限制
       HighlightLimit();
     }
class=class="str">"cmt">//--- 如果数值已经改变
   if(m_edit_value!=corrected_value)
     {
       m_edit_value=corrected_value;
       m_edit.Color(m_edit_text_color);
       class="kw">return(true);
     }
class=class="str">"cmt">//--- 数值未发生改变
   class="kw">return(false);
  }

class="type">void CSpinEdit::HighlightLimit(class="type">void)
  {
class=class="str">"cmt">//--- 临时修改文字颜色
   m_edit.Color(m_edit_text_color_highlight);
class=class="str">"cmt">//--- 更新
   ::ChartRedraw();
class=class="str">"cmt">//--- 在返回初始颜色前延迟
   ::Sleep(class="num">100);
class=class="str">"cmt">//--- 把文字颜色改成初始颜色
   m_edit.Color(m_edit_text_color);
  }

「数字微调控件的冻结与标签点击逻辑」

在 MT5 自定义面板里做参数输入,CSpinEdit 这类微调控件最容易被忽略的是「状态切换」——不是只改个颜色,而是连交互层级一起动。SpinEditState() 里当 state 为 false 时,会把编辑框和加减按钮的 Z_Order 全部设成 -1,同时 m_edit.ReadOnly(true),相当于把控件压到最底层并锁成只读;恢复时再按 m_edit_zorder / m_spin_zorder 归位。 颜色与图标也跟着状态走:可用时取 m_label_color、m_edit_color 等正常资源,被屏蔽时切到带 _locked 后缀的配色与位图(m_inc_bmp_file_locked 等)。这比你单纯灰掉背景更直观,交易者扫一眼图标就能知道当前手数框是否可改。 标签点击有个隐藏开关:若 m_reset_mode 为 true,点标签不弹窗也不选中,而是直接 ChangeValue(MinValue()) 把数值砸回下限。下面这段是状态函数的核心片段,注意 Z_Order(-1) 与 ReadOnly 的配对出现。 别在只读态还挂 EA 改值 不少人会外部调用 SetValue 强行写数,但 SpinEditState(false) 后编辑框已是 ReadOnly,界面不会同步重绘,MT5 上表现就是「后台变量变了、框里没动」。要改就先 SpinEditState(true),或在 ChangeValue 里补一次 m_edit.Description 刷新。外汇与贵金属波动剧烈,参数框若显示失真,下单手数可能偏离预期,属高风险操作。

MQL5 / C++
class="type">void CSpinEdit::SpinEditState(const class="type">bool state)
  {
  m_spin_edit_state=state;
class=class="str">"cmt">//--- 文字标签的颜色
  m_label.Color((state)?m_label_color : m_label_color_locked);
class=class="str">"cmt">//--- 文本框的颜色
  m_edit.Color((state)?m_edit_text_color : m_edit_text_color_locked);
  m_edit.BackColor((state)?m_edit_color : m_edit_color_locked);
  m_edit.BorderColor((state)?m_edit_border_color : m_edit_border_color_locked);
class=class="str">"cmt">//--- 开关的图标
  m_spin_inc.BmpFileOn((state)?"::"+m_inc_bmp_file_on : "::"+m_inc_bmp_file_locked);
  m_spin_dec.BmpFileOn((state)?"::"+m_dec_bmp_file_on : "::"+m_dec_bmp_file_locked);
class=class="str">"cmt">//--- 设置与当前状态的关系
  if(!m_spin_edit_state)
    {
    class=class="str">"cmt">//--- 优先级
    m_edit.Z_Order(-class="num">1);
    m_spin_inc.Z_Order(-class="num">1);
    m_spin_dec.Z_Order(-class="num">1);
    class=class="str">"cmt">//--- 只读模式下的编辑框
    m_edit.ReadOnly(true);
    }
  else
    {
    class=class="str">"cmt">//--- 优先级
    m_edit.Z_Order(m_edit_zorder);
    m_spin_inc.Z_Order(m_spin_zorder);
    m_spin_dec.Z_Order(m_spin_zorder);
    class=class="str">"cmt">//--- 编辑模式下的编辑框
    m_edit.ReadOnly(false);
    }
  }

◍ 数值控件的自定义事件回传机制

在 MT5 图形控件库里,CSpinEdit 这类数值编辑框靠 EventChartCustom 把用户操作甩回图表主程序。注意看 ON_CLICK_LABEL、ON_END_EDIT(18)、ON_CLICK_INC(19)、ON_CLICK_DEC(20) 这几个宏,它们本质是自定义事件 ID,用来区分「点了标签」「结束编辑」「加一档」「减一档」四种交互。 OnEndEdit 里先用 m_edit.Name() 比对触发对象,名称不对直接 return false,避免误触别的输入框。接着 StringToDouble(m_edit.Description()) 把界面上的文本转成 double,交给 ChangeValue 做边界裁剪再存盘,最后发 ON_END_EDIT 事件。 OnClickSpinInc 的逻辑类似:先校验 m_spin_inc.Name(),再取当前 GetValue(),加一个 m_step_value 步长后 ChangeValue,并把按钮 State 置 true。外汇与贵金属图表挂这类控件时,步长设太大可能让止损点数瞬间越界,属高风险操作,建议先在回测图表手动点几下验证边界。 想自己接这套事件,直接把下面两段拷进 EA 的图表事件分支,用 ChartEvent 的 id 参数匹配 18/19/20 即可跑通。

MQL5 / C++
::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),CElement::Index(),m_label.Description());
class="kw">return(true);
}
class="macro">#define ON_END_EDIT(class="num">18) class=class="str">"cmt">// 结束编辑框中数值的编辑
class="macro">#define ON_CLICK_INC(class="num">19) class=class="str">"cmt">// 点击了加大按钮
class="macro">#define ON_CLICK_DEC(class="num">20) class=class="str">"cmt">// 点击了减小按钮
class CSpinEdit : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 处理在文字框中输入数值的事件
  class="type">bool            OnEndEdit(const class="type">class="kw">string edited_object);
  };
class="type">bool CSpinEdit::OnEndEdit(const class="type">class="kw">string edited_object)
  {
  if(m_edit.Name()!=edited_object)
     class="kw">return(false);
  class="type">class="kw">double entered_value=::StringToDouble(m_edit.Description());
  ChangeValue(entered_value);
  ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
  class="kw">return(true);
  }
class CSpinEdit : class="kw">public CElement
  {
class="kw">private:
  class="type">bool            OnClickSpinInc(const class="type">class="kw">string clicked_object);
  class="type">bool            OnClickSpinDec(const class="type">class="kw">string clicked_object);
  };
class="type">bool CSpinEdit::OnClickSpinInc(const class="type">class="kw">string clicked_object)
  {
  if(m_spin_inc.Name()!=clicked_object)
     class="kw">return(false);
  class="type">class="kw">double value=GetValue();
  ChangeValue(value+m_step_value);
  m_spin_inc.State(true);

数值微调控件的点击与事件分发

在 MT5 自定义面板里做可点选的数字输入框(SpinEdit),核心是把「加/减按钮被点」和「文本框直接改值」两路输入统一收口到 OnEvent。下方代码给出了减号按钮处理函数与事件路由的真实写法,复制进你的 CSpinEdit 类就能跑。 减号处理先比对对象名,名不对直接 false 退出,避免误触其他图形;随后取当前值、按 m_step_value 步长调小并做边界检查,再把按钮视觉状态置 true,最后用 EventChartCustom 把 ON_CLICK_DEC 事件甩给图表,参数带控件 ID 与标签描述,方便上层 EA 监听。 OnEvent 里只认两种图表事件:CHARTEVENT_OBJECT_CLICK 走标签/加/减三个子函数,CHARTEVENT_OBJECT_ENDEDIT 走文本框回车提交。两处都先判 m_spin_edit_state,控件被禁用时直接 return,这能省掉一半无效重绘。 类内还留了 FastSwitching 私有方法声明,配合 OnEventTimer 做长按连发加速——只要 CElement::IsDropdown() 为真,计时器触发就能连续步进数值,适合手调止损点数时少点很多次鼠标。外汇与贵金属图表挂这类控件属高频交互,点错参数可能瞬间下错单,实盘前务必在策略测试器里先点熟。

MQL5 / C++
class="type">bool CSpinEdit::OnClickSpinDec(const class="type">class="kw">string clicked_object)
  {
class=class="str">"cmt">//--- 如果名称不同,退出
   if(m_spin_dec.Name()!=clicked_object)
      class="kw">return(false);
class=class="str">"cmt">//--- 取得当前数值
   class="type">class="kw">double value=GetValue();
class=class="str">"cmt">//--- 按照步长减小,并检查是否超出限制
   ChangeValue(value-m_step_value);
class=class="str">"cmt">//--- 把状态设为打开
   m_spin_dec.State(true);
class=class="str">"cmt">//--- 发送相关消息
   ::EventChartCustom(m_chart_id,ON_CLICK_DEC,CElement::Id(),CElement::Index(),m_label.Description());
   class="kw">return(true);
  }
class="type">void CSpinEdit::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">//--- 处理鼠标左键点击事件
   if(id==CHARTEVENT_OBJECT_CLICK)
      {
      class=class="str">"cmt">//--- 如果控件被屏蔽,退出
      if(!m_spin_edit_state)
         class="kw">return;
      class=class="str">"cmt">//--- 处理点击文字标签
      if(OnClickLabel(sparam))
         class="kw">return;
      class=class="str">"cmt">//--- 处理按下编辑按钮
      if(OnClickSpinInc(sparam))
         class="kw">return;
      if(OnClickSpinDec(sparam))
         class="kw">return;
      class=class="str">"cmt">//---
      class="kw">return;
      }
class=class="str">"cmt">//--- 处理文本框内数值变化事件
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
      {
      class=class="str">"cmt">//--- 如果控件被屏蔽,退出
      if(!m_spin_edit_state)
         class="kw">return;
      class=class="str">"cmt">//--- 处理数值的输入
      if(OnEndEdit(sparam))
         class="kw">return;
      }
  }
class CSpinEdit : class="kw">public CElement
  {
class="kw">private:
   class=class="str">"cmt">//--- 编辑框数值的快速滚动
   class="type">void              FastSwitching(class="type">void);
  };
class="type">void CSpinEdit::OnEventTimer(class="type">void)
  {
class=class="str">"cmt">//--- 如果元件是下拉的
   if(CElement::IsDropdown())
      {

「数值框按住连滚的实现细节」

在自定义数字编辑框里,长按上下箭头会触发连续数值滚动,核心逻辑落在 FastSwitching() 里。它先判断控件是否持有鼠标焦点,没有就直接 return,避免后台误触发。 计数器 m_timer_counter 按 TIMER_STEP_MSC 步长累加,鼠标松开时重置为 SPIN_DELAY_MSC,这决定了连滚的起步延迟与节奏。当前值用 StringToDouble 从编辑框描述取出,再根据 m_spin_inc / m_spin_dec 状态加或减 m_step_value。 最后只要增减按钮仍被按住,就把新值通过 DoubleToString 按 m_digits 精度写回编辑框。外汇与贵金属图表挂此类控件时,连滚步长设太大可能瞬间改掉止损距离,属高风险操作,实盘前应在 MT5 策略测试器里按住试一遍手感。

MQL5 / C++
  ChangeObjectsColor();
  FastSwitching();
   }
  else
   {
   class=class="str">"cmt">//--- 进行颜色的改变和快速的数值滚动
   class=class="str">"cmt">//    如果表单没有被屏蔽
   if(!m_wnd.IsLocked())
     {
      ChangeObjectsColor();
      FastSwitching();
     }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 在编辑框内快速滚动数值                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSpinEdit::FastSwitching(class="type">void)
  {
class=class="str">"cmt">//--- 如果控件上没有焦点,退出
  if(!CElement::MouseFocus())
    class="kw">return;
class=class="str">"cmt">//--- 如果鼠标按钮松开,把计数器回复到初始值
  if(!m_mouse_state)
    m_timer_counter=SPIN_DELAY_MSC;
class=class="str">"cmt">//--- 如果鼠标键被按下
  else
   {
   class=class="str">"cmt">//--- 按照设置的步长增加计数器
   m_timer_counter+=TIMER_STEP_MSC;
   class=class="str">"cmt">//--- 如果小于零,退出
   if(m_timer_counter<class="num">0)
     class="kw">return;
   class=class="str">"cmt">//--- 取得编辑框内的当前数值
   class="type">class="kw">double current_value=::StringToDouble(m_edit.Description());
   class=class="str">"cmt">//--- 如果是增加 
   if(m_spin_inc.State())
     SetValue(current_value+m_step_value);
   class=class="str">"cmt">//--- 如果是减小
   else if(m_spin_dec.State())
     SetValue(current_value-m_step_value);
   class=class="str">"cmt">//--- 如果开关按钮还是按下的,改变数值
   if(m_spin_inc.State() || m_spin_dec.State())
     m_edit.Description(::DoubleToString(GetValue(),m_digits));
   }
  }

◍ 用复选框接管编辑框的可用状态

在自定义类里挂一个 CSpinEdit 实例后,真正要验证的是控件能否被外部状态实时接管。这里让第一个复选框的勾选状态直接决定编辑框是否可用,逻辑和之前第二个复选框控制其他元素完全一致。 创建方法里先写死坐标偏移:X 方向留 150 像素、Y 方向留 75 像素,编辑区宽度 76、整体宽 150 高 18。数值范围锁在 -1000 到 1000,步长 1、不保留小数位,初始值若读取异常则回落到 4。 关键一行在创建完成后:m_spin_edit1.SpinEditState(m_checkbox1.CheckButtonState()),它把编辑框的可用标志绑定到复选框当前态。编译进 MT5 图表后,点掉第一个复选框,编辑框会立刻变灰且不可输入,这比在 OnInit 里手动开关更不容易漏状态。 外汇与贵金属图表加载此类自定义面板属高风险操作,图形资源占用和事件冲突可能在快速行情中导致卡顿,建议先在模拟盘验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 用于创建应用程序的类                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CProgram : class="kw">public CWndEvents
  {
class="kw">private:
   class=class="str">"cmt">//--- 编辑框
   CSpinEdit            m_spin_edit1;
   class=class="str">"cmt">//---
class="kw">private:
   class=class="str">"cmt">//--- 编辑框
class="macro">#define SPINEDIT1_GAP_X(class="num">150)
class="macro">#define SPINEDIT1_GAP_Y(class="num">75)
   class="type">bool                 CreateSpinEdit1(const class="type">class="kw">string text);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建编辑框 class="num">1                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateSpinEdit1(class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 保存窗口指针
   m_spin_edit1.WindowPointer(m_window1);
class=class="str">"cmt">//--- 坐标
   class="type">int x=m_window1.X()+SPINEDIT1_GAP_X;
   class="type">int y=m_window1.Y()+SPINEDIT1_GAP_Y;
class=class="str">"cmt">//--- 数值
   class="type">class="kw">double v=(m_spin_edit1.GetValue()==WRONG_VALUE) ?class="num">4 : m_spin_edit1.GetValue();
class=class="str">"cmt">//--- 在创建之前设置属性
   m_spin_edit1.XSize(class="num">150);
   m_spin_edit1.YSize(class="num">18);
   m_spin_edit1.EditXSize(class="num">76);
   m_spin_edit1.MaxValue(class="num">1000);
   m_spin_edit1.MinValue(-class="num">1000);
   m_spin_edit1.StepValue(class="num">1);
   m_spin_edit1.SetDigits(class="num">0);
   m_spin_edit1.SetValue(v);
   m_spin_edit1.ResetMode(true);
   m_spin_edit1.AreaColor(clrWhiteSmoke);
   m_spin_edit1.LabelColor(clrBlack);
   m_spin_edit1.LabelColorLocked(clrSilver);
   m_spin_edit1.EditColorLocked(clrWhiteSmoke);
   m_spin_edit1.EditTextColor(clrBlack);
   m_spin_edit1.EditTextColorLocked(clrSilver);
   m_spin_edit1.EditBorderColor(clrSilver);
   m_spin_edit1.EditBorderColorLocked(clrSilver);
class=class="str">"cmt">//--- 创建控件
   if(!m_spin_edit1.CreateSpinEdit(m_chart_id,m_subwin,text,x,y))
      class="kw">return(false);
class=class="str">"cmt">//--- 可用性将依赖于第一个复选框的当前状态
   m_spin_edit1.SpinEditState(m_checkbox1.CheckButtonState());
class=class="str">"cmt">//--- 把对象加到对象组的通用数组中
   CWndContainer::AddToElementsArray(class="num">0,m_spin_edit1);
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建交易面板                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateTradePanel(class="type">void)
  {
class=class="str">"cmt">//--- 创建控件表单 class="num">1
class=class="str">"cmt">//--- 创建控件:
class=class="str">"cmt">//    主菜单
class=class="str">"cmt">//--- 上下文菜单
class=class="str">"cmt">//--- 创建状态栏
class=class="str">"cmt">//--- 复选框
class=class="str">"cmt">//--- 编辑框
   if(!CreateSpinEdit1("Spin Edit class="num">1:"))
      class="kw">return(false);
class=class="str">"cmt">//--- 重绘图表
   m_chart.Redraw();
   class="kw">return(true);
  }

图表控件事件怎么在EA里接住

MT5 自定义面板跑起来后,真正干活的是事件回调。OnEvent 里靠 CHARTEVENT_CUSTOM 加偏移量区分不同控件动作,比如标签点击、编辑完成、数值加减按钮,全部走同一个入口,靠 id 和 lparam 分流。 下面这段是事件处理函数的骨架。标签被点中时,若 lparam 等于第一个复选框的 Id(),就把第二个复选框和第一个数字编辑框的状态同步过去——典型的联动逻辑,省得手动挨个改。 编辑框输入结束会触发 ON_END_EDIT,加减按钮触发 ON_CLICK_INC / ON_CLICK_DEC,目前都只打了日志。实盘前你至少要在 ON_END_EDIT 里加参数校验,外汇和贵金属波动快,脏值进模型可能直接错单,这类品种高杠杆高风险,事件层就必须拦一道。 开 MT5 把这段代码塞进你的 CProgram 类,编译后点面板控件,终端里能看到对应 id 和 lparam 打印,确认映射没接反再写业务。

MQL5 / C++
class="type">void CProgram::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">//--- 按下文字标签事件
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      class=class="str">"cmt">//--- 如果按下第一个复选框
      if(lparam==m_checkbox1.Id())
        {
         class=class="str">"cmt">//--- 设置第二个复选框和第一个编辑框的状态
         m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState());
         m_spin_edit1.SpinEditState(m_checkbox1.CheckButtonState());
        }
     }
class=class="str">"cmt">//--- 在编辑事件中完成了数值输入
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
class=class="str">"cmt">//--- 切换编辑框按下事件的按钮
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC || id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
  }

「带勾选的编辑框与组合框怎么接进容器」

CCheckBoxEdit 本质是 CSpinEdit 的扩展,把前文 CCheckBox 的栏位和方法直接并进来,所以它能一边输入数值一边带状态勾选;CCheckComboBox 则是 CComboBox 与 CCheckBox 的合体,下拉列表前每项都可独立勾选。 这两类的实现代码在文章附件里,但接进 MT5 界面容器时有个坑:CCheckComboBox 有下拉列表视图,必须像其它带下拉的元件那样,在 WndContainer.mqh 里把它的下拉指针塞进私有的 m_drop_lists[] 数组,否则下拉不跟随父窗口销毁,文章《图形界面 V: 组合框控件》第三章讲过这套机制。 验证办法很直接:测试程序里放两个普通 CCheckBox,再各挂一个 CCheckBoxEdit 和 CCheckComboBox,把 CCheckBoxEdit 的可用状态绑到第三个复选框、CCheckComboBox 绑到第四个。跑起来勾掉对应框,控件置灰即说明事件链路通了。外汇贵金属面板开发属高风险改造,参数误绑可能导致 EA 界面卡死。

◍ 别急着下结论

这套图形界面库到第六部分第一章为止,已经落地了复选框、编辑框、带复选框的编辑框以及复选组合框四类控件,下一步第二章才会补上滑块和双重滑块。想直接验证手感,可下载 easyandfastgui_mql5.zip(247.86 KB)在 MT5 里跑一遍,看控件响应是否符合你的面板预期。 上面那段 CSpinEdit::OnEndEdit 的代码值得留意:它把描述里的逗号强行替换成点再转 double,这正是评论区 Oleksii 说的「输入 , 而不是 . 」的兼容写法。外汇与贵金属插件开发属高风险活动,这类界面逻辑错误可能让输入值静默偏移,实盘前务必在策略测试器里逐项手测。 库还在迭代,CCanvasTable 改单元格背景色之类功能当时只排进后续更新,没进已公布清单。你自己改源码加这类特性也行,但每改一处都应在离线图表先验证事件回调不崩。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 在输入框中输入数值的处理过程
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSpinEdit::OnEndEdit(const class="type">class="kw">string edited_object)
  {
class=class="str">"cmt">//--- 如果外来对象名称为
   if(m_edit.Name()!=edited_object)
      class="kw">return(false);
class=class="str">"cmt">//--- 获取输入值
   class="type">class="kw">string entered_text=m_edit.Description();
   StringReplace(entered_text,",",".");
   class="type">class="kw">double entered_value=::StringToDouble(entered_text);
class=class="str">"cmt">//--- 检查、更正并记住新值
   ChangeValue(entered_value);
class=class="str">"cmt">//--- 发送相关信息
   ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
   class="kw">return(true);
  }
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到控件状态与事件流的异常提示,把重复劳动交给小布,你专注决策。

常见问题

不是。背景在复选框中主要作为鼠标光标边界与按键按下的识别范围,其颜色通常与父窗口一致,不参与状态区分。
小布盯盘内置的 AIGC 看板可识别常见控件状态异常,若你自行编译的库输出标准事件,打开品种页即可看到诊断,不需手动轮询。
核心是多了一个启用开关控制编辑框的可写状态,勾选才允许输入,屏蔽时只读或隐藏光标,避免误改参数。
活动与屏蔽状态对应控件可用性的视觉反馈,两组图标分别处理用户交互许可与禁止,降低误操作概率。
在创建类实例后、调用公开构造方法前,通过私有属性设置方法指定,否则使用库内默认值可能导致布局偏移。