图形界面 VII: 页面控件 (第二章)

Anatoli Kazharski | 19 九月, 2016


目录


简介

第一篇文章, 图形界面 I: 库结构的准备工作 (第一章)详细解释了这个库的目标。您将能在每章末尾找到文章链接的列表,在那里您可以下载当前开发阶段的库的完整版本. 文件必须按照它们在档案中的位置放到相同目录中.

第七部分的第一章介绍了创建三种表格的类: 文字标签型表格(CLabelsTable), 编辑框型表格(CTable) 和绘制型表格(CCanvasTable)。在本文中(第二章)我们将讨论页面(Tabs)控件。这个控件将有两个类 - 简单型以及扩展功能型。

 


页面控件

页面是用于控制预先定义的图形界面控件集合显示与否的,通常情况下,多功能的应用程序需要大量的控件,而图形界面上的空间则是有限的,页面可以用于把控件按照种类分组,而只显示当前所需要的组,这使得界面的访问更加容易,并且对最终用户更有意义。在表面上,页面看起来就像一组带有标签(控件组的名称)的按钮,同时,只有它们其中之一可以被选择(活动状态),

让我们列举出这个控件的所有组件。

  1. 容纳一组控件的背景或者区域
  2. 页面

 图 1. «页面(Tabs)» 控件的组件。

图 1. 页面控件的组件。

让我们针对页面相对于区域的位置创建四种模式: 顶部(top), 底部(bottom), 左边(left)和右边(right)。 

 


开发用于创建页面控件的类

创建Tabs.mqh文件,并在库的WndContainer.mqh文件中包含它:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Tabs.mqh"

CTabs必须在Tabs.mqh文件中创建,在这个类中,和其他控件类一样,需要创建标准方法,以及保存控件所附加的表单指针的方法。

//+------------------------------------------------------------------+
//| 创建页面控件的类                          |
//+------------------------------------------------------------------+
class CTabs : public CElement
  {
private:
   //--- 指向元件附加表单的指针
   CWindow          *m_wnd;
   //---
public:
                     CTabs(void);
                    ~CTabs(void);
   //--- (1) 保存表单指针, (2) 返回滚动条的指针
   void              WindowPointer(CWindow &object)               { m_wnd=::GetPointer(object);      }
   //---
public:
   //--- 图表事件处理函数
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- 计时器
   virtual void      OnEventTimer(void);
   //--- 移动元件
   virtual void      Moving(const int x,const int y);
   //--- (1) 显示, (2) 隐藏, (3) 重置, (4) 删除
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) 设置, (2) 重设鼠标左键点击的优先级
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- 重置颜色
   virtual void      ResetColors(void) {}
  };
//+------------------------------------------------------------------+
//| 构造函数                            |
//+------------------------------------------------------------------+
CTabs::CTabs(void)
  {
  }
//+------------------------------------------------------------------+
//| 析构函数                              |
//+------------------------------------------------------------------+
CTabs::~CTabs(void)
  {
  }

页面的属性可以分为通用的一类和独特的一类,让我们列举这两类属性。

独特的属性

  • 附加到页面上的控件的指针数组
  • 文字
  • 宽度

创建TElements结构来表示这些独特的属性,并声明一个这种类型的动态数组: 

class CTabs : public CElement
  {
private:
   //--- 附加到页面的控件的属性结构数组
   struct TElements
     {
      CElement         *elements[];
      string            m_text;
      int               m_width;
     };
   TElements         m_tab[];
  };

通用属性

  • 页面位置模式
  • 区域背景色
  • 页面在 Y 轴上的大小(高度)
  • 不同状态下页面的颜色
  • 不同状态下页面文字的颜色
  • 页面边框的颜色
  • 鼠标左键点击优先级

为了设置位置模式,需要在 Enums.mqh 文件中加入ENUM_TABS_POSITION枚举: 

//+------------------------------------------------------------------+
//| 页面位置的枚举                          |
//+------------------------------------------------------------------+
enum ENUM_TABS_POSITION
  {
   TABS_TOP    =0,
   TABS_BOTTOM =1,
   TABS_LEFT   =2,
   TABS_RIGHT  =3
  };

在以下的代码中提供了属性的栏位和设定属性方法的声明: 

