图形界面 V: 组合框控件 (第三章)·进阶篇
🧩

图形界面 V: 组合框控件 (第三章)·进阶篇

(2/3)· 滚动条和列表视图刚讲完,复合控件组合框的类开发才是 EA 面板省空间的关键一步

含代码示例 第 2/3 篇
参数选项一多,EA 面板就堆满按钮,交易者点错的概率直线上升。组合框把列表收进下拉,界面清爽了,但自己从头写类容易漏掉状态切换。这篇接着上篇的列表视图,把复合控件的类结构拆给你看。

「按钮与标签的配色接口怎么写」

在 MT5 自定义控件类里,给按钮和文字标签配色通常拆成多组 setter,分别接管常态、停用、悬停、按下四种状态。下面这段接口把背景、边框、文字、标签各自的颜色分开赋值,避免状态切换时界面发灰或看不出焦点。 ButtonYSize 只接一个 int 参数,控制按钮高度像素,比如设 24 就比默认 20 更胖一点,手指点起来容错更高。颜色类 setter 全用 color 类型,直接吃 C'255,255,255' 或 clrWhite 这类常量。 LabelColor / LabelColorOff / LabelColorHover 三组管文字标签:常态显示色、停用色、鼠标悬停色。按钮侧更细,ButtonBackColor 系列覆盖背景四态,ButtonBorderColor 系列管边框两态,ButtonTextColor 只管字色。实盘面板若不做 hover 和 pressed 区分,交易者容易误点未激活的订单按钮,外汇和贵金属波动快、点错可能瞬时滑点亏损,属于高风险操作。 把这些 setter 全接进面板初始化函数,就能在 EA 加载时一次性定调视觉。开 MT5 新建个 panel 类,把下面代码贴进头文件 public 区,编译过即说明配色链路通了。

MQL5 / C++
  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="type">void                LabelColor(class="kw">const class="type">class="kw">color clr)                  { m_label_color=clr;                      }
  class="type">void                LabelColorOff(class="kw">const class="type">class="kw">color clr)               { m_label_color_off=clr;                  }
  class="type">void                LabelColorHover(class="kw">const class="type">class="kw">color clr)             { m_label_color_hover=clr;                }
  class=class="str">"cmt">//--- 按钮颜色
  class="type">void                ButtonBackColor(class="kw">const class="type">class="kw">color clr)             { m_button_color=clr;                     }
  class="type">void                ButtonBackColorOff(class="kw">const class="type">class="kw">color clr)          { m_button_color_off=clr;                 }
  class="type">void                ButtonBackColorHover(class="kw">const class="type">class="kw">color clr)        { m_button_color_hover=clr;               }
  class="type">void                ButtonBackColorPressed(class="kw">const class="type">class="kw">color clr)      { m_button_color_pressed=clr;             }
  class=class="str">"cmt">//--- 按钮边框的颜色
  class="type">void                ButtonBorderColor(class="kw">const class="type">class="kw">color clr)           { m_button_border_color=clr;              }
  class="type">void                ButtonBorderColorOff(class="kw">const class="type">class="kw">color clr)        { m_button_border_color_off=clr;          }
  class=class="str">"cmt">//--- 按钮文字的颜色
  class="type">void                ButtonTextColor(class="kw">const class="type">class="kw">color clr)             { m_button_text_color=clr;                }

下拉框控件的配色与间距 setters

在 MT5 自定义 CComboBox 类里,这一组 setter 专门控制按钮文字与下拉箭头的视觉状态。ButtonTextColorOff 接收 color 类型参数,写入 m_button_text_color_off,决定控件处于禁用态时按钮文字的灰度表现。 DropArrowFileOn 与 DropArrowFileOff 分别接收 string 路径,把活动态和阻止态的下拉箭头图标文件地址存进 m_drop_arrow_file_on / m_drop_arrow_file_off,意味着你可以换 bmp 或 png 来区别可点与非可点。 DropArrowXGap 和 DropArrowYGap 用 int 设定箭头相对标签的水平和垂直边距,直接改 m_drop_arrow_x_gap / m_drop_arrow_y_gap,影响渲染时的贴图偏移。 构造函数里给这些成员给了默认值:标签文字是 "combobox: "、标签纵向偏移 2 像素、按钮高度 18、活动按钮底色 clrGainsboro、悬停色 C'193,218,255'。开 MT5 把这段塞进你的面板类,改一个 m_button_y_size 就能看出按钮胖瘦变化。

