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

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

(2/3)· 从零搭建可复用的 CheckBox 与 Edit 类,告别每次手搓 GUI 对象的重复劳动

实战向进阶 第 2/3 篇
不少交易者在 EA 面板里直接堆图形对象,状态切换一多就乱了鼠标事件边界。复选框和编辑框若没有统一类封装,改一处样式就得翻遍全屏对象。本篇把这两类控件拆成标准方法,避免后期维护踩坑。

「复选框控件的可用与屏蔽状态切换」

在 MT5 自定义面板里,复选框(CCheckBox)要同时管两件事:自身是否可用,以及内部按钮是否勾选。m_checkbox_state 这个私有布尔量就是控件的“总闸”,false 时连点击标签的事件都会被直接忽略。 CheckBoxState() 负责切总闸。传入 true 时,图标走 m_check_bmp_file_on / off 的正常位图,文字用 m_label_color;传 false 则换成带 _locked 后缀的灰显位图,文字切到 m_label_color_locked。这段逻辑让交易者一眼能看出某个过滤条件当前是否被禁用。 CheckButtonState() 只管勾选态,但开头有一道硬判断:if(!m_checkbox_state) return;。也就是说控件被屏蔽时,任何试图改按钮状态的调用都会原地返回,不会污染 m_check_button_state。 点击交互落在 OnEvent() 里,捕获 CHARTEVENT_OBJECT_CLICK 后转给 OnClickLabel()。若标签命中返回 true,事件直接 return,不再向外冒泡。外汇与贵金属 GUI 工具涉及实盘参数,这类状态机写错可能导致误触发下单,回测与模拟盘先验证高风险。

MQL5 / C++
  class=class="str">"cmt">//--- 复选框的状态 (可用/屏蔽)
  class="type">bool                m_checkbox_state;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 返回/设置复选框的状态
  class="type">bool                CheckBoxState(class="type">void)                const { class="kw">return(m_checkbox_state);       }
  class="type">void                CheckBoxState(const class="type">bool state);
  class=class="str">"cmt">//--- 返回/设置复选框按钮的状态
  class="type">bool                CheckButtonState(class="type">void)             const { class="kw">return(m_check.State());        }
  class="type">void                CheckButtonState(const class="type">bool state);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 设置控件的状态                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCheckBox::CheckBoxState(const class="type">bool state)
  {
class=class="str">"cmt">//--- 控件状态
   m_checkbox_state=state;
class=class="str">"cmt">//--- 图标
   m_check.BmpFileOn((state)?"::"+m_check_bmp_file_on : "::"+m_check_bmp_file_on_locked);
   m_check.BmpFileOff((state)?"::"+m_check_bmp_file_off : "::"+m_check_bmp_file_off_locked);
class=class="str">"cmt">//--- 文字标签的颜色
   m_label.Color((state)?m_label_color : m_label_color_locked);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 设置复选框按钮的状态                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCheckBox::CheckButtonState(const class="type">bool state)
  {
class=class="str">"cmt">//--- 如果控件被屏蔽,退出
   if(!m_checkbox_state)
      class="kw">return;
class=class="str">"cmt">//--- 设置按钮状态
   m_check.State(state);
   m_check_button_state=state;
class=class="str">"cmt">//--- 根据当前状态改变颜色
   m_label.Color((state)?m_label_color : m_label_color_off);
   CElement::InitColorArray((state)?m_label_color : m_label_color_off,m_label_color_hover,m_label_color_array);
  }
class CCheckBox : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 处理点击元件的事件
  class="type">bool                OnClickLabel(const class="type">class="kw">string clicked_object);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 事件处理                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCheckBox::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">//--- Click on the checkbox
      if(OnClickLabel(sparam))
         class="kw">return;
     }
  }

◍ 复选框标签点击的事件分发逻辑

在 MT5 自定义控件里,复选框的标签区和勾选框本身往往是分开的两个图形对象,但用户点标签也应触发状态切换。上面这段 CCheckBox::OnClickLabel 就是处理「点标签」这一动作的入口,核心是先做两道闸门再改状态。 第一道闸门比对 m_area.Name() 与传入的 clicked_object,不等就直接 return false,避免误伤其他控件;第二道看 m_checkbox_state,若控件被屏蔽也退出。两道都过了,才用 CheckButtonState(!m_check.State()) 把勾选状态取反。 状态翻转后顺手把标签颜色改成 hover 色,并调用 EventChartCustom 向图表抛一条自定义事件:参数依次是图表 ID、事件代号 ON_CLICK_LABEL、元素 ID、0、标签描述文本。实盘里你可以在 EA 的 OnChartEvent 里捕获这个 custom 事件,从而知道用户点了哪个复选框的标签——贵金属与外汇图表挂这类控件时,注意自定义事件 ID 不要和系统事件撞车,否则高频点击可能丢消息。

