包含图形用户界面 (GUI) 的 EA 交易: 创建面板 (第一部分)

Anatoli Kazharski | 6 八月, 2018

目录

简介

尽管算法交易的发展非常活跃,很多交易者还是倾向于人工交易。然而,很难完全避免一些重复性操作的自动化。

本文展示了一个用于人工交易的多交易品种信号 EA 交易的开发,作为例子,让我们使用来自终端标准发布的 随机振荡指标(Stochastic)的信号。您可以使用这里的代码来开发您自己的含有图形界面(GUI)的 EA 交易:可以在其中包含任何其他指标,也可以根据某些计算结果来做交易决定。

对于执行其它交易者订单的人来说,本文也是有用的,可以用于向客户演示一些技术任务的实例。这个例子可以在您准备开发含有 GUI 的程序需求规格的时候节约您的时间。

这里是本文详细讨论的一些问题:

本文将分为两部分发表, 在这篇文章中,我们会探讨开发面板,而下一篇则描述使用功能来填补它。

GUI 元件

我们将要开发一个含有 GUI 的 EA 交易,这可以使用户和程序之间实现交互,并且可以把数据可视化。可以使用标准库的功能来创建 GUI,但是在我的例子中,它是基于 EasyAndFast 开发库来实现的。它的功能很丰富,可以集中精力于程序的功能方面,而不用被图形部分的优化来分神。

首先,让我们规划一下大致的 GUI 结构,下面的图表显示,GUI 窗口含有两个页面,下面的列表显示了它们要有的功能。这是一个简化过的例子,客户和开发者可以在讨论的时候更加详细地合作。

图 1. 含有注释的 GUI 总体视图

图 1. 含有注释的 GUI 总体视图

可能会有很多的 GUI 控件,所以,让我们首先以分级表单方式列出它们:

在主程序类 (CProgram) 中, 声明方法类以上列出的元件类的实例。用于创建元件的方法的代码位于单独的文件中,使用 MQL 程序类包含在文件中:

//+------------------------------------------------------------------+
//| 应用程序开发类                                                      |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- 窗口
   CWindow           m_window1;
   //--- 状态条
   CStatusBar        m_status_bar;
   //--- 页面
   CTabs             m_tabs1;
   //--- 输入栏位
   CTextEdit         m_symb_filter;
   CTextEdit         m_lot;
   CTextEdit         m_up_level;
   CTextEdit         m_down_level;
   CTextEdit         m_chart_scale;
   //--- 按钮
   CButton           m_request;
   CButton           m_chart_shift;
   CButton           m_buy;
   CButton           m_sell;
   CButton           m_close_all;
   //--- 组合框
   CComboBox         m_timeframes;
   //--- 复选框
   CCheckBox         m_date_scale;
   CCheckBox         m_price_scale;
   CCheckBox         m_show_indicator;
   //--- 表格
   CTable            m_table_positions;
   CTable            m_table_symb;
   //--- 标准图表
   CStandardChart    m_sub_chart1;
   //--- 进度条
   CProgressBar      m_progress_bar;
   //---
public:
   //--- 创建 GUI
   bool              CreateGUI(void);
   //---
