图形界面 VI: 滑动条与双重滑动条控件(第二章)·综合运用
🎚️

图形界面 VI: 滑动条与双重滑动条控件(第二章)·综合运用

(3/3)·从单滑块到双滑块的区间选取,很多自定义面板卡在控件重绘与事件绑定上

含代码示例偏理论 第 3/3 篇
把滑动条当成普通编辑框去读数值,滑块移动后输入栏位不刷新是常见坑。双重滑动条若没限制两针交叠,回测参数区间会直接逻辑错乱。这类问题在自定义面板里反复出现,根源多在控件类缺少状态同步。

控件焦点与点击拦截的底层判定

在 MT5 自定义面板里,鼠标事件先要确认落点是否处于元件包围盒内。用坐标比对 X()/X2() 与 Y()/Y2() 四个边界,就能判定光标当前是否拥有该元件的 MouseFocus,按钮 m_button 同理走一套独立边界检查。 若组合框处于非激活状态(m_combobox_state 为 false),或鼠标左键已松开(sparam 字符串等于 "0"),函数直接 return,不再向下传递事件,这避免了隐藏面板误吞点击。 只有在上述拦截都放过后,才调用 CheckPressedOverButton() 去处理分割按钮的按下逻辑。把这套判定粘进你的 EA 面板类,编译后移动鼠标到按钮边缘,能直观看到焦点闪烁是否符合预期。

MQL5 / C++
class=class="str">"cmt">//--- 检查元件上的焦点
CElement::MouseFocus(x>CElement::X() && x<CElement::X2() &&
                          y>CElement::Y() && y<CElement::Y2());
m_button.MouseFocus(x>m_button.X() && x<m_button.X2() &&
                          y>m_button.Y() && y<m_button.Y2());
class=class="str">"cmt">//--- 如果元件被阻挡,退出
if(!m_combobox_state)
   class="kw">return;
class=class="str">"cmt">//--- 如果鼠标左键松开,退出
if(sparam=="class="num">0")
   class="kw">return;
class=class="str">"cmt">//--- 检查在分割按钮上按下鼠标左键
CheckPressedOverButton();
class="kw">return;

「给面板加一个受复选框管控的滑动条」