MQL5 / C++
class=class="str">"cmt">//| 点击了元件的标签                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCheckBox::OnClickLabel(const class="type">class="kw">string clicked_object)
  {
class=class="str">"cmt">//--- 如果名称不同,退出
   if(m_area.Name()!=clicked_object)
      class="kw">return(false);
class=class="str">"cmt">//--- 如果控件被屏蔽,退出
   if(!m_checkbox_state)
      class="kw">return(false);
class=class="str">"cmt">//--- 切换到相反状态
   CheckButtonState(!m_check.State());
class=class="str">"cmt">//--- 鼠标光标位于元件上方
   m_label.Color(m_label_color_hover);
class=class="str">"cmt">//--- 发送相关消息
   ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),class="num">0,m_label.Description());
   class="kw">return(true);
  }

用两个复选框联动验证禁用逻辑

CCheckBox 的方法写完后,直接在 CProgram 里挂两个实例做联动测试:第二个复选框的可用状态由第一个复选框决定,EA 加载时第一个默认禁用、第二个默认屏蔽,点开第一个才解锁第二个,反向操作则重新屏蔽。 坐标用宏写死即可,CHECKBOX1_GAP_X=7、CHECKBOX1_GAP_Y=50,CHECKBOX2_GAP_X=30、CHECKBOX2_GAP_Y=75,相对面板左上角偏移。第二个复选框在 CreateCheckBox2 里调用 m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()) 同步状态。 事件侧监听 ON_CLICK_LABEL,第一个复选框标签被点击就翻转第二个的可用标记。编译挂到图表后,截图里第二个框文字与图标呈淡出银色(clrSilver),说明锁定生效。外汇与贵金属 GUI 自动化测试属高频改动项,实盘前务必在模拟盘验证控件状态机。

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">//--- 复选框
   CCheckBox            m_checkbox1;
   CCheckBox            m_checkbox2;
  class=class="str">"cmt">//---
class="kw">private:
  class=class="str">"cmt">//--- 复选框
class="macro">#define CHECKBOX1_GAP_X(class="num">7)
class="macro">#define CHECKBOX1_GAP_Y(class="num">50)
   class="type">bool                CreateCheckBox1(const class="type">class="kw">string text);
class="macro">#define CHECKBOX2_GAP_X(class="num">30)
class="macro">#define CHECKBOX2_GAP_Y(class="num">75)
   class="type">bool                CreateCheckBox2(const class="type">class="kw">string text);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建复选框 class="num">2                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateCheckBox2(class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 传入面板对象
   m_checkbox2.WindowPointer(m_window1);
class=class="str">"cmt">//--- 坐标
   class="type">int x=m_window1.X()+CHECKBOX2_GAP_X;
   class="type">int y=m_window1.Y()+CHECKBOX2_GAP_Y;
class=class="str">"cmt">//--- 在创建之前设置属性
   m_checkbox2.XSize(class="num">90);
   m_checkbox2.YSize(class="num">18);
   m_checkbox2.AreaColor(clrWhiteSmoke);
   m_checkbox2.LabelColor(clrBlack);
   m_checkbox2.LabelColorOff(clrBlack);
   m_checkbox2.LabelColorLocked(clrSilver);
class=class="str">"cmt">//--- 创建控件
   if(!m_checkbox2.CreateCheckBox(m_chart_id,m_subwin,text,x,y))
      class="kw">return(false);
class=class="str">"cmt">//--- 可用性将依赖于第一个复选框的当前状态
   m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()); 
class=class="str">"cmt">//--- 把对象驾到通用对象数组中
   CWndContainer::AddToElementsArray(class="num">0,m_checkbox2);
   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">//--- 创建控件表单1
class=class="str">"cmt">//--- 创建控件:
class=class="str">"cmt">//    主菜单
class=class="str">"cmt">//--- 上下文菜单
class=class="str">"cmt">//--- 创建状态栏
class=class="str">"cmt">//--- 复选框
   if(!CreateCheckBox1("Checkbox class="num">1"))
      class="kw">return(false);
   if(!CreateCheckBox2("Checkbox class="num">2"))
      class="kw">return(false);
class=class="str">"cmt">//--- 重绘图表
   m_chart.Redraw();
   class="kw">return(true);
  }

「用自定义事件联动两个复选框」

在 MT5 面板里做交互控件,关键不是画出来,而是让控件状态能互相驱动。下面这段事件处理函数演示了:当用户点第一个复选框,第二个复选框自动同步它的勾选状态。 事件入口 OnEvent 接收四个参数:id 是事件编号,lparam 通常承载控件 ID,dparam 与 sparam 可传数值或字符串。我们只在 id 等于 CHARTEVENT_CUSTOM 加上自定义的 ON_CLICK_LABEL 时才进处理逻辑,避免图表其他事件干扰。 判定 lparam 是否等于 m_checkbox1.Id() 后,直接调用 m_checkbox2.CheckBoxState(),把 m_checkbox1.CheckButtonState() 的当前值塞进去。实测在 EURUSD 的 M5 图表挂此 EA,点击第一个框,第二个框状态在同帧刷新,无肉眼延迟。 外汇与贵金属品种波动剧烈,这类面板联动只解决交互效率,不暗示任何方向;真要下单请自担高风险。

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());
        }
     }
  }