private:
   //--- 表单
   bool              CreateWindow(const string text);
   //--- 状态条
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- 页面
   bool              CreateTabs1(const int x_gap,const int y_gap);
   //--- 输入栏位
   bool              CreateSymbFilter(const int x_gap,const int y_gap,const string text);
   bool              CreateLot(const int x_gap,const int y_gap,const string text);
   bool              CreateUpLevel(const int x_gap,const int y_gap,const string text);
   bool              CreateDownLevel(const int x_gap,const int y_gap,const string text);
   bool              CreateChartScale(const int x_gap,const int y_gap,const string text);
   //--- 按钮
   bool              CreateRequest(const int x_gap,const int y_gap,const string text);
   bool              CreateChartShift(const int x_gap,const int y_gap,const string text);
   bool              CreateBuy(const int x_gap,const int y_gap,const string text);
   bool              CreateSell(const int x_gap,const int y_gap,const string text);
   bool              CreateCloseAll(const int x_gap,const int y_gap,const string text);
   //--- 组合框
   bool              CreateComboBoxTF(const int x_gap,const int y_gap,const string text);
   //--- 复选框
   bool              CreateDateScale(const int x_gap,const int y_gap,const string text);
   bool              CreatePriceScale(const int x_gap,const int y_gap,const string text);
   bool              CreateShowIndicator(const int x_gap,const int y_gap,const string text);
   //--- 表格
   bool              CreatePositionsTable(const int x_gap,const int y_gap);
   bool              CreateSymbolsTable(const int x_gap,const int y_gap);
   //--- 标准图表
   bool              CreateSubChart1(const int x_gap,const int y_gap);
   //--- 进度条
   bool              CreateProgressBar(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| 用于创建控件的代码                                                   |
//+------------------------------------------------------------------+
#include "CreateGUI.mqh"
//+------------------------------------------------------------------+

下一步,让我们着重于 GUI 的组装,用于创建它的元件以及属性的方法。

组装 GUI

在程序 GUI 的开发中,我们将使用10类 GUI 元件。

在这个列表的一些种类中,我们会需要多个元件,所以我们每个组只会讨论一个元件,让我们使用相同的顺序来探讨创建它们的方法。 

用于控件的表单

下面是用于创建容纳所有其他元件的表单的方法,首先,我们需要把表单加到程序 GUI 元件列表中,为此,调用 CWndContainer::AddWindow() 方法, 传入 CWindow 类型的元件对象。然后,在创建表单之前设置它的属性,我们要设置以下属性 (和下面列表中顺序相同):

在设置完属性之后,调用创建表单的方法 — CWindow::CreateWindow() 并传给它:

//+------------------------------------------------------------------+
//| 创建控件表单                                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- 把窗口指针加到窗口数组中
   CWndContainer::AddWindow(m_window1);
//--- 属性
   m_window1.XSize(750);
   m_window1.YSize(450);
   m_window1.FontSize(9);
   m_window1.IsMovable(true);
   m_window1.ResizeMode(true);
   m_window1.CloseButtonIsUsed(true);
   m_window1.CollapseButtonIsUsed(true);
   m_window1.TooltipsButtonIsUsed(true);
   m_window1.FullscreenButtonIsUsed(true);
//--- 设置工具提示
   m_window1.GetCloseButtonPointer().Tooltip("Close");
   m_window1.GetTooltipButtonPointer().Tooltip("Tooltips");
   m_window1.GetFullscreenButtonPointer().Tooltip("Fullscreen");
   m_window1.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
//--- 创建表单
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,1,1))
      return(false);
//---
   return(true);
  }

推荐每次在您加上新的 GUI 元件的时候都编译程序并检查结果。 

图 2. 用于控件的表单

图 2. 用于控件的表单

所有中间过程的结果都显示如下.

状态条

用于根据指定的主元件来创建状态条的方法的代码,也用于计算其中元件的边界以及它们的大小,这在开发应用程序的时候可以节约时间: 一组相关元件可以只根据主元件来改变它们的坐标。为了绑定元件,它的指针要传给 CElement::MainPointer() 方法。在本例中,我们把状态条绑定到表单,所以, 要把表单对象传给这个方法。

然后,再设置状态条的属性。它包含了三个部分,向用户显示信息。 

在设置了属性之后,再创建元件。现在,它已经可以工作了,我们可以在运行时刻改变它分段中的文字,在我们的例子中,第一个分段的文字设为 "For Help, press F1(如需取得帮助,按F1)"

在方法的最后,要确保把所创建元件的指针保存到总的 GUI 元件列表中,为此,要调用 CWndContainer::AddToElementsArray() 方法,并且把表单索引和元件对象传给它。因为我们只有一个表单,它的索引将是0。