在已有四个复选框的测试程序里,再补一个滑动条和第五个复选框,用第五个框的勾选状态去控制滑动条能不能用。这种做法在写 EA 面板时很实用:把不常用的微调元件藏起来,避免界面太乱。 创建滑动条走 CProgram::CreateSlider1(),关键是把数值区间钉在 -1 到 1,步长设成 0.00000001,也就是保留 8 位小数精度。它的初始值若读不到就用 0.84615385 顶上,这个值只是示例代码里的占位,你实测时可以改成自己想要的默认比例。 在 OnEvent() 里要监听第五个复选框的状态变化,同时捕获 ON_END_EDIT 事件来感知滑动条输入栏的数值改动。编译挂到图表后,勾掉第五个框滑动条会立即置灰,勾上又恢复可拖拽,交互符合预期就说明绑定逻辑通了。 外汇和贵金属行情跳动快、点差滑点风险高,这类图形面板只负责参数录入与状态管控,下单决策还得靠你自己的规则。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 用于创建应用程序的类                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CProgram : class="kw">public CWndEvents
  {
class="kw">private:
   class=class="str">"cmt">//--- 复选框
   CCheckBox          m_checkbox5;
   class=class="str">"cmt">//--- 滑动条
   CSlider            m_slider1;
   class=class="str">"cmt">//---
class="kw">private:
   class=class="str">"cmt">//--- 复选框
class="macro">#define CHECKBOX5_GAP_X(class="num">7)
class="macro">#define CHECKBOX5_GAP_Y(class="num">200)
   class="type">bool                CreateCheckBox5(class="kw">const class="type">class="kw">string text);
   class=class="str">"cmt">//--- 滑动条
class="macro">#define SLIDER1_GAP_X(class="num">32)
class="macro">#define SLIDER1_GAP_Y(class="num">225)
   class="type">bool                CreateSlider1(class="kw">const class="type">class="kw">string text);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建滑动条 class="num">1                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateSlider1(class="kw">const class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 保存窗口指针
   m_slider1.WindowPointer(m_window1);
class=class="str">"cmt">//--- 坐标
   class="type">int x=m_window1.X()+SLIDER1_GAP_X;
   class="type">int y=m_window1.Y()+SLIDER1_GAP_Y;
class=class="str">"cmt">//--- 数值
   class="type">class="kw">double v=(m_slider1.GetValue()==WRONG_VALUE) ?class="num">0.84615385 : m_slider1.GetValue();
class=class="str">"cmt">//--- 在创建之前设置属性
   m_slider1.XSize(class="num">264);
   m_slider1.YSize(class="num">40);
   m_slider1.EditXSize(class="num">87);
   m_slider1.MaxValue(class="num">1);
   m_slider1.StepValue(class="num">0.00000001);
   m_slider1.MinValue(-class="num">1);
   m_slider1.SetDigits(class="num">8);
   m_slider1.SetValue(v);
   m_slider1.AreaColor(clrWhiteSmoke);
   m_slider1.LabelColor(clrBlack);
   m_slider1.LabelColorLocked(clrSilver);
   m_slider1.EditColorLocked(clrWhiteSmoke);
   m_slider1.EditBorderColor(clrSilver);
   m_slider1.EditBorderColorLocked(clrSilver);
   m_slider1.EditTextColorLocked(clrSilver);
   m_slider1.SlotLineDarkColor(clrSilver);
   m_slider1.SlotLineLightColor(clrWhite);
   m_slider1.SlotYSize(class="num">4);
   m_slider1.ThumbColorLocked(clrLightGray);
   m_slider1.ThumbColorPressed(clrSilver);
   m_slider1.SlotIndicatorColor(C&class="macro">#x27;class="num">85,class="num">170,class="num">255&class="macro">#x27;);
   m_slider1.SlotIndicatorColorLocked(clrLightGray);
class=class="str">"cmt">//--- 创建控件
   if(!m_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 可用性将依赖于第五个复选框的当前状态
   m_slider1.SliderState(m_checkbox5.CheckButtonState());
class=class="str">"cmt">//--- 把对象加到对象组的通用数组中
   CWndContainer::AddToElementsArray(class="num">0,m_slider1);
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 面板控件联动的事件处理落地

在 MT5 自建交易面板时,CreateTradePanel 负责把复选框、滑动条等控件挂到图表上。代码中两块高亮逻辑最关键:CreateCheckBox5 与 CreateSlider1 任一返回 false 就直接退出,避免半成品面板残留在图上。 事件层用 OnEvent 捕获 CHARTEVENT_CUSTOM 自定义消息。当点击标签 id 匹配 m_checkbox5.Id() 时,把复选框状态通过 SliderState 同步给滑动条——这意味着勾选框能实时驱动滑块位置,适合做“开关+参数微调”一体化界面。 外汇与贵金属波动剧烈、杠杆风险高,这类面板只解决操作效率,不改变信号胜率。复制下面代码到 MT5 的 EA 或指标类里,把 m_checkbox5、m_slider1 换成你自己的成员对象即可验证联动。

MQL5 / C++
class="type">bool CProgram::CreateTradePanel(class="type">void)
  {
class=class="str">"cmt">//--- 创建控件表单1
class=class="str">"cmt">//--- 创建控件:
class=class="str">"cmt">//   主菜单
class=class="str">"cmt">//--- 上下文菜单
class=class="str">"cmt">//--- 创建状态栏
class=class="str">"cmt">//--- 复选框
   if(!CreateCheckBox5("Checkbox class="num">5"))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 滑动条
   if(!CreateSlider1("Slider class="num">1:"))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 重绘图表
   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)
  {
class=class="str">"cmt">//--- 按下文字标签的事件
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL)
     {
     ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     class=class="str">"cmt">//--- 点击了第五个复选框
     if(lparam==m_checkbox5.Id())
       {
       class=class="str">"cmt">//--- 设置第一个滑动条的状态
       m_slider1.SliderState(m_checkbox5.CheckButtonState());
       }
     }
class=class="str">"cmt">//--- 结束输入栏位中的数据输入事件
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
     ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
  }

用两个滑块框住价格区间

双重滑动条和单滑块最大的不同,是它能在同一条滑轨上锁定一段数值区间,而不是只取一个点。左滑块只能从左向右推到右侧滑块为止,右滑块只能从右向左推到左侧滑块为止,两者谁也越不过谁,这就天然把取值卡在一段范围内。 控件下方通常配两个输入栏位,分别绑定左右滑块的当前值。手动敲数字进去,滑块会立刻跳到对应位置;反过来拖滑块,栏位里的数字也实时跟着变。这种双向同步在 MT5 面板里做区间过滤(比如只显示某段点差或某段周期)很顺手。 从对象树看,这个控件由八个基础图形对象拼出来:背景、标题文字、左输入栏、右输入栏、滑块线、左侧移动块、右侧移动块、滑块指示针。也就是说你在 EA 里要自己管这八块的锚点和重绘,不是系统原生控件。 打开 MT5 新建一个空 EA,把背景矩形和两条移动块画出来,就能验证这套对象结构是否如上述八块组成,外汇与贵金属品种波动大,面板重绘频率高,实盘前先在策略测试器里跑一遍避免卡顿。

「双重滑动条控件的类结构拆解」

在自定义面板开发里,双重滑动条(Dual Slider)比单滑块多了左、右两个独立输入栏和拖块,对应的 CDualSlider 类直接挂在 CElement 之下,属性接口与 CSlider 基本一致,差异只在多出来的一套左右状态成员。 类内部用 m_left_edit_value / m_right_edit_value 存输入栏当前值,m_left_current_pos_x / m_right_current_pos_x 存滑块像素坐标,另用两个 ENUM_THUMB_MOUSE_STATE 枚举跟踪鼠标按下状态。这意味着左右拖块可以分别响应,不会互相串扰。 左侧和右侧滑块的移动逻辑高度对称,OnDragLeftThumb / OnDragRightThumb 与 UpdateLeftThumb / UpdateRightThumb 成对出现,只是作用的坐标轴不同。想验证的话,把 DualSlider.mqh 丢进 MT5 的 Include 目录,在 EA 里 new 一个 CDualSlider 对象,分别调 GetLeftValue / GetRightValue 就能看到双端数值独立返回。 下面这段是类声明的核心骨架,注意私有段里左右成员都是平行定义的,没有共用变量:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  WndContainer.mqh |
class=class="str">"cmt">//|                                         Copyright class="num">2015, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                                    [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "DualSlider.mqh"
class CDualSlider : class="kw">public CElement
  {
class="kw">private:
   class=class="str">"cmt">//--- 输入栏位中当前数值 (左侧和右侧)
   class="type">class="kw">double            m_left_edit_value;
   class="type">class="kw">double            m_right_edit_value;
   class=class="str">"cmt">//--- 滑动条滑块的当前位置 (左侧和右侧)
   class="type">class="kw">double            m_left_current_pos;
   class="type">class="kw">double            m_left_current_pos_x;
   class="type">class="kw">double            m_right_current_pos;
   class="type">class="kw">double            m_right_current_pos_x;
   class=class="str">"cmt">//--- 对于滑动条滑块(左侧和右侧),鼠标键的状态(按下/松开)
   ENUM_THUMB_MOUSE_STATE m_clamping_mouse_left_thumb;
   ENUM_THUMB_MOUSE_STATE m_clamping_mouse_right_thumb;
   class=class="str">"cmt">//---
class="kw">public:
   class=class="str">"cmt">//--- 返回输入栏位中的数值 (左侧和右侧)
   class="type">class="kw">double            GetLeftValue(class="type">void)                  class="kw">const { class="kw">return(m_left_edit_value);        }
   class="type">class="kw">double            GetRightValue(class="type">void)                 class="kw">const { class="kw">return(m_right_edit_value);       }
   class="type">bool              SetLeftValue(class="type">class="kw">double value);
   class="type">bool              SetRightValue(class="type">class="kw">double value);
   class=class="str">"cmt">//--- 修改输入栏位中的数值 (左侧和右侧)
   class="type">void              ChangeLeftValue(class="kw">const class="type">class="kw">double value);
   class="type">void              ChangeRightValue(class="kw">const class="type">class="kw">double value);
   class=class="str">"cmt">//---
class="kw">private:
   class=class="str">"cmt">//--- 移动滑动条滑块的处理 (左侧和右侧)
   class="type">void              OnDragLeftThumb(class="kw">const class="type">int x);
   class="type">void              OnDragRightThumb(class="kw">const class="type">int x);
   class=class="str">"cmt">//--- 更新滑动条滑块的位置 (左侧和右侧)
   class="type">void              UpdateLeftThumb(class="kw">const class="type">int new_x_point);
   class="type">void              UpdateRightThumb(class="kw">const class="type">int new_x_point);

◍ 双滑块输入栏回调怎么落地

在 MT5 自定义双滑块控件里,用户手动在左侧或右侧编辑框敲完数值,并不会自动同步滑块位置,必须靠 OnEndEdit 这个私有方法接住事件。它先比对 object_name 是不是 m_left_edit 或 m_right_edit 的 Name(),命中后才进入对应分支处理。 以左侧为例,代码先用 StringToDouble 把 m_left_edit.Description() 的文本内容转成 double,再丢给 ChangeLeftValue 做边界裁剪与存储。随后连续调用 CalculateLeftThumbX、UpdateLeftThumb(m_left_thumb.X())、CalculateLeftThumbPos,把数值映射回像素坐标并刷新滑块与指示针。 右侧分支逻辑完全对称,只是对象换成 m_right_edit 与 m_right_thumb。两个分支末尾都用 EventChartCustom 向图表抛 ON_END_EDIT 自定义事件,携带控件 Id、Index 和标签描述,方便 EA 或脚本监听后做后续动作。 直接把这段 CDualSlider::OnEndEdit 拷进你的面板类,改一下 m_left_edit / m_right_edit 命名,就能在 MT5 里实测:拖动滑块和手填数值会不会出现位置漂移,外汇与贵金属图表上这类自定义控件的高频重绘需注意终端资源占用风险。

MQL5 / C++
class="type">bool CDualSlider::OnEndEdit(class="kw">const class="type">class="kw">string object_name)
  {
class=class="str">"cmt">//--- 如果是在左侧输入栏位中输入数值
   if(object_name==m_left_edit.Name())
     {
      class=class="str">"cmt">//--- 取得输入的数值
      class="type">class="kw">double entered_value=::StringToDouble(m_left_edit.Description());
      class=class="str">"cmt">//--- 检查,调整并保存新的数值
      ChangeLeftValue(entered_value);
      class=class="str">"cmt">//--- 计算滑动条滑块的X坐标
      CalculateLeftThumbX();
      class=class="str">"cmt">//--- 更新滑动条滑块的位置
      UpdateLeftThumb(m_left_thumb.X());
      class=class="str">"cmt">//--- 计算在数值范围内的位置
      CalculateLeftThumbPos();
      class=class="str">"cmt">//--- 检查,调整并存储新的数值
      ChangeLeftValue(m_left_current_pos);
      class=class="str">"cmt">//--- 更新滑动条指示针
      UpdateIndicator();
      class=class="str">"cmt">//--- 发送相关消息
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
      class="kw">return(true);
     }
class=class="str">"cmt">//--- 如果是在右侧输入栏位中输入数值
   if(object_name==m_right_edit.Name())
     {
      class=class="str">"cmt">//--- 取得输入的数值
      class="type">class="kw">double entered_value=::StringToDouble(m_right_edit.Description());
      class=class="str">"cmt">//--- 检查,调整并存储新的数值
      ChangeRightValue(entered_value);
      class=class="str">"cmt">//--- 计算滑动条滑块的X坐标
      CalculateRightThumbX();
      class=class="str">"cmt">//--- 更新滑动条滑块的位置
      UpdateRightThumb(m_right_thumb.X());
      class=class="str">"cmt">//--- 计算数值范围内的位置
      CalculateRightThumbPos();
      class=class="str">"cmt">//--- 检查,调整并存储新的数值
      ChangeRightValue(m_right_current_pos);
      class=class="str">"cmt">//--- Update the slider indicator
      UpdateIndicator();
      class=class="str">"cmt">//--- 发送相关消息
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
      class="kw">return(true);
     }
class=class="str">"cmt">//---
   class="kw">return(class="kw">false);
  }

双滑块拖拽时的鼠标事件分流

在 MT5 自定义控件里,双滑块(dual slider)的实时拖拽依赖图表鼠标移动事件 CHARTEVENT_MOUSE_MOVE。事件回调 OnEvent 拿到 lparam/dparam 分别是鼠标 x、y 像素坐标,sparam 转成 bool 即为左键按下状态,这三行是后续所有命中判定的输入源。 先判可见性与屏蔽态:元件隐藏或 m_slider_state 为假就直接 return,避免后台图表空转消耗 CPU。接着用 x、y 与 X()/X2()/Y()/Y2() 边界比对,给主体和两个滑块分别设 MouseFocus,这一步决定了高亮和后续能否抓住滑块。 左右滑块的命中检测拆得很干净:CheckMouseOnLeftThumb 和 CheckMouseOnRightThumb 各自把 m_clamping_mouse_left_thumb / right 置为 THUMB_PRESSED_INSIDE。一旦左侧按下,就走 OnDragLeftThumb(x) 改坐标、CalculateLeftThumbPos 算数值占比、ChangeLeftValue 写回输入框、UpdateIndicator 刷新指针,右侧完全对称。 开 MT5 把这段塞进你的 CElement 派生类,故意把 m_slider_state 设 false,能看到鼠标划过滑块不再响应——这就是屏蔽态的最小验证点。外汇与贵金属图表挂这类控件时,注意高频鼠标事件可能拖慢 tick 处理,属于高风险环境下的性能权衡。

MQL5 / C++
class="type">void CDualSlider::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_MOUSE_MOVE)
     {
      class=class="str">"cmt">//--- 如果元件是隐藏的,退出
      if(!CElement::IsVisible())
         class="kw">return;
      class=class="str">"cmt">//--- 鼠标左键的坐标和状态
      class="type">int x=(class="type">int)lparam;
      class="type">int y=(class="type">int)dparam;
      m_mouse_state=(class="type">bool)class="type">int(sparam);
      class=class="str">"cmt">//--- 检查元件的焦点
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      m_left_thumb.MouseFocus(x>m_left_thumb.X() && x<m_left_thumb.X2() &&
                              y>m_left_thumb.Y() && y<m_left_thumb.Y2());
      m_right_thumb.MouseFocus(x>m_right_thumb.X() && x<m_right_thumb.X2() &&
                               y>m_right_thumb.Y() && y<m_right_thumb.Y2());
      class=class="str">"cmt">//--- 如果元件被屏蔽,退出
      if(!m_slider_state)
         class="kw">return;
      class=class="str">"cmt">//--- 检查和存储鼠标按键的状态
      CheckMouseOnLeftThumb();
      CheckMouseOnRightThumb();
      class=class="str">"cmt">//--- 改变滑动条滑块的颜色
      ChangeThumbColor();
      class=class="str">"cmt">//--- 处理滑动条滑块在滑轨线上的运动 (左侧)
      if(m_clamping_mouse_left_thumb==THUMB_PRESSED_INSIDE)
        {
         class=class="str">"cmt">//--- 移动滑动条滑块
         OnDragLeftThumb(x);
         class=class="str">"cmt">//--- 计算滑动条滑块在数值范围内的位置
         CalculateLeftThumbPos();
         class=class="str">"cmt">//--- 在输入栏位中设置新的数值
         ChangeLeftValue(m_left_current_pos);
         class=class="str">"cmt">//--- 更新滑块指示针
         UpdateIndicator();
         class="kw">return;
        }
      class=class="str">"cmt">//--- 处理滑动条滑块在滑轨上的运动(右侧)
      if(m_clamping_mouse_right_thumb==THUMB_PRESSED_INSIDE)
        {
         class=class="str">"cmt">//--- 移动滑动条滑块
         OnDragRightThumb(x);
         class=class="str">"cmt">//--- 计算滑动条滑块在数值范围内的位置
         CalculateRightThumbPos();
         class=class="str">"cmt">//--- 在输入栏位中设置新的数值
         ChangeRightValue(m_right_current_pos);
         class=class="str">"cmt">//--- 更新滑动条指示针
         UpdateIndicator();
         class="kw">return;
        }
     }
  }

「给面板挂上双重滑动条」

在自定义类 CProgram 里加一个 CDualSlider 实例,再用 CreateDualSlider1() 把它画到表单上。数值区间设成 -2000 到 1000,左右两个滑块的初始回退值分别是 0 和 500(取不到原值时用这两个兜底)。 控件的可用性直接绑死在第五个复选框状态上:m_dual_slider1.SliderState(m_checkbox5.CheckButtonState()) 这一行决定它灰不灰。和之前那个简单滑动条一样,勾选框不亮,双重滑动条就锁住不能拖。 代码里把控件定位在窗口左上角偏移 (32, 275) 像素处,滑块总长 264×40,编辑框宽 87,步进 1、小数位 0。编译丢进 MT5 图表,能看到两个拖柄中间用蓝色指示条标出选区——外汇和贵金属图表上做这种面板属于高频改界面,注意 GUI 卡顿可能影响下单响应,这类自定义工具高风险,先在模拟盘验。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 用于创建应用程序的类                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CProgram : class="kw">public CWndEvents
  {
class="kw">private:
   class=class="str">"cmt">//--- 滑动条
   CDualSlider        m_dual_slider1;
   class=class="str">"cmt">//---
class="kw">private:
   class=class="str">"cmt">//--- 滑动条
class="macro">#define DUALSLIDER1_GAP_X(class="num">32)
class="macro">#define DUALSLIDER1_GAP_Y(class="num">275)
   class="type">bool              CreateDualSlider1(class="kw">const class="type">class="kw">string text);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建双重滑动条 class="num">1                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateDualSlider1(class="kw">const class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 保存窗口指针
   m_dual_slider1.WindowPointer(m_window1);
class=class="str">"cmt">//--- 坐标
   class="type">int x=m_window1.X()+DUALSLIDER1_GAP_X;
   class="type">int y=m_window1.Y()+DUALSLIDER1_GAP_Y;
class=class="str">"cmt">//--- 数值
   class="type">class="kw">double v1=(m_dual_slider1.GetLeftValue()==WRONG_VALUE) ?class="num">0 : m_dual_slider1.GetLeftValue();
   class="type">class="kw">double v2=(m_dual_slider1.GetRightValue()==WRONG_VALUE) ?class="num">500 : m_dual_slider1.GetRightValue();
class=class="str">"cmt">//--- 在创建之前设置属性
   m_dual_slider1.XSize(class="num">264);
   m_dual_slider1.YSize(class="num">40);
   m_dual_slider1.EditXSize(class="num">87);
   m_dual_slider1.MaxValue(class="num">1000);
   m_dual_slider1.StepValue(class="num">1);
   m_dual_slider1.MinValue(-class="num">2000);
   m_dual_slider1.SetDigits(class="num">0);
   m_dual_slider1.SetLeftValue(v1);
   m_dual_slider1.SetRightValue(v2);
   m_dual_slider1.AreaColor(clrWhiteSmoke);
   m_dual_slider1.LabelColor(clrBlack);
   m_dual_slider1.LabelColorLocked(clrSilver);
   m_dual_slider1.EditColorLocked(clrWhiteSmoke);
   m_dual_slider1.EditBorderColor(clrSilver);
   m_dual_slider1.EditBorderColorLocked(clrSilver);
   m_dual_slider1.EditTextColorLocked(clrSilver);
   m_dual_slider1.SlotLineDarkColor(clrSilver);
   m_dual_slider1.SlotLineLightColor(clrWhite);
   m_dual_slider1.SlotYSize(class="num">4);
   m_dual_slider1.ThumbColorLocked(clrLightGray);
   m_dual_slider1.ThumbColorPressed(clrSilver);
   m_dual_slider1.SlotIndicatorColor(C&class="macro">#x27;class="num">85,class="num">170,class="num">255&class="macro">#x27;);
   m_dual_slider1.SlotIndicatorColorLocked(clrLightGray);
class=class="str">"cmt">//--- 创建控件
   if(!m_dual_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 可用性将依赖于第五个复选框的当前状态
   m_dual_slider1.SliderState(m_checkbox5.CheckButtonState());
class=class="str">"cmt">//--- 把对象添加到对象组的通用数组中
   CWndContainer::AddToElementsArray(class="num">0,m_dual_slider1);
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建交易面板                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateTradePanel(class="type">void)
  {
class=class="str">"cmt">//--- 创建控件表单1
class=class="str">"cmt">//--- 创建控件:
class=class="str">"cmt">//    主菜单
class=class="str">"cmt">//--- 上下文菜单

◍ 用复选框联动控制双滑块状态

在自定义面板里,第五个复选框(m_checkbox5)被点击时,可以直接把它的勾选状态同步给滑动条和双滑块。代码里通过 lparam==m_checkbox5.Id() 捕获这次点击事件,再调用 SliderState() 把 CheckButtonState() 的布尔值灌进去,双滑块 m_dual_slider1 也会跟着切换启用或禁用。 事件入口 OnEvent 接收四个参数:id 是事件编号,lparam 和 dparam 分别是长整型与双精度载荷,sparam 是字符串载荷。当 id 等于 CHARTEVENT_CUSTOM+ON_CLICK_LABEL 时,说明用户点的是文字标签类控件,此时用 Print 把四个参数打印到日志,方便在 MT5 终端里核对每一次交互的原始数据。 另一类事件是 CHARTEVENT_CUSTOM+ON_END_EDIT,对应输入栏结束数值录入。目前处理函数只做了打印,没有改任何控件状态;若你想在用户输入止损点数后实时刷新滑块范围,可以在该分支里读 sparam 转成 double 再喂给 m_dual_slider1。外汇与贵金属波动剧烈,这类界面联动仅降低手动操作误差,不预示任何方向,实盘前请在策略测试器里跑通逻辑。

MQL5 / C++
  if(!CreateDualSlider1("Dual Slider class="num">1:"))
      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">//| 事件处理函数                                                     |
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_LABEL)
     {
     ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     class=class="str">"cmt">//--- 如果点击了第五个复选框
     if(lparam==m_checkbox5.Id())
       {
       class=class="str">"cmt">//--- 设置滑动条的状态
       m_slider1.SliderState(m_checkbox5.CheckButtonState());
       m_dual_slider1.SliderState(m_checkbox5.CheckButtonState());
       }
     }
class=class="str">"cmt">//--- 结束在输入栏位中的数值输入事件
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
     ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
   }

画得少,看得清

这一节把第六部分收了尾:复选框、编辑框、带勾选的编辑框、复选组合框、滑动条和双重滑动条六个控件已并入图形界面库,当前库结构如图 5 所示。后面几篇会接表格和分页,把这套 Easy And Fast GUI 再扩一层。 两个压缩包体积很接近,MQL4 版 248.19 KB、MQL5 版 248.15 KB,下载后直接丢进 MT5 的 MQL5/Include 就能调。遇到滑块初始跑到最右、实际值却是对的这类毛病,先换库的最新版再排查,社区里 2018 年那次反馈就是靠更新解决的。 库只管画界面,不替你下单;外汇和贵金属波动大、杠杆高,接进 EA 前先在策略测试器跑几遍再上真仓。

把控件状态同步交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到滑块区间与面板参数的偏离提示,你只管调策略逻辑。

常见问题

SeparateLine 高度大于两个像素时两线间出现空隙,可当作滑块移动的槽,通常设 4–6 像素较稳。
应在类内对最小值针和最大值针做交叉判断,移动时强制左针不超过右针,否则区间上下限会颠倒。
控件仍保留输入栏位对象,点击后可直接键入数值,类需处理键盘事件并重绘滑块位置。
可以,小布的品种页会标注控件当前区间与策略参数的不一致,省去手动核对 EA 界面的时间。
需 Element.mqh、Window.mqh 以及 SeparateLine.mqh,后者提供画槽线用的 CSeperateLine 类。