class CTabs : public CElement
  {
private:
   //--- 页面的位置
   ENUM_TABS_POSITION m_position_mode;
   //--- 通用区域的背景颜色
   int               m_area_color;
   //--- Y轴上的大小
   int               m_tab_y_size;
   //--- 不同状态下页面的颜色
   color             m_tab_color;
   color             m_tab_color_hover;
   color             m_tab_color_selected;
   color             m_tab_color_array[];
   //--- 不同状态下页面文字的颜色
   color             m_tab_text_color;
   color             m_tab_text_color_selected;
   //--- 页面边框的颜色
   color             m_tab_border_color;
   //--- 鼠标左键点击优先级
   int               m_zorder;
   int               m_tab_zorder;
   //---
public:
   //--- (1) 取得/设置页面的位置 (top/bottom/left/right), (2) 设置页面在Y轴上的大小
   void              PositionMode(const ENUM_TABS_POSITION mode)     { m_position_mode=mode;          }
   ENUM_TABS_POSITION PositionMode(void)                       const { return(m_position_mode);       }
   void              TabYSize(const int y_size)                      { m_tab_y_size=y_size;           }
   //--- (1)通用背景的颜色, (2) 不同状态页面的颜色, (3) 页面边框的颜色
   void              AreaColor(const color clr)                      { m_area_color=clr;              }
   void              TabBackColor(const color clr)                   { m_tab_color=clr;               }
   void              TabBackColorHover(const color clr)              { m_tab_color_hover=clr;         }
   void              TabBackColorSelected(const color clr)           { m_tab_color_selected=clr;      }
   void              TabBorderColor(const color clr)                 { m_tab_border_color=clr;        }
   //--- 不同状态下页面文字的颜色
   void              TabTextColor(const color clr)                   { m_tab_text_color=clr;          }
   void              TabTextColorSelected(const color clr)           { m_tab_text_color_selected=clr; }
  };

在创建控件之前,需要加上所需数量的页面,并指出显示的文字和宽度,让我们为此写出CTabs::AddTab() 方法,这个方法的默认参数值是«» (空字符串) 和 50 (宽度)。 

class CTabs : public CElement
  {
public:
   //--- 加上一个页面
   void              AddTab(const string tab_text="",const int tab_width=50);
  };
//+------------------------------------------------------------------+
//| 加上一个页面                           |
//+------------------------------------------------------------------+
void CTabs::AddTab(const string tab_text,const int tab_width)
  {
//--- 设置页面数组的大小
   int array_size=::ArraySize(m_tabs);
   ::ArrayResize(m_tabs,array_size+1);
   ::ArrayResize(m_tab,array_size+1);
//--- 保存传入的属性
   m_tab[array_size].m_text  =tab_text;
   m_tab[array_size].m_width =tab_width;
//--- 保存页面的数量
   m_tabs_total=array_size+1;
  }

如果某个页面应该在图表上运行MQL应用程序时预先选择好,那么,在创建控件之前,还需要使用CTabs::SelectedTab()方法来设置它的索引。还需要一个私有(private)的CTabs::CheckTabIndex()方法来检查所选页面的索引,以防超出范围。 

class CTabs : public CElement
  {
private:
   //--- 所选择页面的索引
   int               m_selected_tab;
   //---
public:
   //--- (1) 保存 和 (2) 返回所选页面的索引
   void              SelectedTab(const int index)                    { m_selected_tab=index;          }
   int               SelectedTab(void)                         const { return(m_selected_tab);        }
   //---
private:
   //--- 检查所选页面的索引
   void              CheckTabIndex(void);
  };
//+------------------------------------------------------------------+
//| 检查所选页面的索引                         |
//+------------------------------------------------------------------+
void CTabs::CheckTabIndex(void)
  {
//--- 检查是否超出数组范围
   int array_size=::ArraySize(m_tab);
   if(m_selected_tab<0)
      m_selected_tab=0;
   if(m_selected_tab>=array_size)
      m_selected_tab=array_size-1;
  }

为了创建控件,我们需要3个私有(private)方法和1个公有(public)方法: 

