图形界面 X: 简单快速开发库的更新 (版本 3)·综合运用
(3/3)· 从占位指标到消息联动,v3 最后一块拼图教你绕开 MT4 结构坑,跑通 EA 子窗口界面
「表格控件的窗口指针与可视区裁剪」
做 MT5 自定义面板时,CWindow 作为表单基类,把关闭、收起、展开、提示这几个按钮的指针通过 GetCloseButtonPointer 等方法暴露出来。实际写 EA 或指标界面,拿不到这些指针就没法给按钮挂事件,所以继承后第一件事就是确认 m_wnd 非空。 CTable::CreateTable 里有一段很关键:当可视行数大于总行数时,直接把 m_visible_rows_total 裁成 m_rows_total,列方向同理。这个裁剪避免了对不存在单元格的循环创建,在行数动态变化的行情面板里能少建几十个 Edit 对象。 坐标计算上,m_y_size 用 m_row_y_size*m_visible_rows_total-(m_visible_rows_total-1)+2 得出,也就是行高乘可见行数再减去行间隔再加 2 像素边框。若 m_auto_xresize_mode 开启,m_x_size 会按窗口右锚点重算,挂右侧自动拉伸的盘口表时常踩这个坑。 最后若所属窗口是对话框或已最小化,表格直接 Hide(),避免在不可见容器里白跑创建逻辑。外汇与贵金属图表加载这类界面时波动大,建议先在模拟盘验证渲染开销。
class CWindow : class="kw">public CElement { class="kw">public: class=class="str">"cmt">//--- 返回表单按钮指针的方法 CBmpLabel *GetCloseButtonPointer(class="type">void) { class="kw">return(::GetPointer(m_button_close)); } CBmpLabel *GetRollUpButtonPointer(class="type">void) { class="kw">return(::GetPointer(m_button_unroll)); } CBmpLabel *GetUnrollButtonPointer(class="type">void) { class="kw">return(::GetPointer(m_button_rollup)); } CBmpLabel *GetTooltipButtonPointer(class="type">void) { class="kw">return(::GetPointer(m_button_tooltip)); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建编辑框型表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTable::CreateTable(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">//--- 如果没有表单指针 if(!CElement::CheckWindowPointer(::CheckPointer(m_wnd))) class="kw">return(false); class=class="str">"cmt">//--- 可见部分不再需要通用 m_visible_rows_total =(m_visible_rows_total>m_rows_total)? m_rows_total : m_visible_rows_total; m_visible_columns_total =(m_visible_columns_total>m_columns_total)? m_columns_total : m_visible_columns_total; class=class="str">"cmt">//--- 初始化变量 m_id =m_wnd.LastId()+class="num">1; m_chart_id =chart_id; m_subwin =subwin; m_x =x; m_y =y; m_x_size =(m_x_size<class="num">1 || m_auto_xresize_mode)? (m_anchor_right_window_side)? m_wnd.X2()-m_x+m_x_size-(m_wnd.X2()-m_x)+class="num">1-m_auto_xresize_right_offset : m_wnd.X2()-m_x-m_auto_xresize_right_offset : m_x_size; m_y_size =m_row_y_size*m_visible_rows_total-(m_visible_rows_total-class="num">1)+class="num">2; class=class="str">"cmt">//--- 到边缘的距离 CElement::XGap((m_anchor_right_window_side)? m_x : m_x-m_wnd.X()); CElement::YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y()); class=class="str">"cmt">//--- 创建表格 if(!CreateArea()) class="kw">return(false); if(!CreateCells()) class="kw">return(false); if(!CreateScrollV()) class="kw">return(false); if(!CreateScrollH()) class="kw">return(false); class=class="str">"cmt">//--- 如果窗口是对话框或者是最小化状态,隐藏元件 if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); class=class="str">"cmt">//--- class="kw">return(true); }
改一版能演示自适应界面的测试EA
把前一篇的 MQL 应用稍作改动,就能在 SubWindow 指标子窗口里挂一个 EA,用来验证本文的界面逻辑。主窗口尺寸会跟着子窗口自动变,子窗口高度还能手动拖——关键在调用 CWindow::RollUpSubwindowMode() 时传 false,关掉卷起模式。 第一页所有控件以表单右侧为锚:表单宽度一变,它们距右边界的距离不动。创建 CSimpleButton 后只要调 AnchorRightWindowSide(true),按钮就钉在右边,不用自己算坐标。 第二页只放一个 CCanvasTable 绘制表格,子窗口宽高变化时表格跟着撑满。要让它听话,得先开 AutoXResizeMode() 和 AutoYResizeMode(),再用 AutoXResizeRightOffset()、AutoYResizeBottomOffset() 设偏移——样例里右偏移 1 像素、底偏移 25 像素。 第三页的 CLineGraph 线形图同样自主贴合表单边。第四、五页则堆了其他库控件演示右锚效果,具体形态可下测试程序自己翻。外汇与贵金属图表上跑这类 EA 属高风险操作,界面逻辑出错可能干扰下单,先在模拟盘验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建控件表单 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateWindow(class="kw">const class="type">class="kw">string caption_text) { class=class="str">"cmt">//--- 在窗口数组中加入窗口指针 CWndContainer::AddWindow(m_window); class=class="str">"cmt">//--- 坐标 class="type">int x=class="num">1; class="type">int y=class="num">1; class=class="str">"cmt">//--- 属性 m_window.Movable(false); m_window.UseRollButton(); m_window.AutoXResizeMode(true); m_window.AutoYResizeMode(true); m_window.RollUpSubwindowMode(false,false); class=class="str">"cmt">//--- 创建表单 if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) class="kw">return(false); class=class="str">"cmt">//--- 设置工具提示 m_window.GetCloseButtonPointer().Tooltip("Close program"); m_window.GetUnrollButtonPointer().Tooltip("Unroll"); m_window.GetRollUpButtonPointer().Tooltip("Roll up"); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建简单按钮 class="num">1 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateSimpleButton1(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">class="kw">string button_text) { class=class="str">"cmt">//--- 保存窗口指针 m_simple_button1.WindowPointer(m_window); class=class="str">"cmt">//--- 附加到第一个页面 m_tabs.AddToElementsArray(class="num">0,m_simple_button1); class=class="str">"cmt">//--- 坐标 class="type">int x=m_window.X()+x_gap; class="type">int y=m_window.Y()+y_gap; class=class="str">"cmt">//--- 在创建之前设置属性 m_simple_button1.ButtonXSize(class="num">140); m_simple_button1.BackColor(C&class="macro">#x27;class="num">255,class="num">140,class="num">140&class="macro">#x27;); m_simple_button1.BackColorHover(C&class="macro">#x27;class="num">255,class="num">180,class="num">180&class="macro">#x27;); m_simple_button1.BackColorPressed(C&class="macro">#x27;class="num">255,class="num">120,class="num">120&class="macro">#x27;); m_simple_button1.AnchorRightWindowSide(true); class=class="str">"cmt">//--- 创建一个按钮 if(!m_simple_button1.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y)) class="kw">return(false); class=class="str">"cmt">//--- 把元件指针加到库中 CWndContainer::AddToElementsArray(class="num">0,m_simple_button1); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建一个绘制型表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateCanvasTable(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap) { class="macro">#define COLUMNS3_TOTAL class="num">15 class="macro">#define ROWS3_TOTAL class="num">30 class=class="str">"cmt">//--- 保存表单的指针 m_canvas_table.WindowPointer(m_window); class=class="str">"cmt">//--- 附加到第二个页面 m_tabs.AddToElementsArray(class="num">1,m_canvas_table); class=class="str">"cmt">//--- 坐标 class="type">int x=m_window.X()+x_gap;
◍ 给画布表格套上自适应外框
在 MT5 自定义指标里画表格,最烦的是窗口一拉伸表格就错位。下面这段直接把表格设成随子窗口自动伸缩,省去手动重算坐标的麻烦。 核心是先给列宽和对齐方式填数组:默认列宽 70 像素,第 0、1 列加到 100 和 90,对齐上第 0、1 列右对齐、第 2 列左对齐,其余居中。表格本体锁死 400×200 像素、列数行数走宏定义。 自适应关键在两对调用:AutoXResizeMode(true) 与 AutoYResizeMode(true) 打开双向跟随;AutoXResizeRightOffset(1) 和 AutoYResizeBottomOffset(25) 留出右边 1 像素、底部 25 像素的余量,避免贴边被吞。外汇与贵金属图表波动剧烈、多品种切换频繁,这类自适应能降低界面重绘出错概率。 数据循环用双重 for 把 '列:行' 写进每个单元格做占位,再 CreateTable 按子窗口 ID 和坐标落盘;失败直接返 false。最后 AddToElementsArray 挂进容器数组,返 true 即就绪,开 MT5 把这段塞进 CWndContainer 派生类就能看到效果。
class="type">int y=m_window.Y()+y_gap; class=class="str">"cmt">//--- 表格列宽度的数组 class="type">int width[COLUMNS3_TOTAL]; ::ArrayInitialize(width,class="num">70); width[class="num">0]=class="num">100; width[class="num">1]=class="num">90; class=class="str">"cmt">//--- 在列中文字对齐方式的数组 ENUM_ALIGN_MODE align[COLUMNS3_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[class="num">0]=ALIGN_RIGHT; align[class="num">1]=ALIGN_RIGHT; align[class="num">2]=ALIGN_LEFT; class=class="str">"cmt">//--- 在创建之前设置属性 m_canvas_table.XSize(class="num">400); m_canvas_table.YSize(class="num">200); m_canvas_table.TableSize(COLUMNS3_TOTAL,ROWS3_TOTAL); m_canvas_table.TextAlign(align); m_canvas_table.ColumnsWidth(width); m_canvas_table.GridColor(clrLightGray); m_canvas_table.AutoXResizeMode(true); m_canvas_table.AutoYResizeMode(true); m_canvas_table.AutoXResizeRightOffset(class="num">1); m_canvas_table.AutoYResizeBottomOffset(class="num">25); class=class="str">"cmt">//--- 发布表格数据 for(class="type">int c=class="num">0;c<COLUMNS3_TOTAL; c++) for(class="type">int r=class="num">0; r<ROWS3_TOTAL; r++) m_canvas_table.SetValue(c,r,class="type">class="kw">string(c)+":"+class="type">class="kw">string(r)); class=class="str">"cmt">//--- 创建控件 if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y)) class="kw">return(false); class=class="str">"cmt">//--- 把对象加到对象组的通用数组中 CWndContainer::AddToElementsArray(class="num">0,m_canvas_table); class="kw">return(true); }
「把这条线请下神坛」
这一版图形界面开发库的结构已经收敛成图 9 所示的分层形态,控件基类 CElement 把重复方法下沉后,CPU 负载比早期版本有明显下降。压缩包 easyandfastgui_mql5.zip 体积 465.56 KB,解压后可直接丢进 MT5 的 MQL5/Include 目录验证。 有用户反馈在 2016 年 10 月解压后 Menuitem.mqh 第 290 行附近出现双引号损坏,手动清掉约 700 个异常字符才能编译;原作者随后联系编辑修复并重传,说明第三方库落地时先本地 diff 一遍再引用是必要动作。 后续版本会继续扩控件并加功能,但这类封装本质只是降低 MQL5 写 GUI 的门槛,不等于策略自动变强。开 MT5 把库挂上跑一个空面板,确认消息循环和重绘不卡,再谈往上叠逻辑。