DoEasy 函数库中的图形(第八十一部分):将图形集成到函数库对象之中

4 十月 2021, 09:59
Artyom Trishkin
0
618

内容

概述

这次我不打算实施任何新的图形结构或改进。 取而代之,我会开始将已创建的图形元素类集成到函数库对象当中。 这令图形元素的进一步开发和改进成为可能。 稍后,我需要沿图表实现移动对象,从而控制复合图形对象的有效性。 为达此目的,有必要预先考虑将这些对象集成到函数库对象当中,从而我们能够创建管理图形对象的类,以及它们的集合类。

图形对象管理类将包含创建交互窗和后续图形对象的方法,这些方法将返回指向所创建图形对象的指针,以便将来使用它。 稍后我们将需要图形元素集合类来创建与不同函数库对象相关的所有已构建图形对象的列表,以便能够创建在它们之间以及与程序用户之间交互的方法。

在本文中,我仅将图形对象集成到函数库对象中的一个 — 柱线对象。 我需要一些时间来调试所创建的概念。 然后我将在开发和调试机制的帮助下将其添加到其余的函数库对象当中。 之后,我将回到函数库图形对象的开发。

目前的概念如下:

  • 我们有不同的绘制图形元素对象的方法;
  • 我们已有的柱线对象,其对图形对象一无所知;
  • 我们需要创建图形对象管理类,允许我们创建它们,并返回指向所创建对象的指针;
  • 我们需要令这个管理类成为柱线对象的组件之一。

这四个步骤允许我们获取任何先前已创建的函数库对象(当前,这是柱线对象)。 用于管理图形对象的对象令我们能够创建一个必要的对象,并得到指向它的指针,从而可以像我在前几篇文章中讲述的常用函数库图形对象那样来使用它。

调试完概念后,我会将当前于此针对柱线对象所做的一切都部署到所有函数库对象当中,以便集成函数库的图形组件。 所有对象都将获得一个新的“视觉”维度,同时我们将获得一个与函数库交互的新工具。


改进库类

函数库对象所创建的每个图形对象都应意识到这一点。 当然,如果我们只有单一对象能够为自己创建图形对象(目前,这是一个柱线对象),那么新创建的图形对象不需要知道自己是由哪个对象创建的。 但如果每个函数库对象都能够为自己创建图形对象,那么所有创建的图形对象都应该知道它们是由哪个对象在内部创建的,以便可以引用其创建者,并从中获取数据。 这对于在图形对象上显示数据,或在不同对象之间实现更复杂的关系非常有用。

当然,不可能在一篇文章中完成所有这些事情。 我将从最简单的事情开始。 我们需要知道创建图形对象的对象类型的描述。 为达此目的,我们利用对象集合 ID(为每个对象设置一个对象类型对应的集合的 ID)。 ID 允许我们定义函数库对象(图形对象的创建者)所属的对象类型。 当然,这对于特定对象的准确表达是不够的。 但正如我已经说过的,我会从简单的事情开始。

此外,我们还需要为所有之前创建的函数库对象加入显示相应类型的对象描述的方法。 这是 Print() 和 PrintShort() 方法,用于显示对象属性的完整说明和简述。 我们将这些方法虚拟化,并在所有 CBaseObj 函数库对象的父类中声明它们。 为了虚拟化工作,我们需要令这些方法的参数在所有类中完全相同。 此刻,我们在不同的类中为这些方法设置了不同的参数。 有必要将它们变成单一形式,并根据经过修改的方法参数,更正方法调用。

在 \MQL5\Include\DoEasy\Objects\BaseObj.mqh 里的 CBaseObj 类中,采用必要的参数声明两个虚拟方法

//--- Return an object type
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false)  { return;                        }
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false){ return;                        }
   
//--- Constructor

之所以选择这些方法参数,是便于我之前在派生类中实现的所有方法中采用。

例如,COrder 类(函数库整个订单系统的基类)有以下变化:

//--- Return order/position direction
   string            DirectionDescription(void) const;
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
//---
  };
//+------------------------------------------------------------------+