class CTabs : public CElement
  {
private:
   //--- 用于创建元件的对象
   CRectLabel        m_main_area;
   CRectLabel        m_tabs_area;
   CEdit             m_tabs[];
   //---
public:
   //--- 用于创建页面的方法
   bool              CreateTabs(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateMainArea(void);
   bool              CreateTabsArea(void);
   bool              CreateButtons(void);
  };

如果在附加控件之前没有添加页面,那么调用CTabs::CreateTabs()共有方法将会停止图形界面的创建,并且在记录中输出这样的消息: 

//--- 如果组中没有页面,进行报告
   if(m_tabs_total<1)
     {
      ::Print(__FUNCTION__," > 将调用此方法, "
              "一个组至少要包含一个页面!使用 CTabs::AddTab() 方法");
      return(false);
     }

控件中组件坐标的确定和计算将根据所选页面位置模式而有所不同,这些计算需要CTabs::SumWidthTabs()方法, 该方法返回所有页面总的宽度。当页面是偏左(TABS_LEFT)和偏右(TABS_RIGHT)的位置模式时,它将返回第一个页面的宽度,而对于顶部(TABS_TOP)和底部(TABS_BOTTOM)模式, 计算的是所有页面总的宽度。 

class CTabs : public CElement
  {
private:
   //--- 页面的位置
   ENUM_TABS_POSITION m_position_mode;
   //---
private:
   //--- 所有页面的宽度
   int               SumWidthTabs(void);
  };
//+------------------------------------------------------------------+
//| 所有页面的总宽度                          |
//+------------------------------------------------------------------+
int CTabs::SumWidthTabs(void)
  {
   int width=0;
//--- 如果页面是偏左或者偏右的位置,返回第一个页面的宽度
   if(m_position_mode==TABS_LEFT || m_position_mode==TABS_RIGHT)
      return(m_tab[0].m_width);
//--- 所有页面的总宽度
   for(int i=0; i<m_tabs_total; i++)
      width=width+m_tab[i].m_width;
//--- 考虑到一个像素点的重叠
   width=width-(m_tabs_total-1);
   return(width);
  }

CTabs::CreateMainArea()方法是用于创建一组控件的放置区域的,对象大小和坐标的计算如下所示 (此方法的精简版): 

//+------------------------------------------------------------------+
//| 创建通用背景区域                          |
//+------------------------------------------------------------------+
bool CTabs::CreateMainArea(void)
  {
//--- 构造对象名称
   string name=CElement::ProgramName()+"_tabs_main_area_"+(string)CElement::Id();
//--- 坐标
   int x=0;
   int y=0;
//--- 大小
   int x_size=0;
   int y_size=0;
//--- 计算相对页面位置的坐标和大小
   switch(m_position_mode)
     {
      case TABS_TOP :
         x      =CElement::X();
         y      =CElement::Y()+m_tab_y_size-1;
         x_size =CElement::XSize();
         y_size =CElement::YSize()-m_tab_y_size;
         break;
      case TABS_BOTTOM :
         x      =CElement::X();
         y      =CElement::Y();
         x_size =CElement::XSize();
         y_size =CElement::YSize()-m_tab_y_size;
         break;
      case TABS_RIGHT :
         x      =CElement::X();
         y      =CElement::Y();
         x_size =CElement::XSize()-SumWidthTabs()+1;
         y_size =CElement::YSize();
         break;
      case TABS_LEFT :
         x      =CElement::X()+SumWidthTabs()-1;
         y      =CElement::Y();
         x_size =CElement::XSize()-SumWidthTabs()+1;
         y_size =CElement::YSize();
         break;
     }
//--- 创建对象
   if(!m_main_area.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- 设置属性
//--- 到边缘的距离
//--- 保存大小
//--- 保存坐标
//--- 保存对象指针
//...
   return(true);
  }

以下就是根据在CTabs::CreateTabsArea()方法中指定的位置模式而计算的页面背景的大小和坐标: 

//+------------------------------------------------------------------+
//| 创建页面背景                             |
//+------------------------------------------------------------------+
bool CTabs::CreateTabsArea(void)
  {
//--- 构造对象名称
   string name=CElement::ProgramName()+"_tabs_area_"+(string)CElement::Id();
//--- 坐标
   int x=CElement::X();
   int y=CElement::Y();
//--- 大小
   int x_size=SumWidthTabs();
   int y_size=0;
//--- 计算相对页面位置的大小
   if(m_position_mode==TABS_TOP || m_position_mode==TABS_BOTTOM)
     {
      y_size=m_tab_y_size;
     }
   else
     {
      y_size=m_tab_y_size*m_tabs_total-(m_tabs_total-1);
     }
//--- 对于页面在底部和在右方的模式调整坐标
   if(m_position_mode==TABS_BOTTOM)
     {
      y=CElement::Y2()-m_tab_y_size-1;
     }
   else if(m_position_mode==TABS_RIGHT)
     {
      x=CElement::X2()-x_size;
     }
//--- 创建对象
   if(!m_tabs_area.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- 设置属性
//--- 到边缘的距离
//--- 保存大小
//--- 保存坐标
//--- 保存对象指针
//...
   return(true);
  }


CTabs::CreateButtons() 方法只需要计算坐标以创建页面,宽度是在创建控件之前由应用程序的自定义类设置的,否则,就使用默认值(宽度)。以下是该方法的精简版本: 

//+------------------------------------------------------------------+
//| 创建页面                             |
//+------------------------------------------------------------------+
bool CTabs::CreateButtons(void)
  {
//--- 坐标
   int x =CElement::X();
   int y =CElement::Y();
//--- 计算相对页面位置的坐标
   if(m_position_mode==TABS_BOTTOM)
      y=CElement::Y2()-m_tab_y_size-1;
   else if(m_position_mode==TABS_RIGHT)
      x=CElement::X2()-SumWidthTabs();
//--- 计算所选页面的索引
   CheckTabIndex();
//--- 创建页面
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- 构建对象名称
      string name=CElement::ProgramName()+"_tabs_edit_"+(string)i+"__"+(string)CElement::Id();
      //--- 计算每个单独页面上相对页面的坐标
      if(m_position_mode==TABS_TOP || m_position_mode==TABS_BOTTOM)
         x=(i>0) ? x+m_tab[i-1].m_width-1 : CElement::X();
      else
         y=(i>0) ? y+m_tab_y_size-1 : CElement::Y();
      //--- 创建对象
      if(!m_tabs[i].Create(m_chart_id,name,m_subwin,x,y,m_tab[i].m_width,m_tab_y_size))
         return(false);
      //--- 设置属性
      //--- 面板边缘的距离
      //--- 坐标
      //--- 大小
      //--- 初始化梯度数组
      //--- 保存对象指针
     }
//---
   return(true);
  }

把任意控件附加到指定页面上,让我们开发CTabs::AddToElementsArray()方法,它有两个参数: (1) 页面索引, 指定应该附加到哪个页面,以及 (2) 控件的引用, 一个应该保存到页面控件数组的指针。 

class CTabs : public CElement
  {
public:
   //--- 把控件加到页面数组中
   void              AddToElementsArray(const int tab_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| 把控件加到指定页面的数组中                      |
//+------------------------------------------------------------------+
void CTabs::AddToElementsArray(const int tab_index,CElement &object)
  {
//--- 检查是否超出数组范围
   int array_size=::ArraySize(m_tab);
   if(array_size<1 || tab_index<0 || tab_index>=array_size)
      return;
//--- 把传入控件的指针加到指定页面的数组中
   int size=::ArraySize(m_tab[tab_index].elements);
   ::ArrayResize(m_tab[tab_index].elements,size+1);
   m_tab[tab_index].elements[size]=::GetPointer(object);
  }

当切换页面时,需要隐藏前一个页面的控件,并显示新选择的页面的控件,为此,让我们创建CTabs::ShowTabElements()方法。在方法的开始部分检查控件是否可见,如果控件是隐藏的,程序就退出此方法。然后,它会检查活动页面的索引,如果有必要就进行调整,然后它在循环中检查所有的页面并执行方法的主要任务。  

class CTabs : public CElement
  {
public:
   //--- 只显示所选页面的控件
   void              ShowTabElements(void);
  };
//+------------------------------------------------------------------+
//| 只显示所选页面的控件                           |
//+------------------------------------------------------------------+
void CTabs::ShowTabElements(void)
  {
//--- 如果页面是隐藏的,退出
   if(!CElement::IsVisible())
      return;
//--- 计算所选页面的索引
   CheckTabIndex();
//---
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- 取得附加到页面上的控件数量
      int tab_elements_total=::ArraySize(m_tab[i].elements);
      //--- 如果页面被选中
      if(i==m_selected_tab)
        {
         //--- 显示页面控件
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Show();
        }
      //--- 隐藏非活动页面的控件
      else
        {
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Hide();
        }
     }
  }

CTabs::OnClickTab() 方法将用于处理点击按下页面的事件。首先,程序必须通过两项检查: (1) 根据点击对象的名称 然后 (2) 使用CTabs::IdFromObjectName()方法取得对象的ID,如果检查通过了,程序就(1)在循环中找到按下的页面, (2) 保存它的索引 并(3) 设置对应的颜色。在CTabs::ShowTabElements()方法的最后, 只有活动页面的控件成为可见的。 

class CTabs : public CElement
  {
private:
   //--- 处理点击按下页面
   bool              OnClickTab(const string pressed_object);
   //--- 根据对象名称取得ID
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| 在一组中按下一个页面                         |
//+------------------------------------------------------------------+
bool CTabs::OnClickTab(const string clicked_object)
  {
//--- 如果没有按在表格单元上,就退出
   if(::StringFind(clicked_object,CElement::ProgramName()+"_tabs_edit_",0)<0)
      return(false);
//--- 从对象名称中取得ID
   int id=IdFromObjectName(clicked_object);
//--- 如果ID不匹配就退出
   if(id!=CElement::Id())
      return(false);
//---
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- 如果点击了这个页面
      if(m_tabs[i].Name()==clicked_object)
        {
         //--- 保存所选择页面的索引
         SelectedTab(i);
         //--- 设置颜色
         m_tabs[i].Color(m_tab_text_color_selected);
         m_tabs[i].BackColor(m_tab_color_selected);
        }
      else
        {
         //--- 设置非活动页面的颜色
         m_tabs[i].Color(m_tab_text_color);
         m_tabs[i].BackColor(m_tab_color);
        }
     }
//--- 只显示所选页面的控件
   ShowTabElements();
   return(true);
  }

在这种情况下,CTabs::OnEvent() 主事件处理函数的代码如下所示: 

//+------------------------------------------------------------------+
//| 图表事件处理函数                            |
//+------------------------------------------------------------------+
void CTabs::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- 处理鼠标光标移动事件
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- 如果元件是隐藏的就退出
      if(!CElement::IsVisible())
         return;
      //--- 坐标
      int x=(int)lparam;
      int y=(int)dparam;
      for(int i=0; i<m_tabs_total; i++)
         m_tabs[i].MouseFocus(x>m_tabs[i].X() && x<m_tabs[i].X2() && y>m_tabs[i].Y() && y<m_tabs[i].Y2());
      //---
      return;
     }
//--- 处理鼠标左键点击对象事件
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- 按下一个页面
      if(OnClickTab(sparam))
         return;
     }
  }

