图形界面 X: 文本编辑框, 图片滑块和简单控件 (构建 5)·进阶篇
(2/3)· 从数值输入到任意文本与图像拖动,函数库如何把交互控件一次封装到位
◍ 图说滑块怎么摆
在 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 界面开发属高风险环境,参数误设可能导致控件不渲染。
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 或脚本时,用这几个方法就能在图上动态标注价位或状态。 外汇与贵金属图表挂这类控件做提示属高风险环境,任何标注都只是辅助观察,不预示行情方向。
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() 按索引取名字,数组在构造器里初始化。下面这段是类骨架和构造/析构逻辑。
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">// 析构释放
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">// 返回对应字体名 }
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 面板里动态换肤、换文案成为可能,外汇贵金属面板高频刷新时尤其值得验一遍。
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); };