下载MetaTrader 5

图形界面 X: 高级列表和表格管理。代码优化 (集成构建 7)

13 二月 2017, 09:14
Anatoli Kazharski
0
4 319

内容


概论

为了更好地理解这个函数库的目的, 请阅读首篇文章: 图形界面 I: 函数库结构的准备 (第 1 章)。您可在每章结尾处找到包含文章列表的链接。从那里, 您还可以下载当前最新开发阶段的完整版库文件。这些文件必须按照归档结构置于相同的目录中。

函数库代码应进行优化, 令其更规范化, 从而更具可读性, 并易于理解学习。此外, 我们将继续开发以前文章中创建的控件: 列表, 表格和滚动条。我们来添加方法, 允许 MQL 应用程序在运行时以编程方式管理那些控件的属性。 


函数库规划和代码优化方面的变化

在所有函数库文件中与控件相关的代码有些部分已优化。经常重复的代码已被置于单独的方法中, 并且方法本身已被移动到单独的类中。

这是如何做到的。CElement 已更名为 CElementBase。这是函数库中所有控件的基类。现在, 之后的下一个派生类是新的 CElement 类, 它包含所有控件中频繁重复的方法。这些包括:

  • 存储控件所附属表单指针的方法
  • 检查表单指针的可用性
  • 检查激活控件的标识符
  • 绝对坐标的计算
  • 距表单边缘的相对坐标计算

CElementBaseCElement 类位于不同文件, 分别是 ElementBase.mqhElement.mqh。所以, 含有基类的 ElementBase.mqh 文件 被包含在 Element.mqh 文件中。由于 CWindows 类型必须在此定义, 以及包含 Window.mqh 文件。这些会呈现在下面的代码清单中:

//+------------------------------------------------------------------+
//|                                                      Element.mqh |
//|                        版权所有 2016, MetaQuotes 软件公司|
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "ElementBase.mqh"
#include "Controls\Window.mqh"
//+------------------------------------------------------------------+
//| 获取鼠标参数的类                           |
//+------------------------------------------------------------------+
class CElement : public CElementBase
  {
protected:
   //--- 元素所附属的表单指针
   CWindow          *m_wnd;
   //---
public:
                     CElement(void);
                    ~CElement(void);
   //--- 保存指向表单的指针
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }
   //---
protected:
   //--- 检查是否为表单指针
   bool              CheckWindowPointer(void);
   //--- 检查激活控件的标识符
   bool              CheckIdActivatedElement(void);
  
   //--- 计算绝对坐标
   int               CalculateX(const int x_gap);
   int               CalculateY(const int y_gap);
   //--- 计算距表单边缘的相对坐标
   int               CalculateXGap(const int x);
   int               CalculateYGap(const int y);
  };
//+------------------------------------------------------------------+
//| 构造器                                                      |
//+------------------------------------------------------------------+
CElement::CElement(void)
  {
  }
//+------------------------------------------------------------------+
//| 析构器                                                       |
//+------------------------------------------------------------------+
CElement::~CElement(void)
  {
  }

在所有控件类中, 所有这些方法及其代码经常重复。将它们移到单独的类中, 使控件类中的代码变得更容易理解和可读。所有这些方法的代码是简单的, 且字面上适合单行 (见以下代码)。在计算坐标时, 要考虑到相对于表格一侧定位控件。

//+------------------------------------------------------------------+
//| 检查激活控件的标识符                    |
//+------------------------------------------------------------------+
bool CElement::CheckIdActivatedElement(void)
  {
   return(m_wnd.IdActivatedElement()==CElementBase::Id());
  }
