图形界面 X: 文本编辑框, 图片滑块和简单控件 (构建 5)·进阶篇
🧩

图形界面 X: 文本编辑框, 图片滑块和简单控件 (构建 5)·进阶篇

(2/3)· 从数值输入到任意文本与图像拖动,函数库如何把交互控件一次封装到位

含代码示例 第 2/3 篇
不少开发者在 MQL5 里用 CSpinEdit 硬塞文本输入,结果字符截断、对齐错乱还拖慢事件循环。只靠数值框应付所有交互,等于拿扳手拧螺丝——迟早卡死在 UI 层。

◍ 图说滑块怎么摆

在 MT5 自定义面板里,图片滑块是一种偏信息展示的控件,适合做快速参考卡:把某类价格形态截图或指标用法说明直接铺在图表侧边,省去交易者翻文档的成本。 它从结构上看由五块拼成:底板背景、左右切换箭头、一组单选按钮、与单选按钮一一对应的图片序列,以及底部图例。切换箭头本质只是驱动单选按钮状态变化,图片组随之联动。 实际做 EA 面板时,若参考图超过 4 张,建议把单选按钮隐藏只用箭头,否则控件宽度会吃掉太多 K 线可视区,这点在高分屏以外的小屏上尤其明显。

自绘图片滑块控件的类结构

在 WndContainer.mqh 里挂上 PicturesSlider.mqh 后,图片滑块就作为一个独立控件类 CPicturesSlider 存在,继承自 CElement。用户可调的属性集中在背景色、背景框色、图片 Y 轴边距,以及箭头按钮和单选按钮在 x/y 两个方向上的边距与间距。 类内部用 private 成员存配置:m_area_color、m_area_border_color 管底色和边框;m_pictures_y_gap 管图片纵向留白;m_arrows_x_gap / m_arrows_y_gap 管左右箭头偏移;m_radio_buttons_x_gap / y_gap / x_offset 管单选按钮排布。这些都在构造器里给了默认值,比如箭头边距 2、单选按钮组距左缘 20、按钮间距 25、图片 Y 边距 25。 控件实际对象由五个子对象拼成:m_area 背景框、m_pictures[] 位图标签数组、m_radio_buttons 单选组、m_left_arrow 和 m_right_arrow 图标按钮。主创建方法 CreatePicturesSlider() 之前,必须先用 AddPicture() 把图片路径压进 m_file_path[],不传参就掉到省缺路径 Images\EasyAndFastGUI\Icons\bmp64\no_image.bmp。 宽度按单选按钮组距左缘边距加右箭头坐标自动算,高度取第一张图尺寸(假定同尺寸)。想切图就调 SelectPicture(),它内部被箭头和单选按钮的事件处理器触发。外汇和贵金属 MT5 界面开发属高风险环境,参数误设可能导致控件不渲染。

MQL5 / C++
class CPicturesSlider : class="kw">public CElement
  {
 class="kw">private:
   class=class="str">"cmt">//--- 控件背景框颜色
   class="type">color              m_area_color;
   class="type">color              m_area_border_color;
   class=class="str">"cmt">//--- 图片沿 Y 轴的边距
   class="type">int                m_pictures_y_gap;
   class=class="str">"cmt">//--- 按钮边距
   class="type">int                m_arrows_x_gap;
   class="type">int                m_arrows_y_gap;
   class=class="str">"cmt">//--- 单选按钮边距
   class="type">int                m_radio_buttons_x_gap;
   class="type">int                m_radio_buttons_y_gap;
   class="type">int                m_radio_buttons_x_offset;
   class=class="str">"cmt">//--- class="kw">public:
   class=class="str">"cmt">//--- (class="num">1) 背景以及 (class="num">2) 背景框颜色
   class="type">void               AreaColor(class="kw">const class="type">color clr)             { m_area_color=clr;                     }
   class="type">void               AreaBorderColor(class="kw">const class="type">color clr)        { m_area_border_color=clr;              }
   class=class="str">"cmt">//--- 箭头按钮边距
   class="type">void               ArrowsXGap(class="kw">const class="type">int x_gap)             { m_arrows_x_gap=x_gap;                 }
   class="type">void               ArrowsYGap(class="kw">const class="type">int y_gap)             { m_arrows_y_gap=y_gap;                 }
   class=class="str">"cmt">//--- 图片沿 Y 轴边距
   class="type">void               PictureYGap(class="kw">const class="type">int y_gap)            { m_pictures_y_gap=y_gap;               }
   class=class="str">"cmt">//--- (class="num">1) 单选按钮边距, (class="num">2) 单选按钮之间的距离
   class="type">void               RadioButtonsXGap(class="kw">const class="type">int x_gap)       { m_radio_buttons_x_gap=x_gap;          }
   class="type">void               RadioButtonsYGap(class="kw">const class="type">int y_gap)       { m_radio_buttons_y_gap=y_gap;          }
   class="type">void               RadioButtonsXOffset(class="kw">const class="type">int x_offset){ m_radio_buttons_x_offset=x_offset;    }
  };