//+------------------------------------------------------------------+
//| 创建状态条                                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateStatusBar(const int x_gap,const int y_gap)
  {
#define STATUS_LABELS_TOTAL 3
//--- 保存窗口指针
   m_status_bar.MainPointer(m_window1);
//--- 属性
   m_status_bar.AutoXResizeMode(true);
   m_status_bar.AutoXResizeRightOffset(1);
   m_status_bar.AnchorBottomWindowSide(true);
//--- 设置分段的数量以及它们的属性
   int width[STATUS_LABELS_TOTAL]={0,200,110};
   for(int i=0; i<STATUS_LABELS_TOTAL; i++)
      m_status_bar.AddItem(width[i]);
//--- 创建控件
   if(!m_status_bar.CreateStatusBar(x_gap,y_gap))
      return(false);
//--- 在状态条分段中设置文字
   m_status_bar.SetValue(0,"For Help, press F1");
//--- 把对象加到总的对象组数组中
   CWndContainer::AddToElementsArray(0,m_status_bar);
   return(true);
  }

EasyAndFast 开发库中其余元件的创建也是基于相同的原则,所以,我们只会讨论我们 EA 中需要使用的可自定义的属性。

图 3. 加上状态条

图 3. 加上状态条

页面组

让我们在创建页面组的方法中设置以下的元件属性:

下面是方法的代码:

//+------------------------------------------------------------------+
//| 创建页面组 1                                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs1(const int x_gap,const int y_gap)
  {
#define TABS1_TOTAL 2
//--- 保存指向主元件的指针
   m_tabs1.MainPointer(m_window1);
//--- 属性
   m_tabs1.IsCenterText(true);
   m_tabs1.PositionMode(TABS_TOP);
   m_tabs1.AutoXResizeMode(true);
   m_tabs1.AutoYResizeMode(true);
   m_tabs1.AutoXResizeRightOffset(3);
   m_tabs1.AutoYResizeBottomOffset(25);
//--- 使用指定的属性增加页面
   string tabs_names[TABS1_TOTAL]={"Trade","Positions"};
   for(int i=0; i<TABS1_TOTAL; i++)
      m_tabs1.AddTab(tabs_names[i],100);
//--- 创建控件
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      return(false);
//--- 把对象加到总的对象组数组中
   CWndContainer::AddToElementsArray(0,m_tabs1);
   return(true);
  }

图 4. 加上页面组

图 4. 加上页面组

输入栏位

例如,让我们探讨一个输入栏位,用户可以从一个表格中的交易品种列表中指定货币和/或货币对。它的主元件是一个页面组,我们在此需要指定输入栏位应该显示的页面,为此, 要调用 CTabs::AddToElementsArray() 方法,并向它传递页面的索引和附加的元件对象。

现在,让我们探讨这个输入栏位的属性。

CTextEdit 类型的元件包含了几种其他元件,所以,如果您需要改变它的属性,您可以得到它们的指针,我们必须修改一些文本输入栏位 (CTextBox)的一些属性。让我们按照下面代码列表中实现的相同顺序来讨论它们:

输入栏位的复选框默认是启用的. 为此,要在创建元件时立即激活它.

//+------------------------------------------------------------------+
//| 创建一个复选框,带有 "Symbols filter" 输入栏位                         |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsFilter(const int x_gap,const int y_gap,const string text)
  {
//--- 保存指向主元件的指针
   m_symb_filter.MainPointer(m_tabs1);
//---为页面做保留
   m_tabs1.AddToElementsArray(0,m_symb_filter);
//--- 属性
   m_symb_filter.SetValue("USD"); // "EUR,USD" "EURUSD,GBPUSD" "EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCHF"
   m_symb_filter.CheckBoxMode(true);
   m_symb_filter.AutoXResizeMode(true);
   m_symb_filter.AutoXResizeRightOffset(90);
   m_symb_filter.GetTextBoxPointer().XGap(100);
   m_symb_filter.GetTextBoxPointer().AutoXResizeMode(true);
   m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true);
   m_symb_filter.GetTextBoxPointer().DefaultText("Example: EURUSD,GBP,NOK");
//--- 创建控件
   if(!m_symb_filter.CreateTextEdit(text,x_gap,y_gap))
      return(false);