CTabs 类中所有的方法都讨论过了,现在,让我们测试看它如何工作。 

 


测试页面控件

为了测试,让我们使用系列前一部分的EA交易,并删除它的图形界面中除了主菜单和状态条之外的所有内容,我们要使所有的位置模式(顶部/底部/右/左)都易于测试,并且它们的大小(高度)易于调整。为此,在EA的Program.mqh文件的自定义类中加入外部参数:

//--- External parameters of the Expert Advisor
input ENUM_TABS_POSITION TabsPosition =TABS_TOP; // 页面位置
input                int TabsHeight   =20;       // 页面高度

随后,在应用程序的自定义类(CProgram)中,声明一个CTabs类的实例以及在与表单边缘一定距离内创建页面控件的方法: 

class CProgram : public CWndEvents
  {
private:
   //--- 页面
   CTabs             m_tabs;
   //---
private:
   //--- 页面
#define TABS1_GAP_X           (4)
#define TABS1_GAP_Y           (45)
   bool              CreateTabs(void);
  };

总共将有四个页面,页面显示的文字和宽度可以通过初始化数组来进行调整, 它们元件的值是之后通过在循环中使用CTabs::AddTab()方法传入的。页面的高度和位置将通过外部参数来设置。第二个页面(索引 1)将被默认选择(当程序第一次在图表上载入时)。CProgram::CreateTabs() 方法的完整代码如下: 