在此,我已往 Print() 方法里添加了另一个参数,并声明了 PrintShort() 方法

而在类主体之外实现该方法时,我还添加了额外的方法参数

//+------------------------------------------------------------------+
//| Send order properties to the journal                             |
//+------------------------------------------------------------------+
void COrder::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG),": \"",this.StatusDescription(),"\" =============");
   int beg=0, end=ORDER_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",CMessage::Text(MSG_LIB_PARAMS_LIST_END),": \"",this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

以下是改进方法调用的示例,采用的是所实现的参数:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CMBookSeriesCollection::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         continue;
      bookseries.Print(false,true);
     }
  }
//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CMBookSeriesCollection::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         continue;
      bookseries.PrintShort(true);
     }
  }
//+------------------------------------------------------------------+

以前这里只有单个参数,该方法被称为 bookseries.Print(true);,现在 CMBookSeries 类的 Print() 方法在原必要参数之前还加了另一个参数。 因此,我们首先传递 false 给新添加的参数,然后我们传递 true 给原来的必要参数(之前调用方法时所需的那个)。

类似的变化几乎影响了以前编写的函数库对象的所有类,并且它们已在包含这些方法的所有类中,以及所有继承自函数库基准对象的类中实现: 

BookSeriesCollection.mqh, ChartObjCollection.mqh, MQLSignalsCollection.mqh, TickSeriesCollection.mqh, TimeSeriesCollection.mqh.

Account.mqh, MarketBookOrd.mqh, MarketBookSnapshot.mqh, MBookSeries.mqh, ChartObj.mqh, ChartWnd.mqh, MQLSignal.mqh, Order.mqh.

Buffer.mqh, BufferArrow.mqh, BufferBars.mqh, BufferCalculate.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferLine.mqh, BufferSection.mqh, BufferZigZag.mqh, DataInd.mqh, IndicatorDE.mqh.

PendReqClose.mqh, PendReqModify.mqh, PendReqOpen.mqh, PendReqPlace.mqh, PendReqRemove.mqh, PendReqSLTP.mqh, PendRequest.mqh.

Bar.mqh, SeriesDE.mqh, TimeSeriesDE.mqh, DataTick.mqh, TickSeries.mqh.

Symbol.mqh, SymbolBonds.mqh, SymbolCFD.mqh, SymbolCollateral.mqh, SymbolCommodity.mqh, SymbolCommon.mqh, SymbolCrypto.mqh, SymbolCustom.mqh, SymbolExchange.mqh, SymbolFutures.mqh, SymbolFX.mqh, SymbolFXExotic.mqh, SymbolFXMajor.mqh, SymbolFXMinor.mqh, SymbolFXRub.mqh, SymbolIndex.mqh, SymbolIndicative.mqh, SymbolMetall.mqh, SymbolOption.mqh, SymbolStocks.mqh

BaseObj.mqh.

在函数库的某些类中,消息已从标准 Print() 函数替换为 调用 CMessage 类的 ToLog() 方法来显示消息,例如, 在事件集合类的以下方法当中:

//+------------------------------------------------------------------+
//| Select only market pending orders from the list                  |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListMarketPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_ERROR_NOT_MARKET_LIST);
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

以前显示消息是用以下代码:

Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_NOT_MARKET_LIST));

具有此类编辑的文件清单:

EventsCollection.mqh, HistoryCollection.mqh, TimeSeriesCollection.mqh.

在附件中找到这些修改。

如果图表含有已创建的交互窗对象,则可在指定时间帧上依据指定显示标志来隐藏或显示它。 我们将调用 BringToTop() 方法将对象“移动”到所有其它对象之上的前景。
我们没有显示/隐藏图形对象的方法。
我们在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 的 CGCnvElement 图形元素类中创建两个虚拟方法

//--- Set the object above all
   void              BringToTop(void)                          { CGBaseObj::SetVisible(false); CGBaseObj::SetVisible(true);            }
//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true);                                          }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false);                                         }

这些方法简单地设置相应的标志,以便在函数库的基准图形对象的所有时间帧上显示对象。