//--- 启用复选框
   m_symb_filter.IsPressed(true);
//--- 把对象加到对象组的统一数组中
   CWndContainer::AddToElementsArray(0,m_symb_filter);
   return(true);
  }

除了文本输入栏位,在 GUI 中还有数字式输入的。例如, Lot 输入栏位 (用于建立仓位的交易量). 这种类型的其他元件应当是输入栏位.

这里是方法的代码:

//+------------------------------------------------------------------+
//| 创建 "Lot" 输入栏位                                                 |
//+------------------------------------------------------------------+
bool CProgram::CreateLot(const int x_gap,const int y_gap,const string text)
  {
//--- 保存指向主元件的指针
   m_lot.MainPointer(m_tabs1);
//---为页面做保留
   m_tabs1.AddToElementsArray(0,m_lot);
//--- 属性
   m_lot.XSize(80);
   m_lot.MaxValue(1000);
   m_lot.MinValue(0.01);
   m_lot.StepValue(0.01);
   m_lot.SetDigits(2);
   m_lot.SpinEditMode(true);
   m_lot.SetValue((string)0.1);
   m_lot.GetTextBoxPointer().XSize(50);
   m_lot.GetTextBoxPointer().AutoSelectionMode(true);
   m_lot.GetTextBoxPointer().AnchorRightWindowSide(true);
//--- 创建控件
   if(!m_lot.CreateTextEdit(text,x_gap,y_gap))
      return(false);
//--- 把对象加到对象组的统一数组中
   CWndContainer::AddToElementsArray(0,m_lot);
   return(true);
  }

图 5. 增加输入栏位

图 5. 增加输入栏位

图片看起来不是很有逻辑,但是当您加上其它元件的时候,一切就变得合理了。

按钮

让我们在 EA 的 GUI 上加上几个按钮,我们将通过一个按钮探讨主要的属性:用于开启卖出仓位的按钮。

对于买入按钮,属性的变化是相同的,除了指定的背景颜色。

//+------------------------------------------------------------------+
//| 创建 '卖出' 按钮                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateSell(const int x_gap,const int y_gap,const string text)
  {
//--- 保存指向主元件的指针
   m_sell.MainPointer(m_tabs1);
//---为页面做保留
   m_tabs1.AddToElementsArray(0,m_sell);
//--- 属性
   m_sell.XSize(80);
   m_sell.IsCenterText(true);
   m_sell.BackColor(C'255,51,51');
   m_sell.BackColorHover(C'255,100,100');
   m_sell.BackColorPressed(C'195,0,0');
   m_sell.LabelColor(clrWhite);
   m_sell.LabelColorHover(clrWhite);
   m_sell.LabelColorPressed(clrWhite);
   m_sell.BorderColor(clrBlack);
   m_sell.BorderColorHover(clrBlack);
   m_sell.BorderColorPressed(clrBlack);
//--- 创建控件
   if(!m_sell.CreateButton(text,x_gap,y_gap))
      return(false);
//--- 把元件指针加到数据库中
   CWndContainer::AddToElementsArray(0,m_sell);
   return(true);
  }

图 6. 增加按钮

图 6. 增加按钮

带有下拉列表的组合框

为了改变时段,让我们制作一个含有下拉列表的组合框。定义它的配置的属性.

给每个列表项目赋值,有些属性是通过指针随后赋值的.

下面是用于创建组合框方法的代码:

//+------------------------------------------------------------------+
//| 创建用于生成时段的组合框                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateComboBoxTF(const int x_gap,const int y_gap,const string text)
  {
//--- 列表项目总数
#define ITEMS_TOTAL2 21
//--- 传递面板对象
   m_timeframes.MainPointer(m_tabs1);
//--- 锚点设向页面
   m_tabs1.AddToElementsArray(0,m_timeframes);
//--- 属性
   m_timeframes.XSize(115);
   m_timeframes.ItemsTotal(ITEMS_TOTAL2);
   m_timeframes.AnchorRightWindowSide(true);
   m_timeframes.GetButtonPointer().XSize(50);
   m_timeframes.GetButtonPointer().AnchorRightWindowSide(true);
//--- 把项目值保存到组合框列表
   string items_text[ITEMS_TOTAL2]={"M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30","H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"};
   for(int i=0; i<ITEMS_TOTAL2; i++)
      m_timeframes.SetValue(i,items_text[i]);
//--- 取得列表指针
   CListView *lv=m_timeframes.GetListViewPointer();
//--- 设置列表属性
   lv.LightsHover(true);
   lv.SelectItem(18);
//--- 创建控件
   if(!m_timeframes.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- 把元件指针加到数据库中
   CWndContainer::AddToElementsArray(0,m_timeframes);
   return(true);
  }

图 7. 加上组合框

图 7. 加上组合框

复选框

复选框是最简单的元件,只需要为它指定两个属性。

在创建元件之后,我们可以编程启用复选框

//+------------------------------------------------------------------+
//| 创建 "Date scale" 复选框                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateDateScale(const int x_gap,const int y_gap,const string text)
  {
//--- 保存窗口指针
   m_date_scale.MainPointer(m_tabs1);
//---为页面做保留
   m_tabs1.AddToElementsArray(0,m_date_scale);
//--- 属性
   m_date_scale.XSize(70);
   m_date_scale.AnchorRightWindowSide(true);
//--- 创建控件
   if(!m_date_scale.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- 启用复选框
   m_date_scale.IsPressed(true);
//--- 把对象加到对象组的统一数组中
   CWndContainer::AddToElementsArray(0,m_date_scale);
   return(true);
  }

图 8. 加上复选框

图 8. 加上复选框

表格

GUI 含有两个表格,让我们探讨可视化交易品种列表和开启仓位信号的那个表格,它位于第一个页面。首先,声明和初始化用于设置表格属性的数组,设置以下属性.

创建表格后需要设置的表头文字:

//+------------------------------------------------------------------+
//| 创建交易品种表格                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 2
#define ROWS1_TOTAL    1
//--- 保存指向主元件的指针
   m_table_symb.MainPointer(m_tabs1);
//---为页面做保留
   m_tabs1.AddToElementsArray(0,m_table_symb);
//--- 列的宽度数组
   int width[COLUMNS1_TOTAL]={95,58};
//--- 列中文字对齐方式的数组
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]={ALIGN_LEFT,ALIGN_RIGHT};
//--- X轴方向上文字缩进数组
   int text_x_offset[COLUMNS1_TOTAL]={5,5};
//--- 属性
   m_table_symb.XSize(168);
   m_table_symb.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table_symb.ColumnsWidth(width);
   m_table_symb.TextAlign(align);
   m_table_symb.TextXOffset(text_x_offset);
   m_table_symb.ShowHeaders(true);
   m_table_symb.SelectableRow(true);
   m_table_symb.ColumnResizeMode(true);
   m_table_symb.IsZebraFormatRows(clrWhiteSmoke);
   m_table_symb.AutoYResizeMode(true);
   m_table_symb.AutoYResizeBottomOffset(2);
//--- 创建控件
   if(!m_table_symb.CreateTable(x_gap,y_gap))
      return(false);
//--- 设置表头名称
   m_table_symb.SetHeaderText(0,"Symbol");
   m_table_symb.SetHeaderText(1,"Values");
//--- 把对象加到对象组的统一数组中
   CWndContainer::AddToElementsArray(0,m_table_symb);
   return(true);
  }

第二个表格显示了开启仓位的一些属性,有10列显示下面的数据。

下面这些属性在第二个表格中应当另外配置.

第一列中图片单元将作为按钮,通过点击您可以关闭一个仓位,或者指定交易品种是对冲状况可以关闭所有这些仓位。 