//+------------------------------------------------------------------+
//| 创建页面的区域                           |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs(void)
  {
#define TABS1_TOTAL 4
//--- 传入面板对象
   m_tabs.WindowPointer(m_window1);
//--- 坐标
   int x=m_window1.X()+TABS1_GAP_X;
   int y=m_window1.Y()+TABS1_GAP_Y;
//--- 页面文字和宽度的数组
   string tabs_text[]={"Tab 1","Tab 2","Tab 3","Tab 4"};
   int tabs_width[]={90,90,90,90};
//--- 在创建之前设置属性
   m_tabs.XSize(596);
   m_tabs.YSize(243);
   m_tabs.TabYSize(TabsHeight);
   m_tabs.PositionMode(TabsPosition);
   m_tabs.SelectedTab((m_tabs.SelectedTab()==WRONG_VALUE) ? 1 : m_tabs.SelectedTab());
   m_tabs.AreaColor(clrWhite);
   m_tabs.TabBackColor(C'225,225,225');
   m_tabs.TabBackColorHover(C'240,240,240');
   m_tabs.TabBackColorSelected(clrWhite);
   m_tabs.TabBorderColor(clrSilver);
   m_tabs.TabTextColor(clrGray);
   m_tabs.TabTextColorSelected(clrBlack);
//--- 使用指定属性添加页面
   for(int i=0; i<TABS1_TOTAL; i++)
      m_tabs.AddTab(tabs_text[i],tabs_width[i]);
//--- 创建控件
   if(!m_tabs.CreateTabs(m_chart_id,m_subwin,x,y))
      return(false);
//--- 把对象加到对象组的通用数组中
   CWndContainer::AddToElementsArray(0,m_tabs);
   return(true);
  }