class CPicturesSlider : class="kw">public CElement
  {
 class="kw">private:
   class=class="str">"cmt">//--- 创建控件的对象
   CRectLabel        m_area;
   CBmpLabel         m_pictures[];
   CRadioButtons     m_radio_buttons;
   CIconButton       m_left_arrow;
   CIconButton       m_right_arrow;
   class=class="str">"cmt">//--- class="kw">public:
   class=class="str">"cmt">//--- 创建图片滑块方法
   class="type">bool               CreatePicturesSlider(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               CreatePictures(class="type">void);
   class="type">bool               CreateRadioButtons(class="type">void);
   class="type">bool               CreateLeftArrow(class="type">void);
   class="type">bool               CreateRightArrow(class="type">void);
  };

class CPicturesSlider : class="kw">public CElement
  {
 class="kw">private:
   class=class="str">"cmt">//--- 图片数组 (图片路径)
   class="type">class="kw">string             m_file_path[];
   class=class="str">"cmt">//--- 图片的省缺路径
   class="type">class="kw">string             m_default_path;
   class=class="str">"cmt">//--- class="kw">public:
   class=class="str">"cmt">//--- 添加图片
   class="type">void               AddPicture(class="kw">const class="type">class="kw">string file_path="");
  };

CPicturesSlider::CPicturesSlider(class="type">void) : m_default_path("Images\\EasyAndFastGUI\\Icons\\bmp64\\no_image.bmp"),
                                         m_area_color(clrNONE),
                                         m_area_border_color(clrNONE),
                                         m_arrows_x_gap(class="num">2),
                                         m_arrows_y_gap(class="num">2),
                                         m_radio_button_width(class="num">12),
                                         m_radio_buttons_x_gap(class="num">25),
                                         m_radio_buttons_y_gap(class="num">1),
                                         m_radio_buttons_x_offset(class="num">20),
                                         m_pictures_y_gap(class="num">25)
  {
   CElement::ClassName(CLASS_NAME);
   m_zorder=class="num">0;
  }

class="type">void CPicturesSlider::AddPicture(class="kw">const class="type">class="kw">string file_path="")
  {
   class="type">int array_size=::ArraySize(m_pictures);
   class="type">int new_size=array_size+class="num">1;
   ::ArrayResize(m_pictures,new_size);
   ::ArrayResize(m_file_path,new_size);
   m_file_path[array_size]=(file_path=="")? m_default_path : file_path;
  }

「正文」

class CPicturesSlider : public CElement   { public :    //--- 切换指定索引处的图片    void               SelectPicture( const uint index);   }; //+------------------------------------------------------------------+ //指定要显示的图片//+------------------------------------------------------------------+ void CPicturesSlider::SelectPicture( const uint index)   { //--- 获取图片数量    uint pictures_total=PicturesTotal(); //--- 如果组内无图片, 报告    if (pictures_total< 1 )      {       :: Print ( __FUNCTION__ , " > 方法被调用, "                "如果组内至少包含一副图片!使用 CPicturesSlider::AddPicture() 方法" );        return ;      } //--- 如果超出数组范围, 调整索引    uint correct_index=(index>=pictures_total)?pictures_total- 1 : index; //--- 在此索引处选择单选按钮    m_radio_buttons.SelectRadioButton(correct_index); //--- 切换图片    for ( uint i= 0 ; i<pictures_total; i++)      {        if (i==correct_index)          m_pictures[i].Timeframes( OBJ_ALL_PERIODS );        else          m_pictures[i].Timeframes( OBJ_NO_PERIODS );      }   }

当处理箭头按钮时, 控件的事件处理器调用 CPicturesSlider::OnClickLeftArrow () 和 CPicturesSlider::OnClickRightArrow () 方法。下列清单显示了鼠标左键方法的代码。如果必要, 点击图片滑块按钮的事件 可在 MQL 应用程序的自定义类中跟踪。 class CPicturesSlider : public CElement   { public : private :    //--- 点击左按钮处理器    bool               OnClickLeftArrow( const string clicked_object);    //--- 点击右按钮处理器    bool               OnClickRightArrow( const string clicked_object);   }; //+-------------------------------------------------

◍ 独立挂件的文本与图片控件

在 MT5 自定义界面里,若只想放一块说明文字或一张图,而不想把它绑进某个面板容器,可以直接用 CTextLabel 和 CPicture 两个轻量类。它们继承自 CElement,作为独立图形对象存在,创建后仍能随时用代码改内容。 CPicture 的可调属性只有一个:图片路径。构造函数里默认路径是 "Images\\EasyAndFastGUI\\Icons\\bmp64\\no_image.bmp",也就是资源里带的 no_image.bmp;只要没调 Path() 设新路径,界面上就一直显示这张省缺图。即便图形界面已经跑起来了,随时调一次 Path("你的图.bmp") 就能换图。 文本标签更简单,用户侧只暴露四个点:标签文本、文本颜色、字体、字号。对应方法分别是 LabelText() 读写描述、LabelColor() 设颜色、LabelFont() 设字体名、LabelFontSize() 设字号。写 EA 或脚本时,用这几个方法就能在图上动态标注价位或状态。 外汇与贵金属图表挂这类控件做提示属高风险环境,任何标注都只是辅助观察,不预示行情方向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                      Picture.mqh |
class=class="str">"cmt">//|                        版权所有 class="num">2016, MetaQuotes 软件公司|
class=class="str">"cmt">//|                                              [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Element.mqh"
class="macro">#include "Window.mqh"
class=class="str">"cmt">//--- 资源
class="macro">#resource "\Images\EasyAndFastGUI\Icons\bmp64\no_image.bmp"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建图片的类                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CPicture : class="kw">public CElement
  {
class="kw">private:
   class=class="str">"cmt">//--- 图片路径
   class="type">class="kw">string             m_path;
   class=class="str">"cmt">//---
class="kw">public:
   class=class="str">"cmt">//--- 图片路径取值/赋值
   class="type">class="kw">string             Path(class="type">void)               class="kw">const { class="kw">return(m_path);             }
   class="type">void               Path(class="kw">const class="type">class="kw">string path);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造器                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPicture::CPicture(class="type">void) : m_path("Images\EasyAndFastGUI\Icons\bmp64\no_image.bmp")
  {
class=class="str">"cmt">//--- 在基类中保存控件类的名字
   CElement::ClassName(CLASS_NAME);
class=class="str">"cmt">//--- 设置鼠标左键点击优先权
   m_zorder=class="num">0;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 设置图片                                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CPicture::Path(class="kw">const class="type">class="kw">string path)
  {
   m_path=path;
   m_picture.BmpFileOn("::"+path);
   m_picture.BmpFileOff("::"+path);
  }

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建文本标签的类                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTextLabel : class="kw">public CElement
  {
class="kw">public:
   class=class="str">"cmt">//--- 标签文本取值/赋值
   class="type">class="kw">string             LabelText(class="type">void)             class="kw">const { class="kw">return(m_label.Description()); }
   class="type">void               LabelText(class="kw">const class="type">class="kw">string text)      { m_label.Description(text);     }
   class=class="str">"cmt">//--- 设置文本标签的 (class="num">1) 颜色, (class="num">2) 字体和 (class="num">3) 字号
   class="type">void               LabelColor(class="kw">const class="type">color clr)       { m_label.Color(clr);            }
   class="type">void               LabelFont(class="kw">const class="type">class="kw">string font)      { m_label.Font(font);            }
   class="type">void               LabelFontSize(class="kw">const class="type">int size)     { m_label.FontSize(size);        }
  };

GUI 字体池直接用 CFonts 调

做 MT5 自定义面板时,挑字体不用自己列清单。CFonts 类内置了 187 种终端系统字体,就是你在图形对象属性里见过的那批,随取随用。 字体定义文件在 MetaTrader 5\MQL5\Include\EasyAndFastGUI\Fonts.mqh,已经被 Objects.mqh 一路 include 进来,整个库里任意位置都能直接访问,不用再手动挂路径。 类本身很薄:只有 FontsTotal() 返回字体总数、FontsByIndex() 按索引取名字,数组在构造器里初始化。下面这段是类骨架和构造/析构逻辑。

MQL5 / C++
class CFonts
{
class="kw">private:
   class="type">class="kw">string            m_fonts[];      class=class="str">"cmt">// 私有字体名数组
class="kw">public:
                    CFonts(class="type">void);
                    ~CFonts(class="type">void);
   class="type">int               FontsTotal(class="type">void) class="kw">const { class="kw">return(::ArraySize(m_fonts)); } class=class="str">"cmt">// 返回数组大小即字体数
   class="type">class="kw">string            FontsByIndex(class="kw">const class="type">uint index);
class="kw">private:
   class="type">void              InitializeFontsArray(class="type">void); class=class="str">"cmt">// 构造时填数组
};
CFonts::CFonts(class="type">void) { InitializeFontsArray(); } class=class="str">"cmt">// 构造即初始化
CFonts::~CFonts(class="type">void) { ::ArrayFree(m_fonts); } class=class="str">"cmt">// 析构释放
FontsByIndex 内部做了越界保护:传进来的索引大于等于总数时,自动钳到最后一个,避免爆数组。
MQL5 / C++
class="type">class="kw">string CFonts::FontsByIndex(class="kw">const class="type">uint index)
{
   class="type">uint array_size=FontsTotal();             class=class="str">"cmt">// 先拿总数
   class="type">uint i=(index>=array_size)?array_size-class="num">1:index; class=class="str">"cmt">// 超界就取末位
   class="kw">return(m_fonts[i]);                       class=class="str">"cmt">// 返回对应字体名
}
实盘写 EA 界面时,直接 CFonts f; f.FontsByIndex(0) 就能拿到第一种字体名,循环到 f.FontsTotal() 可遍历全部 187 个,外汇贵金属界面高频重绘注意对象数量风险。

MQL5 / C++
class CFonts
{
class="kw">private:
   class="type">class="kw">string            m_fonts[];
class="kw">public:
                    CFonts(class="type">void);
                    ~CFonts(class="type">void);
   class="type">int               FontsTotal(class="type">void) class="kw">const { class="kw">return(::ArraySize(m_fonts)); }
   class="type">class="kw">string            FontsByIndex(class="kw">const class="type">uint index);
class="kw">private:
   class="type">void              InitializeFontsArray(class="type">void);
};
CFonts::CFonts(class="type">void) { InitializeFontsArray(); }
CFonts::~CFonts(class="type">void) { ::ArrayFree(m_fonts); }

class="type">class="kw">string CFonts::FontsByIndex(class="kw">const class="type">uint index)
{
   class="type">uint array_size=FontsTotal();
   class="type">uint i=(index>=array_size)?array_size-class="num">1:index;
   class="kw">return(m_fonts[i]);
}

「工具提示同步与悬停高亮算法优化」

GUI 库这次把对话框工具提示的显示逻辑修掉了:主窗口里工具提示按钮的按下状态现在会广播到图形界面所有窗口,不再各画各的。底层靠新事件标识 ON_WINDOW_TOOLTIPS(在 Defines.mqh 里定义为整数 29)打通,CWindow 类新增 OnClickTooltipsButton() 拦截按钮点击并发自定义事件,CWndEvents 里再靠 OnWindowTooltips() 把状态同步给其余窗口。 代码里能看清事件链路:点击对象名带 '_window_tooltip_' 才认,取控件 id 比对,吻合后把按钮状态写进 m_tooltips_button_state,再 EventChartCustom 抛 29 号事件;引擎侧收到后若窗口标识匹配,就循环把所有窗口的 TooltipButtonState 设成主窗口的值。MT5 里挂上这套库,点一下主窗口提示钮,子窗口 tooltip 应立刻跟随切换。 悬停高亮也重写了。旧逻辑对每个控件遍历全部项目查鼠标位置,资源吃紧;现在只让两个项目参与变色,仅在焦点跨项时做一次周期搜索。以 CListView 为例,加了 m_prev_item_index_focus 存上一次焦点索引,CheckItemFocus() 只在鼠标进区或跨项时触发,ChangeItemsColor() 据此改算法。实测大列表拖动光标,CPU 占用和卡顿倾向明显下降。 另外补了三处可编程能力:控件建完改描述文本、所有控件设/换图标(IconFileOn/Off 管替换)、按钮和选项卡建完用代码切按下释放态。这些不改动事件主干,但让你在 EA 面板里动态换肤、换文案成为可能,外汇贵金属面板高频刷新时尤其值得验一遍。

MQL5 / C++
class="macro">#define ON_WINDOW_TOOLTIPS( class="num">29 ) class=class="str">"cmt">// 在工具提示按钮上点击

class CWindow : class="kw">public CElement
  {
class="kw">private:
   class=class="str">"cmt">//--- 处理点击工具按钮事件
   class="type">bool              OnClickTooltipsButton(class="kw">const class="type">class="kw">string clicked_object);
  };

class="type">void CWindow::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_OBJECT_CLICK)
     {
      class=class="str">"cmt">//--- 如果工具按钮已被点击
      if(OnClickTooltipsButton(sparam))
        class="kw">return;
     }
  }

class="type">bool CWindow::OnClickTooltipsButton(class="kw">const class="type">class="kw">string clicked_object)
  {
   class=class="str">"cmt">//--- 如果窗口是一个对话框, 则无需此按钮
   if(m_window_type==W_DIALOG)
      class="kw">return(false);
   class=class="str">"cmt">//--- 如果无按钮点击, 离开
   if(::StringFind(clicked_object,CElement::ProgramName()+"_window_tooltip_",class="num">0)<class="num">0)
      class="kw">return(false);
   class=class="str">"cmt">//--- 从对象名获取控件标识符
   class="type">int id=CElement::IdFromObjectName(clicked_object);
   class=class="str">"cmt">//--- 如果控件标识符不匹配, 离开
   if(id!=CElement::Id())
      class="kw">return(false);
   class=class="str">"cmt">//--- 在类字段中保存状态
   m_tooltips_button_state=m_button_tooltip.State();
   class=class="str">"cmt">//--- 发送有关消息
   ::EventChartCustom(m_chart_id,ON_WINDOW_TOOLTIPS,CElement::Id(),CElement::Index(),"");
   class="kw">return(true);
  }

class CWndEvents : class="kw">public CWndContainer
  {
class="kw">private:
   class=class="str">"cmt">//--- 启用/禁用工具提示
   class="type">bool              OnWindowTooltips(class="type">void);
  };

class="type">void CWndEvents::ChartEventCustom(class="type">void)
  {
   if(OnWindowTooltips())
      class="kw">return;
  }

class="type">bool CWndEvents::OnWindowTooltips(class="type">void)
  {
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_TOOLTIPS)
      class="kw">return(false);
   if(m_lparam!=m_windows[class="num">0].Id())
      class="kw">return(true);
   class="type">int windows_total=WindowsTotal();
   for(class="type">int w=class="num">0;w<windows_total;w++)
     {
      if(w>class="num">0)
        m_windows[w].TooltipButtonState(m_windows[class="num">0].TooltipButtonState());
     }
   class="kw">return(true);
  }

class CListView : class="kw">public CElement
  {
class="kw">private:
   class="type">int               m_prev_item_index_focus;
   class="type">void              ChangeItemsColor(class="type">void);
   class="type">void              CheckItemFocus(class="type">void);
  };
交给小布盯盘看控件开销
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 CPU 负载与重绘频率,帮你判断自定义面板是否拖累执行环境。

常见问题

CSpinEdit 仅限数值输入与步进,CTextEdit 支持任意文本、对齐模式与选择光标,适用搜索框或品种数组录入。
目前小布盯盘内置的是标准品种页诊断,自定义 mqh 面板需自行部署到终端,但可参考其开销指标优化你的控件重绘逻辑。
图片滑块本质是图像绑定拖动事件,理论上可映射价格拖拽,但外汇贵金属波动快、高风险,实盘前务必在模拟环境验证延迟。
它统一了字体加载与回退策略,避免文本标签在俄文、中文环境下出现方框或基线偏移,控件描述更易读。
多来自事件去重与局部重绘,旧版全控件刷新改为脏矩形判定,简单控件越多收益越明显。