CForm 交互窗对象类是图形元素对象的衍生后代,交互窗可由多个图形元素对象组成。 因此,我们需要为它单独实现这些方法。
打开 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh,并在公开部分声明两个虚方法:

//+------------------------------------------------------------------+
//| Visual design methods                                            |
//+------------------------------------------------------------------+
//--- (1) Show and (2) hide the form
   virtual void      Show(void);
   virtual void      Hide(void);

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+

我们在类主体之外编写它们的实现:

//+------------------------------------------------------------------+
//| Show the form                                                    |
//+------------------------------------------------------------------+
void CForm::Show(void)
  {
//--- If the object has a shadow, display it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Display the main form
   CGCnvElement::Show();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *elment=this.m_list_elements.At(i);
      if(elment==NULL)
         continue;
      //--- and display it
      elment.Show();
     }
//--- Update the form
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+
//| Hide the form                                                    |
//+------------------------------------------------------------------+
void CForm::Hide(void)
  {
//--- If the object has a shadow, hide it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Hide();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *elment=this.m_list_elements.At(i);
      if(elment==NULL)
         continue;
      //--- and hide it
      elment.Hide();
     }
//--- Hide the main form and update the object
   CGCnvElement::Hide();
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

这两个方法在代码清单中都有详细的注释。 简而言之,隐藏对象时,隐藏它们的顺序没有太大区别。 然而,在显示对象时,重要的是在主交互窗上所有绑定对象位置的整体恢复顺序。 因此,显示是分层进行的 — 首先,显示最底层的对象(交互窗阴影)。 然后主交互窗显示在阴影上面。 随后显示绑定到主交互窗的所有图形元素。 在此实现中,显示顺序对应于它们被添加到绑定对象列表中的顺序。
创建复杂(复合)交互窗对象时要检验该算法。

现在是开始将图形对象集成到函数库对象中的时候了。

图形对象管理类

那么我们如何赋予每个函数库对象创建自己的图形对象的能力呢?

大多数函数库对象都是从CBaseObj 函数库基准对象派生而来的。 如果我们加入一个能够创建所有可能图形对象(可用的和进一步规化开发的)的类的实例,并提供访问所创建对象的指针,那么它的所有衍生后代都将能够操控图形对象。

鉴于我们未来会拥有大量不同的图形对象,因此我们需要一个“知道”每个此类对象,并能够创建和管理它们的类。 这就是管理图形对象的类。

在 \MQL5\Include\DoEasy\Objects\Graph\ 里,创建 CGraphElmControl 类的新文件 GraphElmControl.mqh。 该类 应该派生自构造 MQL5 标准库的基类 CObject。 该类的清单应包含三个文件 — 指向 CObject 类实例,及其衍生后代类的动态指针数组的文件,服务函数文件和交互窗对象类文件

//+------------------------------------------------------------------+
//|                                              GraphElmControl.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\..\Services\DELib.mqh"
#include "Form.mqh"
//+------------------------------------------------------------------+
//| Class for managing graphical elements                            |
//+------------------------------------------------------------------+
class CGraphElmControl : public CObject
  {
private:
   int               m_type_node;                     // Type of the object the graphics is constructed for
public:
//--- Return itself
   CGraphElmControl *GetObject(void)                  { return &this;               }
//--- Set a type of the object the graphics is constructed for
   void              SetTypeNode(const int type_node) { this.m_type_node=type_node; }
   
//--- Create a form object
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h);

//--- Constructors
                     CGraphElmControl(){;}
                     CGraphElmControl(int type_node);
  };
//+------------------------------------------------------------------+

m_type_node 变量存储所包含类对象的类型。 当创建一个新对象(目前,它是一个柱线对象)时,它的构造函数调用 SetTypeNode() 方法接收需在 m_type 变量中设置的柱线对象类型( 如果是柱线图,则它是柱线对象集合 ID)。 因此,管理图形对象的对象知道其构造者所属的类。 现在,我将只使用集合 ID。 将来,我会考虑将指针传递给构建图形的对象。

