图形界面III:简单与多功能按钮组(第二章)·综合运用
📘

图形界面III:简单与多功能按钮组(第二章)·综合运用

第 3/3 篇

「单选按钮组的创建与状态读取」

在 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 属高风险操作,面板逻辑出错可能误发订单,建议先在策略测试器里跑通再上实盘。

MQL5 / C++
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 逻辑,互不污染。

MQL5 / C++
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 逐个生成,偏移量加窗口坐标即为实际位置。

MQL5 / C++
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 策略测试器外挂窗体里点几下验证响应延迟。

MQL5 / C++
  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 图形对象在极端行情重绘时可能卡顿的高风险。

MQL5 / C++
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 属高风险操作,改界面代码也先在策略测试器跑一遍。

MQL5 / C++
  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 占用概率。

MQL5 / C++
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) 就能验证按钮组是否会避开下方单选组。

MQL5 / C++
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 的更新。

常见问题

用单选按钮组控件,创建时把多个按钮归到同一组并设初始选中项,读取时只取组内唯一激活态即可。
在单选按钮的事件回调里判断选中值,调用另一组控件的启用/禁用接口,实现联动禁启用。
小布可根据你的品种页需求生成控件布局与事件联动代码草稿,你只需微调参数就能跑。
设计类成员保存图标资源与位置,暴露统一接口增删和响应点击,把多个图标按钮作为一组控件维护。
用相对坐标绑定父容器,创建时传入基准点,避免硬编码绝对位置导致不同分辨率下偏移。