图形界面 V: 组合框控件 (第三章)·进阶篇
(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 区,编译过即说明配色链路通了。
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 就能看出按钮胖瘦变化。
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 反向指针,用来在列表收起时通知所属组合框——这种双向引用是标准库控件联动的基础。
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 自动化涉及实盘交互,属于高风险操作,参数误用可能导致控件失效。
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 里也只在「是下拉元件且列表隐藏」时才跑这套逻辑,意味着展开状态下悬停重绘是暂停的。
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 策略面板都这德行,误操作概率随元件数上升。
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()); } }