//+------------------------------------------------------------------+
//| 计算绝对 X 坐标                              |
//+------------------------------------------------------------------+
int CElement::CalculateX(const int x_gap)
  {
   return((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x_gap : m_wnd.X()+x_gap);
  }
//+------------------------------------------------------------------+
//| 计算绝对 Y 坐标                              |
//+------------------------------------------------------------------+
int CElement::CalculateY(const int y_gap)
  {
   return((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y_gap : m_wnd.Y()+y_gap);
  }
//+------------------------------------------------------------------+
//| 计算距表单边缘的相对 X 坐标     |
//+------------------------------------------------------------------+
int CElement::CalculateXGap(const int x)
  {
   return((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x : x-m_wnd.X());
  }
//+------------------------------------------------------------------+
//| 计算距表单边缘的相对 Y 坐标    |
//+------------------------------------------------------------------+
int CElement::CalculateYGap(const int y)
  {
   return((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y : y-m_wnd.Y());
  }

有人可能会问: "为什么这些方法没有放在旧版本的 CElement 类中?" 这是不可能的: 当包含 Window.mqh 文件并试图编译时, 会出现未声明类型错误, 其结果就是 — 引出许多其它相关错误:

 图例. 1. 编译时缺少元素类型的消息

图例. 1. 编译时缺少元素类型的消息


试图在 CElement 类实体之后包含 Window.mqh 来绕过此难题, 当 CWindow 对象已在类的实体中声明时, 会导致类似的指定类型缺失编译错误。

 图例. 2. 编译时 CWindow 类型缺失的消息

图例. 2. 编译时 CWindow 类型缺失的消息


因此, 决定创建一个附加的继承中间类来放置频繁重复的代码和方法, 以便处理控件所附属表单的指针。函数库方案中在表单和控件之间相关联的部分如下:

 图例. 3. 函数库方案中在表单和控件之间相关联的部分。

图例. 3. 函数库方案中在表单和控件之间相关联的部分


正如以上方案中可见, CWindow 类直接继承自 CElementBase 类, 而过渡类 CElement 现在已有些多余且不适当。所有其它控件类都派生自过渡类 CElement。 

 

可编程控制滚动条

在函数库应用期间需要可编程控制滚动条。为此目的, 已在 CScrollVCScrollH 类中实现了 MovingThumb() 方法, 它可将滚动条的滑块移到指定位置。 

以面列表仅显示垂直滚动条的代码, 因为它几乎与水平滚动条相同。方法有一个参数, 省缺为 WRONG_VALUE。如果未指定位置 (使用省缺值) 调用方法, 则将滑块移到列表的最后一个位置。当程序运行时有项目添加到列表时, 这很有用, 并且还能实现列表的自动滚动。

//+------------------------------------------------------------------+
//| 管理垂直滚动条的类                        |
//+------------------------------------------------------------------+
class CScrollV : public CScroll
  {
public:
   //--- 移动滑块到指定位置
   void              MovingThumb(const int pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| 移动滑块到指定位置                        |
//+------------------------------------------------------------------+
void CScrollV::MovingThumb(const int pos=WRONG_VALUE)
  {
//--- 如果不需要滚动条, 离开
   if(m_items_total<=m_visible_items_total)
      return;
// --- 检查滑块位置
   uint check_pos=0;
//--- 若超出范围则调整位置
   if(pos<0 || pos>m_items_total-m_visible_items_total)
      check_pos=m_items_total-m_visible_items_total;
   else
      check_pos=pos;
//--- 保存滑块位置
   CScroll::CurrentPos(check_pos);
//--- 计算并设置滚动条滑块 Y 坐标
   CalculateThumbY();
  }

 

可编程控制列表

已经实现了管理列表的数种公共方法, 它们执行以下动作:

  • 重构列表
  • 在列表末尾添加项目
  • 清除列表 (删除所有项目)
  • 滚动列表

此外, 作为函数库代码优化的一部分, 重复代码的私有方法已添加到列表类中:

  • 计算项目的 Y 坐标
  • 计算项目的宽度
  • 计算列表沿 Y 轴的大小

我们来仔细看看 CListView 类中这些方法的结构。在类的不同部分重复超过一次的代码放进私有方法作为辅助。它们在每个方法中只占用一行:

//+------------------------------------------------------------------+
//| 创建列表视图的类                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
private:
   //--- 计算项目的 Y 坐标
   int               CalculationItemY(const int item_index=0);
   //--- 计算项目的宽度
   int               CalculationItemsWidth(void);
   //--- 计算列表沿 Y 轴的大小
   int               CalculationYSize(void);
//+------------------------------------------------------------------+
//| 计算项目的 Y 坐标                      |
//+------------------------------------------------------------------+
int CListView::CalculationItemY(const int item_index=0)
  {
   return((item_index>0)?m_items[item_index-1].Y2()-1 : CElementBase::Y()+1);
  }
//+------------------------------------------------------------------+
//| 计算项目的宽度                                   |
//+------------------------------------------------------------------+
int CListView::CalculationItemsWidth(void)
  {
   return((m_items_total>m_visible_items_total) ?CElementBase::XSize()-m_scrollv.ScrollWidth()-1 : CElementBase::XSize()-2);
  }
//+------------------------------------------------------------------+
//| 计算列表沿 Y 轴的大小                    |
//+------------------------------------------------------------------+
int CListView::CalculationYSize(void)
  {
   return(m_item_y_size*m_visible_items_total-(m_visible_items_total-1)+2);
  }

清除列表不言而喻: 所有项目都从列表中删除。为此, 使用 CListView::Clear() 方法。在此, 首先清除图形基元, 释放指向那些对象的指针数组, 并为类的某些字段设置省缺值。之后, 列表大小设置为零, 且滚动条参数被重置。在方法靠最后的地方, 有必要将指向列表背景的指针再次添加到指针数组中, 因为它以前已被 CElementBase::FreeObjectsArray() 方法删除。

//+------------------------------------------------------------------+
//| 创建列表视图的类                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- 清除列表 (删除所有项目)
   void              Clear(void);
  };
//+------------------------------------------------------------------+
//| 清除列表 (删除所有项目)                              |
//+------------------------------------------------------------------+
void CListView::Clear(void)
  {
//--- 删除项目对象
   for(int r=0; r<m_visible_items_total; r++)
      m_items[r].Delete();
//--- 清除指向对象的指针数组
   CElementBase::FreeObjectsArray();
//--- 设置省缺值
   m_selected_item_text  ="";
   m_selected_item_index =0;
//--- 列表大小清零
   ListSize(0);
//--- 重置滚动条值
   m_scrollv.Hide();
   m_scrollv.MovingThumb(0);
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
//--- 添加列表背景到控件对象指针数组
   CElementBase::AddToArray(m_area);
  }

为了重构列表, 使用 CListView::Rebuilding() 方法。重构是一种必须完全重新创建列表的情况。此方法可用于更改项目总数和可见项目数。也就是说, 如果可见项目的数量设置为不同于原始值, 则列表大小也将更改。

列表会在 CListView::Rebuilding() 方法的开始处被清除。然后, 如果可见项目的数量已更改, 传递的参数值用于设置新的大小, 并调整列表高度。下一步, 调整滚动条对象的大小。之后, 创建列表, 且如果项目的总数超过指定的可见项目的数量, 则显示滚动条。 

//+------------------------------------------------------------------+
//| 创建列表视图的类                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- 重构列表
   void              Rebuilding(const int items_total,const int visible_items_total);
  };
//+------------------------------------------------------------------+
//| 重构列表                                              |
//+------------------------------------------------------------------+
void CListView::Rebuilding(const int items_total,const int visible_items_total)
  {
//--- 清除列表
   Clear();
//--- 设置列表视图大小和其可视部分
   ListSize(items_total);
   VisibleListSize(visible_items_total);
//--- 调整列表大小
   int y_size=CalculationYSize();
   if(y_size!=CElementBase::YSize())
     {
      m_area.YSize(y_size);
      m_area.Y_Size(y_size);
      CElementBase::YSize(y_size);
     }
//---调整滚动条大小
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
   m_scrollv.ChangeYSize(y_size);
//--- 创建列表
   CreateList();
//--- 如有必要, 显示滚动条
   if(m_items_total>m_visible_items_total)
     {
      if(CElementBase::IsVisible())
         m_scrollv.Show();
     }
  }

用于创建单一项目的独立 CListView::CreateItem() 方法已实现, 其代码不仅在 CListView::CreateList() 方法内循环创建整个列表时, 并且在 CListView::AddItem() 运行时, 当添加项目至列表时也会被调用。 

CListView::AddItem() 仅接收一个参数 - 项目的显示文本。省缺情况下, 它是一个空字符串。也可以在项目被创建之后使用 CListView::SetItemValue() 方法添加文本。在 CListView::AddItem() 方法的开始处, 项目数组递增一个元素。然后, 如果项目的总数量不大于当前可见项目的数量, 则意味着需要创建图形对象。如果超过可见额度的数量, 则需要显示滚动条并调整其滑块大小, 以及调整项目的宽度。 

//+------------------------------------------------------------------+
//| 创建列表视图的类                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- 添加项目至列表
   void              AddItem(const string value="");
  };
//+------------------------------------------------------------------+
//| 添加项目至列表                                             |
//+------------------------------------------------------------------+
void CListView::AddItem(const string value="")
  {
//--- 数组大小递增一个元素
   int array_size=ItemsTotal();
   m_items_total=array_size+1;
   ::ArrayResize(m_item_value,m_items_total);
   m_item_value[array_size]=value;
//--- 如果项目总数大于可见
   if(m_items_total>m_visible_items_total)
     {
      //--- 调整滑块大小并显示滚动条
      m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
      if(CElementBase::IsVisible())
         m_scrollv.Show();
      //--- 如果数组小于一个元素, 离开
      if(m_visible_items_total<1)
         return;
      //--- 计算项目列表视图宽度
      int width=CElementBase::XSize()-m_scrollv.ScrollWidth()-1;
      if(width==m_items[0].XSize())
         return;
      //--- 设置列表项目的新大小
      for(int i=0; i<m_items_total && i<m_visible_items_total; i++)
        {
         m_items[i].XSize(width);
         m_items[i].X_Size(width);
        }
      //---
      return;
     }
//--- 计算坐标
   int x=CElementBase::X()+1;
   int y=CalculationItemY(array_size);
//--- 计算列表项目视图宽度
   int width=CalculationItemsWidth();
//--- 创建对象
   CreateItem(array_size,x,y,width);
//--- 高亮所选项目
   HighlightSelectedItem();
//--- 保存所选项目的文本
   if(array_size==1)
      m_selected_item_text=m_item_value[0];
  }

方法 CListView::Scrolling() 设计用于可编程滚动列表项目。列表中的位置编号被视为唯一的参数。省缺值为 WRONG_VALUE, 表示将列表移到最后一个位置。 

//+------------------------------------------------------------------+
//| 创建列表视图的类                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- 滚动列表
   void              Scrolling(const int pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| 滚动列表                                               |
//+------------------------------------------------------------------+
void CListView::Scrolling(const int pos=WRONG_VALUE)
  {
//--- 如果不需要滚动条, 离开
   if(m_items_total<=m_visible_items_total)
      return;
//--- 判断滑块位置
   int index=0;
//--- 最后位置的索引
   int last_pos_index=m_items_total-m_visible_items_total;
//--- 若超出范围则调整
   if(pos<0)
      index=last_pos_index;
   else
      index=(pos>last_pos_index)?last_pos_index : pos;
//--- 移动滚动条滑块
   m_scrollv.MovingThumb(index);
//--- 移动列表
   UpdateList(index);
  }

类似的方法已经在 CCheckBoxList 类型的列表中实现。 

 

CTable 类型表格代码的优化

CTable 类的代码已经优化。由于添加了许多包含频繁重复代码的私有方法, 它已变得更加紧凑并具可读性。这些方法是:

  • 调整行数组大小
  • 使用省缺值初始化单元格
  • 计算沿 X 轴的表格大小
  • 计算沿 Y 轴的表格大小
  • 计算单元格的 X 坐标
  • 计算单元格的 Y 坐标
  • 计算列宽
  • 更改列宽
  • 更改沿 Y 轴的表格大小
//+------------------------------------------------------------------+
//| 创建可编辑表格的类                             |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- 调整行数组大小
   void              RowResize(const uint column_index,const uint new_size);
   //--- 使用省缺值初始化单元格
   void              CellInitialize(const uint column_index,const int row_index=WRONG_VALUE);
   //--- 计算沿 X 轴的表格大小
   int               CalculationXSize(void);
   //--- 计算沿 Y 轴的表格大小
   int               CalculationYSize(void);
   //--- 计算单元格的 X 坐标
   int               CalculationCellX(const int column_index=0);
   //--- 计算单元格的 Y 坐标
   int               CalculationCellY(const int row_index=0);
   //--- 计算列宽
   int               CalculationColumnWidth(const bool is_last_column=false);
   //--- 计算列宽
   void              ColumnsXResize(void);
   //--- 更改沿 Y 轴的表格大小
   void              YResize(void);
  };

CTable::CalculationColumnWidth() 方法旨在计算表格的宽度, 并且仅有一个参数值为 false。省缺值用于计算所有列的总宽度。如果传递的值为 true, 则将计算最后一列的宽度。在此情况下, 使用 递归方法调用。需要分别计算总宽度以及最后一列的宽度, 因为通常在计算中, 最后一列的右边缘也许不能与表格的右边缘匹配。

//+------------------------------------------------------------------+
//| 计算列宽                                  |
//+------------------------------------------------------------------+
int CTable::CalculationColumnWidth(const bool is_last_column=false)
  {
   int width=0;
//--- 检查垂直滚动条的存在
   bool is_scrollv=m_rows_total>m_visible_rows_total;
//---
   if(!is_last_column)
     {
      if(m_visible_columns_total==1)
         width=(is_scrollv)?m_x_size-m_scrollv.ScrollWidth() : width=m_x_size-2;
      else
        {
         if(is_scrollv)
            width=(m_x_size-m_scrollv.ScrollWidth())/int(m_visible_columns_total);
         else
            width=m_x_size/(int)m_visible_columns_total+1;
        }
     }
   else
     {
      width=CalculationColumnWidth();
      int last_column=(int)m_visible_columns_total-1;
      int w=m_x_size-(width*last_column-last_column);
      width=(is_scrollv) ?w-m_scrollv.ScrollWidth()-1 : w-2;
     }
//---
   return(width);
  }

当创建表格或更改表格宽度时, 将调用 CTable::ColumnsXResize() 方法。此处, CTable::CalculationColumnWidth() 方法 会在计算列宽时调用, 这已经在上边讨论过。在方法结束时, 如需对表格进行排序, 则需要 调整排序表格的箭头符号位置。 

//+------------------------------------------------------------------+
//| 修改列宽                                    |
//+------------------------------------------------------------------+
void CTable::ColumnsXResize(void)
  {
//--- 计算列宽
   int width=CalculationColumnWidth();
//--- 列
   for(uint c=0; c<m_columns_total && c<m_visible_columns_total; c++)
     {
      //--- 计算 X 坐标
      int x=CalculationCellX(c);
      //--- 调整最后一列宽度
      if(c+1>=m_visible_columns_total)
         width=CalculationColumnWidth(true);

      //--- 行
      for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
        {
         //--- 坐标
         m_columns[c].m_rows[r].X(x);
         m_columns[c].m_rows[r].X_Distance(x);
         //--- 宽度
         m_columns[c].m_rows[r].XSize(width);
         m_columns[c].m_rows[r].X_Size(width);
         //--- 距面板边缘的空余
         m_columns[c].m_rows[r].XGap(CalculateXGap(x));
        }
     }
//--- 如果表格无需排序, 离开
   if(m_is_sorted_column_index==WRONG_VALUE)
      return;
//--- 如果启用了固定表头模式, 则索引平移一位
   int l=(m_fix_first_column) ?1 : 0;
//--- 获取垂直和水平滚动条的当前滑块位置
   int h=m_scrollh.CurrentPos()+l;
//--- 如果未超出数组范围
   if(m_is_sorted_column_index>=h && m_is_sorted_column_index<(int)m_visible_columns_total)
     {
      //--- 将箭头平移到已排序表格的列
      ShiftSortArrow(m_is_sorted_column_index);
     }
  }

您可以轻松地研究本节开头列表中提供的其它私有方法, 因为它们不包含任何复杂的、可能会引起问题的代码。

除了上述方法之外, 作为优化的一部分, 用于创建表格单元的独立私有 CTable::CreateCell() 方法已实现。在此更新中, 添加了另一个有用的 CTable 类型自动斑马样式格式化。以前, 如果函数库用户需要令表格条形化以便更好地理解数据数组, 则必须使用 CTable::CellColor() 方法。也就是说, 有必要单独地为表格的每个单元分配颜色。这很不方便且耗费时间。现在, 为了令表格条形化, 在创建控件之前, 只需调用 CTable::IsZebraFormatRows() 方法, 传递第二个颜色作为唯一的参数。所有表格单元省缺值使用 CTable::CellColor() 方法 (省缺 — 白色) 作为第一个颜色。 

//+------------------------------------------------------------------+
//| 创建可编辑表格的类                             |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //---表格的斑马条样式颜色模式
   color             m_is_zebra_format_rows;
   //---
public:
   //--- 斑马条样式行格式化模式
   void              IsZebraFormatRows(const color clr)                         { m_is_zebra_format_rows=clr;      }
  };

如果指定了斑马样式中格式化的第二种颜色, 则在必要时调用私有 CTable::ZebraFormatRows() 方法。 

//+------------------------------------------------------------------+
//| 创建可编辑表格的类                             |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- 表格格式化为斑马样式
   void              ZebraFormatRows(void);
  };
//+------------------------------------------------------------------+
//| 表格格式化为斑马样式                                 |
//+------------------------------------------------------------------+
void CTable::ZebraFormatRows(void)
  {
//--- 如果此禁用模式, 离开
   if(m_is_zebra_format_rows==clrNONE)
      return;
//--- 省缺颜色
   color clr=m_cell_color;
//---
   for(uint c=0; c<m_columns_total; c++)
     {
      for(uint r=0; r<m_rows_total; r++)
        {
         if(m_fix_first_row)
           {
            if(r==0)
               continue;
            //---
            clr=(r%2==0)?m_is_zebra_format_rows : m_cell_color;
           }
         else
            clr=(r%2==0)?m_cell_color : m_is_zebra_format_rows;
         //--- 在公共数组里保存单元背景色
         m_vcolumns[c].m_cell_color[r]=clr;
        }
     }
  }

 

CTable 类型表格的可编程控制

在此次函数库更新中, 只有 CTable 类型表格接收可编程控件。已实现多个公共方法来执行以下操作:

  • 重构表格
  • 添加一列
  • 添加一行
  • 清除表格 (删除所有列和行)
  • 表格的水平和垂直滚动
//+------------------------------------------------------------------+
//| 创建可编辑表格的类                             |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
public:
   //--- 重构表格
   void              Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total);
   //--- 在表格里添加一列
   void              AddColumn(void);
   //--- 在表格里添加一行
   void              AddRow(void);
   //--- 清除表格 (删除所有行和列)
   void              Clear(void);
   //--- 表格滚动: (1) 垂直以及 (2) 水平
   void              VerticalScrolling(const int pos=WRONG_VALUE);
   void              HorizontalScrolling(const int pos=WRONG_VALUE);
  };

