图形界面III:简单与多功能按钮(第一章)·进阶篇
多状态按钮的坐标与配色落地
上面这段是 CProgram 里第三个简单按钮的创建函数,重点在 TwoState(true) 让按钮支持按下/弹起双状态,适合做开关类面板控件。 坐标用窗口基准加偏移:X 取 m_window.X()+7,Y 取 m_window.Y()+75,宽固定 237 像素,和按钮1纵向错开 25 像素避免重叠。 配色分三层:常态 LightGray,悬停 C'193,218,255',按下 C'153,178,215',文字色双向均用 clrBlack。最后通过 AddToElementsArray(0,m_simple_button3) 挂进容器数组,索引 0 表示归到第一组元素。 在 MT5 里把这段塞进你的面板类,改 BUTTON3_GAP_Y 从 75 调到 100,就能直观看到第三行按钮下移、不与第二行(GAP_Y=50)挤在一起。
class="type">bool CProgram::CreateSimpleButton3(class="type">class="kw">string button_text) { class=class="str">"cmt">//--- 传入面板对象 m_simple_button3.WindowPointer(m_window); class=class="str">"cmt">//--- 坐标 class="type">int x=m_window.X()+BUTTON3_GAP_X; class="type">int y=m_window.Y()+BUTTON3_GAP_Y; class=class="str">"cmt">//--- 在创建之前设置属性 m_simple_button3.TwoState(true); m_simple_button3.ButtonXSize(class="num">237); m_simple_button3.TextColor(clrBlack); m_simple_button3.TextColorPressed(clrBlack); m_simple_button3.BackColor(clrLightGray); m_simple_button3.BackColorHover(C&class="macro">#x27;class="num">193,class="num">218,class="num">255&class="macro">#x27;); m_simple_button3.BackColorPressed(C&class="macro">#x27;class="num">153,class="num">178,class="num">215&class="macro">#x27;); class=class="str">"cmt">//--- 创建一个按钮 if(!m_simple_button3.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 把对象加到对象组的通用数组中 CWndContainer::AddToElementsArray(class="num">0,m_simple_button3); class="kw">return(true); }
◍ 按钮联动与鼠标焦点判定怎么写
在 MT5 自定义面板里,三个简单按钮的创建必须串行校验,任一返回 false 整体就中止,这比抛异常更适合嵌入式 GUI 的容错逻辑。下面这段 CreateTradePanel 就是标准做法:先建按钮 1/2/3,全部成功才 Redraw 图表,否则直接退出。 [CODE] bool CProgram::CreateTradePanel(void) { if(!CreateSimpleButton1("Simple Button 1")) return(false); if(!CreateSimpleButton2("Simple Button 2")) return(false); if(!CreateSimpleButton3("Simple Button 3")) return(false); m_chart.Redraw(); return(true); } [/CODE] 逐行拆解:函数返回 bool 供上层判断面板是否就绪;三个 Create 调用若失败立即 return false,避免半残界面;m_chart.Redraw() 在末尾强制重绘,确保控件一次性上屏。 事件分发层用 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 拦截点击。若 sparam 等于按钮 3 的文本,就读取它的按下状态去反转按钮 1 的 ButtonState——这种「一个按钮控另一个按钮」的写法,在外汇高频面板里常用来做开仓/锁仓互斥。 [CODE] void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); if(sparam==m_simple_button3.Text()) { if(m_simple_button3.IsPressed()) m_simple_button1.ButtonState(false); else m_simple_button1.ButtonState(true); } } } [/CODE] CSimpleButton::OnEvent 处理鼠标移动:先判 IsVisible 和窗口 IsLocked,再算 x/y 是否落在本按钮矩形内(X()~X2()、Y()~Y2())来设 MouseFocus。sparam=="0" 表示左键松开,直接 return 不处理;m_button_state 为 false 时按钮被禁用的逻辑也在这层挡掉。外汇、贵金属交易使用此类自定义面板存在滑点及断连高风险,参数联动仅作概率性辅助,不构成方向判断。
class="type">bool CProgram::CreateTradePanel(class="type">void) { if(!CreateSimpleButton1("Simple Button class="num">1")) class="kw">return(class="kw">false); if(!CreateSimpleButton2("Simple Button class="num">2")) class="kw">return(class="kw">false); if(!CreateSimpleButton3("Simple Button class="num">3")) class="kw">return(class="kw">false); m_chart.Redraw(); class="kw">return(true); } 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) { if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); if(sparam==m_simple_button3.Text()) { if(m_simple_button3.IsPressed()) m_simple_button1.ButtonState(class="kw">false); else m_simple_button1.ButtonState(true); } } } class="type">void CSimpleButton::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) { if(id==CHARTEVENT_MOUSE_MOVE) { if(!CElement::IsVisible()) class="kw">return; class="type">int x=(class="type">int)lparam; class="type">int y=(class="type">int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); if(m_wnd.IsLocked()) class="kw">return; if(sparam=="class="num">0") class="kw">return; if(!m_button_state) class="kw">return; } }
「按钮焦点与配色的状态分支」
在自定义控件的鼠标事件处理里,焦点状态直接决定按钮的视觉反馈逻辑。上述代码演示了当控件失去鼠标焦点时,若按钮处于松开状态,则恢复默认文字色与背景色;一旦拿到焦点,立即切换为按下态配色。 逐行看这段:先判断 CElement::MouseFocus() 是否为假,即当前无焦点;无焦点情况下再判断 m_button.State() 是否为假(按钮未被按下),两个条件都满足才把 Color 和 BackColor 设回 m_text_color 与 m_back_color。若已有焦点,则无条件写入 m_text_color_pressed 和 m_back_color_pressed。 这种写法把「焦点」和「按下」解耦,避免在失去焦点时残留按下态颜色。你在 MT5 里接自己的面板时,可把这几行直接塞进 OnEvent 的鼠标分支,改一下四个颜色变量就能验证反馈是否符合预期。外汇与贵金属 GUI 工具开发同样涉及平台稳定性风险,参数调试请在模拟环境先跑。
class=class="str">"cmt">//--- 如果没有焦点 if(!CElement::MouseFocus()) { class=class="str">"cmt">//--- 如果按钮被松开 if(!m_button.State()) { m_button.Color(m_text_color); m_button.BackColor(m_back_color); } class=class="str">"cmt">//--- class="kw">return; } class=class="str">"cmt">//--- 如果有焦点 else { m_button.Color(m_text_color_pressed); m_button.BackColor(m_back_color_pressed); class="kw">return; } class=class="str">"cmt">//--- class="kw">return; } }
图标按钮类的字段与纯图标模式
图标按钮在 MT5 图形界面里不是单一对象,而是由背景、图标、文字标签三个基本图形对象拼出来的。文字标签的位置是自由的,可以放底部也可以放上部,取决于你后面怎么排布控件坐标。 CIconButton 类沿用了简单按钮 CSimpleButton 的字段和方法框架,但多了几组专属属性:图标与边框的距离(m_icon_x_gap / m_icon_y_gap)、文字相对控件的坐标偏移(m_label_x_gap / m_label_y_gap)、以及激活/禁用两套图标路径(m_icon_file_on / m_icon_file_off)。 纯图标模式由布尔字段 m_only_icon 控制。一旦置 true,按钮就只用一个 OBJ_BITMAP_LABEL 类型的对象,不再建背景和文字标签。创建流程里会在 CreateButton 和 CreateLabel 开头检查这个标志,纯图标时直接 return,省掉多余对象。 如果用户开了纯图标模式却没给图标路径,界面构建会在 CreateIcon() 这一步终止,并在日志提示该模式下图标为必填项。下面这段类声明只列出与简单按钮不同的部分,方便你对照改自己的 WndContainer。
class CIconButton : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 用于创建按钮的对象 CButton m_button; CBmpLabel m_icon; CLabel m_label; class=class="str">"cmt">//--- 按钮属性: class=class="str">"cmt">// 按钮中激活和禁用状态的图标 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_icon_x_gap; class="type">int m_icon_y_gap; class=class="str">"cmt">//--- 文本标签中的文字和边缘 class="type">class="kw">string m_label_text; class="type">int m_label_x_gap; class="type">int m_label_y_gap; class=class="str">"cmt">//--- 如果按钮只由BmpLabel对象构成,它就是单纯图标模式 class="type">bool m_only_icon; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 创建按钮的方法 class="type">bool CreateIconButton(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">class="kw">string button_text,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="type">void); class="type">bool CreateIcon(class="type">void); class="type">bool CreateLabel(class="type">void); class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 设置单纯图标模式 class="type">void OnlyIcon(class="kw">const class="type">bool flag) { m_only_icon=flag; } class=class="str">"cmt">//--- 设置按钮在激活和禁用状态下的图标 class="type">void IconFileOn(class="kw">const class="type">class="kw">string file_path) { m_icon_file_on=file_path; } class="type">void IconFileOff(class="kw">const class="type">class="kw">string file_path) { m_icon_file_off=file_path; } class=class="str">"cmt">//--- 图标边缘
◍ 图标按钮的间距与默认外观参数
在自定义面板里做图标按钮,先得把图标和文字标签的相对位置钉死。类里用四个 setter 把间隙暴露出来:IconXGap / IconYGap 控制图标相对按钮左上角的偏移,LabelXGap / LabelYGap 控制文字标签的偏移,单位都是像素。 构造函数给出了开箱即用的默认值:图标 x 偏移 4、y 偏移 3,文字标签 x 偏移 25、y 偏移 4。也就是说标签默认被推到图标右边约 21 像素处(25−4),垂直方向只错开 1 像素(4−3),排版偏紧凑。 按钮本身默认高 18 像素,背景色用 LightGray,悬停变 Silver,按下变 Black;关闭态背景仍是 LightGray 但边框转 DarkGray。外汇和贵金属图表上挂这类控件属于高风险环境下的辅助显示,参数仅影响交互观感,不改变任何下单逻辑。 想立刻验证,把 m_label_x_gap 从 25 改成 40,编译后按钮文字会明显右移,可据此判断你的图标宽度是否吃掉了标签空间。
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=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CIconButton::CIconButton(class="type">void) : m_icon_x_gap(class="num">4), m_icon_y_gap(class="num">3), m_label_x_gap(class="num">25), m_label_y_gap(class="num">4), m_icon_file_on(""), m_icon_file_off(""), m_button_state(true), m_two_state(class="kw">false), m_only_icon(class="kw">false), m_button_y_size(class="num">18), m_back_color(clrLightGray), m_back_color_off(clrLightGray), m_back_color_hover(clrSilver), m_back_color_pressed(clrBlack), m_border_color(clrWhite), m_border_color_off(clrDarkGray),
「图标按钮类的构造与三种创建分支」
CIconButton 的构造函数里先给标签配了四套颜色:常态 m_label_color 用 clrBlack,失活 m_label_color_off 用 clrDarkGray,悬停与按下态仍保持 clrBlack。视觉上只有失活能看出灰度差,其余状态靠图标切换区分。 基类名称通过 CElement::ClassName(CLASS_NAME) 写入,左键点击优先级 m_button_zorder 设 1、元件基础层 m_zorder 设 0,意味着按钮总压在普通元件之上。 CreateButton 与 CreateLabel 在 m_only_icon 为真时直接 return(true)——纯图标模式跳过后台文字与底图绘制,省一次 OBJ_LABEL 创建。CreateIcon 则反过来:非纯图标模式下若 m_icon_file_on / m_icon_file_off 为空串就退出;纯图标模式下这两个串为空会 Print 报错并返回 false,因此纯图标按钮必须预置两态位图。 图标坐标按模式偏移:非纯图标时 x = m_x + m_icon_x_gap、y = m_y + m_icon_y_gap;纯图标时直接吃 m_x / m_y。位图对象名由 ProgramName()_icon_button_bmp_ 加 Id() 拼成,BmpFileOn / BmpFileOff 都加了 '::' 前缀走 MQL5 标准资源路径,State(true) 开启状态联动,Anchor 取自创建时的锚点,Selectable(false) 让图标不被鼠标单独选中。
m_label_color(clrBlack), m_label_color_off(clrDarkGray), m_label_color_hover(clrBlack), m_label_color_pressed(clrBlack) { class=class="str">"cmt">//--- 在基类中保存元件类的名称 CElement::ClassName(CLASS_NAME); class=class="str">"cmt">//--- 设置鼠标左键点击的优先级 m_button_zorder =class="num">1; m_zorder =class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建按钮背景 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIconButton::CreateButton(class="type">void) { class=class="str">"cmt">//--- 如果是单纯图标模式 if(m_only_icon) class="kw">return(true); class=class="str">"cmt">//--- ... 等等. } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建按钮文字 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIconButton::CreateLabel(class="type">void) { class=class="str">"cmt">//--- 如果是单纯图标模式 if(m_only_icon) class="kw">return(true); class=class="str">"cmt">//--- ... 等等. } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建一个按钮图标 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIconButton::CreateIcon(class="type">void) { class=class="str">"cmt">//--- 如果禁用单纯图标模式 if(!m_only_icon) { class=class="str">"cmt">//--- 如果不需要按钮图标就退出 if(m_icon_file_on=="" || m_icon_file_off=="") class="kw">return(true); } class=class="str">"cmt">//--- 如果启用了单纯图标模式 else { class=class="str">"cmt">//--- 如果图标没有被定义,打印消息并退出 if(m_icon_file_on=="" || m_icon_file_off=="") { ::Print(__FUNCTION__," > 在 \"单纯图标\"模式下必须定义图标."); class="kw">return(class="kw">false); } } class=class="str">"cmt">//--- 构建对象名称 class="type">class="kw">string name=CElement::ProgramName()+"_icon_button_bmp_"+(class="type">class="kw">string)CElement::Id(); class=class="str">"cmt">//--- 坐标 class="type">int x =(!m_only_icon)? m_x+m_icon_x_gap : m_x; class="type">int y =(!m_only_icon)? m_y+m_icon_y_gap : m_y; class=class="str">"cmt">//--- 设置图标 if(!m_icon.Create(m_chart_id,name,m_subwin,x,y)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 设置属性 m_icon.BmpFileOn("::"+m_icon_file_on); m_icon.BmpFileOff("::"+m_icon_file_off); m_icon.State(true); m_icon.Corner(m_corner); m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor); m_icon.Selectable(class="kw">false);