English Русский 中文 Español 日本語 Português
Grafik in der Bibliothek DoEasy (Teil 81): Integration von Grafiken in Bibliotheksobjekt

Grafik in der Bibliothek DoEasy (Teil 81): Integration von Grafiken in Bibliotheksobjekt

MetaTrader 5Beispiele | 28 September 2021, 09:47
301 0
Artyom Trishkin
Artyom Trishkin

Inhalt

Konzept

Dieses Mal werde ich keine neuen grafischen Konstruktionen oder Verbesserungen implementieren. Stattdessen werde ich damit beginnen, die bereits erstellten grafischen Elementklassen in Bibliotheksobjekte zu integrieren. Dies ermöglicht eine weitere Entwicklung und Verbesserung der grafischen Elemente. Später werde ich das Verschieben von Objekten entlang eines Charts implementieren müssen, um die Gültigkeit von zusammengesetzten grafischen Objekten zu kontrollieren. Um dies zu erreichen, ist es notwendig, die Integration dieser Objekte in die Bibliotheksobjekte vorher zu überdenken, sodass wir in der Lage sind, die Klasse zur Verwaltung der grafischen Objekte sowie ihre Sammelklasse zu erstellen.

Die Klasse zur Verwaltung der grafischen Objekte soll Methoden zur Erstellung von Formularen und nachfolgenden grafischen Objekten enthalten, die den Zeiger auf das erstellte grafische Objekt zurückgeben sollen, damit man später damit arbeiten kann. Die Klasse für die Sammlung grafischer Elemente wird später benötigt, um die Listen aller erstellten grafischen Objekte zu erstellen, die mit verschiedenen Bibliotheksobjekten verbunden sind, sodass es möglich ist, Methoden für ihre Interaktion untereinander und mit den Programmbenutzern zu erstellen.

In diesem Artikel werde ich grafische Objekte nur in eines der Bibliotheksobjekte integrieren — das Bar-Objekt. Ich werde einige Zeit brauchen, um das erstellte Konzept zu debuggen. Dann werde ich es mit Hilfe des entwickelten und getesteten Mechanismus zu den übrigen Bibliotheksobjekten hinzufügen. Danach werde ich mich wieder an die Entwicklung der grafischen Bibliotheksobjekte machen.

Das aktuelle Konzept sieht folgendermaßen aus:

  • Wir haben das grafische Elementobjekt mit verschiedenen Zeichenmethoden;
  • wir haben das Bar-Objekt, das noch nichts über grafische Objekte weiß;
  • wir müssen eine Verwaltungsklasse für grafische Objekte erstellen, die es uns ermöglicht, diese zu erstellen und den Zeiger auf das erstellte Objekt zurückzugeben;
  • wir müssen diese Verwaltungsklasse zu einer der Komponenten des Balkenobjekts machen.

Diese vier Schritte ermöglichen es uns, ein beliebiges zuvor erstelltes Bibliotheksobjekt zu erhalten (derzeit ist dies das Balkenobjekt). Das Objekt zur Verwaltung von grafischen Objekten ermöglicht es uns, ein notwendiges Objekt zu erstellen und den Zeiger darauf zu erhalten, sodass es möglich ist, damit weiter zu arbeiten wie mit einem gewöhnlichen grafischen Bibliotheksobjekt, das ich in den vorherigen Artikeln besprochen habe.

Nach dem Debugging des Konzepts werde ich alles, was ich derzeit mit dem Balkenobjekt mache, hier auf alle Bibliotheksobjekte übertragen, um die grafische Komponente der Bibliothek zu integrieren. Alle Objekte werden eine neue "visuelle" Dimension erhalten, während wir ein neues Werkzeug für die Interaktion mit der Bibliothek bekommen werden.


Verbesserung der Klassenbibliothek