将不会涉及用于清除表格的 CTable::Clear() 方法: 它实际上与列表中的一样, 在本文的前面部分中已经介绍过。

若要重建表格, 必须调用 CTable::Rebuilding() 方法, 其中必须将列和行的总数及其可见额度作为参数传递。此处, 在方法的开始, 清除表格。也就是说, 它的所有列和行都被删除。然后基于所传递的参数值为数组设置新的大小。滚动条根据当前相对于其可见额度的行和列的总数来设置。在进行所有计算完毕后, 创建表格单元, 并且如果需要, 令滚动条可见。 

//+------------------------------------------------------------------+
//| 重构表格                                             |
//+------------------------------------------------------------------+
void CTable::Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total)
  {
//--- 清除表格
   Clear();
//--- 设置表格和其可见部分大小
   TableSize(columns_total,rows_total);
   VisibleTableSize(visible_columns_total,visible_rows_total);
//--- 调整滚动条大小
   m_scrollv.ChangeThumbSize(rows_total,visible_rows_total);
   m_scrollh.ChangeThumbSize(columns_total,visible_columns_total);
//---检查垂直滚动条是否存在
   bool is_scrollv=m_rows_total>m_visible_rows_total;
//--- 检查水平滚动条是否存在
   bool is_scrollh=m_columns_total>m_visible_columns_total;
//--- 计算沿 Y 轴的表格大小
   int y_size=CalculationYSize();
//--- 改变垂直滚动条大小
   m_scrollv.ChangeYSize(y_size);
//--- 改变表格大小
   m_y_size=(is_scrollh)?y_size+m_scrollh.ScrollWidth()-1 : y_size;
   m_area.YSize(m_y_size);
   m_area.Y_Size(m_y_size);
//--- 调整水平滚动条沿 Y 轴的位置
   m_scrollh.YDistance(CElementBase::Y2()-m_scrollh.ScrollWidth());
//--- 如果需要水平滚动条
   if(is_scrollh)
     {
      //--- 根据垂直滚动条的存在设置大小
      if(!is_scrollv)
         m_scrollh.ChangeXSize(m_x_size);
      else
        {
         //--- 计算和更改水平滚动条的宽度
         int x_size=m_area.XSize()-m_scrollh.ScrollWidth()+1;
         m_scrollh.ChangeXSize(x_size);
        }
     }
//--- 创建表格单元
   CreateCells();
//--- 若有必要, 显示滚动条
   if(rows_total>visible_rows_total)
     {
      if(CElementBase::IsVisible())
         m_scrollv.Show();
     }
   if(columns_total>visible_columns_total)
     {
      if(CElementBase::IsVisible())
         m_scrollh.Show();
     }
  }