我们研究该类的方法。

在类的参数型构造函数中,将方法参数中传递的对象类型赋值给 m_type_node 变量:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElmControl::CGraphElmControl(int type_node)
  {
   this.m_type_node=m_type_node;
  }
//+------------------------------------------------------------------+


在指定子窗口中的指定图表上创建交互窗对象的方法:

//+----------------------------------------------------------------------+
//| Create the form object on a specified chart in a specified subwindow |
//+----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   CForm *form=new CForm(chart_id,wnd,name,x,y,w,h);
   if(form==NULL)
      return NULL;
   form.SetID(form_id);
   form.SetNumber(0);
   return form;
  }
//+------------------------------------------------------------------+

该方法接收所创建交互窗对象的独有 ID、图表 ID、图表子窗口索引、交互窗对象名称、用于创建交互窗的 X/Y 坐标,以及交互窗宽度和高度。
接着,用传递给方法的参数创建一个新的交互窗对象。 如果创建成功,为该对象设置交互窗 ID,和位于对象列表中的索引(当前为 0,因为该对象不包含其它绑定交互窗对象,且是主要的交互窗对象)。 返回指向新创建对象的指针

在指定子窗口的当前图表上创建交互窗对象的方法:

//+----------------------------------------------------------------------+
//| Create the form object on the current chart in a specified subwindow |
//+----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   return this.CreateForm(form_id,::ChartID(),wnd,name,x,y,w,h);
  }
//+------------------------------------------------------------------+

该方法接收所创建交互窗对象的独有 ID、图表子窗口索引、交互窗对象名称、创建交互窗的 X/Y 坐标,以及交互窗宽度和高度。 该方法返回依据明确指示的当前图表ID 调用方法后,上述交互窗的操作结果

在图表主窗口的当前图表上创建交互窗对象的方法:

//+----------------------------------------------------------------------+
//| Create the form object on the current chart in the chart main window |
//+----------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
  {
   return this.CreateForm(form_id,::ChartID(),0,name,x,y,w,h);
  }
//+------------------------------------------------------------------+

该方法接收所创建交互窗对象的独有 ID、交互窗对象名称、创建交互窗的 X/Y 坐标,以及交互窗宽度和高度。 该方法返回依据明确指示的当前图表 ID和图表主窗口索引调用该方法后的第一个交互窗的操作结果

我们在此不需要任何其它东西来创建交互窗对象。 在所创建交互窗对象上创建各种动画的所有工作都将依据指向该对象的指针来执行。 指针则由上面研究过的方法返回。

我们准备开始将图形处理集成到所有函数库对象当中,所有这些函数库对象都是 CBaseObj 函数库基准对象的衍生后代。


将图形集成到函数库当中

如此,我们需要每个函数库对象都能“看到”图形对象类,并能够自行创建这些对象。 为了达此目的,我们需要在所有函数库对象的基准对象类中简单地声明管理图形对象类的实例。 它的所有衍生后代均可立即被赋予基于我刚刚研究的 CGraphElmControl 类实例来创建图形的能力。

打开 \MQL5\Include\DoEasy\Objects\BaseObj.mqh,并包含图形对象管理类的文件

//+------------------------------------------------------------------+
//|                                                      BaseObj.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Services\DELib.mqh"
#include "..\Objects\Graph\GraphElmControl.mqh"
//+------------------------------------------------------------------+

在 CBaseObj 类的受保护部分,声明一个图形对象管理类对象的实例

//+------------------------------------------------------------------+
//| Base object class for all library objects                        |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   CGraphElmControl  m_graph_elm;                              // Instance of the class for managing graphical elements
   ENUM_LOG_LEVEL    m_log_level;                              // Logging level
   ENUM_PROGRAM_TYPE m_program;                                // Program type
   bool              m_first_start;                            // First launch flag
   bool              m_use_sound;                              // Flag of playing the sound set for an object
   bool              m_available;                              // Flag of using a descendant object in the program
   int               m_global_error;                           // Global error code
   long              m_chart_id_main;                          // Control program chart ID
   long              m_chart_id;                               // Chart ID
   string            m_name;                                   // Object name
   string            m_folder_name;                            // Name of the folder storing CBaseObj descendant objects 
   string            m_sound_name;                             // Object sound file name
   int               m_type;                                   // Object type (corresponds to the collection IDs)