Jedes grafische Objekt, das von einem Bibliotheksobjekt erstellt wird, sollte dies kennen. Natürlich, wenn wir ein einziges Objekt haben, das in der Lage ist, für sich selbst grafische Objekte zu erstellen (derzeit ist dies ein Bar-Objekt), dann muss das neu erstellte grafische Objekt nicht wissen, welches Objekt es erstellt hat. Wenn aber jedes Bibliotheksobjekt in der Lage ist, für sich selbst grafische Objekte zu erstellen, dann sollten alle erstellten grafischen Objekte wissen, in welchem Objekt sie erstellt wurden, sodass es möglich ist, auf das Objekt zu verweisen, das dieses grafische Objekt erstellt hat, und Daten von ihm zu erhalten. Dies kann nützlich sein, um die Daten auf dem grafischen Objekt anzuzeigen oder um komplexere Beziehungen zwischen verschiedenen Objekten herzustellen.

Natürlich ist es unmöglich, dies alles in einem einzigen Artikel zu behandeln. Ich werde mit den einfachsten Dingen beginnen. Wir müssen die Beschreibung des Typs des Objekts kennen, aus dem das grafische Objekt erstellt wurde. Um dies zu erreichen, verwenden wir die ID der Objektsammlung (die ID der Sammlung, die einem Objekttyp entspricht, wird für jedes Objekt festgelegt). Anhand der ID können wir den Objekttyp bestimmen, zu dem das Bibliotheksobjekt (aus dem das grafische Objekt erstellt wurde) gehört. Natürlich reicht dies für die genaue Angabe eines bestimmten Objekts nicht aus. Aber wie ich schon sagte, ich beginne mit einfachen Dingen.

Außerdem benötigen wir die Methoden zur Anzeige von Objektbeschreibungen desselben Typs für alle zuvor erstellten Bibliotheksobjekte. Dies sind die Methoden Print() und PrintShort(), die die vollständigen und kurzen Beschreibungen der Objekteigenschaften anzeigen. Wir machen diese Methoden virtuell und deklarieren sie in der Elternklasse aller CBaseObj-Bibliotheksobjekte. Damit die Virtualisierung funktioniert, müssen wir die Argumente dieser Methoden in allen Klassen exakt gleich machen. Im Moment haben wir unterschiedliche Parameter für diese Methoden in verschiedenen Klassen. Es ist notwendig, sie in eine einheitliche Form zu bringen und die Methodenaufrufe entsprechend der geänderten Parameter in den Methodenargumenten zu korrigieren.

In der Klasse CBaseObj in \MQL5\Include\DoEasy\Objects\BaseObj.mqh, deklarieren wir die beiden virtuellen Methoden mit den notwendigen Parametern:

//--- 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

Die Parameter in den Methodenargumenten werden so ausgewählt, dass sie in allen Methoden verwendet werden können, die ich zuvor in den abgeleiteten Klassen implementiert habe.

Die Klasse COrder (die Basisklasse des gesamten Bestellsystems der Bibliothek) weist beispielsweise die folgenden Änderungen auf:

//--- 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);
//---
  };
//+------------------------------------------------------------------+

Hier habe ich ein weiteres Argument zur Methode Print() hinzugefügt und die Methode PrintShort() deklariert.

Bei der Implementierung der Methode außerhalb des Klassenkörpers habe ich auch das zusätzliche Argument der Methode hinzugefügt:

//+------------------------------------------------------------------+
//| 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");
  }
//+------------------------------------------------------------------+

Im Folgenden finden Sie ein Beispiel für die Verbesserung von Methodenaufrufen mit den in den Argumenten implementierten Parametern:

//+------------------------------------------------------------------+
//| 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);
     }
  }
//+------------------------------------------------------------------+

Bisher gab es hier einen einzigen Parameter und die Methode wurde als bookseries.Print(true) aufgerufen. Jetzt hat die Print()-Methode der Klasse CMBookSeries noch einen weiteren Parameter vor dem notwendigen. Deshalb übergeben wir zuerst false für den hinzugefügten Parameter, und dann übergeben wir true für den notwendigen (den, der vorher beim Aufruf der Methode vorhanden war).

Ähnliche Änderungen betreffen fast alle bisher geschriebenen Klassen der Bibliotheksobjekte, und sie wurden bereits in alle Klassen implementiert, die diese Methoden und die vom Basisobjekt aller Bibliotheksobjekte geerbten Methoden enthalten: 

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.

