内容
概论
为了更好地理解这个函数库的目的, 请阅读首篇文章: 图形界面 I: 函数库结构的准备 (第 1 章)。您可在每章结尾处找到包含文章列表的链接。从那里, 您还可以下载当前最新开发阶段的完整版库文件。这些文件必须按照归档结构置于相同的目录中。
函数库继续增长。将要讨论的是时间和复选框列表控件。此外, CTable 类现已提供按照升序或降序对数据排序的能力。这些和其它更新将会在本文中描述。
时间控件
有时, 为指标或智能系统创建图形界面时也许有必要指定时间范围。有时这种必要性出现在日内交易中。日历和下拉日历 已经演示过了, 它们可用于设置日期, 但不是时间 (小时和分钟)。
我们来列举时间控件的所有组件:
- 背景
- 图标
- 描述
- 两个编辑框
图例. 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 控件 作为编辑框。
{
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)); }
};
为了以编程方式获取和设置编辑框中的当前值 (小时和分钟), 请使用以下代码清单中的方法:
{
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 应用程序的用户可以只选择他在交易中需要的那些。
用于创建复选框列表控件的组件列表:
- 控件的常用背景
- 垂直滚动条
- 复选框组:
- 背景
- 图标
- 文本标签
图例. 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);
};
除了项目 (项目描述) 的值之外, 还需要复选框状态 (开/关) 的数组。还需要通过列表的指定索引设置和获取数值的方法:
{
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. 拥有排序数据的表格示例
在列标题中显示典型的箭头作为数据排序的标记。如果方向向下, 如上面截图所示, 那么数据按照降序排序, 反之亦然。
因此, 将在本文中完成 CTable 类型表格的排序。它已经包含列标题的启用能力, 而不仅只需要使其具有交互性。首先, 当鼠标悬浮在标题时, 以及当点击它们时, 有必要改变标题的颜色。为此, 向 CTable类中添加字段和方法, 以便设置不同状态下列标题的颜色 (请参阅下面的代码)。
{
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() 方法。
{
private:
//— 数据排序模式依据列
bool m_is_sort_mode;
//—
public:
//— 数据排序模式
void IsSortMode(const bool flag) { m_is_sort_mode=flag; }
};
私有 CTable::HeaderColorByHover() 方法将用于鼠标光标悬浮时更改标题颜色。它在控件的事件处理程序中被调用。
{
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() 方法。如果在创建表格之前尚未启用排序, 则 不会创建图标。如果启用排序模式, 则将在其创建后立即 隐藏图标, 表格初始化时数据未排序。
{
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);
}
表格单元的数值和属性的结构如果是实数, 则必须由带 小数位数 的数组补充, 以及表格每列的数据类型字段。
{
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[];
};
当在表格单元中输入数值时, 省缺情况下, 其小数位数设置为零:
{
public:
//— 设置指定表格单元的数值
void SetValue(const uint column_index,const uint row_index,const string value=“”,const uint digits=0);
};
若要设置特定表格列中的数据类型, 以及获取类型, 请使用 CTable::DataType() 方法:
{
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() 方法完成。在此, 交换通过表格行直接完成。不仅要交换单元值, 而且文本颜色也要交换。
{
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() 方法。
{
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 个元素。
快速排序方法被证明是最快的。此方法的代码取自标准函数库 – QuickSort() 方法。在此测试中, 它甚至展示出比 ArraySort() 函数更加的结果。使用 GetMicrosecondCount() 函数测量算法操作时间以获得更高的精度。我们只留下显示最佳结果的算法 (小于 1 秒)。
图例. 5. 数组大小为 100,000 个元素的排序方法的最佳测试结果图表。
将数组大小增加 10 倍, 即现在将对 1000000 (一百万) 个元素的数组进行排序。
图例. 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() 方法, 已在本文前面展示过, 以黄色高亮显示。
{
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() 方法的最后一行将为已排序表格设置相应的箭头标记图标。
{
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) 此列的数据类型。
{
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() 方法完成。
{
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 点。
{
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_DATETIME。
- 第二列 – TYPE_DOUBLE。
- 第三列 – TYPE_LONG。
其余列省缺为 TYPE_STRING 类型。下面的屏幕截图显示了第一个选项卡上的表格图形界面的外观。
图例. 7. 根据第二列排序 (升序) 表格的示例。
在第二个选项卡上创建四个控件:
- 下拉日历 (CDropCalendar 类)。
- 时间控件 (CTimeEdit 类)。
- 复选框列表 (CCheckBoxList 类)。
- 列表视图 (CListView 类)。
下面的屏幕截图显示了测试 MQL 应用程序的图形界面外观:
图例. 8. 第二个选项卡上的控件。
此测试应用程序的源代码在文章结尾处提供。
结论
目前, 创建图形界面的函数库的一般示意图如下所示:
图例. 9. 当前开发阶段的库结构
这并非关于图形界面系列的最后一篇文章。我们将继续改进它, 并为它补充新的功能。您可以从下面下载最新版本的函数库和文件进行测试。
如果您在使用这些文件时对提供的材料有疑问, 可以参考本系列文章之一的函数库开发详细描述, 或在本文的讨论中提出您的问题。
本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/2897
MyFxtop迈投-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。