此方法必须在应用程序创建图形界面的主方法中调用(参见以下的精简版方法代码): 

//+------------------------------------------------------------------+
//| 创建EA的面板                           |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- 为控件创建表单1
//--- 创建控件:
//    主菜单
//--- 上下文菜单
//--- 页面
   if(!CreateTabs())
      return(false);
//--- 重绘图表
   m_chart.Redraw();
   return(true);
  }

编译程序并在图表中载入,页面位置的模式可以通过在外部参数中成功改变 (参见以下屏幕截图):

 图 2. 页面位置模式 — «顶部(Top)».

图 2. 页面位置模式 — «顶部(Top)».

 图 3. 页面位置模式 — «底部(Bottom)».

图 3. 页面位置模式 — «底部(Bottom)».

 图 4. 页面位置模式 — «左边(Left)».

图 4. 页面位置模式 — «左边(Left)».

 图 5. 页面位置模式 — «右边(Right)».

图 5. 页面位置模式 — «右边(Right)».


现在让我们测试看附加到各个页面的控件组如何工作,为此,创建并复制一个相同EA,然后删除外部参数,在此,页面位置都是在工作区域的顶部(TABS_TOP), 

  1. 第一个页面上附加了一个文字标签型表格, 
  2. 第二个页面上附加了一个编辑框型表格, 
  3. 而第三个页面上附加的是绘制型表格,
  4. 第四个 - 一组控件,包含:
    • 四个复选框;
    • 四个带有复选框的编辑框;
    • 四个带有复选框的组合框;
    • 一条分隔线。 

在测试程序的自定义类(CProgram)中,声明 (1) 这些控件的实例, (2) 创建它们的方法 以及 (3) 和表单边缘的距离 (参见以下代码): 