In einigen Klassen der Bibliothek wurden Nachrichten durch die Standardfunktion Print() ersetzt, indem Meldungen mit der Methode ToLog() der Klasse CMessage angezeigt werden, wie zum Beispiel in der folgenden Methode der Klasse der Ereigniskollektion:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die folgende Zeichenfolge wurde zuvor zur Anzeige einer Meldung verwendet:

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

Die Liste der Dateien, die solche Bearbeitungen aufweisen:

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

Die Änderungen finden Sie in den angehängten Dateien.

Wenn der Chart das bereits erstellte Formularobjekt enthält, kann er durch Angabe der Anzeigeflags für bestimmte Zeitrahmen ein- oder ausgeblendet werden. Das werden wir nutzen, um das Objekt mit der Methode BringToTop() vor allen anderen in den Vordergrund zu "schieben".
Wir haben keine Methoden zum Ein- und Ausblenden von grafischen Objekten.
Wir erstellen zwei virtuelle Methoden in der grafischen Elementklasse CGCnvElement in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:

//--- 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);                                         }

Die Methoden setzen einfach die entsprechenden Flags für die Anzeige des Objekts in allen Zeitfenstern im Basisobjekt der grafischen Objekte der Bibliothek.

Die Formularobjektklasse CForm ist vom grafischen Elementobjekts abgeleitet, und das Formularobjekt kann aus mehreren grafischen Elementobjekten bestehen. Daher benötigen wir eine separate Implementierung dieser Methoden für sie.
Wir öffnen \MQL5\Include\DoEasy\Objects\Graph\Form.mqh und deklarieren zwei virtuelle Methoden im öffentlichen Abschnitt:

//+------------------------------------------------------------------+
//| 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                |
//+------------------------------------------------------------------+

Schreiben wir ihre Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| 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();
  }
//+------------------------------------------------------------------+

Beide Methoden sind im Code-Listing ausführlich kommentiert. Kurz gesagt, beim Ausblenden von Objekten gibt es keinen großen Unterschied in der Reihenfolge, in der sie ausgeblendet werden. Beim Einblenden von Objekten ist es jedoch wichtig, die gesamte Reihenfolge der Position aller gebundenen Objekte auf dem Hauptformular wiederherzustellen. Daher erfolgt die Anzeige schichtweise — zunächst wird das unterste Objekt angezeigt (der Formularschatten). Das Hauptformular wird dann oberhalb des Schattens angezeigt. Danach werden alle grafischen Elemente angezeigt, die an das Hauptformular gebunden sind. Bei dieser Implementierung entspricht die Anzeigereihenfolge der Reihenfolge, in der sie in die Liste der gebundenen Objekte aufgenommen werden.
Der Algorithmus ist bei der Erstellung komplexer (zusammengesetzter) Formularobjekte zu überprüfen.

Nun ist es an der Zeit, die Integration der grafischen Objekte in die Bibliotheksobjekte zu beginnen.

Grafische Objektverwaltungsklasse

Wie können wir also jedes Bibliotheksobjekt mit der Fähigkeit ausstatten, grafische Objekte für sich selbst zu erstellen?

Die meisten Bibliotheksobjekte sind von dem Basisobjekt aller Bibliotheksobjekte CBaseObj abgeleitet. Wenn wir eine Instanz der Klasse hinzufügen, die die Fähigkeit hat, alle möglichen grafischen Objekte (sowohl verfügbare als auch für die weitere Entwicklung geplante) zu erstellen und den Zugriff auf den Zeiger auf das erstellte Objekt zu ermöglichen, dann werden alle ihre Nachkommen in der Lage sein, mit grafischen Objekten zu arbeiten.

Da wir eine große Anzahl von verschiedenen grafischen Objekten haben werden, brauchen wir eine Klasse, die jedes dieser Objekte "kennt" und in der Lage ist, sie zu erstellen und zu verwalten. Dies wird die Klasse für die Verwaltung grafischer Objekte sein.