//+------------------------------------------------------------------+
//| 创建仓位表格                                                       |
//+------------------------------------------------------------------+
bool CProgram::CreatePositionsTable(const int x_gap,const int y_gap)
  {
...
//--- 属性
   m_table_positions.TableSize(COLUMNS2_TOTAL,ROWS2_TOTAL);
   m_table_positions.ColumnsWidth(width);
   m_table_positions.TextAlign(align);
   m_table_positions.TextXOffset(text_x_offset);
   m_table_positions.ImageXOffset(image_x_offset);
   m_table_positions.ImageYOffset(image_y_offset);
   m_table_positions.ShowHeaders(true);
   m_table_positions.IsSortMode(true);
   m_table_positions.SelectableRow(true);
   m_table_positions.ColumnResizeMode(true);
   m_table_positions.IsZebraFormatRows(clrWhiteSmoke);
   m_table_positions.AutoXResizeMode(true);
   m_table_positions.AutoYResizeMode(true);
   m_table_positions.AutoXResizeRightOffset(2);
   m_table_positions.AutoYResizeBottomOffset(2);
...
   return(true);
  }

图 9. 在第一个页面上加上表格

图 9. 在第一个页面上加上表格

图 10. 在第二个页面上加上表格

图 10. 在第二个页面上加上表格

操作表格的详细内容在主程序文件 (CProgram) 中,会在下面文章部分中讨论。

标准图表

CStandardChart 类型的元件是用于根据交易品种可视化数据的。默认显示 EURUSD D1它含有下面的特性:

如有必要,可以创建一个水平方向上的图表数组,为此, 要使用 CStandardChart::AddSubChart() 方法,并且传递交易品种和时段作为参数。但是,在本例中,我们只需要一个图表,而交易品种和时段的切换是使用其它控件切换的。

//+------------------------------------------------------------------+
//| 创建标准图表 1                                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateSubChart1(const int x_gap,const int y_gap)
  {
//--- 保存窗口指针
   m_sub_chart1.MainPointer(m_tabs1);
//--- 为第一个页面保留
   m_tabs1.AddToElementsArray(0,m_sub_chart1);
//--- 属性
   m_sub_chart1.XScrollMode(true);
   m_sub_chart1.AutoXResizeMode(true);
   m_sub_chart1.AutoYResizeMode(true);
   m_sub_chart1.AutoXResizeRightOffset(125);
   m_sub_chart1.AutoYResizeBottomOffset(2);
//--- 加上图表
   m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1);
//--- 创建控件
   if(!m_sub_chart1.CreateStandardChart(x_gap,y_gap))
      return(false);
//--- 把对象加到对象组的通用数组中
   CWndContainer::AddToElementsArray(0,m_sub_chart1);
   return(true);
  }

图 11. 加上图表

图 11. 加上图表

进度条

进度条可以使用户了解到程序现在正在做什么,所以让我们把它加到 GUI 上。下面是我们例子中的属性 (与代码中的顺序相同).

使用进度条的实例在下面显示.

//+------------------------------------------------------------------+
//| 创建进度条                                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateProgressBar(const int x_gap,const int y_gap,const string text)
  {
//--- 保存指向主元件的指针
   m_progress_bar.MainPointer(m_status_bar);
//--- 属性
   m_progress_bar.YSize(17);
   m_progress_bar.BarYSize(14);
   m_progress_bar.BarXGap(0);
   m_progress_bar.BarYGap(1);
   m_progress_bar.LabelXGap(5);
   m_progress_bar.LabelYGap(2);
   m_progress_bar.PercentXGap(5);
   m_progress_bar.PercentYGap(2);
   m_progress_bar.IsDropdown(true);
   m_progress_bar.Font("Consolas");
   m_progress_bar.BorderColor(clrSilver);
   m_progress_bar.IndicatorBackColor(clrWhiteSmoke);
   m_progress_bar.IndicatorColor(clrLightGreen);
   m_progress_bar.AutoXResizeMode(true);
   m_progress_bar.AutoXResizeRightOffset(2);
//--- 创建元件
   if(!m_progress_bar.CreateProgressBar(text,x_gap,y_gap))
      return(false);
//--- 把元件指针加到数据库中
   CWndContainer::AddToElementsArray(0,m_progress_bar);
   return(true);
  }