用于添加列 CTable::AddColumn() 和行 CTable::AddRow() 的方法其算法十分类似, 所以, 在此仅研究它们当中的一个。 

CTable::AddColumn() 方法的开头, 列和行的数组只有列被设置。之后以省缺值添加列并使用 CTable::CellInitialize() 方法初始化单元。之后, 如果列的总数不大于指定的可见额度: 

  1. 计算列宽度
  2. 为添加的列创建一定数量的图形对象 (表格单元)
  3. 如有必要, 表格以斑马样式格式化
  4. 且在方法结束时, 表格被更新

如果在增加列和行的数组后 列和行的总数大于指定的可见额度, 则需显示水平滚动条, 从而调整表格高度。此后, 表格以斑马样式格式化, 程序退出该方法。 

//+------------------------------------------------------------------+
//| 在表格里添加一列                                       |
//+------------------------------------------------------------------+
void CTable::AddColumn(void)
  {
//--- 数组大小递增一个元素
   uint array_size=ColumnsTotal();
   m_columns_total=array_size+1;
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- 设置行数组大小
   RowResize(array_size,m_rows_total);
//--- 以省缺值初始化数组
   CellInitialize(array_size);
//--- 如果列总数超过可见额度
   if(m_columns_total>m_visible_columns_total)
     {
      //--- 调整表格沿 Y 轴的大小
      YResize();
      //--- 如果没有垂直滚动条, 令水平滚动条占据表格的全宽
      if(m_rows_total<=m_visible_rows_total)
         m_scrollh.ChangeXSize(m_x_size);
      //--- 调整滑块大小并显示滚动条
      m_scrollh.ChangeThumbSize(m_columns_total,m_visible_columns_total);
      //--- 显示滚动条
      if(CElementBase::IsVisible())
         m_scrollh.Show();
      //--- 将行格式化为斑马样式
      ZebraFormatRows();
      //--- 刷新表格
      UpdateTable();
      return;
     }
//--- 计算列宽
   int width=CalculationColumnWidth();
//--- 调整最后一列宽度
   if(m_columns_total>=m_visible_columns_total)
      width=CalculationColumnWidth(true);
//--- 计算 X 坐标
   int x=CalculationCellX(array_size);
//---
   for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
     {
      //--- 计算 Y 坐标
      int y=CalculationCellY(r);
      //--- 创建对象
      CreateCell(array_size,r,x,y,width);
      //--- 为表头设置相应的颜色
      if(m_fix_first_row && r==0)
         m_columns[array_size].m_rows[r].BackColor(m_headers_color);
     }
//--- 将行格式化为斑马样式
   ZebraFormatRows();
//--- 刷新表格
   UpdateTable();
  }

滚动表格的 CTable::VerticalScrolling() 和 CTable::HorizontalScrolling() 方法实际上与列表部分中讨论的相同, 因此它们的代码不会在这里提供。您可以在本文附带的文件中找到它们。

现在, 我们来创建一个测试 MQL 的应用程序, 它将演示 CTable 类型。 

 

测试控件的应用程序

出于测试目的, 我们来创建一个 MQL 应用程序, 它可以立即看到 CTable 类型列表和表格类中所添加方法的操作。在此应用程序的图形界面中创建两个选项卡。第一个选项卡将包含 CTable 类型的表格, 以及位于表格上方的用于管理表格属性的控件。这些将由两个按钮和四个数字编辑框组成:

  • «清除表格» 按钮用于清除表格 (删除所有列和行)
  • «重构表格» 按钮用来基于数字编辑框中指定参数重构表格
  • «行总数» 编辑框可输入表格的行总数
  • «列总数» 编辑框可输入表格的列总数
  • «可视行总数» 编辑框可输入表格可视行的数量
  • «可视列总数» 编辑框可输入表格可视列的数量

以下截图显示了它的外观:

 图例. 4. 第一个选项卡上的控件组。

图例. 4. 第一个选项卡上的控件组


第二个选项卡将包括两个列表 (列表视图和列表复选框)。为了演示列表的程序化管理, 将提供以下控件:

  • «清除列表» 按钮用于清除列表 (删除所有项目)
  • «重构列表» 按钮用于根据数字编辑框中指定的参数重建列表
  • «项目总数» 编辑框用于输入列表项目的总数
  • «可见项目总数» 编辑框用于输入可见列表项目的数量

 以下屏幕截图显示了第二个选项卡上的控件。作为附属, 还添加了两个控件: 下拉日历和时间控件。 

 图例. 5. 第二个选项卡上的控件组。

图例. 5. 第二个选项卡上的控件组


继续演示此更新中实现的列表和表格功能之前, 我们来看另外一个, 这将有助于 MQL 开发人员在他的 MQL 应用程序中掌握计时器的操作。这就是 CTimeCounter 类。它可用指定时间间隔管理独立的图形界面控件组的刷新 (重绘) 频率。CTimeCounter 类仅包含三个字段和两个方法 (参看以下代码)。

//+------------------------------------------------------------------+
//|                                                  TimeCounter.mqh |
//|                        版权所有 2016, MetaQuotes 软件公司|
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 定时计数                                                     |
//+------------------------------------------------------------------+
class CTimeCounter
  {
private:
   //--- 计数器步长
   uint              m_step;
   //--- 定时间隔
   uint              m_pause;
   //--- 定时计数器
   uint              m_time_counter;
   //---
public:
                     CTimeCounter(void);
                    ~CTimeCounter(void);
   //--- 设置步长和定时间隔
   void              SetParameters(const uint step,const uint pause);
   //--- 检查指定的时间间隔是否已过
   bool              CheckTimeCounter(void);
  };
//+------------------------------------------------------------------+
//| 构造器                                                      |
//+------------------------------------------------------------------+
CTimeCounter::CTimeCounter(void) : m_step(16),
                                   m_pause(1000),
                                   m_time_counter(0)
                                  
  {
  }
//+------------------------------------------------------------------+
//| 析构器                                                       |
//+------------------------------------------------------------------+
CTimeCounter::~CTimeCounter(void)
  {
  }

CTimeCounter::SetParameters() 方法可用于设置计数器增量和暂停的时间间隔:

//+------------------------------------------------------------------+
//| 设置步长和定时间隔                               |
//+------------------------------------------------------------------+
void CTimeCounter::SetParameters(const uint step,const uint pause)
  {
   m_step  =step;
   m_pause =pause;
  }

CTimeCounter::CheckTimeCounter() 方法设计用于检查类参数中指定的时间间隔是否已过。如果时间间隔已过, 则方法返回 true

//+------------------------------------------------------------------+
//| 检查是否指定的时间间隔已过                 |
//+------------------------------------------------------------------+
bool CTimeCounter::CheckTimeCounter(void)
  {
//--- 如果指定时间间隔未过, 则增加计数器
   if(m_time_counter<m_pause)
     {
      m_time_counter+=m_step;
      return(false);
     }
//--- 计数器清零
   m_time_counter=0;
   return(true);
  }

在进一步处理, 应当注意, 已开发函数库中目录的文件位置也已改变。只有包含控件类的文件现在位于 «MetaTrader 5\MQL5\Include\EasyAndFastGUI\Controls» 目录。所有其它文件已移至函数库根目录: «MetaTrader 5\MQL5\Include\EasyAndFastGUI»。因此, 要将函数库包含在自定义类的文件中, 必须 指定路径, 如以下清单所示。它还显示了如何 包含具有 CTimeCounter 类的文件 (将在测试示例中使用)。 

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        版权所有 2016, MetaQuotes 软件公司|
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndEvents.mqh>
#include <EasyAndFastGUI\TimeCounter.mqh>

时间计数器的参数将在自定义计数器的构造函数中设置:

//+------------------------------------------------------------------+
//| 创建应用程序类                                |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- 定时计数器
   CTimeCounter      m_counter1; // 用于更新状态条
   CTimeCounter      m_counter2; // 用于更新列表和表格
  };
//+------------------------------------------------------------------+
//| 构造器                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
//--- 设置定时计数器参数
   m_counter1.SetParameters(16,500);
   m_counter2.SetParameters(16,150);
  }

这些控件清除完成之后, 将在计时器中实现向列表中添加项目以及向表格中添加列和行的演示。在指定时间间隔之后, 如果项目/行/列的数量少于相应编辑框中指定的数量, 则它们将被添加到此块中 (请参阅下面的代码)。为了演示滚动条的程序化管理, 每次将项目添加到列表时, 滚动条的滑块将移动到列表的结尾。 