In \MQL5\Include\DoEasy\Objects\Graph\ erstellen wir eine neue Datei GraphElmControl.mqh für die Klasse CGraphElmControl. Die Klasse sollte von der Basisklasse abgeleitet sein, um mit CObject eine MQL5-Standardbibliothek zu erstellen. Dem Klassenverzeichnis müssen drei Dateien beigefügt werden — die Datei der Klasse des dynamischen Arrays von Zeigern auf CObject-Klasseninstanzen und deren Nachkommen, die Service-Funktionsdatei und die Datei der Formularobjektklasse:

//+------------------------------------------------------------------+
//|                                              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);
  };
//+------------------------------------------------------------------+

Die Variable m_type_node speichert den Typ des Objekts, das das Klassenobjekt enthält. Bei der Erstellung eines neuen Objekts (derzeit ist es ein Bar-Objekt), ruft sein Konstruktor die SetTypeNode()-Methode auf und erhält den Typ des Bar-Objekts in seiner Variablen m_type (im Falle einer Bar ist es die ID der Kollektion des Bar-Objekts). Auf diese Weise weiß das Objekt, das die grafischen Objekte verwaltet, von der Klasse, die seine Objekte erstellt. Für den Moment werde ich nur die Kollektions-ID verwenden. In Zukunft werde ich besprechen, den Zeiger auf das Objekt zu übergeben, aus dem die Grafik aufgebaut ist.

Besprechen wir nun die Methoden der Klasse.

Im parametrischen Konstruktor der Klasse, setzen wir den in den Methodenargumenten übergebenen Objekttyp auf die Variable m_type_node:

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


Die Methode, die das Formularobjekt auf einem bestimmten Chart in einem bestimmten Teilfenster erstellt:

//+----------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die Methode erhält eine eindeutige ID des erstellten Formularobjekts, die Chart-ID, den Index des Chart-Subfensters, den Namen des Formularobjekts, die X/Y-Koordinaten für die Erstellung des Formulars sowie die Breite und Höhe des Formulars.
Anschließend erzeugen wir ein neues Formularobjekt mit den an die Methode übergebenen Parametern. Wenn es erfolgreich erstellt wurde, setze die Formular-ID und den Index in der Objektliste für das Objekt (momentan ist er Null, da das Objekt keine anderen gebundenen Formularobjekte enthält und das Hauptformularobjekt ist). Rückgabe des Zeigers auf ein neu erstelltes Objekt.

Die Methode erstellt das Formularobjekt auf dem aktuellen Chart in einem angegebenen Unterfenster:

//+----------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Die Methode erhält eine eindeutige ID des erstellten Formularobjekts, den Index des Chart-Subfensters, den Namen des Formularobjekts, die X/Y-Koordinaten für die Erstellung des Formulars sowie die Breite und Höhe des Formulars. Die Methode gibt das Operationsergebnis des obigen Formulars für den Aufruf der Methode zurück mit einer expliziten Angabe der aktuellen Chart ID.

Die Methode erstellt das Formularobjekt auf dem aktuellen Chart im Chart-Hauptfenster:

//+----------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Die Methode erhält eine eindeutige ID des erstellten Formularobjekts, den Namen des Formularobjekts, die X/Y-Koordinaten für die Erstellung des Formulars sowie die Breite und Höhe des Formulars. Die Methode gibt das Operationsergebnis der ersten Form des Methodenaufrufs zurück mit einer expliziten Angabe der aktuellen Chart ID und dem Index des Chart Hauptfensters.

Mehr brauchen wir hier nicht, um Formularobjekte zu erstellen. Die gesamte Arbeit zur Erstellung verschiedener Animationen auf dem erstellten Formularobjekt wird durch den Zeiger auf das Objekt ausgeführt. Der Zeiger wird von den oben besprochenen Methoden zurückgegeben.

Wir sind bereit, mit der Integration von Grafiken in alle Bibliotheksobjekte zu beginnen, die Nachkommen des Basisobjekts aller Bibliotheksobjekte CBaseObj sind.


Integration von Grafiken in die Bibliothek