我们已经描述了在我们 EA 的 GUI 中所有的控件,现在,这只是一个图形化的外壳,下一步,我们将会开发所有所需的方法,来使得一切都按照最初的想法来工作。

EasyAndFast 开发库的更新

 EasyAndFast 开发库中,  CTable::SortData() 公有方法已经在 CTable 类中修改过了,现在,对第二个参数,您可以指定 表格的排序方向 (可选参数)。之前,  CTable::SortData() 方法会开始根据当前方向相反的方向开始排序。另外,还加上了用于取得 当前排序方向和排序列的索引的方法。如果表格已经由用户手动排序过 (通过点击一个表头), 而表格中的数据没有使用相同顺序更新,可以在得到当前排序方向后恢复它。 

//+------------------------------------------------------------------+
//| 用于创建绘制表格的类                                                 |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
public:
...
   //--- 根据指定列排序数据
   void              SortData(const uint column_index=0,const int direction=WRONG_VALUE);
   //--- (1) 当前排序方向, (2) 排序数组索引
   int               IsSortDirection(void)             const { return(m_last_sort_direction);    }
   int               IsSortedColumnIndex(void)         const { return(m_is_sorted_column_index); }
...
  };
//+------------------------------------------------------------------+
//| 根据指定列排序数据                                                   |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0,const int direction=WRONG_VALUE)
  {
//--- 如果超出表格边界就退出
   if(column_index>=m_columns_total)
      return;
//--- 开始排序的索引
   uint first_index=0;
//--- 最后的索引
   uint last_index=m_rows_total-1;
//--- 方向不是由用户管理的
   if(direction==WRONG_VALUE)
     {
      //--- 第一次,根据升序排序,随后每次按照相反方向排序
      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;
     }
   else
     {
      m_last_sort_direction=(ENUM_SORT_MODE)direction;
     }
//--- 记住最后一次排序的列的索引
   m_is_sorted_column_index=(int)column_index;
//--- Sorting
   QuickSort(first_index,last_index,column_index,m_last_sort_direction);
  }

另外,还在  CKeys 类的 CKeys::KeySymbol() 方法中加入了一点小的改进,数字键盘 (键盘右侧部分中单独的一个按键块) 之前没有做处理,现在您也可以从键盘的这个部分输入数字和字符了。

//+------------------------------------------------------------------+
//| 返回按下的按钮字符                                                  |
//+------------------------------------------------------------------+
string CKeys::KeySymbol(const long key_code)
  {
   string key_symbol="";
//--- 如果需要空格 (空格键)
   if(key_code==KEY_SPACE)
     {
      key_symbol=" ";
     }
//--- 如果需要 (1)一个字母字符或者 (2) 数字字符 或者 (3) 特殊字符
   else if((key_code>=KEY_A && key_code<=KEY_Z) ||
           (key_code>=KEY_0 && key_code<=KEY_9) ||
           (key_code>=KEY_NUMLOCK_0 && key_code<=KEY_NUMLOCK_SLASH) ||
           (key_code>=KEY_SEMICOLON && key_code<=KEY_SINGLE_QUOTE))
     {
      key_symbol=::ShortToString(::TranslateKey((int)key_code));
     }
//--- 返回字符
   return(key_symbol);
  }

新版本的 CTable  CKeys 类可以在文章末尾下载。

结论

这是文章的第一部分,我们已经讨论了如何不花费额外经历为任何复杂程序开发 GUI,您可以继续开发这个程序,为您自己的目标使用它。在文章的第二部分,我将会向您展示如何操作 GUI,其中最重要的是 - 怎样填补功能。 

在下面,您可以下载文件来测试和仔细学习本文提供的代码。

文件名 注释
MQL5\Experts\TradePanel\TradePanel.mq5 用于有GUI的人工交易 EA
MQL5\Experts\TradePanel\Program.mqh 含有程序类的文件
MQL5\Experts\TradePanel\CreateGUI.mqh 实现在 Program.mqh 中程序类开发 GUI 的方法的文件
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh 更新过的 CTable 类
MQL5\Include\EasyAndFastGUI\Keys.mqh 更新过的 CKeys 类