class CProgram : public CWndEvents
  {
private:
   //--- 文字标签型表格
   CLabelsTable      m_labels_table;
   //--- 编辑框型表格
   CTable            m_table;
   //--- 绘制型表格
   CCanvasTable      m_canvas_table;
   //--- 复选框
   CCheckBox         m_checkbox1;
   CCheckBox         m_checkbox2;
   CCheckBox         m_checkbox3;
   CCheckBox         m_checkbox4;
   //--- 带有复选框的编辑框
   CCheckBoxEdit     m_checkboxedit1;
   CCheckBoxEdit     m_checkboxedit2;
   CCheckBoxEdit     m_checkboxedit3;
   CCheckBoxEdit     m_checkboxedit4;
   //--- 带有复选框的组合框
   CCheckComboBox    m_checkcombobox1;
   CCheckComboBox    m_checkcombobox2;
   CCheckComboBox    m_checkcombobox3;
   CCheckComboBox    m_checkcombobox4;
   //--- 分隔线
   CSeparateLine     m_sep_line;
   //---
private:
   //--- 文字标签型表格
#define TABLE1_GAP_X          (5)
#define TABLE1_GAP_Y          (65)
   bool              CreateLabelsTable(void);
   //--- 编辑框型表格
#define TABLE2_GAP_X          (5)
#define TABLE2_GAP_Y          (65)
   bool              CreateTable(void);
   //--- 绘制型表格
#define TABLE3_GAP_X          (5)
#define TABLE3_GAP_Y          (65)
   bool              CreateCanvasTable(void);
   //--- 分隔线
#define SEP_LINE_GAP_X        (300)
#define SEP_LINE_GAP_Y        (70)
   bool              CreateSepLine(void);
   //--- 复选框
#define CHECKBOX1_GAP_X       (18)
#define CHECKBOX1_GAP_Y       (75)
   bool              CreateCheckBox1(const string text);
#define CHECKBOX2_GAP_X       (18)
#define CHECKBOX2_GAP_Y       (175)
   bool              CreateCheckBox2(const string text);
#define CHECKBOX3_GAP_X       (315)
#define CHECKBOX3_GAP_Y       (75)
   bool              CreateCheckBox3(const string text);
#define CHECKBOX4_GAP_X       (315)
#define CHECKBOX4_GAP_Y       (175)
   bool              CreateCheckBox4(const string text);
   //--- 带有复选框的编辑框
#define CHECKBOXEDIT1_GAP_X   (40)
#define CHECKBOXEDIT1_GAP_Y   (105)
   bool              CreateCheckBoxEdit1(const string text);
#define CHECKBOXEDIT2_GAP_X   (40)
#define CHECKBOXEDIT2_GAP_Y   (135)
   bool              CreateCheckBoxEdit2(const string text);
#define CHECKBOXEDIT3_GAP_X   (337)
#define CHECKBOXEDIT3_GAP_Y   (105)
   bool              CreateCheckBoxEdit3(const string text);
#define CHECKBOXEDIT4_GAP_X   (337)
#define CHECKBOXEDIT4_GAP_Y   (135)
   bool              CreateCheckBoxEdit4(const string text);
   //--- 带有复选框的组合框
#define CHECKCOMBOBOX1_GAP_X  (40)
#define CHECKCOMBOBOX1_GAP_Y  (205)
   bool              CreateCheckComboBox1(const string text);
#define CHECKCOMBOBOX2_GAP_X  (40)
#define CHECKCOMBOBOX2_GAP_Y  (235)
   bool              CreateCheckComboBox2(const string text);
#define CHECKCOMBOBOX3_GAP_X  (337)
#define CHECKCOMBOBOX3_GAP_Y  (205)
   bool              CreateCheckComboBox3(const string text);
#define CHECKCOMBOBOX4_GAP_X  (337)
#define CHECKCOMBOBOX4_GAP_Y  (235)
   bool              CreateCheckComboBox4(const string text);
  };

在前面的文章中,已经演示了如何创建控件并把它们附加到表单上,所以,在此我们只提供其中的一段代码来演示如何把控件附加到页面上,这些控件中最简单的 - 分隔线 - 用来做例子就足够了。代码如下所示,调用CTabs::AddToElementsArray() 方法的代码行已经用黄色突出显示,第一个参数是它所附加的页面的索引,这里的索引是3, 也就是第四个页面,第二个参数是需要附加到指定页面的控件对象。

//+------------------------------------------------------------------+
//| 创建分隔线                               |
//+------------------------------------------------------------------+
bool CProgram::CreateSepLine(void)
  {
//--- 保存窗口指针
   m_sep_line.WindowPointer(m_window1);
//--- 附加到第四个页面
   m_tabs.AddToElementsArray(3,m_sep_line);
//--- 坐标  
   int x=m_window1.X()+SEP_LINE_GAP_X;
   int y=m_window1.Y()+SEP_LINE_GAP_Y;
//--- 大小
   int x_size=2;
   int y_size=210;
//--- 在创建之前设置属性
   m_sep_line.DarkColor(C'213,223,229');
   m_sep_line.LightColor(clrWhite);
   m_sep_line.TypeSepLine(V_SEP_LINE);
//--- 创建元件
   if(!m_sep_line.CreateSeparateLine(m_chart_id,m_subwin,0,x,y,x_size,y_size))
      return(false);
//--- 把元件指针加到库中
   CWndContainer::AddToElementsArray(0,m_sep_line);
   return(true);
  }

当应用程序的图形界面创建完毕后,必须调用CTabs::ShowTabElements()方法以便只显示活动页面的控件 (参见以下精简版的方法代码),如果不这样,所有页面的所有控件都回显示出来。 