Jedes Bibliotheksobjekt muss also die Klassen der grafischen Objekte "sehen" und in der Lage sein, diese Objekte für sich selbst zu erstellen. Um dies zu erreichen, müssen wir einfach die Instanz der Klasse für die Verwaltung grafischer Objekte innerhalb der Klasse des Basisobjekts aller Bibliotheksobjekte deklarieren. Alle seine Nachkommen werden sofort mit der Fähigkeit ausgestattet, Grafiken zu erstellen, indem sie über die Instanz der Klasse CGraphElmControl arbeiten, die ich gerade besprochen habe.

Wir öffnen \MQL5\Include\DoEasy\Objects\BaseObj.mqh und binden die Datei der grafischen Objektverwaltungsklasse ein:

//+------------------------------------------------------------------+
//|                                                      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"
//+------------------------------------------------------------------+

Im geschützten Abschnitt der Klasse CBaseObj deklarieren wir eine Instanz der Klasse für die Verwaltung grafischer Objekte:

//+------------------------------------------------------------------+
//| 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:

Die Methoden zur Erstellung eines Formularobjekts schreiben wir in den öffentlichen Teil der Klasse:

//--- 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

Die Methoden vereinfachen das Operationsergebnis der drei gleichnamigen, oben besprochenen Methoden der grafischen Objektverwaltungsklasse.

Nun ist jedes der Nachfolgeobjekte der Klasse CBaseObj in der Lage, beim Aufruf der Methoden ein Formularobjekt zu erzeugen.

Heute werde ich den Umgang mit grafischen Objekten anhand der Objektklasse "Bar" überprüfen.
Wir öffnen die Datei der Klasse \MQL5\Include\DoEasy\Objects\Series\Bar.mqh und fügen die Übergabe des Typs Bar-Objekt an die Klasse zur Verwaltung grafischer Objekte in der Methode SetProperties() hinzu:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Fast alles ist bereit für den Test. Aber es gibt einen Vorbehalt. Als ich anfing, mit grafischen Objekten zu arbeiten, habe ich sie nicht in die Hauptbibliothek aufgenommen, indem ich einfach grafische Elementklassen "wie sie sind" verwendet habe. Jetzt müssen wir alles richtig machen — alle Bibliotheksobjekte sind über ihr Hauptobjekt zugänglich — die Klasse CEngine, mit der die Sammlungsdateien aller Objekte verbunden sind. Aber die grafischen Objekte besitzen noch keine Klasse ihrer Kollektion, da noch nicht alle Objekte erstellt sind. Aber wir sind in der Lage, eine vorläufige Kollektionsklasse von grafischen Objekten zu erstellen. Nachdem ich alle Objekte erstellt habe, werde ich darauf zurückkommen.

Mit diesen Überlegungen im Hinterkopf werde ich die vorläufige Version der Klasse für die Sammlung grafischer Objekte erstellen. Wir müssen die ID der grafischen Kollektionsliste der Grafikobjekte in \MQL5\Include\DoEasy\Defines.mqh festlegen:

//--- 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

In Bibliotheksverzeichnis \MQL5\Include\DoEasy\Collections\ erstellen wir die neue Datei GraphElementsCollection.mqh der Klasse CGraphElementsCollection:

//+------------------------------------------------------------------+
//|                                      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);
  }
//+------------------------------------------------------------------+

Die Klassenstruktur ähnelt der Struktur der Kollektionsklasse anderer Bibliotheksobjekte. Das Einzige, was hier wichtig ist, ist der Klassenkonstruktor — alle anderen Methoden entsprechen denen in anderen Bibliotheksobjektsammlungen. Wir werden sie später implementieren. Zur Zeit ist es wichtig, dass die Klasse die Klassendatei des Formularobjekts enthält, die es bibliotheksbasierten Programmen ermöglicht, grafische Objekte zu sehen. Der Klassenkonstruktor für das aktuelle Chart ermöglicht die Verfolgung von Mausbewegungen und Mausrad-Scroll-Events.

Alles ist eingestellt. Die restlichen Dinge werden später erledigt — nachdem alle grafischen Objekte der Bibliothek erstellt wurden.