MQL5 / C++
class="type">void ButtonTextColorOff(class="kw">const class="type">class="kw">color clr) { m_button_text_color_off=clr; }
class=class="str">"cmt">//--- 设置在活动与阻止状态下下拉菜单按钮的图标
class="type">void DropArrowFileOn(class="kw">const class="type">class="kw">string file_path) { m_drop_arrow_file_on=file_path; }
class="type">void DropArrowFileOff(class="kw">const class="type">class="kw">string file_path) { m_drop_arrow_file_off=file_path; }
class=class="str">"cmt">//--- 标签边缘距离
class="type">void DropArrowXGap(class="kw">const class="type">int x_gap) { m_drop_arrow_x_gap=x_gap; }
class="type">void DropArrowYGap(class="kw">const class="type">int y_gap) { m_drop_arrow_y_gap=y_gap; }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造函数                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
CComboBox::CComboBox(class="type">void) : m_area_color(C&class="macro">#x27;class="num">15,class="num">15,class="num">15&class="macro">#x27;),
              m_label_text("combobox: "),
              m_label_x_gap(class="num">0),
              m_label_y_gap(class="num">2),
              m_label_color(clrWhite),
              m_label_color_off(clrGray),
              m_label_color_hover(C&class="macro">#x27;class="num">85,class="num">170,class="num">255&class="macro">#x27;),
              m_button_text(""),
              m_button_y_size(class="num">18),
              m_button_text_color(clrBlack),
              m_button_text_color_off(clrDarkGray),
              m_button_color(clrGainsboro),
              m_button_color_off(clrLightGray),
              m_button_color_hover(C&class="macro">#x27;class="num">193,class="num">218,class="num">255&class="macro">#x27;),

◍ 下拉框控件的层级与组合结构

在 MT5 自定义图形界面里,组合框(ComboBox)不是单一对象,而是由矩形背景、标签、按钮、下拉箭头和列表视图五个子对象拼起来的复合控件。上面这段初始化代码里,按下态按钮色给的是 C'153,178,215'(淡蓝),边框统一用 clrWhite,下拉箭头和背景的横纵间距分别设为 16 与 1 像素,箭头图标文件留空表示用默认绘制。 层级顺序直接决定鼠标命中优先级:m_zorder=0 是控件整体,m_area_zorder=1 是点击热区,m_button_zorder=2 是按钮,数值越大越靠前响应。如果你在做 EA 面板时发现点按钮没反应,先查这三个 zorder 是否被别的元件覆盖。 CComboBox 类通过 GetListViewPointer() 和 GetScrollVPointer() 向外暴露内部列表与滚动条指针,意味着下拉项的管理和滚动逻辑可以独立操作。CListView 里还藏了一个 CElement *m_combobox 反向指针,用来在列表收起时通知所属组合框——这种双向引用是标准库控件联动的基础。

MQL5 / C++
m_button_color_pressed(C&class="macro">#x27;class="num">153,class="num">178,class="num">215&class="macro">#x27;),
m_button_border_color(clrWhite),
m_button_border_color_off(clrWhite),
m_drop_arrow_x_gap(class="num">16),
m_drop_arrow_y_gap(class="num">1),
m_drop_arrow_file_on(""),
m_drop_arrow_file_off("")
{
class=class="str">"cmt">//--- 设置鼠标左键点击优先级
 m_zorder       =class="num">0;
 m_area_zorder  =class="num">1;
 m_button_zorder=class="num">2;
}
class CComboBox : class="kw">public CElement
 {
class="kw">private:
  class=class="str">"cmt">//--- 用于创建组合框的对象
  CRectLabel       m_area;
  CLabel           m_label;
  CEdit            m_button;
  CBmpLabel        m_drop_arrow;
  CListView        m_listview;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 用于创建组合框的方法
  class="type">bool              CreateComboBox(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              CreateArea(class="type">void);
  class="type">bool              CreateLabel(class="type">void);
  class="type">bool              CreateButton(class="type">void);
  class="type">bool              CreateDropArrow(class="type">void);
  class="type">bool              CreateList(class="type">void);
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 返回(class="num">1)列表视图 和(class="num">2)滚动条 的指针
  CListView        *GetListViewPointer(class="type">void)                  { class="kw">return(::GetPointer(m_listview));             }
  CScrollV         *GetScrollVPointer(class="type">void)                  { class="kw">return(m_listview.GetScrollVPointer());      }
  };
class CListView : class="kw">public CElement
 {
class="kw">private:
  class=class="str">"cmt">//--- 管理列表视图是否可见的元件的指针
  CElement        *m_combobox;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 保存组合框指针

「下拉列表视图的指针绑定与隐藏逻辑」

在 MT5 自定义 GUI 里,下拉式 CListView 不是独立存活的,它必须拿到所属组合框 CComboBox 的指针才能正常创建。代码里用 ComboBoxPointer(CElement &object) 把外部对象地址写进 m_combobox,若 IsDropdown() 为真却检测到指针无效,CreateListView 会直接 Print 报错并返回 false,不会往下跑。 CComboBox 构造时就把 m_listview.IsDropdown(true) 打开,随后 CreateList() 内连续调用 m_listview.WindowPointer(m_wnd) 与 m_listview.ComboBoxPointer(this),把表单和自身指针双向下发;列表坐标按 X2()-m_button_x_size、Y()+m_button_y_size 算,创建完立刻 m_listview.Hide(),也就是默认收起状态。 列表容量由两组参数控:ItemsTotal() 决定总项目数(对应 ListSize),VisibleItemsTotal() 控制可视条数(VisibleListSize)。开 MT5 接这套类时,先确认下拉模式已开且组合框指针已传,否则列表视图概率性建不出来,只会看到终端打印那句缺失指针的提示。外汇与贵金属 GUI 自动化涉及实盘交互,属于高风险操作,参数误用可能导致控件失效。

MQL5 / C++
class="type">void ComboBoxPointer(CElement &object) { m_combobox=::GetPointer(object); }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建列表视图                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CListView::CreateListView(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=class="str">"cmt">//--- 如果列表视图是下拉的,则需要它所附加的组合框的指针
   if(CElement::IsDropdown())
     {
class=class="str">"cmt">//--- 如果没有组合框的指针,退出
       if(::CheckPointer(m_combobox)==POINTER_INVALID)
         {
          ::Print(__FUNCTION__,"> 在创建下拉列表视图之前, 必须向此类传入"
                                 "组合框的指针: CListView::ComboBoxPointer(CElement &object)");
          class="kw">return(class="kw">false);
         }
     }
class=class="str">"cmt">//--- 初始化变量
class=class="str">"cmt">//--- 边缘距离
class=class="str">"cmt">//--- 创建按钮
class=class="str">"cmt">//--- 如果窗口是对话框或者是最小化的,隐藏元件
class=class="str">"cmt">//---
   class="kw">return(true);
  }
CComboBox::CComboBox(class="type">void)
  {
class=class="str">"cmt">//--- 下拉列表视图模式
   m_listview.IsDropdown(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建列表视图                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CComboBox::CreateList(class="type">void)
  {
class=class="str">"cmt">//--- 保存表单和组合框的指针
   m_listview.WindowPointer(m_wnd);
   m_listview.ComboBoxPointer(this);
class=class="str">"cmt">//--- 坐标
   class="type">int x=CElement::X2()-m_button_x_size;
   class="type">int y=CElement::Y()+m_button_y_size;
class=class="str">"cmt">//--- 设置属性
   m_listview.Id(CElement::Id());
   m_listview.XSize(m_button_x_size);
class=class="str">"cmt">//--- 创建控件
   if(!m_listview.CreateListView(m_chart_id,m_subwin,x,y))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 隐藏列表视图
   m_listview.Hide();
   class="kw">return(true);
  }
class CListView : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 设置 (class="num">1)列表视图的大小(项目的数量) 以及(class="num">2)它的可见部分
   class="type">void ItemsTotal(class="kw">const class="type">int items_total) { m_listview.ListSize(items_total); }
   class="type">void VisibleItemsTotal(class="kw">const class="type">int visible_items_total) { m_listview.VisibleListSize(visible_items_total); }

  class=class="str">"cmt">//--- 根据指定的索引在列表视图中保存传入的值
   class="type">void ValueToList(class="kw">const class="type">int item_index,class="kw">const class="type">class="kw">string item_text);
  };

组合框的下拉与悬停状态切换

在 MT5 自定义控件里,CComboBox 的下拉列表本质是一个内嵌的 CListView 实例。通过 ValueToList 把选项写进列表视图后,用户点选动作会触发 SelectedItemByIndex,里面直接拿索引去高亮对应行,并把选中文本同步到按钮标题上。 ComboBoxState 负责整体可用性的视觉反馈:传入 true/false 会切换标签、按钮文字、背景与边框四组颜色。实测中若只改 m_combobox_state 而忘记调 Color,界面会保留旧色调,看起来像「点了没反应」。 悬停变色由 ChangeObjectsColor 处理,但它第一行就判断 m_combobox_state,被禁用的组合框直接 return,不会浪费定时器资源去重绘。OnEventTimer 里也只在「是下拉元件且列表隐藏」时才跑这套逻辑,意味着展开状态下悬停重绘是暂停的。

MQL5 / C++
class="type">void CComboBox::ValueToList(class="kw">const class="type">int item_index,class="kw">const class="type">class="kw">string item_text)
  {
  m_listview.ValueToList(item_index,item_text);
  }
class CListView : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 根据指定的索引突出显示项目
  class="type">void              SelectedItemByIndex(class="kw">const class="type">int index);
  };
class="type">void CComboBox::SelectedItemByIndex(class="kw">const class="type">int index)
  {
class=class="str">"cmt">//--- 突出显示列表视图的项目
  m_listview.SelectedItemByIndex(index);
class=class="str">"cmt">//--- 在按钮上保存和设置文字
  m_button_text=m_listview.SelectedItemText();
  m_button.Description(m_listview.SelectedItemText());
  }
class CListView : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 获取及设置对象的当前状态
  class="type">bool              ComboBoxState(class="type">void)                class="kw">const { class="kw">return(m_combobox_state);               }
  class="type">void              ComboBoxState(class="kw">const class="type">bool state);
  };
class="type">void CComboBox::ComboBoxState(class="kw">const class="type">bool state)
  {
  m_combobox_state=state;
class=class="str">"cmt">//--- 根据对象的当前状态设置对应的颜色
  m_label.Color((state)?m_label_color : m_label_color_off);
  m_button.Color((state)?m_button_text_color : m_button_text_color_off);
  m_button.BackColor((state)?m_button_color : m_button_color_off);
  m_button.BorderColor((state)?m_button_border_color : m_button_border_color_off);
  m_drop_arrow.State(state);
  }
class CListView : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 当光标掠过时改变对象的颜色
  class="type">void              ChangeObjectsColor(class="type">void);
  };
class="type">void CComboBox::ChangeObjectsColor(class="type">void)
  {
class=class="str">"cmt">//--- 如果元件被阻止,退出
  if(!m_combobox_state)
     class="kw">return;
class=class="str">"cmt">//--- 改变对象颜色
  CElement::ChangeObjectColor(m_label.Name(),CElement::MouseFocus(),OBJPROP_COLOR,m_label_color,m_label_color_hover,m_label_color_array);
  CElement::ChangeObjectColor(m_button.Name(),CElement::MouseFocus(),OBJPROP_BGCOLOR,m_button_color,m_button_color_hover,m_button_color_array);
  }
class="type">void CComboBox::OnEventTimer(class="type">void)
  {
class=class="str">"cmt">//--- 如果这是一个下拉元件并且列表视图是隐藏的
  if(CElement::IsDropdown() && !m_listview.IsVisible())
     ChangeObjectsColor();

◍ 组合框下拉态的锁定与解锁逻辑

自定义组合框在 MT5 面板里展开列表时,必须同步锁住父窗口,否则用户点空白处会漏到下层图表。上面这段 CComboBox::ChangeComboBoxListState 就是干这件事的:列表可见就收起并解锁,隐藏就展开并锁定。 注意 m_wnd.IsLocked(true) 只在列表 Show() 分支调用,Hide() 分支里只对非下拉元件解除锁定——这意味着纯下拉式组合框收起后不会主动放锁,得靠外部事件回收。 跑一遍就能验证:在 EA 的 OnChartEvent 里挂一个组合框,点开时图表拖不动,点选后立刻恢复。外汇和贵金属 GUI 策略面板都这德行,误操作概率随元件数上升。

MQL5 / C++
else
    {
    class=class="str">"cmt">//--- 如果表单和元件没有被阻挡
    if(!m_wnd.IsLocked() && m_combobox_state)
        ChangeObjectsColor();
    }
}
class CListView : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 把组合框的当前状态设成相反状态
  class="type">void                ChangeComboBoxListState(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把组合框的当前状态改成相反状态                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CComboBox::ChangeComboBoxListState(class="type">void)
  {
class=class="str">"cmt">//--- 如果元件被阻挡,退出
  if(!m_combobox_state)
      class="kw">return;
class=class="str">"cmt">//--- 如果列表视图可见
  if(m_listview.IsVisible())
    {
    class=class="str">"cmt">//--- 隐藏列表视图
    m_listview.Hide();
    class=class="str">"cmt">//--- 设置颜色
    m_label.Color(m_label_color_hover);
    m_button.BackColor(m_button_color_hover);
    class=class="str">"cmt">//--- 如果不是下拉元件
    if(!CElement::IsDropdown())
      {
      class=class="str">"cmt">//--- 解除表单的阻挡
      m_wnd.IsLocked(class="kw">false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
      }
    }
class=class="str">"cmt">//--- 如果列表视图是隐藏的
  else
    {
    class=class="str">"cmt">//--- 显示列表视图
    m_listview.Show();
    class=class="str">"cmt">//--- 设置颜色
    m_label.Color(m_label_color_hover);
    m_button.BackColor(m_button_color_pressed);
    class=class="str">"cmt">//--- 阻挡表单
    m_wnd.IsLocked(true);
    m_wnd.IdActivatedElement(CElement::Id());
    }
  }
交给小布盯盘管界面样板
这些控件类的样板代码小布盯盘的 AIGC 已内置,打开对应品种页即可调取片段,你只改参数和事件逻辑。

常见问题

需要 CElement 作为控件基类、CWindow 作为挂载表单、CListView 由组合框管理显隐,三者都在前几章的库文件里。
选中列表视图项目后,该项目文字写入按钮属性并隐藏列表,按钮重绘即显示所选内容。
可以,小布盯盘的 AIGC 片段库含 ComboBox.mqh 结构示例,填入颜色与边距参数即可跑通。
避免未设属性时对象使用随机内存值导致界面渲染异常,也方便后续用户只覆写需要改的项。
是的,活动与阻止两种状态的下拉标记应分开指定图标与 XY 边距,否则禁用时箭头会错位。