图形界面 VI: 复选框控件,编辑框控件以及它们的组合类型 (第一章)·进阶篇
(2/3)· 从零搭建可复用的 CheckBox 与 Edit 类,告别每次手搓 GUI 对象的重复劳动
「复选框控件的可用与屏蔽状态切换」
在 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 工具涉及实盘参数,这类状态机写错可能导致误触发下单,回测与模拟盘先验证高风险。
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 不要和系统事件撞车,否则高频点击可能丢消息。
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 自动化测试属高频改动项,实盘前务必在模拟盘验证控件状态机。
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,点击第一个框,第二个框状态在同帧刷新,无肉眼延迟。 外汇与贵金属品种波动剧烈,这类面板联动只解决交互效率,不暗示任何方向;真要下单请自担高风险。
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() 在定时器里反复加减,和列表视图的滚动思路一致。代码里私有字段把颜色、边距、尺寸全拆开存,方便逐状态重绘。
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">//--- 不同状态下文本框和文字的颜色