Es bleibt nur noch die Datei der grafischen Objektsammelklasse in die Hauptobjektdatei der CEngine-Bibliothek einzubinden, die sich in \MQL5\Include\DoEasy\Engine.mqh befindet:

//+------------------------------------------------------------------+
//|                                                       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
  {

Jetzt sind wir bereit, die in die Klasse Bar-Objekt integrierten grafischen Objekte zu testen.


Test

Erstellen wir die Zeitreihenliste für das aktuelle Symbol und die Chart-Periode. In der Liste werden Bar-Objekte gespeichert. Diese Objekte verfügen nun über die Klasse zur Verwaltung von grafischen Objekten, die es ermöglicht, für jeden Balken ein eigenes Formularobjekt zu erstellen.

Wir gehen folgendermaßen vor: Wenn Sie die Strg-Taste gedrückt halten und mit der Maus über den Chart fahren, wird für den Balken, auf dem sich der Mauszeiger befindet, ein Bar-Objekt mit einem Schatten und einer Beschreibung des Balkentyps (bullish/bearish/doji) erstellt. Sobald wir die Strg-Taste gedrückt halten, wird die Möglichkeit, den Chart mit der Maus und dem Mausrad zu scrollen, deaktiviert und das Formular mit der Balkenbeschreibung wird sofort angezeigt. Nach dem Loslassen der Strg-Taste wird die Liste der erstellten Formularobjekte beim Eintreffen eines neuen Ticks oder beim Verschieben des Charts gelöscht, da die Verfolgung der Momente des Haltens und Loslassens der Strg-Taste für den Test nicht erforderlich ist. Selbst das Löschen der Liste der erstellten Objekte ist nur notwendig, um einige Probleme zu "verstecken", die beim Ändern der Chart-Skala auftreten. Die zuvor erstellten Formularobjekte werden an ihrem alten Platz angezeigt, d.h. sie entsprechen nicht mehr der aktuellen Kerzenposition auf der geänderten Chart-Skala. Um die Geschwindigkeit zu testen, ist es einfacher, die Liste zu löschen, als die Objektkoordinaten beim Wechsel des Chart-Maßstabs neu zu berechnen.

Um zu testen werde ich den EA aus dem vorherigen Artikel verwenden und ihn in \MQL5\Experts\TestDoEasy\Part81\ als TestDoEasyPart81.mq5 speichern.

In dem globalen Bereich, statt die Dateien einzubinden,

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

binden wir die Datei der Hauptbibliothek des Objektes ein und deklarieren die Instanzen:

//+------------------------------------------------------------------+
//|                                             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[];

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

Wir löschen in der Funktion OnInit() des EA den Code, der Formularobjekte erstellt. Wir müssen nur ein verwendetes Symbol in der Bibliothek angeben und die Zeitreihe für das aktuelle Symbol und den Zeitraum erstellen. Im Ergebnis sieht die Funktion wie folgt aus:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Wir entfernen die Funktionen FigureType() und FigureProcessing() aus dem EA. Wir brauchen sie für diesen Test nicht, während sie fast das gesamte EA-Codevolumen einnehmen.

Wir ersetzen sie durch drei Funktionen.

Die Funktion, die das Flag zurückgibt, das das Vorhandensein eines Formulars mit einem bestimmten Namen anzeigt:

//+-------------------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die Funktion blendet alle Formulare außer demjenigen mit dem angegebenen Namen aus:

//+------------------------------------------------------------------+
//| 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();
     }
  }
//+------------------------------------------------------------------+

Die Funktion, die das Flag für das Halten der Strg-Taste zurückgibt:

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

Alle Funktionen sind recht einfach. Ich glaube, sie benötigen keine Erklärungen.

Entfernen wir noch die Behandlung von Tastenanschlägen und Objektklicks aus der Ereignisbehandlung durch OnChartEvent(). Wir werden sie hier brauchen. Ergänzung der Behandlung der Mausbewegungen:

//+------------------------------------------------------------------+
//| 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();
        }
     }
  }
//+------------------------------------------------------------------+

