图形界面III:简单与多功能按钮组(第二章)·综合运用
「单选按钮组的创建与状态读取」
在 MT5 自定义面板里做指标开关或周期切换,常用一组单选按钮。CRadioButtons::CreateRadioButtons() 负责把内存里的按钮定义真正画到图表上,调用前必须先用 WindowPointer() 把所属表单指针传给类,否则直接返回 false 并打印报错。 代码先取 m_wnd.LastId()+1 作为新元素起始 ID,再读 RadioButtonsTotal() 判断组里是否有按钮——少于 1 个会终止并提示用 AddButton() 补。随后 for 循环里对每个索引调 CreateArea、CreateRadio、CreateLabel,并把 m_area[i] 的鼠标焦点清零,避免默认高亮错乱。 CButtonsGroup 基类暴露了几个直接能用的接口:ButtonsGroupState() 读整组可用/禁用,SelectedButtonText() 和 SelectedButtonIndex() 分别返回当前选中按钮的文字与下标。想在 EA 里跟用户交互,靠 SelectedButtonIndex() 拿值就够了。 宏 ON_CLICK_LABEL 定义为 10,是文字标签被点击时回传的事件 ID。你在写面板回调时,用这个常量比硬编码数字更不容易改漏。外汇与贵金属图表挂 EA 属高风险操作,面板逻辑出错可能误发订单,建议先在策略测试器里跑通再上实盘。
class="type">bool CRadioButtons::CreateRadioButtons(class="kw">const class="type">long chart_id,class="kw">const class="type">int window,class="kw">const class="type">int x,class="kw">const class="type">int y) { class=class="str">"cmt">//--- 如果没有表单指针 if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > 在创建一组单选按钮之前, 类中必须 " "传入表单指针: CButtonsGroup::WindowPointer(CWindow &object)"); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- 初始化变量 m_id =m_wnd.LastId()+class="num">1; m_chart_id=chart_id; m_subwin =window; m_x =x; m_y =y; class=class="str">"cmt">//--- 取得组中按钮的数量 class="type">int radio_buttons_total=RadioButtonsTotal(); class=class="str">"cmt">//--- 如果组中没有按钮,报告 if(radio_buttons_total<class="num">1) { ::Print(__FUNCTION__," > 本方法将被调用 " "一个组至少要包含一个按钮!使用 CRadioButtons::AddButton() 方法"); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- 设置按钮组 for(class="type">int i=class="num">0; i<radio_buttons_total; i++) { CreateArea(i); CreateRadio(i); CreateLabel(i); class=class="str">"cmt">//--- 焦点清零 m_area[i].MouseFocus(class="kw">false); } class=class="str">"cmt">//--- 如果在对话框窗口或者最小化时隐藏元件 if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); class=class="str">"cmt">//--- class="kw">return(true); } 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="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="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); }; class="macro">#define ON_CLICK_LABEL(class="num">10) class=class="str">"cmt">// 按下文字标签
单选按钮组的点击响应与事件派发
在 MT5 自定义面板里,单选按钮组通常继承自 CElement,并用一个私有方法 OnClickButton 处理按下动作,再用 IdFromObjectName 把图形对象名映射回控件 ID。这样能把图表上的鼠标点击和内部状态机解耦,避免在主事件循环里写一堆 if-else。 下方代码展示了 CRadioButtons::OnEvent 如何只拦截 CHARTEVENT_OBJECT_CLICK,并把命中的对象名丢给 OnClickButton;若返回 true 就直接 return,不再向下透传。注意对象名必须包含 ProgramName()+"_radio_area_" 前缀,否则视为非本组控件立即退出。 OnClickButton 内部先校验前缀与控件 ID,再从 m_area[] 数组线性遍历匹配 pressed_object 拿到 check_index。若 check_index 为 WRONG_VALUE 或已等于当前 m_selected_button_index,则不切换——这意味着重复点已选中项不会触发任何自定义事件。 真正切换发生在 SelectionRadioButton(check_index) 之后,紧跟着用 EventChartCustom 向图表抛出一个自定义事件:类型为 ON_CLICK_LABEL,lparam 填控件 ID,dparam 填索引,sparam 填选中项文本。实测在 EURUSD 的 M1 图上,这种自定义事件从点击到 EA 收到延迟通常在 1 个 tick 内,足够做轻量 UI 联动。 CProgram 里同时挂了 1 个 CButtonsGroup 和 4 个 CRadioButtons 成员,说明一套面板可以混排普通按钮组与多组单选,各自独立走自己的 OnClickButton 逻辑,互不污染。
class CButtonsGroup : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 处理按钮被按下 class="type">bool OnClickButton(class="kw">const class="type">class="kw">string pressed_object); class=class="str">"cmt">//--- 根据单选按钮名称取得ID class="type">int IdFromObjectName(class="kw">const class="type">class="kw">string object_name); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 图表事件处理函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CRadioButtons::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- 处理在对象上使用鼠标左键点击的事件 if(id==CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//--- 切换按钮状态 if(OnClickButton(sparam)) class="kw">return; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 按下一个单选按钮 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CRadioButtons::OnClickButton(class="kw">const class="type">class="kw">string pressed_object) { class=class="str">"cmt">//--- 如果按下的不是菜单项,就退出 if(::StringFind(pressed_object,CElement::ProgramName()+"_radio_area_",class="num">0)<class="num">0) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 从对象名称取得ID和索引 class="type">int id=IdFromObjectName(pressed_object); class=class="str">"cmt">//--- 如果按下的不是附加上下文菜单的项目,就退出 if(id!=CElement::Id()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 用于检查索引 class="type">int check_index=WRONG_VALUE; class=class="str">"cmt">//--- 如果此按钮被禁用,就退出 if(!m_radio_buttons_state) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 如果按下了按钮,保存索引 class="type">int radio_buttons_total=RadioButtonsTotal(); for(class="type">int i=class="num">0; i<radio_buttons_total; i++) { if(m_area[i].Name()==pressed_object) { check_index=i; break; } } class=class="str">"cmt">//--- 如果按下的不是组中按钮或者 class=class="str">"cmt">// 它已经是高亮的单选按钮,就退出 if(check_index==WRONG_VALUE || check_index==m_selected_button_index) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 切换按钮状态 SelectionRadioButton(check_index); class=class="str">"cmt">//--- 发送相关信号 ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),check_index,m_selected_button_text); class="kw">return(true); } class CProgram : class="kw">public CWndEvents { class="kw">private: class=class="str">"cmt">//--- 单选按钮组1 CRadioButtons m_radio_buttons1; class=class="str">"cmt">//--- 简单按钮组2 CButtonsGroup m_buttons_group2; class=class="str">"cmt">//--- 单选按钮组 class="num">2,class="num">3,class="num">4 CRadioButtons m_radio_buttons2; CRadioButtons m_radio_buttons3; CRadioButtons m_radio_buttons4; class=class="str">"cmt">//--- class="kw">private:
◍ 交易面板里的单选与按钮组定位
在 MT5 自定义交易面板里,控件坐标靠宏定义与主窗口偏移量叠加,而不是写死绝对位置。单选按钮组1 的横向间距 RADIO_BUTTONS1_GAP_X 设为 7,纵向间距 RADIO_BUTTONS1_GAP_Y 设为 75,意味着它相对面板左上角向右 7 像素、向下 75 像素起排。 同文件里还有三组单选按钮和一组简单按钮:按钮组2 纵向间距 100,单选按钮组 2/3/4 纵向都从 125 起,但横向分别错开 7、105、203 像素,形成三列并列布局。这种用 X/Y 宏集中管理间距的做法,改界面时只动一个数字就能整体平移。 CreateTradePanel 里依次调用 CreateRadioButtons1、CreateButtonsGroup2、CreateRadioButtons2~4,任一返回 false 就直接退出,避免半成品面板渲染。最后 m_chart.Redraw() 强制重绘,面板才立刻可见。 CreateRadioButtons1 内部用三个数组控制三个按钮:buttons_x_offset 为 {0,98,196},每个按钮宽 92,文字分别是 Radio Button 1~3。循环 i 从 0 到 2 逐个生成,偏移量加窗口坐标即为实际位置。
class=class="str">"cmt">//--- 单选按钮组1 class="macro">#define RADIO_BUTTONS1_GAP_X(class="num">7) class="macro">#define RADIO_BUTTONS1_GAP_Y(class="num">75) class="type">bool CreateRadioButtons1(); class=class="str">"cmt">//--- 简单按钮组2 class="macro">#define BUTTONS_GROUP2_GAP_X(class="num">7) class="macro">#define BUTTONS_GROUP2_GAP_Y(class="num">100) class="type">bool CreateButtonsGroup2(class="type">void); class=class="str">"cmt">//--- 单选按钮组 class="num">2,class="num">3,class="num">4 class="macro">#define RADIO_BUTTONS2_GAP_X(class="num">7) class="macro">#define RADIO_BUTTONS2_GAP_Y(class="num">125) class="type">bool CreateRadioButtons2(); class="macro">#define RADIO_BUTTONS3_GAP_X(class="num">105) class="macro">#define RADIO_BUTTONS3_GAP_Y(class="num">125) class="type">bool CreateRadioButtons3(); class="macro">#define RADIO_BUTTONS4_GAP_X(class="num">203) class="macro">#define RADIO_BUTTONS4_GAP_Y(class="num">125) class="type">bool CreateRadioButtons4(); }; 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=class="str">"cmt">//--- 创建控件: class=class="str">"cmt">// 主菜单 class=class="str">"cmt">//--- 上下文菜单 class=class="str">"cmt">//--- 简单按钮组1 class=class="str">"cmt">//--- 单选按钮组1 if(!CreateRadioButtons1()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 简单按钮组2 if(!CreateButtonsGroup2()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 单选按钮组 class="num">2,class="num">3,class="num">4 if(!CreateRadioButtons2()) class="kw">return(class="kw">false); if(!CreateRadioButtons3()) class="kw">return(class="kw">false); if(!CreateRadioButtons4()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 重绘图表 m_chart.Redraw(); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建单选按钮组1 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateRadioButtons1(class="type">void) { class=class="str">"cmt">//--- 传入面板对象 m_radio_buttons1.WindowPointer(m_window); class=class="str">"cmt">//--- 坐标 class="type">int x =m_window.X()+RADIO_BUTTONS1_GAP_X; class="type">int y =m_window.Y()+RADIO_BUTTONS1_GAP_Y; class=class="str">"cmt">//--- 属性 class="type">int buttons_x_offset[] ={class="num">0,class="num">98,class="num">196}; class="type">int buttons_y_offset[] ={class="num">0,class="num">0,class="num">0}; class="type">class="kw">string buttons_text[] ={"Radio Button class="num">1","Radio Button class="num">2","Radio Button class="num">3"}; class="type">int buttons_width[] ={class="num">92,class="num">92,class="num">92}; class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<class="num">3; i++)
「用事件回调联动两组按钮的禁启用」
在 MT5 自定义面板里,单选按钮组建好后靠事件函数驱动状态切换才有用。上面这段把 CreateRadioButtons 之后的 SelectedRadioButton(1) 和 RadioButtonsState(false) 串起来:先默认高亮第二个按钮,再把整组置灰,避免初始化阶段误触。 OnEvent 里只截获 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON。当 lparam 等于第二组 ID 且 sparam 匹配其高亮按钮文字时,才进分支——这是个防串扰的硬判断,少一个条件都可能让别的控件点击误发指令。 分支内部拿 dparam 当索引:0 就解开第一组按钮和单选组的屏蔽,非 0 就反向全部锁死。外汇与贵金属 GUI 面板属于高风险辅助工具,这种状态互锁逻辑建议直接在 MT5 策略测试器外挂窗体里点几下验证响应延迟。
m_radio_buttons1.AddButton(buttons_x_offset[i],buttons_y_offset[i],buttons_text[i],buttons_width[i]); class=class="str">"cmt">//--- 创建一组按钮 if(!m_radio_buttons1.CreateRadioButtons(m_chart_id,m_subwin,x,y)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 高亮显示组中的第二个按钮 m_radio_buttons1.SelectedRadioButton(class="num">1); class=class="str">"cmt">//--- 屏蔽单选按钮 m_radio_buttons1.RadioButtonsState(class="kw">false); class=class="str">"cmt">//--- 在对象组的共用数组中加入一个对象 CWndContainer::AddToElementsArray(class="num">0,m_radio_buttons1); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件处理函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- 按下按钮的事件 if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); class=class="str">"cmt">//--- 如果第二组的ID或者简单按钮以及 class=class="str">"cmt">// 这个组中高亮按钮的文字满足消息中的字符串参数 if(lparam==m_buttons_group2.Id() && sparam==m_buttons_group2.SelectedButtonText()) { class=class="str">"cmt">//--- 如果索引是第一个按钮,解除指定控件的屏蔽 if((class="type">int)dparam==class="num">0) { m_buttons_group1.ButtonsGroupState(true); m_radio_buttons1.RadioButtonsState(true); } class=class="str">"cmt">//--- 如果索引是第二个按钮,屏蔽指定的控件 else { m_buttons_group1.ButtonsGroupState(class="kw">false); m_radio_buttons1.RadioButtonsState(class="kw">false); } } class="kw">return; } }
把图标按钮打包成一组控件
在已有 CIconButton 单键类的基础上,新建 CIconButtonsGroup 类即可把多个图标按钮归并成一组统一管理。类文件建议放在 Controls 目录下命名为 IconButtonsGroup.mqh,并在 WndContainer.mqh 中 include 进来,其虚方法结构与之前 CButtonsGroup、CRadioButtons 基本一致,核心是在主方法里用索引循环调私有函数逐个生成按钮对象,索引同时用于拼出图形对象名避免重名。 悬停换色是这个组的默认行为:鼠标掠过按钮时背景与文字颜色会切到 hover 配色,事件处理逻辑和单选按钮组共用一套,不需要重写。下面直接给出类声明,方法体在附件里,编译后挂到之前测过按钮组的同一个 EA 里就能看到图 5 那样的横向图标条。 实测时把组创建代码塞进 EA 的自定义类,编译载入图表,按钮组会按 m_buttons_x_gap / m_buttons_y_gap 数组排布,单个键高度由 m_buttons_y_size 控制,外汇与贵金属图表上做这种面板属于高频交互改造,仍需注意 MT5 图形对象在极端行情重绘时可能卡顿的高风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建图标按钮组的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIconButtonsGroup : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 控件附加表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- 用于创建按钮的对象 CButton m_buttons[]; CBmpLabel m_icons[]; CLabel m_labels[]; class=class="str">"cmt">//--- 文字标签的渐变色 class="kw">struct IconButtonsGradients { class="type">class="kw">color m_back_color_array[]; class="type">class="kw">color m_label_color_array[]; }; IconButtonsGradients m_icon_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">string m_icon_file_on[]; class="type">class="kw">string m_icon_file_off[]; class=class="str">"cmt">//--- 按钮高度 class="type">int m_buttons_y_size; class=class="str">"cmt">//--- 不同模式下的背景色 class="type">class="kw">color m_back_color; class="type">class="kw">color m_back_color_off; class="type">class="kw">color m_back_color_hover; class="type">class="kw">color m_back_color_pressed; 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">int m_icon_x_gap; class="type">int m_icon_y_gap; 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_off; class="type">class="kw">color m_label_color_hover; class="type">class="kw">color m_label_color_pressed; class=class="str">"cmt">//--- 高亮按钮的(class="num">1) 文字 和 (class="num">2) 索引
◍ 图标按钮组的类成员与接口设计
在 MT5 自定义面板开发里,图标按钮组通常用独立类封装。下面这段类声明展示了核心私有成员与公开方法,直接决定了按钮的点击优先级和可用状态。 私有成员里,m_selected_button_text 存当前选中按钮文字,m_selected_button_index 存其序号;m_zorder 管非点击对象层叠顺序,m_buttons_zorder 专管鼠标左键点击响应优先级,m_icon_buttons_state 标记整组是否可用。 公开接口分两路:CreateIconButtonsGroup() 接收 chart_id、window、x、y 四个参数完成整组创建;而 CreateButton / CreateIcon / CreateLabel 被藏进 private,由创建流程内部调用,外部不可见。 setter 类方法几乎都是单行内联,例如 WindowPointer() 用 ::GetPointer(object) 拿窗体指针,ButtonsYSize() 写按钮高度,IconButtonsTotal() 返回 m_icons 数组大小即按钮总数。BackColor 系列三个方法分别控制常态、禁用、悬停背景色,改一处即可换肤。 实盘面板若要做成多按钮工具条,把这段类骨架拷进 MQ5 文件,调 BackColorHover 换个高对比色,MT5 里编译后悬停反馈立刻不一样。外汇与贵金属图表挂 EA 属高风险操作,改界面代码也先在策略测试器跑一遍。
class="type">class="kw">string m_selected_button_text; class="type">int m_selected_button_index; class=class="str">"cmt">//--- 不能点击对象的一般优先级 class="type">int m_zorder; class=class="str">"cmt">//--- 鼠标左键点击优先级 class="type">int m_buttons_zorder; class=class="str">"cmt">//--- 可用/禁用 class="type">bool m_icon_buttons_state; class=class="str">"cmt">//--- class="kw">public: CIconButtonsGroup(class="type">void); ~CIconButtonsGroup(class="type">void); class=class="str">"cmt">//--- 创建按钮的方法 class="type">bool CreateIconButtonsGroup(class="kw">const class="type">long chart_id,class="kw">const class="type">int window,class="kw">const class="type">int x,class="kw">const class="type">int y); class=class="str">"cmt">//--- class="kw">private: class="type">bool CreateButton(class="kw">const class="type">int index); class="type">bool CreateIcon(class="kw">const class="type">int index); class="type">bool CreateLabel(class="kw">const class="type">int index); class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- (class="num">1) 保存表单指针, (class="num">2) 按钮高度, (class="num">3) 按钮数量, class=class="str">"cmt">// (class="num">4) 按钮的一般状态 (可用/被屏蔽) class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class="type">void ButtonsYSize(class="kw">const class="type">int y_size) { m_buttons_y_size=y_size; } class="type">int IconButtonsTotal(class="type">void) class="kw">const { class="kw">return(::ArraySize(m_icons)); } class="type">bool IconButtonsState(class="type">void) class="kw">const { class="kw">return(m_icon_buttons_state); } class="type">void IconButtonsState(class="kw">const class="type">bool state); class=class="str">"cmt">//--- 按钮背景色 class="type">void BackColor(class="kw">const class="type">class="kw">color clr) { m_back_color=clr; } class="type">void BackColorOff(class="kw">const class="type">class="kw">color clr) { m_back_color_off=clr; } class="type">void BackColorHover(class="kw">const class="type">class="kw">color clr) { m_back_color_hover=clr; }
「按钮控件的属性接口与事件钩子」
在 MT5 自定义面板里做单选按钮组,核心是先通过 setter 把外观和布局参数灌进类内部变量。下面这段接口声明给出了按压背景色、图标与文字的边距控制方式,以及当前选中项的回读方法。 BackColorPressed 接收 color 类型参数直接写 m_back_color_pressed,决定鼠标按下瞬间的底色;IconXGap / IconYGap 和 LabelXGap / LabelYGap 分别用 int 控制图标和文字相对按钮框的像素偏移,默认不设置时往往贴边,实测 X 方向留 4~6 像素更不容易和边框重叠。 SelectedButtonText 与 SelectedButtonIndex 是两个 const 方法,分别回传选中按钮的文字串和索引,做条件分支时比全局变量可靠。AddButton 在创建前一次性传入坐标间隙、宽度与两态图标路径,意味着图标资源要提前放进 terminal 的 Images 目录。 公开区里 OnEvent 接管图表点击事件,OnEventTimer 供你做高亮轮询,Moving 处理拖拽重绘;Show / Hide 只切可见性不销毁对象,配合外汇或贵金属的高波动环境做面板隐显时,注意频繁显隐可能增加 CPU 占用概率。
class="type">void BackColorPressed(class="kw">const class="type">class="kw">color clr) { m_back_color_pressed=clr; } class=class="str">"cmt">//--- 图标边缘 class="type">void IconXGap(class="kw">const class="type">int x_gap) { m_icon_x_gap=x_gap; } class="type">void IconYGap(class="kw">const class="type">int y_gap) { m_icon_y_gap=y_gap; } class=class="str">"cmt">//--- 文字标签边缘 class="type">void LabelXGap(class="kw">const class="type">int x_gap) { m_label_x_gap=x_gap; } class="type">void LabelYGap(class="kw">const class="type">int y_gap) { m_label_y_gap=y_gap; } 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 SelectedRadioButton(class="kw">const class="type">int index); 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">string icon_file_on,class="kw">const class="type">class="kw">string icon_file_off); class=class="str">"cmt">//--- 改变颜色 class="type">void ChangeObjectsColor(class="type">void); class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 图表事件处理函数 class="kw">virtual class="type">void OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam); class=class="str">"cmt">//--- 计时器 class="kw">virtual class="type">void OnEventTimer(class="type">void); class=class="str">"cmt">//--- 移动控件 class="kw">virtual class="type">void Moving(class="kw">const class="type">int x,class="kw">const class="type">int y); class=class="str">"cmt">//--- (class="num">1) 显示, (class="num">2) 隐藏, (class="num">3) 重置, (class="num">4) 删除 class="kw">virtual class="type">void Show(class="type">void); class="kw">virtual class="type">void Hide(class="type">void);
图标按钮组的类结构与实例化落点
在自定义交易面板里,图标按钮组被封装成一个独立类,对外只暴露 Reset、Delete 以及设置/重置鼠标左键点击优先级的接口。Zorder 相关方法控制按钮组在图表上的叠放层级,避免被其他图形对象吞掉点击。 私有段里能看到实际干活的函数:OnClickButton 处理按下事件,CheckPressedOverButton 轮询左键是否落在组按钮区域,IdFromObjectName 则把对象名映射回按钮 ID,方便状态机切换。 主程序 CProgram 继承 CWndEvents,用 m_icon_buttons_group1 持有实例,并通过两个宏固定偏移:IBUTTONS_GROUP1_GAP_X 为 7 像素、IBUTTONS_GROUP1_GAP_Y 为 190 像素,这决定了按钮组相对表单左上角的落点。 CreateTradePanel 中高亮部分才是真正挂载点——只有 CreateIconButtonsGroup1() 返回 true 才继续重绘,否则直接 return(false) 中断面板构建。开 MT5 把这段偏移改成 (7, 250) 就能验证按钮组是否会避开下方单选组。
class="kw">virtual class="type">void Reset(class="type">void); class="kw">virtual class="type">void Delete(class="type">void); class=class="str">"cmt">//--- (class="num">1) 设置, (class="num">2) 重置鼠标左键点击的优先级 class="kw">virtual class="type">void SetZorders(class="type">void); class="kw">virtual class="type">void ResetZorders(class="type">void); class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 处理按钮被按下 class="type">bool OnClickButton(class="kw">const class="type">class="kw">string pressed_object); class=class="str">"cmt">//--- 检查鼠标左键在组按钮上按下 class="type">void CheckPressedOverButton(class="type">void); class=class="str">"cmt">//--- 根据单选按钮名称取得ID class="type">int IdFromObjectName(class="kw">const class="type">class="kw">string object_name); }; class CProgram : class="kw">public CWndEvents { class="kw">private: class=class="str">"cmt">//--- 图标按钮组 class="num">1 CIconButtonsGroup m_icon_buttons_group1; class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 图标按钮组 class="num">1 class="macro">#define IBUTTONS_GROUP1_GAP_X(class="num">7) class="macro">#define IBUTTONS_GROUP1_GAP_Y(class="num">190) class="type">bool CreateIconButtonsGroup1(class="type">void); }; 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=class="str">"cmt">//--- 创建控件: class=class="str">"cmt">// 主菜单 class=class="str">"cmt">//--- 上下文菜单 class=class="str">"cmt">//--- 简单按钮组1 class=class="str">"cmt">//--- 单选按钮组1 class=class="str">"cmt">//--- 简单按钮组2 class=class="str">"cmt">//--- 单选按钮组 class="num">2,class="num">3,class="num">4 class=class="str">"cmt">//--- 图标按钮组 class="num">1 if(!CreateIconButtonsGroup1()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 重绘图表 m_chart.Redraw(); class="kw">return(true); }
◍ 收束
到这一阶段,GUI 库已具备表单与若干可挂载控件,文件包 easyandfastgui_mql5.zip 体积 166.06 KB,可在 MT5 直接加载验证。下一阶段会切入多窗口模式与左键点击优先级管理,状态栏和工具提示元件也已排进开发表。
有读者在 MT5 编译 splitButton.mqh 时撞到 GetContextMenuPointer 的 const 报错,实测去掉 const 即可通过:把 CContextMenu *GetContextMenuPointer(void) const 改成 CContextMenu *GetContextMenuPointer(void)。外汇与贵金属标的高波动,用这类第三方库搭面板前先在策略测试器跑通再上真仓。
库还在迭代,后续预计共 25 篇,当前资料以俄文原版为主,英文与中文资料滞后,介意的可盯作者 t.me/moduloalgo 的更新。