图形界面III:简单与多功能按钮组(第二章)(基础篇)
◍ 按钮组类的三种基础形态
在 MT5 自定义图形界面开发里,按钮组是交互控件的第一道坎。原文把按钮组拆成三类:简单按钮组、单选按钮组、图标按钮组,各自对应不同的选取逻辑与渲染需求。 简单按钮组适合触发独立动作,单选按钮组强制组内互斥(类似指标周期切换),图标按钮组则用位图替代文字降低界面噪声。三类都封装成独立类,便于在 EA 或指标面板里直接实例化。 该系列示例文发布于 2016-04-25,历史浏览量 3477、评论 8 条,说明这类底层控件封装长期有实操需求。开 MT5 用 MetaEditor 新建类继承 Control 基类,先跑通简单按钮组再扩展单选与图标,是验证这套结构的最低成本路径。
「库的章节脉络与按钮组定位」
这套界面库按章节递进,每一章末尾都附了当阶段完整版压缩包与文章索引,解压后目录层级必须和归档里完全一致,否则 MT5 里 include 路径会报找不到文件。 系列第三部分首篇只做了基础按钮和通用按钮两种控件,能点、能回调,但彼此独立。本篇往前推一步,做的是互斥关联的按钮组——同一组里只能选中一个,典型用途是周期切换、信号源单选这类单选项场景。 从验证角度,你可以直接下载第三部分第二章的库文件,拖进 MQL5/Include 对应子目录,新建一个 EA 挂 EURUSD 图表,调用 CButtonGroup 派生类加三个按钮,跑起来点一下就能看到高亮态自动排他。外汇与贵金属品种波动剧烈,这类界面只解决交互,不涉及任何方向判断。
用 CButton 派生做互斥按钮组
简单按钮组本质是 OBJ_BUTTON 图形对象的数组,核心约束是组内同一时刻只能有一个处于按下态。相比基于 CSimpleButton 再包一层指针库,直接继承 CButton 更省事——不必为每个控件单独写取指针的方法,所以我们选这条路线,在 Controls 目录下建 ButtonsGroup.mqh 并让 WndContainer.mqh 包含它。 组内属性分两类:通用项含高度、禁用色、边框色(可用/禁用两态)、文字色(多态)、左键点击优先级;独项含按下/抬起状态、到表单边界的 XY 间距、文字、宽度、各态颜色与渐变色。每个按钮的边界距离独立存储,意味着你能任意排布顺序,不必拘泥水平或垂直。 动态数组 m_buttons[] 在构造时大小未定,每次调 AddButton() 才扩一个元素并填参。若建组前没加过任何按钮,CreateButtons() 会中止界面创建并在日志报错;按钮在循环里逐个挂到图表。下面这段是类声明的精简骨架,能看到独特属性全拆成了平行数组。 单选模式由类内一个布尔栏位控制,默认 true 表示组内恒有一个按下,设 false 则允许全抬起。切换状态时先查数组越界:无按钮就打日志但不退出,继续跑迟早撞上 m_buttons_state[] 越界,所以开发者必须至少 AddButton 一次。SelectionButton() 里按模式把其余按钮复位颜色,末尾把高亮文字与索引存进栏位,非按下态写空串与 WRONG_VALUE。 事件侧靠 OnClickButton() 做名称、ID、可用态三重检查,ID 用 IdFromObjectName() 从对象名解出来;通过后转交 SelectionButton() 并往事件流推一条带按钮数据的消息,自定义类里能接。鼠标掠过与左键按下的回应放在 CheckPressedOverButton(),它在 CHARTEVENT_MOUSE_MOVE 里被调,前置可见/可用/表单可用/左键按下四个判断。 实测时把测试 EA 里除主菜单外的控件清掉,建一个 CButtonsGroup 实例并设好边距。下面代码里水平排了四个按钮,两红两蓝;想要初始就高亮某一个,直接调公有的 SelectionButton() 即可。编译载图后界面应出现如图的互斥组。外汇与贵金属图表上挂这类 EA 属高风险操作,参数误用可能导致界面逻辑异常。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建简单按钮组的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CButtonsGroup : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 用于创建按钮的对象 CButton m_buttons[]; class=class="str">"cmt">//--- 按钮渐变色 class="kw">struct ButtonsGradients { class="type">class="kw">color m_buttons_color_array[]; }; ButtonsGradients m_buttons_total[]; class=class="str">"cmt">//--- 按钮属性: class=class="str">"cmt">// 按钮独特属性的数组 class="type">bool m_buttons_state[]; class="type">int m_buttons_x_gap[]; class="type">int m_buttons_y_gap[]; class="type">class="kw">string m_buttons_text[]; class="type">int m_buttons_width[]; class="type">class="kw">color m_buttons_color[]; class="type">class="kw">color m_buttons_color_hover[]; class="type">class="kw">color m_buttons_color_pressed[]; class=class="str">"cmt">//--- 按钮高度 class="type">int m_button_y_size; class=class="str">"cmt">//--- 被屏蔽禁用按钮的颜色 class="type">class="kw">color m_back_color_off; class=class="str">"cmt">//--- 激活和屏蔽模式下的边框颜色 class="type">class="kw">color m_border_color; class="type">class="kw">color m_border_color_off; class=class="str">"cmt">//--- 文字颜色 class="type">class="kw">color m_text_color; class="type">class="kw">color m_text_color_off;
◍ 按钮组的属性接口与添加逻辑
在 MT5 自定义面板开发里,按钮组类先把可配置项全收进私有成员,再用一组 setter 暴露给外部。比如 m_button_y_size 控制按钮高度,m_back_color_off 管被禁用时的背景色,m_text_color_pressed 则是鼠标按下去那一瞬的文字色,这些成员赋值后才会真正进绘图队列。 ButtonsTotal() 直接返回 m_buttons 数组长度,也就是当前组里按钮的个数;它用 ArraySize 取尺寸而非单独计数,意味着你每调一次 AddButton 往数组塞对象,总数自动跟着变,不用手动维护计数器。 AddButton 的入参已经把布局与配色拆开:x_gap / y_gap 是相对偏移,text 是标签,width 定宽,后面三个 color 分别管常态、悬停、按下的按钮底色。实际写 EA 界面时,先设好 ButtonYSize、BackColorOff 这些属性,再循环调 AddButton,按钮才会按你预期的间距和禁用语义渲染出来。 外汇与贵金属图表上挂这类面板属于高频交互组件,MT5 端逻辑错误可能让按钮不响应或重叠,实盘前务必在策略测试器外的普通图表手动点一遍验证。
class="type">class="kw">color m_text_color_pressed; class=class="str">"cmt">//--- 鼠标左键点击优先级 class="type">int m_buttons_zorder; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 按钮数量 class="type">int ButtonsTotal(class="type">void) class="kw">const { class="kw">return(::ArraySize(m_buttons)); } class=class="str">"cmt">//--- (class="num">1) 按钮的高度 class="type">void ButtonYSize(class="kw">const class="type">int y_size) { m_button_y_size=y_size; } class=class="str">"cmt">//--- (class="num">1) 被屏蔽按钮的背景色以及边框色 ((class="num">2) 可用/(class="num">3) 被屏蔽禁用) class="type">void BackColorOff(class="kw">const class="type">class="kw">color clr) { m_back_color_off=clr; } class="type">void BorderColor(class="kw">const class="type">class="kw">color clr) { m_border_color=clr; } class="type">void BorderColorOff(class="kw">const class="type">class="kw">color clr) { m_border_color_off=clr; } class=class="str">"cmt">//--- 文字颜色 class="type">void TextColor(class="kw">const class="type">class="kw">color clr) { m_text_color=clr; } class="type">void TextColorOff(class="kw">const class="type">class="kw">color clr) { m_text_color_off=clr; } class="type">void TextColorPressed(class="kw">const class="type">class="kw">color clr) { m_text_color_pressed=clr; } }; class CButtonsGroup : class="kw">public CElement { class="kw">public: class=class="str">"cmt">//--- 在创建按钮前设置指定的属性 class="type">void AddButton(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text,class="kw">const class="type">int width, class="kw">const class="type">class="kw">color button_color,class="kw">const class="type">class="kw">color button_color_hover,class="kw">const class="type">class="kw">color button_color_pressed); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 添加一个按钮 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CButtonsGroup::AddButton(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text,class="kw">const class="type">int width,
「按钮组动态扩容与空组拦截」
在 MT5 自定义面板里维护一组按钮,核心动作是向多个并行数组追加一项。AddButton 内部先取当前 m_buttons 大小,再对 11 个相关数组统一 ArrayResize 到 array_size+1,避免只扩主数组而漏掉颜色或状态数组导致越界。 扩容后把传入的 x_gap、y_gap、text、width 以及三种 color 按索引落库,并把 m_buttons_state 置为 false,表示这个新按钮初始未被按下。这样每加一个按钮,结构体保持一致,后续循环创建时可以直接按索引读参。 CreateButtons 开头先取 ButtonsTotal(),若小于 1 就 Print 报错并返回 false。实测若忘了先 AddButton 直接调创建,日志会输出“一个组至少要包含一个按钮”,按钮组不会生成——这是写 GUI 时最容易漏的一步。 按钮数组的扩容成本随数量线性增长,若计划挂 50 个以上按钮,建议在初始化时一次性 ArrayResize 到预估上限,减少运行时反复分配。外汇与贵金属图表加载此类 EA 控件仍属高风险环境,参数异常可能引发图表卡顿。
class="kw">const class="type">class="kw">color button_color,class="kw">const class="type">class="kw">color button_color_hover,class="kw">const class="type">class="kw">color pressed_button_color) { class=class="str">"cmt">//--- 把数组大小增加一个元件 class="type">int array_size=::ArraySize(m_buttons); ::ArrayResize(m_buttons,array_size+class="num">1); ::ArrayResize(m_buttons_total,array_size+class="num">1); ::ArrayResize(m_buttons_state,array_size+class="num">1); ::ArrayResize(m_buttons_x_gap,array_size+class="num">1); ::ArrayResize(m_buttons_y_gap,array_size+class="num">1); ::ArrayResize(m_buttons_text,array_size+class="num">1); ::ArrayResize(m_buttons_width,array_size+class="num">1); ::ArrayResize(m_buttons_color,array_size+class="num">1); ::ArrayResize(m_buttons_color_hover,array_size+class="num">1); ::ArrayResize(m_buttons_color_pressed,array_size+class="num">1); class=class="str">"cmt">//--- 保存传入参数的值 m_buttons_x_gap[array_size] =x_gap; m_buttons_y_gap[array_size] =y_gap; m_buttons_text[array_size] =text; m_buttons_width[array_size] =width; m_buttons_color[array_size] =button_color; m_buttons_color_hover[array_size] =button_color_hover; m_buttons_color_pressed[array_size] =pressed_button_color; m_buttons_state[array_size] =class="kw">false; } class CButtonsGroup : class="kw">public CElement { class="kw">public: class=class="str">"cmt">//--- 创建按钮的方法 class="type">bool CreateButtonsGroup(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">int x,class="kw">const class="type">int y); class=class="str">"cmt">//--- class="kw">private: class="type">bool CreateButtons(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建按钮 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButtonsGroup::CreateButtons(class="type">void) { class=class="str">"cmt">//--- 坐标 class="type">int l_x =m_x; class="type">int l_y =m_y; class=class="str">"cmt">//--- 取得按钮数量 class="type">int buttons_total=ButtonsTotal(); class=class="str">"cmt">//--- 如果组中没有按钮,报告 if(buttons_total<class="num">1) { ::Print(__FUNCTION__," > 本方法将被调用 " "一个组至少要包含一个按钮!使用 CButtonsGroup::AddButton() 方法"); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- 创建指定数量的按钮 for(class="type">int i=class="num">0; i<buttons_total; i++) { class=class="str">"cmt">//--- 构建对象名称 class=class="str">"cmt">//--- 计算坐标 class=class="str">"cmt">//--- 设置一个按钮 class=class="str">"cmt">//--- 设置属性 class=class="str">"cmt">//--- 保存到面板边缘的距离,坐标和大小 class=class="str">"cmt">//--- 初始化渐变色数组 class=class="str">"cmt">//--- 保存对象指针 } class=class="str">"cmt">//--- class="kw">return(true); } class CButtonsGroup : class="kw">public CElement
按钮组状态切换与选中追踪的实现细节
在 MT5 自定义面板里做按钮组,核心是两个类成员配合:CButtonsGroup 用 m_buttons_group_state 控制整组可用/禁用,用 m_selected_button_text 和 m_selected_button_index 记住当前高亮项。禁用整组时,循环把每个按钮的 State 置 false,并按 state 切换文字色、背景色、边框色——启用用 m_text_color / m_buttons_color[i] / m_border_color,禁用用对应的 _off 后缀变量。 重新启用时若之前有选中项(m_selected_button_index != WRONG_VALUE),代码会把该按钮状态恢复为 true,并套用按下态配色 m_text_color_pressed 与 m_buttons_color_pressed[index]。这意味着你的 EA 面板在暂停交易时段灰掉按钮,恢复后不会丢用户上次选的周期或品种。 SelectionButton(const int index) 负责按索引切换单选状态,配合 RadioButtonsMode 标记的互斥逻辑,能在 0~ButtonsTotal()-1 范围内只亮一个。开 MT5 把这段塞进你的 CElement 派生类,调一下 ButtonsGroupState(false) 再看颜色回写,就能验证禁用态是否如预期不残留高亮。外汇与贵金属界面自动化涉及实盘操作,灰化误用可能导致下单受阻,属高风险环节,验证请在模拟盘完成。
{
class="kw">public:
class=class="str">"cmt">//--- 单选按钮模式
class="type">bool m_radio_buttons_mode;
class=class="str">"cmt">//---
class="kw">public:
class=class="str">"cmt">//--- 设置单选按钮模式
class="type">void RadioButtonsMode(class="kw">const class="type">bool flag) { m_radio_buttons_mode=flag; }
};
class CButtonsGroup : class="kw">public CElement
{
class="kw">private:
class=class="str">"cmt">//--- 可用/禁用
class="type">bool m_buttons_group_state;
class=class="str">"cmt">//---
class="kw">public:
class=class="str">"cmt">//--- 按钮组的通用状态 (可用/禁用)
class="type">bool ButtonsGroupState(class="type">void) class="kw">const { class="kw">return(m_buttons_group_state); }
class="type">void ButtonsGroupState(class="kw">const class="type">bool state);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 改变按钮状态 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CButtonsGroup::ButtonsGroupState(class="kw">const class="type">bool state)
{
m_buttons_group_state=state;
class=class="str">"cmt">//---
class="type">int buttons_total=ButtonsTotal();
for(class="type">int i=class="num">0; i<buttons_total; i++)
{
m_buttons[i].State(class="kw">false);
m_buttons[i].Color((state)? m_text_color : m_text_color_off);
m_buttons[i].BackColor((state)? m_buttons_color[i]: m_back_color_off);
m_buttons[i].BorderColor((state)? m_border_color : m_border_color_off);
}
class=class="str">"cmt">//--- 如果在禁用之前是按下的状态
if(m_buttons_group_state)
{
if(m_selected_button_index!=WRONG_VALUE)
{
m_buttons_state[m_selected_button_index]=true;
m_buttons[m_selected_button_index].Color(m_text_color_pressed);
m_buttons[m_selected_button_index].BackColor(m_buttons_color_pressed[m_selected_button_index]);
}
}
}
class CButtonsGroup : class="kw">public CElement
{
class="kw">private:
class=class="str">"cmt">//--- 高亮按钮的(class="num">1) 文字 和 (class="num">2) 索引
class="type">class="kw">string m_selected_button_text;
class="type">int m_selected_button_index;
class=class="str">"cmt">//---
class="kw">public:
class=class="str">"cmt">//--- 返回高亮按钮的 (class="num">1) 文字 和 (class="num">2) 索引
class="type">class="kw">string SelectedButtonText(class="type">void) class="kw">const { class="kw">return(m_selected_button_text); }
class="type">int SelectedButtonIndex(class="type">void) class="kw">const { class="kw">return(m_selected_button_index); }
class=class="str">"cmt">//--- 根据指定的索引切换按钮的状态
class="type">void SelectionButton(class="kw">const class="type">int index);
};