//+------------------------------------------------------------------+
//| 创建EA的面板                           |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- 为控件创建表单1
//--- 创建控件:
//--- 主菜单
//--- 上下文菜单
//--- 状态条
//--- 页面
//...
//--- 文字标签型表格
   if(!CreateLabelsTable())
      return(false);
//--- 编辑框型表格
   if(!CreateTable())
      return(false);
//--- 创建绘制型表格
   if(!CreateCanvasTable())
      return(false);
//--- 分隔线
   if(!CreateSepLine())
      return(false);
//--- 复选框
   if(!CreateCheckBox1("Checkbox 1"))
      return(false);
   if(!CreateCheckBox2("Checkbox 2"))
      return(false);
   if(!CreateCheckBox3("Checkbox 3"))
      return(false);
   if(!CreateCheckBox4("Checkbox 4"))
      return(false);
//--- 带有复选框的编辑框
   if(!CreateCheckBoxEdit1("Checkbox Edit 1:"))
      return(false);
   if(!CreateCheckBoxEdit2("Checkbox Edit 2:"))
      return(false);
   if(!CreateCheckBoxEdit3("Checkbox Edit 3:"))
      return(false);
   if(!CreateCheckBoxEdit4("Checkbox Edit 4:"))
      return(false);
//--- 带有复选框的组合框
   if(!CreateCheckComboBox1("CheckCombobox 1:"))
      return(false);
   if(!CreateCheckComboBox2("CheckCombobox 2:"))
      return(false);
   if(!CreateCheckComboBox3("CheckCombobox 3:"))
      return(false);
   if(!CreateCheckComboBox4("CheckCombobox 4:"))
      return(false);
//--- 只显示活动页面的控件
   m_tabs.ShowTabElements();
//--- 重绘图表
   m_chart.Redraw();
   return(true);
  }

结果如以下屏幕截图所示

 图 6. 第一个页面的控件

图 6. 第一个页面的控件

图 7. 第二个页面的控件。 

图 7. 第二个页面的控件。

 图 8. 第三个页面的控件。

图 8. 第三个页面的控件。

 图 9. 第四个页面的控件。

图 9. 第四个页面的控件。


一切都按计划运行。本图形开发库系列的第七部分现在可以结束了,作为补充,您可以下载另外一个类的代码(CIconTabs),它具有创建页面的扩展功能,可以在本文的附件中找到。和CTabs类不同, 一个CIconTabs类型的控件的每个页面都有可以调整的图标。这有助于使图形界面对用户更加友好, 

图标和显示的文字可以使用特定的方法准确放置到相对每个页面边缘的位置 (参见以下代码): 

//+------------------------------------------------------------------+
//| 创建图标页面的类                         |
//+------------------------------------------------------------------+
class CIconTabs : public CElement
  {
private:
   //--- 标签边缘
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- 文字标签边缘
   int               m_label_x_gap;
   int               m_label_y_gap;
   //---
public:
   //--- 标签边缘
   void              IconXGap(const int x_gap)                       { m_icon_x_gap=x_gap;            }
   void              IconYGap(const int y_gap)                       { m_icon_y_gap=y_gap;            }
   //--- 文字标签边缘
   void              LabelXGap(const int x_gap)                      { m_label_x_gap=x_gap;           }
   void              LabelYGap(const int y_gap)                      { m_label_y_gap=y_gap;           }
  };

例子中图标页面控件的外观在以下屏幕截图中显示:

 图 10. «图标页面» 控件。

图 10. 图标页面控件。

此EA的代码也可以在本文附件中下载。 

 

 


结论

现在,用于创建图形界面库的结构看起来如下:

 图 11. 当前开发阶段的库结构。

图 11. 当前开发阶段的库结构。

MetaTrader交易终端中创建图形界面的系列文章的第七部分介绍了表格和页面控件,有三个类(CLabelsTable, CTableCCanvasTable) 用于创建表格,而有两个类(CTabsCIconTabs) 用于创建页面, 

系列文章的下一部分(第八部分)将探讨以下控件:

  • 静态和下拉的日历;
  • 树形视图;
  • 文件浏览器。

您可以在附件中下载本系列的第七部分的资料并测试看它如何工作,如果您有关于如何使用这些资料的问题,您可以参考以下列表中对应的库开发文章来找到详细描述,或者也可以在下面的留言中问问题。

第七部分的文章(章节)列表: