下载MetaTrader 5

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

13 二月 2017, 09:11
Anatoli Kazharski
0
2 596

内容


概论

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

函数库继续增长。将要讨论的是时间和复选框列表控件。此外, CTable 类现已提供按照升序或降序对数据排序的能力。这些和其它更新将会在本文中描述。

 

时间控件

有时, 为指标或智能系统创建图形界面时也许有必要指定时间范围。有时这种必要性出现在日内交易中。日历和下拉日历 已经演示过了, 它们可用于设置日期, 但不是时间 (小时和分钟)。

我们来列举时间控件的所有组件:

  • 背景
  • 图标
  • 描述
  • 两个编辑框

图例. 1. 时间控件组件

图例. 1. 时间控件组件

 

我们来仔细观察这个控件的类。

 

创建时间控件的类

文件 TimeEdit.mqh 在创建时利用了 CTimeEdit 类, 它拥有函数库引擎中包含的所有控件的标准方法 (WndContainer.mqh 文件)。以下是可用于用户自定义的控件属性。

  • 控件背景的颜色
  • 控件激活和阻塞状态的图标
  • 图标沿两个轴 (x, y) 的边距
  • 控件的说明文本
  • 标签文本沿两个轴 (x, y) 的边距
  • 控件文本在不同状态下的颜色
  • 编辑框的宽度
  • 编辑框沿两个轴 (x, y) 的边距
  • 控件状态 (可用/阻塞)
  • 在编辑框中数值重置的模式
//+------------------------------------------------------------------+
//| 创建时间控件的类                              |
//+------------------------------------------------------------------+
class CTimeEdit : public CElement
  {
private:
   //--- 控件背景颜色
   color             m_area_color;
   //--- 控件激活和阻塞状态的图标
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- 图表空余
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- 控件的说明文本
   string            m_label_text;
   //--- 文本标签空余
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- 文本在不同状态下的颜色
   color             m_label_color;
   color             m_label_color_hover;
   color             m_label_color_locked;
   color             m_label_color_array[];
   //--- Size of the Edit box
   int               m_edit_x_size;
   //--- 编辑框空余
   int               m_edit_x_gap;
   int               m_edit_y_gap;
   //--- 控件状态 (可用/阻塞)
   bool              m_time_edit_state;
   //--- 数值重置的模式
   bool              m_reset_mode;
   //---
public:
   //--- (1) 背景颜色, (2) 图表空余
   void              AreaColor(const color clr)                     { m_area_color=clr;                  }
   void              IconXGap(const int x_gap)                      { m_icon_x_gap=x_gap;                }
   void              IconYGap(const int y_gap)                      { m_icon_y_gap=y_gap;                }
   //--- (1) 控件的说明文本, (2) 文本标签空余
   string            LabelText(void)                          const { return(m_label.Description());     }
   void              LabelText(const string text)                   { m_label.Description(text);         }
   void              LabelXGap(const int x_gap)                     { m_label_x_gap=x_gap;               }
   void              LabelYGap(const int y_gap)                     { m_label_y_gap=y_gap;               }
   //--- 文本标签在不同状态下的颜色
   void              LabelColor(const color clr)                    { m_label_color=clr;                 }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;           }
   void              LabelColorLocked(const color clr)              { m_label_color_locked=clr;          }
   //--- (1) 编辑框大小, (2) 编辑框空余
   void              EditXSize(const int x_size)                    { m_edit_x_size=x_size;              }
   void              EditXGap(const int x_gap)                      { m_edit_x_gap=x_gap;                }
   void              EditYGap(const int y_gap)                      { m_edit_y_gap=y_gap;                }
   //--- (1) 当按下文本标签时的重置模式, (2) 文本选择模式
   bool              ResetMode(void)                                { return(m_reset_mode);              }
   void              ResetMode(const bool mode)                     { m_reset_mode=mode;                 }
  };

若要创建时间控件, 需要五个 private 和一个 public 方法。此控件是复合的, 且需准备 CSpinEdit 控件 作为编辑框。 

class CTimeEdit : public CElement
  {
private:
   //--- 创建控件对象
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CSpinEdit         m_hours;
   CSpinEdit         m_minutes;

   //---
public:
   //--- 创建控件方法
   bool              CreateTimeEdit(const long chart_id,const int subwin,const string label_text,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateHoursEdit(void);
   bool              CreateMinutesEdit(void);
   //---
public:
   //--- 返回编辑框指针
   CSpinEdit        *GetHoursEditPointer(void)                      { return(::GetPointer(m_hours));     }
   CSpinEdit        *GetMinutesEditPointer(void)                    { return(::GetPointer(m_minutes));   }

  };

为了以编程方式获取和设置编辑框中的当前值 (小时和分钟), 请使用以下代码清单中的方法: 

class CTimeEdit : public CElement
  {
public:
   //--- 获取和设置编辑框值
   int               GetHours(void)                           const { return((int)m_hours.GetValue());   }
   int               GetMinutes(void)                         const { return((int)m_minutes.GetValue()); }
   void              SetHours(const uint value)                     { m_hours.ChangeValue(value);        }
   void              SetMinutes(const uint value)                   { m_minutes.ChangeValue(value);      }
  };

下面将进一步提供控件在终端图表上的样貌示例。 


复选框列表控件

之前的一篇文章中讨论了列表视图控件 (CListView 类), 可用来从所提供的列表中选择一个项目。但有时您可能需要选择多个项目。例如, 也许需要创建一个品种或时间帧的列表, MQL 应用程序的用户可以只选择他在交易中需要的那些。

用于创建复选框列表控件的组件列表:

  1. 控件的常用背景
  2. 垂直滚动条
  3. 复选框组:
    • 背景
    • 图标
    • 文本标签

图例. 2. 复选框列表控件的组件

图例. 2. 复选框列表控件的组件

 

接下来, 我们简单地研究这种类型的列表 (CCheckBoxList) 和一种简单类型的列表 (CListView) 之间的差异, 这已在前面讨论过。

 

创建复选框列表控件的类

文件 CheckBoxList.mqh 在创建时利用了 CCheckBoxList 类, 包含了函数库中所有控件的标准方法。与 CListView 类型列表的第一个不同之处在于其列表项是由 三个图形对象 构成的 (参见以下代码)。每种对象类型均创建了单独的私有方法。

//+------------------------------------------------------------------+
//| 创建复选框列表的类                        |
//+------------------------------------------------------------------+
class CCheckBoxList : public CElement
  {
private:
   //--- 创建列表对象
   CRectLabel        m_area;
   CEdit             m_items[];
   CBmpLabel         m_checks[];
   CLabel            m_labels[];

   CScrollV          m_scrollv;
   //---
public:
   //--- 创建控件方法
   bool              CreateCheckBoxList(const long chart_id,const int subwin,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateList(void);
   bool              CreateItems(void);
   bool              CreateChecks(void);
   bool              CreateLabels(void);

   bool              CreateScrollV(void);
  };

除了项目 (项目描述) 的值之外, 还需要复选框状态 (开/关) 的数组。还需要通过列表的指定索引设置和获取数值的方法: 

class CCheckBoxList : public CElement
  {
private:
   //--- 列表中复选框数值和状态的数组
   string            m_item_value[];
   bool              m_item_state[];
   //---
public:
   //--- 返回/保存 (1) 状态和 (2) 指定索引处的列表项目文本
   void              SetItemState(const uint item_index,const bool state);
   void              SetItemValue(const uint item_index,const string value);
   bool              GetItemState(const uint item_index);
   string            GetItemValue(const uint item_index);
  };
//+------------------------------------------------------------------+
//| 设置状态                                                |
//+------------------------------------------------------------------+
void CCheckBoxList::SetItemState(const uint item_index,const bool state)
  {
   uint array_size=::ArraySize(m_item_state);
//--- 如果列表中没有项目, 报告
   if(array_size<1)
      ::Print(__FUNCTION__," > 如果列表内有至少一个项目, 则此方法被调用!");
//--- 范围超出的情况下进行调整
   uint check_index=(item_index>=array_size)?array_size-1 : item_index;
//--- 保存数值
   m_item_state[check_index]=state;
//--- 沿滚动条移动列表
   ShiftList();
  }
//+------------------------------------------------------------------+
//| 获取复选框列表状态                               |
//+------------------------------------------------------------------+
bool CCheckBoxList::GetItemState(const uint item_index)
  {
   uint array_size=::ArraySize(m_item_state);
//--- 如果列表中没有项目, 报告
   if(array_size<1)
      ::Print(__FUNCTION__," > 如果列表内有至少一个项目, 则此方法被调用!");
//--- 范围超出的情况下进行调整
   uint check_index=(item_index>=array_size)?array_size-1 : item_index;
//--- 保存数值
   return(m_item_state[check_index]);
  }

与列表控制相关的方法已相应地进行了修改。您可以自行评估它们。 


表格排序

如果应用程序的图形界面使用带数据的表格, 有时可能需要根据用户指定的列对它们进行排序。在许多图形界面的实现中, 会以如下方式实现: 通过点击列标题对数据进行排序。首次单击标题按照升序对数据进行排序, 即从最小值到最大值。第二次单击按照降序对数据进行排序, 即从最大值到最小值。

下面的屏幕截图显示来自 MetaEditor 的工具箱窗口, 其中含有一个包含三列的表格。表格根据第三列 (包含日期) 进行排序 (降序)。

图例. 3. 拥有排序数据的表格示例

图例. 3. 拥有排序数据的表格示例

 

在列标题中显示典型的箭头作为数据排序的标记。如果方向向下, 如上面截图所示, 那么数据按照降序排序, 反之亦然。

因此, 将在本文中完成 CTable 类型表格的排序。它已经包含列标题的启用能力, 而不仅只需要使其具有交互性。首先, 当鼠标悬浮在标题时, 以及当点击它们时, 有必要改变标题的颜色。为此, 向 CTable类中添加字段和方法, 以便设置不同状态下列标题的颜色 (请参阅下面的代码)。

class CTable : public CElement
  {
private:
   //--- 标题背景色
   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;

   //---
public:
   //--- 标题背景色
   void              HeadersColor(const color clr)                              { m_headers_color=clr;                     }
   void              HeadersColorHover(const color clr)                         { m_headers_color_hover=clr;               }
   void              HeadersColorPressed(const color clr)                       { m_headers_color_pressed=clr;             }

  };

由用户决定表格是否需要排序功能。省缺情况下将禁用排序模式。若要启用它, 使用 CTable::IsSortMode() 方法。 

class CTable : public CElement
  {
private:
   //--- 数据排序模式依据列
   bool              m_is_sort_mode;
   //---
public:
   //--- 数据排序模式
   void              IsSortMode(const bool flag)                                { m_is_sort_mode=flag;                     }
  };

私有 CTable::HeaderColorByHover() 方法将用于鼠标光标悬浮时更改标题颜色。它在控件的事件处理程序中被调用。 

class CTable : public CElement
  {
private:
   //--- 当鼠标光标悬浮时更改表个标题颜色
   void              HeaderColorByHover(void);
  };
//+------------------------------------------------------------------+
//| 当鼠标光标悬浮时更改表个标题颜色     |
//+------------------------------------------------------------------+
void CTable::HeaderColorByHover(void)
  {
//--- 如果禁用列数据排序模式, 离开
   if(!m_is_sort_mode || !m_fix_first_row)
      return;
//---
   for(uint c=0; c<m_visible_columns_total; c++)
     {
      //--- 检查当前聚焦的标题
      bool condition=m_mouse.X()>m_columns[c].m_rows[0].X() && m_mouse.X()<m_columns[c].m_rows[0].X2() &&
                     m_mouse.Y()>m_columns[c].m_rows[0].Y() && m_mouse.Y()<m_columns[c].m_rows[0].Y2();
      //---
      if(!condition)
         m_columns[c].m_rows[0].BackColor(m_headers_color);
      else
        {
         if(!m_mouse.LeftButtonState())
            m_columns[c].m_rows[0].BackColor(m_headers_color_hover);
         else
            m_columns[c].m_rows[0].BackColor(m_headers_color_pressed);
        }
     }
  }

若要为排序数据创建标记图标, 必须添加私有 CTable::CreateSignSortedData() 方法。如果在创建表格之前尚未启用排序, 则 不会创建图标。如果启用排序模式, 则将在其创建后立即 隐藏图标, 表格初始化时数据未排序。 

class CTable : public CElement
  {
private:
   //--- 创建表格对象
   CBmpLabel         m_sort_arrow;
   //---
private:
   //--- 创建表格方法
   bool              CreateSignSortedData(void);
  };
//+------------------------------------------------------------------+
//| 创建一个箭头图标作为数据排序的标志                   |
//+------------------------------------------------------------------+
bool CTable::CreateSignSortedData(void)
  {
//--- 如果禁用排序模式, 离开
   if(!m_is_sort_mode)
      return(true);

//--- 形成对象名
   string name=CElement::ProgramName()+"_table_sort_array_"+(string)CElement::Id();
//--- 坐标
   int x =(m_anchor_right_window_side)?m_columns[0].m_rows[0].X()-m_columns[0].m_rows[0].XSize()+m_sort_arrow_x_gap : m_columns[0].m_rows[0].X2()-m_sort_arrow_x_gap;
   int y =(m_anchor_bottom_window_side)?CElement::Y()-m_sort_arrow_y_gap : CElement::Y()+m_sort_arrow_y_gap;
//--- 如果未指定箭头的图标, 请设置默认值
   if(m_sort_arrow_file_on=="")
      m_sort_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
   if(m_sort_arrow_file_off=="")
      m_sort_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//--- 设置对象
   if(!m_sort_arrow.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- 设置属性
   m_sort_arrow.BmpFileOn("::"+m_sort_arrow_file_on);
   m_sort_arrow.BmpFileOff("::"+m_sort_arrow_file_off);
   m_sort_arrow.Corner(m_corner);
   m_sort_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_sort_arrow.Selectable(false);
   m_sort_arrow.Z_Order(m_zorder);
   m_sort_arrow.Tooltip("\n");
//--- 保存坐标
   m_sort_arrow.X(x);
   m_sort_arrow.Y(y);
//--- 保存大小 (对象)
   m_sort_arrow.XSize(m_sort_arrow.X_Size());
   m_sort_arrow.YSize(m_sort_arrow.Y_Size());
//--- 距边缘空余
   m_sort_arrow.XGap((m_anchor_right_window_side)?x : x-m_wnd.X());
   m_sort_arrow.YGap((m_anchor_bottom_window_side)?y : y-m_wnd.Y());
//--- 隐藏对象
   m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- 保存对象指针
   CElement::AddToArray(m_sort_arrow);
   return(true);
  }

表格单元的数值和属性的结构如果是实数, 则必须由带 小数位数 的数组补充, 以及表格每列的数据类型字段

class CTable : public CElement
  {
private:
   //--- 表格数值和属性的数组
   struct TOptions
     {
      ENUM_DATATYPE     m_type;
      string            m_vrows[];
      uint              m_digits[];
      ENUM_ALIGN_MODE   m_text_align[];
      color             m_text_color[];
      color             m_cell_color[];
     };
   TOptions          m_vcolumns[];
  };

当在表格单元中输入数值时, 省缺情况下, 其小数位数设置为零: 

class CTable : public CElement
  {
public:
   //--- 设置指定表格单元的数值
   void              SetValue(const uint column_index,const uint row_index,const string value="",const uint digits=0);
  };

若要设置特定表格列中的数据类型, 以及获取类型, 请使用 CTable::DataType() 方法:

class CTable : public CElement
  {
public:
   //--- 获取/设置数据类型
   ENUM_DATATYPE     DataType(const uint column_index)                          { return(m_vcolumns[column_index].m_type); }
   void              DataType(const uint column_index,const ENUM_DATATYPE type) { m_vcolumns[column_index].m_type=type;    }
  };

在创建表格之前, 应指定列和行的总数。m_type 字段和 m_digits 数组使用省缺数值进行初始化。起初, 所有列均有 字符串 (TYPE_STRING) 类型, 所有单元的小数位数为 。 

//+------------------------------------------------------------------+
//| 设置表格大小                                        |
//+------------------------------------------------------------------+
void CTable::TableSize(const uint columns_total,const uint rows_total)
  {
//--- 至少要有一列
   m_columns_total=(columns_total<1) ?1 : columns_total;
//--- 至少要有两行
   m_rows_total=(rows_total<2) ?2 : rows_total;
//--- 设置列数组的大小
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- 设置行数组的大小
   for(uint i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_digits,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_align,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_color,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_cell_color,m_rows_total);
      //--- 使用省缺值初始化单元背景颜色数组
      m_vcolumns[i].m_type=TYPE_STRING;
      ::ArrayInitialize(m_vcolumns[i].m_digits,0);
      ::ArrayInitialize(m_vcolumns[i].m_text_align,m_align_mode);
      ::ArrayInitialize(m_vcolumns[i].m_cell_color,m_cell_color);
      ::ArrayInitialize(m_vcolumns[i].m_text_color,m_cell_text_color);
     }
  }

表格数组的排序需要多个私有方法, 它们将执行以下操作:

  • 排序算法。
  • 比较指定条件下的值。
  • 交换数组元素的值。

交换数组元素的值使用 CTable::Swap() 方法完成。在此, 交换通过表格行直接完成。不仅要交换单元值, 而且文本颜色也要交换。 

class CTable : public CElement
  {
private:
   //--- 指定单元数值交换
   void              Swap(uint c,uint r1,uint r2);
  };
//+------------------------------------------------------------------+
//| 交换元素                                                |
//+------------------------------------------------------------------+
void CTable::Swap(uint c,uint r1,uint r2)
  {
//--- 在循环里迭代所有列
   for(uint i=0; i<m_columns_total; i++)
     {
      //--- 交换文本
      string temp_text          =m_vcolumns[i].m_vrows[r1];
      m_vcolumns[i].m_vrows[r1] =m_vcolumns[i].m_vrows[r2];
      m_vcolumns[i].m_vrows[r2] =temp_text;
      //--- 交换文本颜色
      color temp_text_color          =m_vcolumns[i].m_text_color[r1];
      m_vcolumns[i].m_text_color[r1] =m_vcolumns[i].m_text_color[r2];
      m_vcolumns[i].m_text_color[r2] =temp_text_color;
     }
  }

为了正确排序, 应使用与 m_type 字段中指定的类型转换后的数值进行比较。为此目的, 创建了一个单独的 CTable::CheckSortCondition() 方法。

class CTable : public CElement
  {
private:
   //--- 检查排序条件
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
  };
//+------------------------------------------------------------------+
//| 按照指定条件比较数值          |
//+------------------------------------------------------------------+
//| 条件: true (>), false (<)                                   |
//+------------------------------------------------------------------+
bool CTable::CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction)
  {
   bool condition=false;
//---
   switch(m_vcolumns[column_index].m_type)
     {
      case TYPE_STRING :
        {
         string v1=m_vcolumns[column_index].m_vrows[row_index];
         string v2=check_value;
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DOUBLE :
        {
         double v1=double(m_vcolumns[column_index].m_vrows[row_index]);
         double v2=double(check_value);
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DATETIME :
        {
         datetime v1=::StringToTime(m_vcolumns[column_index].m_vrows[row_index]);
         datetime v2=::StringToTime(check_value);
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
      //---
      default :
        {
         long v1=(long)m_vcolumns[column_index].m_vrows[row_index];
         long v2=(long)check_value;
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
     }
//---
   return(condition);
  }

CTable::Swap() 和 CTable::CheckSortCondition() 方法将会在排序算法中使用。我们来看看已经选择了哪种特定算法来对数据进行排序。已经有十种算法经过了测试, 包括使用 ArraySort() 函数的 MQL 标准排序:

  • MQL 排序
  • 选择排序
  • 气泡排序
  • 鸡尾酒摇瓶排序
  • 插入排序
  • 贝壳排序
  • 二叉树排序
  • 快速排序
  • 堆栈排序
  • 合并排序

这些方法已在大小为 100,000 (十万) 个元素的数组上测试过。数组已使用随机数初始化。测试结果显示在下面的直方图中:

图例. 4. 不同排序方法的测试结果图表。数组大小为 100,000 个元素。

图例. 4. 不同排序方法的测试结果图表。数组大小为 100,000 个元素。


快速排序方法被证明是最快的。此方法的代码取自标准函数库 – QuickSort() 方法。在此测试中, 它甚至展示出比 ArraySort() 函数更加的结果。使用 GetMicrosecondCount() 函数测量算法操作时间以获得更高的精度。我们只留下显示最佳结果的算法 (小于 1 秒)。 

图例. 5. 数组大小为 100,000 个元素的排序方法的最佳测试结果图表。

图例. 5. 数组大小为 100,000 个元素的排序方法的最佳测试结果图表。


将数组大小增加 10 倍, 即现在将对 1000000 (一百万) 个元素的数组进行排序。

图例. 6. 数组大小为 1,000,000 个元素的排序方法的测试结果图表。

图例. 6. 数组大小为 1,000,000 个元素的排序方法的测试结果图表。


标准算法 — the ArraySort() 函数在本轮测试中是最好的。快速排序方法证明了自身只是略差, 因此, 它将被选择。ArraySort() 不适合此次任务, 因为: (1) 需要在两个方向上排序的能力, (2) CTable 类使用一个结构数组, 且 (3) 不仅要控制表格单元中数值的位置, 还有它们的其它属性。 

应将 ENUM_SORT_MODE 枚举添加到 Enums.mqh 文件中, 以便进行双向排序:

  • SORT_ASCEND – 升序。
  • SORT_DESCEND – 降序。
//+------------------------------------------------------------------+
//| 排序模式枚举                                 |
//+------------------------------------------------------------------+
enum ENUM_SORT_MODE
  {
   SORT_ASCEND  =0,
   SORT_DESCEND =1
  };

CTable::QuickSort() 方法中使用的当前快速排序算法版本如下面的代码所示。CTable::Swap() 和 CTable::CheckSortCondition() 方法, 已在本文前面展示过, 以黄色高亮显示。 

class CTable : public CElement
  {
private:
   //--- 快速排序方法
   void              QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
  };
//+------------------------------------------------------------------+
//| 快速排序算法                                              |
//+------------------------------------------------------------------+
void CTable::QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND)
  {
   uint   r1         =beg;
   uint   r2         =end;
   uint   c          =column;
   string temp       =NULL;
   string value      =NULL;
   uint   data_total =m_rows_total-1;
//--- 在左侧索引小于最右侧索引时运行算法
   while(r1<end)
     {
      //---从行的中间取值
      value=m_vcolumns[c].m_vrows[(beg+end)>>1];
      //--- 在左侧索引小于找到的右侧索引时运行算法
      while(r1<r2)
        {
         //--- 在查找指定条件的值时向右侧移动索引
         while(CheckSortCondition(c,r1,value,(mode==SORT_ASCEND)?false : true))
           {
            //--- 检查是否超出数组范围
            if(r1==data_total)
               break;
            r1++;
           }
         //--- 在查找指定条件的值时, 将索引向左侧移动
         while(CheckSortCondition(c,r2,value,(mode==SORT_ASCEND)?true : false))
           {
            //--- 检查是否超出数组范围
            if(r2==0)
               break;
            r2--;
           }
         //--- 如果左侧索引仍然不大于右侧的索引
         if(r1<=r2)
           {
            //--- 数值交换
            Swap(c,r1,r2);
            //--- 如果已达到左侧限制
            if(r2==0)
              {
               r1++;
               break;
              }
            //---
            r1++;
            r2--;
           }
        }
      //--- 算法继续递归, 直到达到范围的开始
      if(beg<r2)
         QuickSort(beg,r2,c,mode);
      //--- 缩窄下一次迭代的范围
      beg=r1;
      r2=end;
     }
  }

所有这些方法都是私有的。现在研究公有 CTable::SortData() 方法, 设计将被调用 (1) 当点击表格列标题的头部, 或 (2) 根据 MQL 应用程序的作者编程需要, 在任何其它的时间。CTable::SortData() 应传递列索引, 省缺第一列已排序。如果指定的列首次排序, 或者最后按降序排序, 则值将按升序排序。在 数据排序 之后, 表格将被刷新。并且 CTable::SortData() 方法的最后一行将为已排序表格设置相应的箭头标记图标。 

class CTable : public CElement
  {
private:
   //--- 排序列索引 (WRONG_VALUE – 表格未排序)
   int               m_is_sorted_column_index;
   //--- 最后排序方向
   ENUM_SORT_MODE    m_last_sort_direction;
   //---
public:
   //--- 根据指定列进行数据排序
   void              SortData(const uint column_index=0);
  };
//+------------------------------------------------------------------+
//| 根据指定列进行数据排序                  |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0)
  {
//--- 排序开始的索引 (考虑头部的存在)
   uint first_index=(m_fix_first_row) ?1 : 0;
//--- 数组的最后索引
   uint last_index=m_rows_total-1;
//--- 第一次它将按升序排序, 每次之后它将按相反的方向排序
   if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND)
      m_last_sort_direction=SORT_ASCEND;
   else
      m_last_sort_direction=SORT_DESCEND;
//--- 保存最后排序的数据列的索引
   m_is_sorted_column_index=(int)column_index;
//--- 排序
   QuickSort(first_index,last_index,column_index,m_last_sort_direction);
//--- 刷新表格
   UpdateTable();
//--- 根据排序方向设置图标
   m_sort_arrow.State((m_last_sort_direction==SORT_ASCEND)?true : false);
  }

需要用于处理点击列标题的方法 — CTable::OnClickTableHeaders(), 当 数据排序模式启用时。属于此控件的对象经过所有检查之后, 在循环中确定标题 (列) 的索引, 然后 表格进行排序。在表格进行排序之后, 立即生成 具有新 ON_SORT_DATA 标识符的事件。除了此事件标识符, 消息包含 (1) 控件标识符, (2) 排序列的索引和 (3) 此列的数据类型。

class CTable : public CElement
  {
private:
   //--- 处理表格标题点击
   bool              OnClickTableHeaders(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| 处理表格标题点击                            |
//+------------------------------------------------------------------+
bool CTable::OnClickTableHeaders(const string clicked_object)
  {
//--- 如果禁用排序模式, 离开
   if(!m_is_sort_mode)
      return(false);

//--- 如果未有表格单元点击, 离开
   if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- 从对象名中获取标识符
   int id=CElement::IdFromObjectName(clicked_object);
//--- 如果标识符不匹配, 离开
   if(id!=CElement::Id())
      return(false);
//--- 如果不是表格标题, 离开
   if(RowIndexFromObjectName(clicked_object)>0)
      return(false);
//--- 用于确定列索引
   uint column_index=0;
//--- 如果启用了固定标题模式, 平移一个索引
   int l=(m_fix_first_column) ?1 : 0;
//--- 获取当前水平滚动条的滑块位置
   int h=m_scrollh.CurrentPos()+l;
//--- 列
   for(uint c=l; c<m_visible_columns_total; c++)
     {
      //--- 如果并非在此单元按下
      if(m_columns[c].m_rows[0].Name()==clicked_object)
        {
         //--- 获取列索引
         column_index=(m_fix_first_column && c==0) ?0 : h;
         break;
        }
      //---
      h++;
     }
//--- 根据指定列进行数据排序
   SortData(column_index);
//--- 发送有关消息
   ::EventChartCustom(m_chart_id,ON_SORT_DATA,CElement::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
   return(true);
  }

如果列的总数大于可视列的数量, 则在移动水平滚动条时必须调整排序表格的箭头标记位置。这可使用 CTable::ShiftSortArrow() 方法完成。

class CTable : public CElement
  {
private:
   //--- 平移已排序数据的箭头标记
   void              ShiftSortArrow(const uint column);
  };
//+------------------------------------------------------------------+
//| 将箭头平移至已排序表格列                    |
//+------------------------------------------------------------------+
void CTable::ShiftSortArrow(const uint column)
  {
//--- 如果元素未隐藏, 则显示对象
   if(CElement::IsVisible())
      m_sort_arrow.Timeframes(OBJ_ALL_PERIODS);
//--- 计算并设置坐标
   int x=m_columns[column].m_rows[0].X2()-m_sort_arrow_x_gap;
   m_sort_arrow.X(x);
   m_sort_arrow.X_Distance(x);
//--- 距边缘空余
   m_sort_arrow.XGap((m_anchor_right_window_side)?m_wnd.X2()-x : x-m_wnd.X());
  }

此方法将在 CTable::UpdateTable() 方法中, 标题平移的代码块中被调用。以下是 CTable::UpdateTable() 方法的简化版本, 含有 附加的片段。在此, 如果在第一次循环中找到排序的列, 则设置标志并移动箭头标记。循环完成后, 可能会发现存在已排序的列, 但在前一次循环中未发现。这可能意味着它已经脱离可见区域, 应该被隐藏。如果这是第一列 (索引零), 同时它是固定的且不能移动, 则为它设置箭头标记。 

//+------------------------------------------------------------------+
//| 参考最近的变化更新表格数据       |
//+------------------------------------------------------------------+
void CTable::UpdateTable(void)
  {
//...

//--- 在顶行中平移标题
   if(m_fix_first_row)
     {
      //--- 用于确定排序图标的移位
      bool is_shift_sort_arrow=false;
      //--- 列
      for(uint c=l; c<m_visible_columns_total; c++)
        {
         //--- 如果未超出数组范围
         if(h>=l && h<m_columns_total)
           {
            //--- 如果发现已排序列
            if(!is_shift_sort_arrow && m_is_sort_mode && h==m_is_sorted_column_index)
              {
               is_shift_sort_arrow=true;
               //--- 调整排序图标
               uint column=h-(h-c);
               if(column>=l && column<m_visible_columns_total)
                  ShiftSortArrow(column);
              }

            //--- 调整 (1) 数值, (2) 背景颜色, (3) 文本颜色和 (4) 文本在单元内对齐
            SetCellParameters(c,0,m_vcolumns[h].m_vrows[0],m_headers_color,m_headers_text_color,m_vcolumns[h].m_text_align[0]);
           }
         //---
         h++;
        }
      //--- 如果存在已排序表格, 但未发现
      if(!is_shift_sort_arrow && m_is_sort_mode && m_is_sorted_column_index!=WRONG_VALUE)
        {
         //--- 隐藏, 如果索引大于零
         if(m_is_sorted_column_index>0 || !m_fix_first_column)
            m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
         //--- 设置首列的标题
         else
            ShiftSortArrow(0);
        }

     }
//...

  }

含有测试系统的文件可在文章结尾处下载。您可以使用它们自行测试操作。

 

其它函数库更新

作为附加功能, 此版集成编译包括以下函数库更新:

1. 现在可以为每个控件单独设置字体和字号。为此目的, 已将适当的字段和方法添加到控件的 CElement 基类中。省缺时, 使用 Calibri 字体, 字号为 8。 

class CElement
  {
protected:
   //--- 字体
   string            m_font;
   int               m_font_size;
   //---
public:
   //--- (1) 字体和 (2) 字号
   void              Font(const string font)                         { m_font=font;                          }
   string            Font(void)                                const { return(m_font);                       }
   void              FontSize(const int font_size)                   { m_font_size=font_size;                }
   int               FontSize(void)                            const { return(m_font_size);                  }
  };
//+------------------------------------------------------------------+
//| 构造器                                                      |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_font("Calibri"),
                           m_font_size(8)
  {
  }

相应地, 用于创建控件的所有方法从基类中获取数值, 而字体需要指定。以下示例 CCheckBox 类的标签。在函数库的所有类中同样的事情均已完成。 

//+------------------------------------------------------------------+
//| 创建复选框的文本标签                           |
//+------------------------------------------------------------------+
bool CCheckBox::CreateLabel(void)
  {
//--- 形成对象名
   string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id();
//--- 坐标
   int x =(m_anchor_right_window_side)?m_x-m_label_x_gap : m_x+m_label_x_gap;
   int y =(m_anchor_bottom_window_side)?m_y-m_label_y_gap : m_y+m_label_y_gap;
//--- 依据状态的文本颜色
   color label_color=(m_check_button_state) ?m_label_color : m_label_color_off;
//--- 设置对象
   if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- 设置属性
   m_label.Description(m_label_text);
   m_label.Font(CElement::Font());
   m_label.FontSize(CElement::FontSize());

   m_label.Color(label_color);
   m_label.Corner(m_corner);
   m_label.Anchor(m_anchor);
   m_label.Selectable(false);
   m_label.Z_Order(m_zorder);
   m_label.Tooltip("\n");
//--- 距边缘的空余
   m_label.XGap((m_anchor_right_window_side)?x : x-m_wnd.X());
   m_label.YGap((m_anchor_bottom_window_side)?y : y-m_wnd.Y());
//--- 初始化梯度数组
   CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
//--- 保存对象指针
   CElement::AddToArray(m_label);
   return(true);
  }

 

2. 图形界面每个控件距表单边缘的空余现在需要直接传递到 创建控件的方法。计算将自动完成。例如, 下面的列表显示了从 CProgram 自定义类创建下拉日历的方法。 

//+------------------------------------------------------------------+
//| 创建下拉日历                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar(const int x_gap,const int y_gap,const string text)
  {
//--- 传递对象至面板
   m_drop_calendar.WindowPointer(m_window);
//--- 附加第二个选项卡
   m_tabs.AddToElementsArray(1,m_drop_calendar);
//--- 在创建之前设置属性
   m_drop_calendar.XSize(140);
   m_drop_calendar.YSize(20);
   m_drop_calendar.AreaBackColor(clrWhite);
//--- 创建控件
   if(!m_drop_calendar.CreateDropCalendar(m_chart_id,m_subwin,text,x_gap,y_gap))
      return(false);
//--- 将控件指针加入基类
   CWndContainer::AddToElementsArray(0,m_drop_calendar);
   return(true);
  }

 


测试控件的应用程序

现在, 我们来创建一个测试 MQL 应用程序, 在此所有新的控件均可以测试。创建一个包括主菜单 (CMenuBar), 带有下拉子菜单, 状态条和两个选项卡。第一个选项卡包含一个 CTable 类型的表格, 已启用排序模式。 

表格的前三列将具有以下数据类型:

其余列省缺为 TYPE_STRING 类型。下面的屏幕截图显示了第一个选项卡上的表格图形界面的外观。 

图例. 7. 根据第二列排序 (升序) 表格的示例。

图例. 7. 根据第二列排序 (升序) 表格的示例。


在第二个选项卡上创建四个控件: 

  • 下拉日历 (CDropCalendar 类)。
  • 时间控件 (CTimeEdit 类)。
  • 复选框列表 (CCheckBoxList 类)。
  • 列表视图 (CListView 类)。

下面的屏幕截图显示了测试 MQL 应用程序的图形界面外观:

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

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


此测试应用程序的源代码在文章结尾处提供。 

 

结论

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

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

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


这并非关于图形界面系列的最后一篇文章。我们将继续改进它, 并为它补充新的功能。您可以从下面下载最新版本的函数库和文件进行测试。

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

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

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

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

交易货币篮子时可用的形态 交易货币篮子时可用的形态

跟随我们以前关于货币篮子交易原理的文章, 这里我们将分析交易者可以检测的形态。我们还将研究每种形态的优点和缺点, 并就其使用提供一些建议。基于威廉姆斯振荡器的指标将用作分析工具。

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

函数库的代码需要进行优化: 它应该更规范化, 这样可以 — 更具可读性并易于理解学习。此外, 我们将继续开发之前创建的控件: 列表, 表格和滚动条。

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

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