public:

在类的公开部分编写创建交互窗对象的方法

//--- Return an object type
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false)  { return;                        }
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false){ return;                        }
//--- Create a form object on a specified chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,chart_id,wnd,name,x,y,w,h);                }
//--- Create a form object on the current chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,wnd,name,x,y,w,h);                         }
//--- Create the form object on the current chart in the main window
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,name,x,y,w,h);                             }
   
//--- Constructor

这些方法简化了上面研究过的图形对象管理类的三个同名方法的操作结果。

现在,CBaseObj 类的每个衍生后代对象都能够在调用这些方法时创建一个交互窗对象。

今天我将验证利用 “Bar” 对象类处理图形对象。
打开 \MQL5\Include\DoEasy\Objects\Series\Bar.mqh 类文件,在 SetProperties() 方法中添加传递柱线对象类型给管理图形对象类

//+------------------------------------------------------------------+
//| Set bar object parameters                                        |
//+------------------------------------------------------------------+
void CBar::SetProperties(const MqlRates &rates)
  {
   this.SetProperty(BAR_PROP_SPREAD,rates.spread);
   this.SetProperty(BAR_PROP_VOLUME_TICK,rates.tick_volume);
   this.SetProperty(BAR_PROP_VOLUME_REAL,rates.real_volume);
   this.SetProperty(BAR_PROP_TIME,rates.time);
   this.SetProperty(BAR_PROP_TIME_YEAR,this.TimeYear());
   this.SetProperty(BAR_PROP_TIME_MONTH,this.TimeMonth());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_YEAR,this.TimeDayOfYear());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_WEEK,this.TimeDayOfWeek());
   this.SetProperty(BAR_PROP_TIME_DAY,this.TimeDay());
   this.SetProperty(BAR_PROP_TIME_HOUR,this.TimeHour());
   this.SetProperty(BAR_PROP_TIME_MINUTE,this.TimeMinute());
//---
   this.SetProperty(BAR_PROP_OPEN,rates.open);
   this.SetProperty(BAR_PROP_HIGH,rates.high);
   this.SetProperty(BAR_PROP_LOW,rates.low);
   this.SetProperty(BAR_PROP_CLOSE,rates.close);
   this.SetProperty(BAR_PROP_CANDLE_SIZE,this.CandleSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_BODY,this.BodySize());
   this.SetProperty(BAR_PROP_CANDLE_BODY_TOP,this.BodyHigh());
   this.SetProperty(BAR_PROP_CANDLE_BODY_BOTTOM,this.BodyLow());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP,this.ShadowUpSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN,this.ShadowDownSize());
//---
   this.SetProperty(BAR_PROP_TYPE,this.BodyType());
//--- Set the object type to the object of the graphical object management class
   this.m_graph_elm.SetTypeNode(this.m_type);
  }
//+------------------------------------------------------------------+

进行测试的一切几乎都已准备就绪。 但还有一个警告。 当我开始操控图形对象时,我并没有简单地把图形元素类“按原样”包含到主函数库之中。 现在我们要做的一切 — 所有函数库对象都可以通过它的主对象访问 — CEngine 类,所有对象的集合文件都与它相连接。 但图形对象还没有其集合类,因为并非所有对象都已被创建。 但我们能够为图形对象创建初步类集合。 创建所有对象后,我再返回它。

考虑到这些因素,我将创建图形对象集合类的初步版本。 我们需要在 \MQL5\Include\DoEasy\Defines.mqh为图形对象集合列表指定 ID