Der Code dieser Ereignisbehandlung wird im Listing ausführlich kommentiert. Ich hoffe, dort ist alles klar. Wenn Sie Fragen haben, können Sie diese gerne in den Kommentaren stellen.

Kompilieren Sie den EA und starten Sie ihn auf dem Chart. Halten Sie die Strg-Taste gedrückt und bewegen Sie die Maus über den Chart. Für jeden Balken wird ein eigenes Formularobjekt erstellt, das die Beschreibung des Balkentyps (bearish/bullish/doji) enthält. Wenn Sie die Strg-Taste loslassen, werden alle erstellten Objekte entfernt.



Was kommt als Nächstes?

Im nächsten Artikel werde ich mit der Integration von grafischen Objekten in die Bibliotheksobjekte fortfahren.

Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit der Test-EA-Datei für MQL5 zum Testen und Herunterladen angehängt.
Ihre Fragen und Vorschläge schreiben Sie bitte in den Kommentarteil.

Zurück zum Inhalt

*Frühere Artikel dieser Serie:

Grafiken in der Bibliothek DoEasy (Teil 73): Das Formularobjekt eines grafischen Elements
Grafiken in der Bibliothek DoEasy (Teil 74): Das grafisches Basiselement, das von der Klasse CCanvas unterstützt wird
Grafiken in der Bibliothek DoEasy (Teil 75): Methoden zur Handhabung von Primitiven und Text im grafischen Grundelement
Grafiken in der Bibliothek DoEasy (Teil 76): Das Formularobjekt und vordefinierte Farbschemata
Grafik in der Bibliothek DoEasy (Teil 77): Objektklasse der Schatten
Grafik in der Bibliothek DoEasy (Teil 78): Animationsprinzipien in der Bibliothek. Schneiden von Bildern
Grafik in der Bibliothek DoEasy (Teil 79): Die Objektklasse "Animationsrahmen" und ihre abgeleiteten Objekte
Grafik in der Bibliothek DoEasy (Teil 80): Die Objektklasse "Geometrischer Animationsrahmen"

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/9751

Beigefügte Dateien |
MQL5.zip (4087.48 KB)
Kombinatorik und Wahrscheinlichkeitsrechnung für den Handel (Teil II): Das universelle Fraktal Kombinatorik und Wahrscheinlichkeitsrechnung für den Handel (Teil II): Das universelle Fraktal
In diesem Artikel werden wir das Studium der Fraktale fortsetzen und besonderes Augenmerk auf die Zusammenfassung des gesamten Materials legen. Zu diesem Zweck werde ich versuchen, alle früheren Entwicklungen in eine kompakte Form zu bringen, die für die praktische Anwendung im Handel geeignet und verständlich ist.
Grafik in der Bibliothek DoEasy (Teil 80): Die Objektklasse "Geometrischer Animationsrahmen" Grafik in der Bibliothek DoEasy (Teil 80): Die Objektklasse "Geometrischer Animationsrahmen"
In diesem Artikel werde ich den Code der Klassen aus den vorhergehenden Artikeln optimieren und die geometrische Animationsrahmen-Objektklasse erstellen, die es uns ermöglicht, regelmäßige Polygone mit einer bestimmten Anzahl von Scheitelpunkten zu zeichnen.
Grafiken in der Bibliothek DoEasy (Teil 82): Die Umgestaltung von Bibliotheksobjekten und Kollektion von grafischen Objekten Grafiken in der Bibliothek DoEasy (Teil 82): Die Umgestaltung von Bibliotheksobjekten und Kollektion von grafischen Objekten
In diesem Artikel werde ich alle Bibliotheksobjekte verbessern, indem ich jedem Objekt einen eindeutigen Typ zuordne und die Entwicklung der Klasse der grafischen Bibliotheksobjekte Kollektion fortsetze.
Erkunden der Möglichkeiten mehrfarbige Kerzen zu erstellen Erkunden der Möglichkeiten mehrfarbige Kerzen zu erstellen
In diesem Artikel gehe ich auf die Möglichkeiten der Erstellung von individuellen Indikatoren mit Kerzen ein und zeige deren Vor- und Nachteile auf.