//+------------------------------------------------------------------+
//| 定时器                                                            |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
...
//--- 在控件刷新之间的暂停
   if(m_counter2.CheckTimeCounter())
     {
      //--- 如果总数小于指定, 在表格里添加一行
      if(m_table.RowsTotal()<m_spin_edit1.GetValue())
         m_table.AddRow();
      //--- 如果总数小于指定, 在表格里添加一列
      if(m_table.ColumnsTotal()<m_spin_edit2.GetValue())
         m_table.AddColumn();
      //--- 如果总数小于指定, 在列表视图里添加一项
      if(m_listview.ItemsTotal()<m_spin_edit5.GetValue())
        {
         m_listview.AddItem("SYMBOL "+string(m_listview.ItemsTotal()));
         //--- 将滚动条滑块移到列表末尾
         m_listview.Scrolling();
        }
      //--- 如果总数小于指定, 在复选框列表里添加一项
      if(m_checkbox_list.ItemsTotal()<m_spin_edit5.GetValue())
        {
         m_checkbox_list.AddItem("Checkbox "+string(m_checkbox_list.ItemsTotal()));
         //--- 将滚动条滑块移到列表末尾
         m_checkbox_list.Scrolling();
        }
      //--- 图表重绘
      m_chart.Redraw();
     }
  }

清除和重建列表和表格的按钮事件处理如下方式: 

//+------------------------------------------------------------------+
//| 图表事件处理器                                              |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- 按钮按下事件
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //--- 第一个按钮事件
      if(lparam==m_simple_button1.Id())
        {
         //--- 清除表格
         m_table.Clear();
         return;
        }
      //--- 第二个按钮事件
      if(lparam==m_simple_button2.Id())
        {
         //--- 重构表格
         m_table.Rebuilding((int)m_spin_edit3.GetValue(),(int)m_spin_edit4.GetValue(),
                            (int)m_spin_edit1.GetValue(),(int)m_spin_edit2.GetValue());
         //--- 表格初始化
         InitializeTable();
         //--- 刷新表格以便显示变化
         m_table.UpdateTable();
         return;
        }
      //--- 第三个按钮事件
      if(lparam==m_simple_button3.Id())
        {
         //--- 清除列表
         m_listview.Clear();
         m_checkbox_list.Clear();
         return;
        }
      //--- 第二个按钮事件
      if(lparam==m_simple_button4.Id())
        {
         //--- 重构列表
         m_checkbox_list.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
         m_listview.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
         //--- 在列表视图中选择第八个项目
         m_listview.SelectItem(7);
         //--- 用数据填充列表视图
         int items_total=m_listview.ItemsTotal();
         for(int i=0; i<items_total; i++)
            m_listview.SetItemValue(i,"SYMBOL "+string(i));
         //--- 用数据填充复选框列表, 每隔一秒勾选复选框
         items_total=m_checkbox_list.ItemsTotal();
         for(int i=0; i<items_total; i++)
           {
            m_checkbox_list.SetItemValue(i,"Checkbox "+string(i));
            m_checkbox_list.SetItemState(i,(i%2!=0)?true : false);
           }
         //---
         return;
        }
      //---
      return;
     }
  }

本文介绍的测试应用程序可从以下链接下载, 以供进一步学习。 

 

结论

目前, 创建图形界面的函数库的一般示意图如下所示:

 图例. 6. 当前开发阶段的库结构

图例. 6. 当前开发阶段的库结构


在函数库的下一个版本中, 现有的控件将被改进并补充新的功能。您可以从下面下载最新版本的函数库和文件进行测试。

如果您在使用这些文件时对提供的材料有疑问, 可以参考本系列文章之一的函数库开发详细描述, 或在本文的讨论中提出您的问题。 

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/2943

附加的文件 |
图形界面 X: 时间控件, 复选框列表控件和表格排序 (集成编译 6) 图形界面 X: 时间控件, 复选框列表控件和表格排序 (集成编译 6)

创建图形界面的函数库开发续篇。这次会涵盖时间和复选框列表控件。此外, CTable 类现已提供按照升序或降序对数据排序的能力。

一个为莫斯科交易所期货开发的点差策略实例 一个为莫斯科交易所期货开发的点差策略实例

MetaTrader 5 可以开发和测试同时交易多种金融资产的交易机器人。其内建的策略测试器能够自动从经纪商的服务器中下载所需的订单时刻历史,并会考虑到账户的合约规范,所以开发人员不用做任何人工工作。这可以使交易环境条件的重建能够简单和可靠,包括乃至不同交易品种中订单来临之间毫秒级的间隔。在本文中,我们将演示在两种莫斯科交易所期货上开发和测试一种点差策略。

在您的网站上免费嵌入 MetaTrader 4/5 网页版终端并赚取利润 在您的网站上免费嵌入 MetaTrader 4/5 网页版终端并赚取利润

交易者会非常熟悉 WebTerminal, 它允许直接从浏览器在金融市场上交易。将 WebTerminal 小部件添加到您的网站 — 这样做是绝对免费的。如果您有网站, 您可开始向经纪商引荐潜在客户 — 我们已为您准备好了一个即用型的网页版解决方案。您需要做的所有事情就是将一个 iframe 嵌入您的网站。

根据特定的价格变化自动侦测极值点 根据特定的价格变化自动侦测极值点

与图形模式相关的交易策略自动化需要能够在图表中搜索极值点以备进一步处理和解释,现有的工具并不能一直提供这种功能。本文中描述的方法可以在图表上找到极值点,这里讨论的工具在有趋势和平盘市场上都一样有效,取得的结果不会被所选时段很大影响,也不会只针对特定的缩放尺度。