//--- Parameters of the chart collection timer
#define COLLECTION_CHARTS_PAUSE        (500)                      // Chart collection timer pause in milliseconds
#define COLLECTION_CHARTS_COUNTER_STEP (16)                       // Chart timer counter increment
#define COLLECTION_CHARTS_COUNTER_ID   (9)                        // Chart timer counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Indicator buffer collection list ID
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Indicator collection list ID
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Indicator data collection list ID
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Tick series collection list ID
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // DOM series collection list ID
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // MQL5 signals collection list ID
#define COLLECTION_CHARTS_ID           (0x7786)                   // Chart collection list ID
#define COLLECTION_CHART_WND_ID        (0x7787)                   // Chart window list ID
#define COLLECTION_GRAPH_OBJ_ID        (0x7788)                   // Graphical object collection list ID
//--- Pending request type IDs

在 \MQL5\Include\DoEasy\Collections\ 函数库文件夹中,创建 CGraphElementsCollection 类的新文件 GraphElementsCollection.mqh

//+------------------------------------------------------------------+
//|                                      GraphElementsCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
class CGraphElementsCollection : public CBaseObj
  {
private:
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
//--- Return the flag indicating the graphical element object in the list of graphical objects
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
   //--- Return the full collection list 'as is'
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_all_graph_obj;   }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetList(),property,value,mode);  }
   //--- Return the number of new graphical objects
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   //--- Constructor
                     CGraphElementsCollection();
//--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
  }
//+------------------------------------------------------------------+

类结构类似于其它函数库对象的集合类结构。 这里唯一在意的是类构造函数 — 所有其他方法都与其它函数库对象集合中的方法相匹配。 我们稍后会实现它们。 目前,重要的是该类包含交互窗对象类文件,允许基于函数库的程序查看图形对象。 当前图表的类构造函数启用跟踪鼠标移动和鼠标滚轮滚动事件

一切就绪。 剩下的事情将在稍后处理 — 在创建所有函数库图形对象之后。

只剩下将图形对象集合类的文件包含到位于 \MQL5\Include\DoEasy\Engine.mqh 的 CEngine 函数库主对象文件当中:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "Collections\BuffersCollection.mqh"
#include "Collections\IndicatorsCollection.mqh"
#include "Collections\TickSeriesCollection.mqh"
#include "Collections\BookSeriesCollection.mqh"
#include "Collections\MQLSignalsCollection.mqh"
#include "Collections\ChartObjCollection.mqh"
#include "Collections\GraphElementsCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {

现在我们准备测试在柱线对象类里集成的图形对象。


测试

我们为当前品种和图表周期创建时间序列列表。 该列表存储柱线对象。 这些对象现在含有管理图形对象的类,允许为每根柱线创建单独的交互窗对象。

我们这样做:当按住 Ctrl 并将鼠标悬停在图表上时,将为鼠标光标所在的柱线创建一个具有阴影和柱线类型描述(看涨/看跌/十字星)的交互窗对象。 一旦我们按住 Ctrl 键,跟踪鼠标和滚轮来滚动图表的功能就会被禁用,并立即显示该柱线说明的交互窗。 释放 Ctrl 后,在新的价格变动到达、或移动图表时,清除所创建交互窗对象列表,因为测试不需要跟踪 Ctrl 键的保持/释放时刻。 甚至清除所创建对象列表也只是为了“隐藏”图表比例变化时出现的一些问题。 先前创建的交互窗对象在它们原有位置处显示,这意味着它们不再与变化后的图表当前蜡烛位置相对应。 为了测试速度,在图表比例变化时,清除列表比重新计算对象坐标更容易。

为了执行测试,我借用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part81\,命名 TestDoEasyPart81.mq5

在全局区域,而不是包含文件

#include <Arrays\ArrayObj.mqh>
#include <DoEasy\Services\Select.mqh>
#include <DoEasy\Objects\Graph\Form.mqh>

包含主函数库对象的文件,并声明其实例

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart81.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define        FORMS_TOTAL (4)   // Number of created forms
#define        START_X     (4)   // Initial X coordinate of the shape
#define        START_Y     (4)   // Initial Y coordinate of the shape
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CEngine        engine;
CArrayObj      list_forms;  
color          array_clr[];

//+------------------------------------------------------------------+

