图形界面III:简单与多功能按钮(第一章)·进阶篇
📘

图形界面III:简单与多功能按钮(第一章)·进阶篇

第 2/3 篇

多状态按钮的坐标与配色落地

上面这段是 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)挤在一起。

MQL5 / C++
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 时按钮被禁用的逻辑也在这层挡掉。外汇、贵金属交易使用此类自定义面板存在滑点及断连高风险,参数联动仅作概率性辅助,不构成方向判断。

MQL5 / C++
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 工具开发同样涉及平台稳定性风险,参数调试请在模拟环境先跑。

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

MQL5 / C++
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,编译后按钮文字会明显右移,可据此判断你的图标宽度是否吃掉了标签空间。

MQL5 / C++
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) 让图标不被鼠标单独选中。

MQL5 / C++
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);

常见问题

在按钮类里用成员变量存 x/y 坐标和颜色值,切换状态时重绘对应区域即可,注意配色用常量统一管理避免硬编码。
在鼠标移动事件里做坐标命中检测,命中则置焦点标志并触发高亮,移出后清除标志恢复默认配色。
可以,把代码贴给小布,它能指出状态分支遗漏和坐标判定边界问题,并给修改建议。
间距常取 5~10 像素,默认外观用中等圆角和浅灰底,具体按界面密度微调,避免图标挤在一起。
分支一用于纯代码建默认图标,分支二带坐标和尺寸,分支三额外载入手绘资源,按是否需要自定义资源选对应分支。