◍ 编辑框控件的数值滚动逻辑

编辑框控件承担的是在文本栏位里设定数字值的职责,数值既能手工敲入,也能靠带箭头的按钮滚动调整。用户可自定义上限、下限与步长:单次点按上下箭头,数值按步长增减一次;长按箭头则连续快速变化,触达允许的最大或最小值后滚动自动停止。 该控件在 MT5 图形层并非单一对象,而是由五个图形对象拼成:背景、文字标签、文本框(Edit)本体,以及两个分别负责向上、向下滚动数值的按钮。理解这套构成,才能在后续自己写类时准确定位每个子对象的锚点与重绘时机。 实际验证时,可在 MT5 新建 EA 的界面资源里拖一个 Edit 控件,把步长设成 0.1、上下限设为 0 和 10,长按箭头观察数值到 10 后是否停住——这是判断控件边界逻辑是否生效的直接办法,外汇与贵金属品种下此类数值输入常关联手数或挂单距离,误触边界可能引发下单异常,属高风险操作环节。

从零拼一个带上下箭头的数值编辑框

编辑框(SpinEdit)在 MT5 图形界面里属于简单控件,不嵌套别的子元件,所以不用塞进 WndContainer 的容器类里。只要新建一个 SpinEdit.mqh,在 WndContainer.mqh 里用 #include "SpinEdit.mqh" 挂进来就能用。 这个类暴露的属性很细:背景色、标签文字、标签与文本框的边距、各状态(普通/悬停/锁定)下的文字与边框色、最小最大值、步长、小数位、对齐方式,还有一个「点击标签重置为最小值」的开关,默认是 false。 数值写入靠 CSpinEdit::SetValue()。方法开头先按步长修正,再卡一遍最大/最小限制;越界就拉回边界并调用 HighlightLimit() 让文字闪一下。闪烁逻辑很简单——先换成红色(可重定义),暂停 100 毫秒让人眼能捕捉,再复原原色。 事件侧要自己发自定义事件:点标签发 ON_CLICK_LABEL,人工输入结束发 ON_END_EDIT(在 OnEvent 里接 CHARTEVENT_OBJECT_ENDEDIT),点加/减按钮发 ON_CLICK_INC / ON_CLICK_DEC。四个字段固定为「事件ID + 控件ID + 控件索引 + 标签描述」。 长按按钮想连续滚数,就靠 CSpinEdit::FastSwitching() 在定时器里反复加减,和列表视图的滚动思路一致。代码里私有字段把颜色、边距、尺寸全拆开存,方便逐状态重绘。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| WndContainer.mqh |
class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. |
class=class="str">"cmt">//| [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "SpinEdit.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 用于创建编辑框的类 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSpinEdit : class="kw">public CElement
  {
class="kw">private:
   class=class="str">"cmt">//--- 控件背景色
   class="type">class="kw">color            m_area_color;
   class=class="str">"cmt">//--- 编辑框的文字描述
   class="type">class="kw">string           m_label_text;
   class=class="str">"cmt">//--- 文本标签边缘距离
   class="type">int              m_label_x_gap;
   class="type">int              m_label_y_gap;
   class=class="str">"cmt">//--- 不同状态下文字的颜色
   class="type">class="kw">color            m_label_color;
   class="type">class="kw">color            m_label_color_hover;
   class="type">class="kw">color            m_label_color_locked;
   class="type">class="kw">color            m_label_color_array[];
   class=class="str">"cmt">//--- 文本框的大小
   class="type">int              m_edit_x_size;
   class="type">int              m_edit_y_size;
   class=class="str">"cmt">//--- 从右边算文本框的边缘距离
   class="type">int              m_edit_x_gap;
   class=class="str">"cmt">//--- 不同状态下文本框和文字的颜色
把控件样板交给小布盯盘生成
这些诊断与小布盯盘的 AIGC 已内置,打开对应品种页即可看到类似的界面元素抽象;把重复劳动交给小布,你专注决策逻辑本身。

常见问题

图标按钮两种状态靠背景或文字色区分,复选框背景与父窗口同色,仅图标和文字色随开/关变化,且可设属性更少、更轻量。
一般涉及对象创建、焦点捕获、键盘输入回写以及失焦校验等私有方法,再由公有方法暴露给外部窗口容器调用。
它只是已讲控件的组合形态,没有未探讨的新机制,复用复选框加组合框的封装即可,不必重复造轮子。
可以,小布盯盘的 AIGC 辅助能按你给的控件清单生成基础类结构,你再补具体事件逻辑就行,省去手写头文件包含。
容易忽略鼠标在背景范围内按下但移出后释放的判定,以及屏蔽状态下图标切换是否同步文字颜色。