从 EA 的 OnInit() 处理程序中,删除创建交互窗对象的代码。 我们只需指定一个在函数库中所用的品种,并为当前品种和周期创建时间序列。 作为结果,处理程序如下所示:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'246,244,244';     // Original ≈pale gray
   array_clr[1]=C'249,251,250';     // Final ≈pale gray-green
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

从 EA 中删除 FigureType()FigureProcessing() 函数。 在此次测试中我们不需要它们,而它们几乎占据了整个 EA 代码量。

用三个函数替换它们。

该函数返回指示含有指定名称交互窗存在的标志:

//+-------------------------------------------------------------------------------+
//| Return the flag that indicates the existence of a form with a specified name  |
//+-------------------------------------------------------------------------------+
bool IsPresentForm(const string name)
  {
   //--- In the loop by the list of form objects,
   for(int i=0;i<list_forms.Total();i++)
     {
      //--- get the next form object
      CForm *form=list_forms.At(i);
      if(form==NULL)
         continue;
      //--- form the desired object name as "Program name_" + the form name passed to the function
      string nm=MQLInfoString(MQL_PROGRAM_NAME)+"_"+name;
      //--- If the current form object has such a name, return 'true'
      if(form.NameObj()==nm)
         return true;
     }
   //--- Upon the loop completion, return 'false'
   return false;
  }
//+------------------------------------------------------------------+

该函数隐藏除了指定名称的交互窗之外的所有交互窗:

//+------------------------------------------------------------------+
//| Hide all forms except the one with the specified name            |
//+------------------------------------------------------------------+
void HideFormAllExceptOne(const string name)
  {
   //--- In the loop by the list of form objects,
   for(int i=0;i<list_forms.Total();i++)
     {
      //--- get the next form object
      CForm *form=list_forms.At(i);
      if(form==NULL)
         continue;
      //--- form the desired object name as "Program name_" + the form name passed to the function
      string nm=MQLInfoString(MQL_PROGRAM_NAME)+"_"+name;
      //--- If the current form object has such a name, display it,
      if(form.NameObj()==nm)
         form.Show();
      //--- otherwise - hide
      else
         form.Hide();
     }
  }
//+------------------------------------------------------------------+

返回按住 Ctrl 标志的函数:

//+------------------------------------------------------------------+
//| Return the flag of holding Ctrl                                  |
//+------------------------------------------------------------------+
bool IsCtrlKeyPressed(void)
  {
   return((TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)&0x80)!=0);
  }
//+------------------------------------------------------------------+

所有的函数都很简单。 我相信,不需要解释。

OnChartEvent() 处理程序中删除击键和点击对象的处理。 我们在这里需要它。 添加鼠标移动处理:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If the mouse is moved
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      CForm *form=NULL;
      datetime time=0;
      double price=0;
      int wnd=0;
      
      //--- If Ctrl is not pressed,
      if(!IsCtrlKeyPressed())
        {
         //--- clear the list of created form objects and allow scrolling a chart with the mouse
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true);
         return;
        }
      
      //--- If X and Y chart coordinates are successfully converted into time and price,
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- get the bar index the cursor is hovered over
         int index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         if(index==WRONG_VALUE)
            return;
         
         //--- Get the bar index by index
         CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index);
         if(bar==NULL)
            return;
         
         //--- Convert the coordinates of a chart from the time/price representation of the bar object to the X and Y coordinates
         int x=(int)lparam,y=(int)dparam;
         if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y))
            return;
         
         //--- Disable moving a chart with the mouse
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false);
         
         //--- Create the form object name and hide all objects except one having such a name
         string name="FormBar_"+(string)index;
         HideFormAllExceptOne(name);
         
         //--- If the form object with such a name does not exist yet,
         if(!IsPresentForm(name))
           {
            //--- create a new form object
            form=bar.CreateForm(index,name,x,y,76,16);
            if(form==NULL)
               return;
            
            //--- Set activity and unmoveability flags for the form
            form.SetActive(true);
            form.SetMovable(false);
            //--- Set the opacity of 200
            form.SetOpacity(200);
            //--- The form background color is set as the first color from the color array
            form.SetColorBackground(array_clr[0]);
            //--- Form outlining frame color
            form.SetColorFrame(C'47,70,59');
            //--- Draw the shadow drawing flag
            form.SetShadow(true);
            //--- Calculate the shadow color as the chart background color converted to the monochrome one
            color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
            //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
            //--- Otherwise, use the color specified in the settings for drawing the shadow
            color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
            //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
            //--- Set the shadow opacity to 200, while the blur radius is equal to 4
            form.DrawShadow(2,2,clr,200,3);
            //--- Fill the form background with a vertical gradient
            form.Erase(array_clr,form.Opacity());
            //--- Draw an outlining rectangle at the edges of the form
            form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
            //--- If failed to add the form object to the list, remove the form and exit the handler
            if(!list_forms.Add(form))
              {
               delete form;
               return;
              }
            //--- Capture the form appearance
            form.Done();
           }
         //--- If the form object exists,
         if(form!=NULL)
           {
            //--- draw a text with the bar type description on it and show the form. The description corresponds to the mouse cursor position
            form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21');
            form.Show();
           }
         //--- Redraw the chart
         ChartRedraw();
        }
     }
  }
//+------------------------------------------------------------------+

处理程序代码在清单里含有详细的注释。 我希望,此处的一切都清晰明了。 如果您有任何疑问,请随时在评论中提问。

编译 EA,并在图表上启动它。 按住 Ctrl 并将鼠标移到图表上。 每根柱线都有其交互窗对象,包含柱线类型描述(看跌/看涨/十字星)。 释放 Ctrl 则删除所有已创建对象。



下一步是什么?

在下一篇文章中,我将继续把图形对象集成到函数库对象当中。

以下是该函数库当前版本的所有文件,以及 MQL5 的测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。

返回内容目录

*该系列的前几篇文章:

DoEasy 函数库中的图形(第七十三部分):图形元素的交互窗对象
DoEasy 函数库中的图形(第七十四部分):由 CCanvas 类提供强力支持的基本图形元素
DoEasy 函数库中的图形(第七十五部分):处理基本图形元素图元和文本的方法
DoEasy 函数库中的图形(第七十六部分):会话窗对象和预定义的颜色主题
DoEasy 函数库中的图形(第七十七部分):阴影对象类
DoEasy 函数库中的图形(第七十八部分):函数库中的动画原理 图像切片
DoEasy 函数库中的图形(第七十九部分):“动画框”对象类及其衍生对象
DoEasy 函数库中的图形(第八十部分):“几何动画框”对象类

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

附加的文件 |
MQL5.zip (4083.47 KB)
更好的程序员(第 02 部分):停止做这 5 件事变为一名成功的 MQL5 程序员 更好的程序员(第 02 部分):停止做这 5 件事变为一名成功的 MQL5 程序员
对于任何想要提高编程职业生涯的人来说,这是一篇必读文章。 本系列文章旨在尽最大可能令您成为最佳程序员,无论您有多少经验。 研讨的思路适用于 MQL5 编程萌新和专业人士。
DoEasy 函数库中的图形(第八十部分):“几何动画框”对象类 DoEasy 函数库中的图形(第八十部分):“几何动画框”对象类
在本文中,我将优化前几篇文章中的类代码,并创建几何动画框对象类,允许我们绘制给定顶点数的正多边形。
针对交易的组合数学和概率论(第二部分):通用分形 针对交易的组合数学和概率论(第二部分):通用分形
在本文中,我们将继续研究分形,并会特别留意总结所有材料。 为此,我将尝试把所有早期开发归纳为一个紧凑的形式,这对于交易中的实际应用来说将是方便和易于理解的。
DoEasy 函数库中的图形(第七十九部分):“动画框”对象类及其衍生对象 DoEasy 函数库中的图形(第七十九部分):“动画框”对象类及其衍生对象
在本文中,我将开发单个的动画框,及其衍生类。 该类允许绘制造型,同时维护,并恢复它们得下层背景。