English Русский 中文 Español 日本語 Português
Grafiken in der DoEasy-Bibliothek (Teil 95): Steuerelemente für zusammengesetzte grafische Objekte

Grafiken in der DoEasy-Bibliothek (Teil 95): Steuerelemente für zusammengesetzte grafische Objekte

MetaTrader 5Beispiele | 29 März 2022, 12:49
159 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

In diesem Artikel werde ich die Entwicklung von zusammengesetzten grafischen Objekten fortsetzen. Dabei handelt es sich um Standard-Grafikobjekte, die aus mehreren Objekten bestehen und zu einem einzigen Grafikobjekt zusammengefasst werden. In der Bibliothek werden grafische Objekte, die in einem zusammengesetzten Objekt enthalten sind, als erweiterte Standard-Grafikobjekte definiert. Solche Objekte haben einige zusätzliche Eigenschaften und Funktionen, die es ihnen ermöglichen, andere grafische Objekte einzubinden und zu integrieren.
Das Konzept eines zusammengesetzten grafischen Objekts erfordert die Funktionsweise, das Objekt an dem Punkt zu halten, an dem es mit einem anderen Objekt verbunden ist, und seine Position anzupassen, wenn ein übergeordnetes Objekt geändert oder verschoben wird.
Im vorangegangenen Artikel habe ich mit der Erstellung der Handler für Ereignisse für zusammengesetzte grafische Objekte begonnen, die Handhabung des Entfernens eines zusammengesetzten grafischen Objekts implementiert und mit der Entwicklung des Handlers für die Verschiebung des Objekts begonnen.

Heute werde ich ein wenig vom Verschieben eines zusammengesetzten grafischen Objekts abweichen und den Handler für Änderungsereignisse in einem Chart mit einem zusammengesetzten grafischen Objekt implementieren. Außerdem werde ich mich auf die Steuerelemente für die Verwaltung eines zusammengesetzten grafischen Objekts konzentrieren.
Warum wohl? Ich werde die Echtzeit-Erstellung von zusammengesetzten grafischen Objekten implementieren, indem ich ein untergeordnetes Objekt an ein Basisobjekt anhänge, indem ich das untergeordnete Objekt auf das Basisobjekt ziehe. Das grafische Basisobjekt erkennt, wenn ein anderes Objekt mit der Maus auf es gezogen wird. Der Mechanismus zum Anhängen von Objekten wird in einem bestimmten Abstand von einem seiner Chart-Ankerpunkte aktiviert. Die Linien, die den Ankerpunkt des angehängten Objekts mit dem Ankerpunkt des Basisobjekts verbinden, werden visuell angezeigt, um darauf hinzuweisen, dass das gezogene Objekt bereit ist, an das Basisobjekt angehängt zu werden. Um dies zu erreichen, sollte jeder Ankerpunkt des grafischen Objekts ein Formularobjekt einer bestimmten Größe enthalten. Die Linien, die anzeigen, dass die Objekte zur Interaktion bereit sind, werden auf dem Formular selbst angezeigt. Solche Formulare sind unsichtbar an jedem Drehpunkt des grafischen Objekts vorhanden. Die Größe des Bereichs ist nur zu Debugging-Zwecken sichtbar, indem das Zeichnen eines Rechtecks entlang der Formularränder aktiviert wird:

Außerdem zeigt das Formular die Ankerpunkte des grafischen Objekts an, die nur angezeigt werden, wenn der Mauszeiger über den aktiven Bereich des Formulars bewegt wird. Auf diese Weise können wir das erweiterte grafische Objekt verschieben und verändern, indem wir den Mauszeiger über den Formularbereich bewegen, anstatt es durch einen Mausklick zu markieren. Sobald wir den Mauszeiger über den aktiven Bereich des Formulars bewegen (in der Abbildung oben durch Rechtecke gekennzeichnet), erscheinen die Beschriftungen im Ankerpunkt des grafischen Objekts (der blaue Punkt in der Mitte des Kreises). Wenn wir beginnen, das Formular mit der Maus zu ziehen, wird der entsprechende Drehpunkt des grafischen Objekts dem Cursor folgen und das Objekt selbst zusammen mit dem zusammengesetzten grafischen Objekt ändern.
Wenn der Mauszeiger bei gedrückter Maustaste in den aktiven Bereich des Formulars eintritt, bedeutet dies (sofern verifiziert), dass wir ein weiteres grafisches Objekt auf das Formular anwenden und damit den Mechanismus der Bindung eines Objekts an ein anderes aktivieren. Die Formulare ermöglichen es uns also, mehrere Ziele auf einmal zu erreichen.

Ich werde das Anhängen eines Objekts an ein anderes hier nicht implementieren, da die Vorbereitungen noch nicht abgeschlossen sind. Stattdessen werde ich die Formulare erstellen, sie an die Ankerpunkte des grafischen Objekts anhängen und den Mechanismus implementieren, mit dem sie entlang der Koordinaten des Objektdrehpunkts verschoben werden können, wenn das Chart verändert wird, d. h. wenn es verschoben oder sein Anzeigemaßstab geändert wird. Dies sollte möglich sein, da das Formularobjekt die Koordinaten in Bildschirmpixeln hat, während die meisten grafischen Objekte in Zeit-/Preiswerten angezeigt werden.


Verbesserung der Klassenbibliothek

In \MQL5\Include\DoEasy\Data.mqh fügen wir die neue Nachrichtenindizes hinzu:

//--- CLinkedPivotPoint
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X,                // Not a single pivot point is set for the object along the X axis
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y,                // Not a single pivot point is set for the object along the Y axis
   MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE,             // The object is not attached to the basic graphical object
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ,       // Failed to create a data object for the X and Y pivot points
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X,            // Number of base object pivot points for calculating the X coordinate: 
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y,            // Number of base object pivot points for calculating the Y coordinate: 

//--- CGStdGraphObjExtToolkit
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA,     // Failed to change the size of the pivot point time data array
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA,    // Failed to change the size of the pivot point price data array
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM,   // Failed to create a form object to manage a pivot point
   
  };
//+------------------------------------------------------------------+

und die Nachrichtentexte entsprechend den neu hinzugefügten Indizes:

//--- CLinkedPivotPoint
   {"Для объекта не установлено ни одной опорной точки по оси X","The object does not have any pivot points set along the x-axis"},
   {"Для объекта не установлено ни одной опорной точки по оси Y","The object does not have any pivot points set along the y-axis"},
   {"Объект не привязан к базовому графическому объекту","The object is not attached to the base graphical object"},
   {"Не удалось создать объект данных опорной точки X и Y.","Failed to create X and Y reference point data object"},
   {"Количество опорных точек базового объекта для расчёта координаты X: ","Number of reference points of the base object to set the X coordinate: "},
   {"Количество опорных точек базового объекта для расчёта координаты Y: ","Number of reference points of the base object to set the Y coordinate: "},
   
//--- CGStdGraphObjExtToolkit
   {"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"},
   {"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"},
   {"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"},
   
  };
//+---------------------------------------------------------------------+


Wir ersetzen die Makrosubstitution in \MQL5\Include\DoEasy\Defines.mqh

#define CLR_DEFAULT                    (0xFF000000)               // Default symbol background color in the navigator

mit besser verständlichen

#define CLR_MW_DEFAULT                 (0xFF000000)               // Default symbol background color in the Market Watch

und die Makro-Substitution

#define NULL_COLOR                     (0x00FFFFFF)               // Zero for the canvas with the alpha channel

mit besser verständlichen

#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel

und wir fügen die neuen Makrosubstitutionen zum Setzen der Standardwerte für die hier zu erstellenden Formularobjekte hinzu:

//--- Graphical object parameters
#define PROGRAM_OBJ_MAX_ID             (10000)                    // Maximum value of an ID of a graphical object belonging to a program
#define CTRL_POINT_SIZE                (5)                        // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_FORM_SIZE                 (40)                       // Size of the control point form for managing graphical object pivot points
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+


Wir ersetzen die alten Makro-Ersatznamen in allen Dateien.

Drücken Sie einfach Strg+Umschalt+H, geben Sie die folgenden Werte ein und markieren Sie die unten stehenden Kästchen:


Klicken Sie auf "Ersetzen in Dateien". Der Editor nimmt Ersetzungen in allen Bibliotheksdateien vor.

NULL_COLOR wird auf die gleiche Weise durch CLR_CANV_NULL ersetzt.

Die neuen Makro-Ersatznamen sind anschaulicher und weisen auf ihre Funktion hin, wodurch sich die Anzahl möglicher Fehler verringert (ich habe z. B. CLR_DEFAULT eingegeben, um einen transparenten Leinwandhintergrund einzustellen, und viel Zeit damit verbracht, zu verstehen, warum es nicht funktionierte).

Außerdem habe ich einige kleine Änderungen in allen Klassendateien der grafischen Basisobjekte vorgenommen (durch einfaches Hinzufügen eines Kommas in den Text der Methode, die eine kurze Objektbeschreibung im Journal anzeigt):

//+------------------------------------------------------------------+
//| Display a short description of the object in the journal         |
//+------------------------------------------------------------------+
void CGStdArrowBuyObj::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print
     (
      (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(string)this.GetProperty(GRAPH_OBJ_PROP_ID,0),
      ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
     );
  }
//+------------------------------------------------------------------+

Dies ist eine rein "kosmetische" Verbesserung.
Sie wurde in allen Dateien in \MQL5\Include\DoEasy\Objects\Graph\Standard\.
implementiert.


Erweiterte Hilfsklasse für Standard-Grafikobjekt

Beginnen wir mit der Erstellung eines Toolkits für den Umgang mit erweiterten grafischen Objekten. Dies wird eine Klasse sein, die alle notwendigen Methoden zur Erstellung von Formularobjekten und zur Arbeit mit ihnen enthält. Jedes erweiterte grafische Objekt soll einen Zeiger auf das Objekt der entsprechenden Klasse haben. Gegebenenfalls (wenn es sich um ein Basisobjekt innerhalb eines zusammengesetzten grafischen Objekts handelt) wird das Klassenobjekt beim Erstellen eines erweiterten Objekts dynamisch erzeugt und beim Löschen des letzteren gelöscht.

Das Objekt erhält die notwendigen Parameter des grafischen Basisobjekts (seine Koordinaten, Typ, Name usw.). Die Koordinaten des Basisobjekts werden verfolgt und die Koordinaten des Formularobjekts werden innerhalb des Objekts angepasst. Letztendlich werden die Formularobjekte die Verwaltung des Basisobjekts ermöglichen.

Aber das Wichtigste zuerst...

In \MQL5\Include\DoEasy\Objects\Graph\ erstellen wir den neuen Ordner Extend\, der die Datei CGStdGraphObjExtToolkit. mqh der Klasse CGStdGraphObjExtToolkit abgeleitet von der Basisklasse CObject zum Aufbau der MQL5 Standardbibliothek:

//+------------------------------------------------------------------+
//|                                      CGStdGraphObjExtToolkit.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
  }

Die Formularobjekt Klassendatei sollte in die Klassendatei aufgenommen werden.

Im privaten Teil der Klasse deklarieren wir die Variablen, die alle notwendigen Eigenschaften des Basisobjekts, die Eigenschaften für die Konstruktion von Formularen, die Liste für deren Speicherung und die Methoden für die Erstellung eines Formularobjekts und die Rückgabe seiner Bildschirmkoordinaten speichern:

//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
private:
   long              m_base_chart_id;           // Base graphical object chart ID
   int               m_base_subwindow;          // Base graphical object chart subwindow
   ENUM_OBJECT       m_base_type;               // Base object type
   string            m_base_name;               // Base object name
   int               m_base_pivots;             // Number of base object reference points
   datetime          m_base_time[];             // Time array of base object reference points
   double            m_base_price[];            // Price array of base object reference points
   int               m_base_x;                  // Base object X coordinate
   int               m_base_y;                  // Base object Y coordinate
   int               m_ctrl_form_size;          // Size of forms for managing reference points
   int               m_shift;                   // Shift coordinates for adjusting the form location
   CArrayObj         m_list_forms;              // List of form objects for managing reference points
//--- Create a form object on a base object reference point
   CForm            *CreateNewControlPointForm(const int index);
//--- Return X and Y screen coordinates of the specified reference point of the graphical object
   bool              GetControlPointCoordXY(const int index,int &x,int &y);
public:

Wir verwenden die Arrays, um Preis- und Zeitkoordinaten zu speichern, da ein einzelnes grafisches Objekt mehrere Pivotpunkte haben kann. Die Koordinaten jedes Punktes werden also in den entsprechenden Array-Zellen gespeichert. Die Koordinate des ersten Punktes — durch den Index 0 des Arrays, die Koordinate des zweiten Punktes — durch den Index 1 des Arrays, die Koordinate des dritten Punktes — durch den Index 2, usw.
Die Verschiebung der Formularkoordinaten ermöglicht es uns, das Formular genau in der Mitte des Objektdrehpunkts zu positionieren. Diese Verschiebung deckt die Hälfte der Punktgröße ab. Wenn die Größe des Formulars ein Vielfaches von zwei ist, z. B. 10, wird sie durch Hinzufügen von 1, d. h. 11, angepasst. Auf diese Weise kann das Formular genau in der Mitte des Drehpunkts des grafischen Objekts platziert werden, so dass keine Seite eine andere auch nur um ein einziges Pixel überragt.
Die Liste der Formulare speichert alle erstellten Formulare. Der Zugriff auf sie wird durch den Zeiger gewährt. Die Methode zur Berechnung der Bildschirmkoordinaten ermöglicht es uns, die Koordinaten des Bildschirms zu kennen, auf dem das Formular platziert werden soll. Dies ermöglicht seine genaue Platzierung auf dem Drehpunkt des grafischen Objekts.

Im öffentlichen Teil der Klasse deklarieren wir alle notwendigen Methoden für die Handhabung der Klasse:

public:
//--- Set the parameters of the base object of a composite graphical object
   void              SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                const long base_chart_id,const int base_subwindow,
                                const int base_pivots,const int ctrl_form_size,
                                const int base_x,const int base_y,
                                const datetime &base_time[],const double &base_price[]);
//--- Set the base object (1) time, (2) price, (3) time and price coordinates
   void              SetBaseObjTime(const datetime time,const int index);
   void              SetBaseObjPrice(const double price,const int index);
   void              SetBaseObjTimePrice(const datetime time,const double price,const int index);
//--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates
   void              SetBaseObjCoordX(const int value)                        { this.m_base_x=value;                          }
   void              SetBaseObjCoordY(const int value)                        { this.m_base_y=value;                          }
   void              SetBaseObjCoordXY(const int value_x,const int value_y)   { this.m_base_x=value_x; this.m_base_y=value_y; }
//--- (1) Set and (2) return the size of the form of pivot point management control points
   void              SetControlFormSize(const int size);
   int               GetControlFormSize(void)                          const { return this.m_ctrl_form_size;                }
//--- Return the pointer to the pivot point form by (1) index and (2) name
   CForm            *GetControlPointForm(const int index)                     { return this.m_list_forms.At(index);           }
   CForm            *GetControlPointForm(const string name,int &index);
//--- Return the number of the base object pivot points
   int               GetNumPivotsBaseObj(void)                          const { return this.m_base_pivots;                    }
//--- Create form objects on the base object pivot points
   bool              CreateAllControlPointForm(void);
//--- Remove all form objects from the list
   void              DeleteAllControlPointForm(void);
   
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructor/destructor
                     CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name,
                                             const long base_chart_id,const int base_subwindow,
                                             const int base_pivots,const int ctrl_form_size,
                                             const int base_x,const int base_y,
                                             const datetime &base_time[],const double &base_price[])
                       {
                        this.m_list_forms.Clear();
                        this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price);
                        this.CreateAllControlPointForm();
                       }
                     CGStdGraphObjExtToolkit(){;}
                    ~CGStdGraphObjExtToolkit(){;}
  };
//+------------------------------------------------------------------+

Im Klassenkonstruktor leeren wir die Liste der Formularobjekte, alle notwendigen Werte des Basisobjekts (die in den Konstruktorparametern übergeben werden) auf die Klassenvariablen setzen und die Formularobjekte zur Verwaltung des Basisobjekts an jedem Drehpunkt des grafischen Basisobjekts erzeugen.

Die Methode setzt die Parameter des Basisobjekts eines zusammengesetzten grafischen Objekts:

//+------------------------------------------------------------------+
//| Set the base object parameters of the                            |
//| composite graphical object                                       |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                         const long base_chart_id,const int base_subwindow,
                                         const int base_pivots,const int ctrl_form_size,
                                         const int base_x,const int base_y,
                                         const datetime &base_time[],const double &base_price[])
  {
   this.m_base_chart_id=base_chart_id;       // Base graphical object chart ID
   this.m_base_subwindow=base_subwindow;     // Base graphical object chart subwindow
   this.m_base_type=base_type;               // Base object type
   this.m_base_name=base_name;               // Base object name
   this.m_base_pivots=base_pivots;           // Number of base object reference points
   this.m_base_x=base_x;                     // Base object X coordinate
   this.m_base_y=base_y;                     // Base object Y coordinate
   this.SetControlFormSize(ctrl_form_size);  // Size of forms for managing reference points
   
   if(this.m_base_type==OBJ_LABEL            || this.m_base_type==OBJ_BUTTON  ||
      this.m_base_type==OBJ_BITMAP_LABEL     || this.m_base_type==OBJ_EDIT    ||
      this.m_base_type==OBJ_RECTANGLE_LABEL  || this.m_base_type==OBJ_CHART)
      return;
   
   if(::ArraySize(base_time)==0)
     {
      CMessage::ToLog(DFUN+"base_time: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return;
     }
   if(::ArraySize(base_price)==0)
     {
      CMessage::ToLog(DFUN+"base_price: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return;
     }
   if(::ArrayResize(this.m_base_time,this.m_base_pivots)!=this.m_base_pivots)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA);
      return;
     }
   if(::ArrayResize(this.m_base_price,this.m_base_pivots)!=this.m_base_pivots)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA);
      return;
     }
   for(int i=0;i<this.m_base_pivots;i++)
     {
      this.m_base_time[i]=base_time[i];      // Time (i) of the base object pivot point
      this.m_base_price[i]=base_price[i];    // Price (i) of the base object pivot point
     }
  }
//+------------------------------------------------------------------+

Die Methode erhält alle notwendigen Werte der Eigenschaften des Basisobjekts. Als nächstes wird der Typ des Basisobjekts überprüft. Wenn es sich um ein Objekt handelt, das nicht auf den Preis/Zeit-Koordinaten basiert, verlassen wir die Methode. Wir behandeln solche Objekte noch nicht. Als Nächstes überprüfen wir Größe der Arrays der Basisobjektkoordinaten, die an die Methode übergeben werden. Wenn sie gleich Null sind (die Methode hat ein leeres Array erhalten), informiere darüber und verlasse die Methode. Danach wird die Größe der internen Koordinaten-Arrays entsprechend den übergebenen Arrays geändert. Wenn das Array nicht geändert werden konnte, wird darüber informiert und die Methode verlassen. Am Ende kopiert man einfach die eingegebenen Arrays in die internen Arrays Element für Element.

Die Methode legt die Größe der Referenzpunkte für die Verwaltung der Pivotpunkte fest:

//+------------------------------------------------------------------+
//|Set the size of reference points for managing pivot points        |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetControlFormSize(const int size)
  {
   this.m_ctrl_form_size=(size>254 ? 255 : size<5 ? 5 : size%2==0 ? size+1 : size);
   this.m_shift=(int)ceil(m_ctrl_form_size/2)+1;
  }
//+------------------------------------------------------------------+

Die Methode erhält die erforderliche Formulargröße. Wenn die Größe 254 überschreitet, wird sie auf 255 gesetzt (ungerader Wert), wenn die übergebene Größe kleiner als 5 ist, wird sie auf 5 gesetzt (dies wird der minimale Wert für die Formulargröße sein). Andererseits, wenn die übergebene Größe gleich zwei ist, wird Eins dazu addiert und es kann benutzt werden. Wenn keiner der geprüften Werte wahr ist, wird die an die Methode übergebene Größe verwendet.
Als Nächstes wird die Koordinatenverschiebung berechnet, da die Form so eingestellt werden soll, dass der Drehpunkt des grafischen Objekts genau in der Mitte liegt. Um dies zu erreichen, müssen wir den Verschiebungswert vom Koordinatenwert subtrahieren. Die berechnete Formulargröße durch zwei teilen, den nächsthöheren ganzzahligen Wert nehmen und einen addieren.

Die Methode zum Setzen der Zeitkoordinate des Basisobjekts:

//+------------------------------------------------------------------+
//| Set the time coordinate of the base object                       |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjTime(const datetime time,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_time[index]=time;
  }
//+------------------------------------------------------------------+

Die Methode erhält Zeit und Index des Pivotpunkts des Objekts. Wenn der Index die Anzahl der Pivotpunkte im Objekt übersteigt, wird die Anfrage außerhalb des Array-Bereichs gemeldet und verlassen. Als Ergebnis wird der Zeitwert, der an die Methode übergeben wird, in die Zelle gesetzt, die dem Index im Zeit-Array entspricht.
Die Methode ist notwendig, um die Zeit des Basisobjekts im Klassenobjekt festzulegen, wenn das Objekt geändert wird.

Die Methode setzt die Preiskoordinate des Basisobjekts:

//+------------------------------------------------------------------+
//| Set the coordinate of the base object price                      |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjPrice(const double price,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_price[index]=price;
  }
//+------------------------------------------------------------------+

Die Methode ist identisch mit der oben beschriebenen, mit dem Unterschied, dass hier der Preis des Pivotpunkts des Basisobjekts, der durch den Index spezifiziert wird, zum Array Preis der Klasse hinzugefügt wird.

Die Methode setzt die Zeit- und Preiskoordinate des Basisobjekts:

//+------------------------------------------------------------------+
//| Set the time and price coordinates of the base object            |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjTimePrice(const datetime time,const double price,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_time[index]=time;
   this.m_base_price[index]=price;
  }
//+------------------------------------------------------------------+

Die Methode ist identisch mit den beiden oben genannten Methoden, mit dem Unterschied, dass sowohl der Preis als auch die Zeit in den Klassenarrays festgelegt werden.

Die Methode gibt die X- und Y-Koordinaten des angegebenen Pivotpunkts des grafischen Objekts in Bildschirmkoordinaten zurück:

//+------------------------------------------------------------------+
//| Return the X and Y coordinates of the specified pivot point      |
//| of the graphical object in screen coordinates                    |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y)
  {
   switch(this.m_base_type)
     {
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        x=this.m_base_x;
        y=this.m_base_y;
        break;
      default:
        if(!::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y))
          {
           x=0;
           y=0;
           return false;
          }
     }
   return true;
  }
//+------------------------------------------------------------------+

Die Methode erhält den Index des gewünschten Drehpunkts des grafischen Basisobjekts. Das ist der Pivotpunkt, für den die Bildschirmkoordinaten (in Pixeln von der linken oberen Bildschirmecke) und zwei Variablen (über den Link), die die Bildschirmkoordinaten des Formulars erhalten sollen, empfangen werden sollen. Wenn sich das Objekt bereits innerhalb der Bildschirmkoordinaten befindet, werden diese Koordinaten zurückgegeben.
Befindet sich das Objekt innerhalb der Preis/Zeit-Koordinaten, berechnen wir diese mit der Funktion ChartTimePriceToXY(). Wenn es nicht gelingt, die Koordinaten in die Bildschirmkoordinaten umzuwandeln, setzen wir die Koordinaten auf Null und geben false zurück.
Als Ergebnis wird true
zurückgegeben.

Die Methode gibt den Zeiger auf das Formular des Pivotpunkts mit Namen zurück:

//+------------------------------------------------------------------+
//| Return the pointer to the pivot point form by name               |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index)
  {
   index=WRONG_VALUE;
   for(int i=0;i<this.m_list_forms.Total();i++)
     {
      CForm *form=this.m_list_forms.At(i);
      if(form==NULL)
         continue;
      if(form.Name()==name)
        {
         index=i;
         return form;
        }
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Die Methode erhält den Namen des gewünschten Formulars und die Variable über die Verknüpfung mit dem Index des gefundenen Formulars in der Liste der Formularobjekte.
In der Schleife durch die Liste der Formularobjekte, das nächste Objekt holen. Wenn dessen Name mit dem gewünschten übereinstimmt, schreiben wir den Schleifenindex in die Variable und geben den Zeiger auf das erkannte Objekt zurück. Am Ende der Schleife wird NULL zurückgegeben — das Formular wurde nicht gefunden, der Index ist gleich -1. Dieser Wert wurde vor dem Start der Schleife gesetzt.

Die Methode erstellt ein Formularobjekt auf einem Basisobjekt-Referenzpunkt:

//+------------------------------------------------------------------+
//| Create a form object on a base object reference point            |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index)
  {
   string name=this.m_base_name+"_TKPP_"+(index<this.m_base_pivots ? (string)index : "X");
   CForm *form=this.GetControlPointForm(index);
   if(form!=NULL)
      return NULL;
   int x=0, y=0;
   if(!this.GetControlPointCoordXY(index,x,y))
      return NULL;
   return new CForm(this.m_base_chart_id,this.m_base_subwindow,name,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize());
  }
//+------------------------------------------------------------------+

Die Methode erhält den Index des gewünschten Drehpunktes, an dem das Formularobjekt erstellt werden soll.
Wir erstellen den Namen des Formularobjekts, bestehend aus dem Namen des Basisobjekts + der Abkürzung "ToolKit Pivot Point" (_TKPP) + dem Index des Pivotpunkts. Beim Erstellen der Indexbeschreibung wird der den Wert des Indexes überprüft. Ist er kleiner als die Anzahl der Pivotpunkte des Basisobjekts (die Berechnung beginnt bei Null), verwenden wir die String-Darstellung des an die Methode übergebenen Index. Andernfalls verwenden wir das X-Symbol. Warum brauchen wir das? Später wird es möglich sein, untergeordnete Objekte nicht nur an den Drehpunkten, sondern auch zwischen den Drehpunkten an das Basisobjekt anzuhängen. Außerdem müssen wir ein Kontrollformular in der Mitte der Basisobjektlinie erstellen, über die hinaus das gesamte Objekt verschoben wird, um es zu verschieben. Daher sollte der Formularname die Möglichkeit bieten, das Formular nicht nur für Pivotpunkte, sondern auch für andere Punkte zu erstellen.
Als Nächstes wird das Vorhandensein des Formulars in der Liste anhand des an die Methode übergebenen Index geprüft. Wenn das Formularobjekt bereits durch den Index in der Liste vorhanden ist (der Zeiger darauf ist nicht gleich NULL), wird NULL zurückgegeben.
Danach transformieren wir die Koordinaten des Pivotpunkts durch seinen Index in die Bildschirmkoordinaten und geben das Ergebnis der Erstellung eines Formularobjekts auf den erhaltenen Koordinaten zurück. Der Verschiebungswert wird von beiden Koordinaten subtrahiert, um den Mittelpunkt des Formulars präzise auf dem Pivotpunkt zu positionieren.
Ich könnte einfach den Wert für den Formularankerpunkt setzen, aber unsere Bibliothekskonvention besagt, dass der Ankerpunkt aller Formulare unverändert bleibt — in der oberen linken Ecke. Daher wird die Verschiebung für die Positionierung des Formularobjekts verwendet, wo immer es nötig ist.

Die Methode zur Erstellung von Formularobjekten auf den Pivotpunkten des Basisobjekts:

//+------------------------------------------------------------------+
//| Create form objects on the base object pivot points              |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void)
  {
   bool res=true;
   //--- In the loop by the number of base object pivot points
   for(int i=0;i<this.m_base_pivots;i++)
     {
      //--- Create a new form object on the current pivot point corresponding to the loop index
      CForm *form=this.CreateNewControlPointForm(i);
      //--- If failed to create the form, inform of that and add 'false' to the final result
      if(form==NULL)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM);
         res &=false;
        }
      //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result
      if(!this.m_list_forms.Add(form))
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
         delete form;
         res &=false;
        }
      //--- Set all the necessary properties for the created form object
      form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM); // Object is created programmatically
      form.SetActive(true);                     // Form object is active
      form.SetMovable(true);                    // Movable object
      form.SetActiveAreaShift(0,0,0,0);         // Object active area - the entire form
      form.SetFlagSelected(false,false);        // Object is not selected
      form.SetFlagSelectable(false,false);      // Object cannot be selected by mouse
      form.Erase(CLR_CANV_NULL,0);              // Fill in the form with transparent color and set the full transparency
      //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver); // Draw an outlining rectangle for visual display of the form location
      form.DrawCircle((int)floor(form.Width()/2),(int)floor(form.Height()/2),CTRL_POINT_SIZE,clrDodgerBlue);   // Draw a circle in the form center
      form.DrawCircleFill((int)floor(form.Width()/2),(int)floor(form.Height()/2),2,clrDodgerBlue);             // Draw a point in the form center
      form.Done();                              // Save the initial form object state (its appearance)
     }
   //--- Redraw the chart for displaying changes (if successful) and return the final result
   if(res)
      ::ChartRedraw(this.m_base_chart_id);
   return res;
  }
//+------------------------------------------------------------------+

Die gesamte Logik ist in den Codekommentaren beschrieben. Kurz gesagt, erstellen wir in der Schleife nach der Anzahl der Drehpunkte des Basisobjekts ein neues Formularobjekt für jeden Drehpunkt, fügen Sie es der Liste der Formularobjekte hinzu und setzen Sie die Koordinaten des entsprechenden Drehpunkts des Basisobjekts auf jedes Formular. Jedes Formular enthält den Kreis in der Mitte und den Punkt, der angibt, dass es sich um das Objekt handelt, das den Drehpunkt des Basisobjekts verwaltet.

Diese Objekte sind zunächst unsichtbar und werden erst sichtbar, wenn der Mauszeiger über dem Formularbereich schwebt. Für den Moment werde ich sie sichtbar machen, um ihr Verhalten bei Änderungen im Chart zu testen. In späteren Artikeln werde ich an meinem Plan festhalten — die Objekte werden zunächst unsichtbar sein und erst erscheinen, wenn der Mauszeiger über ihren aktiven Bereich (d. h. die gesamte Größe des Formularobjekts) bewegt wird.

Die Methode entfernt alle Formularobjekte aus der Liste:

//+------------------------------------------------------------------+
//| Remove all form objects from the list                            |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DeleteAllControlPointForm(void)
  {
   this.m_list_forms.Clear();
  }
//+------------------------------------------------------------------+

Wir verwenden einfach die Methode Clear(), um die gesamte Liste vollständig zu löschen.

In der Ereignisbehandlung, verarbeiten wir die Ereignisse des Formularobjekts entsprechend des aufgetretenen Ereignisses:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      for(int i=0;i<this.m_list_forms.Total();i++)
        {
         CForm *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         int x=0, y=0;
         if(!this.GetControlPointCoordXY(i,x,y))
            continue;
         form.SetCoordX(x-this.m_shift);
         form.SetCoordY(y-this.m_shift);
         form.Update();
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
  }
//+------------------------------------------------------------------+

Zurzeit behandeln wir nur das Chart-Änderungsereignis. In der Schleife durch alle Formularobjekte, holen wir uns das nächste Formular aus der Liste. Wenn wir die Bildschirmkoordinaten entsprechend dem Pivot-Punkt, auf dem es gezeichnet ist, nicht erhalten haben, gehen wir zum nächsten Formular über. Wir legen die neuen Bildschirmkoordinaten für das Formular fest und aktualisieren das Formular. Nach Abschluss der Schleife zeichnen wir das Chart neu, um die Änderungen anzuzeigen.

Da das Hilfsobjekt des erweiterten Standard-Grafikobjekts im Objekt der Klasse des Standard-Grafikobjekts gespeichert ist, müssen wir die Klasse in \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh verbessern.

Wir fügen zunächst die Dateien des Formularobjekts und des neu erstellten Toolkit-Objekts des erweiterten Standard-Grafikobjekts in die Datei ein:

//+------------------------------------------------------------------+
//|                                                 GStdGraphObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\GBaseObj.mqh"
#include "..\..\..\Services\Properties.mqh"
#include "..\..\Graph\Form.mqh"
#include "..\..\Graph\Extend\CGStdGraphObjExtToolkit.mqh"
//+------------------------------------------------------------------+
//| Class of the dependent object pivot point data                   |
//+------------------------------------------------------------------+

Im privaten Bereich der Klasse des abstrakten Standard-Grafikobjekts deklarieren wir den Zeiger auf das Hilfsobjekt des erweiterten Standard-Grafikobjekts:

//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   CArrayObj         m_list;                                            // List of subordinate graphical objects
   CProperties      *Prop;                                              // Pointer to the property object
   CLinkedPivotPoint m_linked_pivots;                                   // Linked pivot points
   CGStdGraphObjExtToolkit *ExtToolkit;                                 // Pointer to the extended graphical object toolkit
   int               m_pivots;                                          // Number of object reference points
//--- Read and set (1) the time and (2) the price of the specified object pivot point
   void              SetTimePivot(const int index);
   void              SetPricePivot(const int index);
//--- Read and set (1) color, (2) style, (3) width, (4) value, (5) text of the specified object level
   void              SetLevelColor(const int index);
   void              SetLevelStyle(const int index);
   void              SetLevelWidth(const int index);
   void              SetLevelValue(const int index);
   void              SetLevelText(const int index);
//--- Read and set the BMP file name for the "Bitmap Level" object. Index: 0 - ON, 1 - OFF
   void              SetBMPFile(const int index);

public:

Im öffentlichen Abschnitt schreiben wir die Methode, die den Zeiger auf das Toolkit-Objekt zurückgibt:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)     { this.Prop.Curr.SetLong(property,index,value);    }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)    { this.Prop.Curr.SetDouble(property,index,value);  }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)    { this.Prop.Curr.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)          const { return this.Prop.Curr.GetLong(property,index);   }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)           const { return this.Prop.Curr.GetDouble(property,index); }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index)           const { return this.Prop.Curr.GetString(property,index); }
//--- Set object's previous (1) integer, (2) real and (3) string properties
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.SetLong(property,index,value);    }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.SetDouble(property,index,value);  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)      const { return this.Prop.Prev.GetLong(property,index);   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)       const { return this.Prop.Prev.GetDouble(property,index); }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index)       const { return this.Prop.Prev.GetString(property,index); }
   
//--- Return (1) itself, (2) properties and (3) the change history
   CGStdGraphObj    *GetObject(void)                                       { return &this;            }
   CProperties      *Properties(void)                                      { return this.Prop;        }
   CChangeHistory   *History(void)                                         { return this.Prop.History;}
   CGStdGraphObjExtToolkit *GetExtToolkit(void)                            { return this.ExtToolkit;  }
//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property)  { return true;             }

In der öffentlichen Sektion der Klasse deklarieren wir den Handler für grafische Objekt-Ereignisse. Im Konstruktor setzen wir den Standardwert NULL für den Zeiger auf das Toolkit-Objekt, während wir im Destruktor der Klasse die Gültigkeit des Zeigers prüfen und zuerst alle Formen aus dem Toolkit-Objekt entfernen und dann das Objekt selbst löschen:

private:
//--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property
   void              SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);

public:
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; this.ExtToolkit=NULL; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                        if(this.ExtToolkit!=NULL)
                          {
                           this.ExtToolkit.DeleteAllControlPointForm();
                           delete this.ExtToolkit;
                          }
                       }
protected:
//--- Protected parametric constructor
                     CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                                   const ENUM_GRAPH_ELEMENT_TYPE elm_type,
                                   const ENUM_GRAPH_OBJ_BELONG belong,
                                   const ENUM_GRAPH_OBJ_SPECIES species,
                                   const long chart_id, const int pivots,
                                   const string name);
                     
public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+

In den Methodenblock für einen vereinfachten Zugriff und das Setzen von grafischen Objekteigenschaften, schreiben wir die Methode, die die Anzahl der grafischen Objektdrehpunkte zurückgibt:

public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+
//--- Number of object reference points
   int               Pivots(void)                  const { return this.m_pivots;                                                          }
//--- Object index in the list
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM,0);                            }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,0,number);                                 }


Im geschützten des parametrischen Konstruktor prüfen wir den Typ des grafischen Elements. Wenn es sich um ein erweitertes grafisches Objekt handelt, erstellen wir ein neues Hilfsobjekt und speichern den Zeiger darauf in der Variablen ExtToolkit. Am Ende der Konstruktorauflistung initialisieren wir das Hilfsobjekt:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_ELEMENT_TYPE elm_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_SPECIES species,
                             const long chart_id,const int pivots,
                             const string name)
  {
//--- Create the property object with the default values
   this.Prop=new CProperties(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL);
   this.ExtToolkit=(elm_type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? new CGStdGraphObjExtToolkit() : NULL);
//--- Set the number of pivot points and object levels
   this.m_pivots=pivots;
   int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS);

//--- Set the property array dimensionalities according to the number of pivot points and levels
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2);
   
//--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits
   this.m_type=obj_type;
   this.SetName(name);
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(elm_type);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetSpecies(species);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save the integer properties inherent in all graphical objects but not present in the current one
   this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID());                // Chart ID
   this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());               // Chart subwindow index
   this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject());            // Graphical object type (ENUM_OBJECT)
   this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement());   // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong());                   // Graphical object affiliation
   this.SetProperty(GRAPH_OBJ_PROP_SPECIES,0,CGBaseObj::Species());                 // Graphical object species
   this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,0);                                      // Graphical object group
   this.SetProperty(GRAPH_OBJ_PROP_ID,0,0);                                         // Object ID
   this.SetProperty(GRAPH_OBJ_PROP_BASE_ID,0,0);                                    // Base object ID
   this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0);                                        // Object index in the list
   this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,false);                         // Flag of storing the change history
   this.SetProperty(GRAPH_OBJ_PROP_BASE_NAME,0,this.Name());                        // Base object name
   
//--- Save the properties inherent in all graphical objects and present in a graphical object
   this.PropertiesRefresh();
   
//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME,0);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);

//--- Initialize the extended graphical object toolkit
   if(this.GraphElementType()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      datetime times[];
      double prices[];
      if(::ArrayResize(times,this.Pivots())!=this.Pivots())
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA);
      if(::ArrayResize(prices,this.Pivots())!=this.Pivots())
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA);
      for(int i=0;i<this.Pivots();i++)
        {
         times[i]=this.Time(i);
         prices[i]=this.Price(i);
        }
      this.ExtToolkit.SetBaseObj(this.TypeGraphObject(),this.Name(),this.ChartID(),this.SubWindow(),this.Pivots(),CTRL_FORM_SIZE,this.XDistance(),this.YDistance(),times,prices);
      this.ExtToolkit.CreateAllControlPointForm();
      this.SetFlagSelected(false,false);
      this.SetFlagSelectable(false,false);
     }

//--- Save the current properties to the previous ones
   this.PropertiesCopyToPrevData();
  }
//+-------------------------------------------------------------------+

Bei der Initialisierung des Hilfsobjekts deklarieren wir zunächst die Arrays der Zeit- und Preiseigenschaften, passen ihre Größe an die Anzahl der grafischen Objektdrehpunkte an und setzen die Preis- und Zeitwerte von den Objektdrehpunkten, die dem Schleifenindex entsprechen.
Als Nächstes rufen wir die Methode zur Initialisierung des Hilfsobjekts auf und übergeben ihr die erforderlichen Parameter des grafischen Objekts zusammen mit den neu gefüllten Arrays der Preis- und Zeiteigenschaften. Nach der Initialisierung rufen wir die Methode zum Erstellen von Formularobjekten an den Pivotpunkten des grafischen Objekts auf. Schließlich setzen wir den Status des nicht ausgewählten Objekts für das grafische Objekt und deaktivieren Sie die Möglichkeit, es mit der Maus auszuwählen.

In der Methode, die die Änderungen der Objekteigenschaften prüft, d.h. in dem Codeblock, der das erweiterte Standard-Grafikobjekt behandelt, fügen wir den Codeblock für das Verschieben von Kontrollpunkten (Formularobjekten) zu den neuen Bildschirmkoordinaten hinzu, wenn wir die Position der Drehpunkte des erweiterten Standard-Grafikobjekts ändern:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   CGBaseObj::ClearEventsList();
   bool changed=false;
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME)
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      if(this.AllowChangeHistory())
        {
         int total=HistoryChangesTotal();
         if(this.CreateNewChangeHistoryObj(total<1))
            ::Print
              (
               DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total),
               ": ",this.HistoryChangedObjTimeChangedToString(total-1)
              );
        }
      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- get the data object of its pivot points,
            CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
            if(pp==NULL)
               continue;
            //--- get the number of coordinate points the object is attached to
            int num=pp.GetNumLinkedCoords();
            //--- In the loop by the object coordinate points,
            for(int j=0;j<num;j++)
              {
               //--- get the number of coordinate points of the base object for setting the X coordinate
               int numx=pp.GetBasePivotsNumX(j);
               //--- In the loop by each coordinate point for setting the X coordinate,
               for(int nx=0;nx<numx;nx++)
                 {
                  //--- get the property for setting the X coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyX(j,nx);
                  int modifier_from=pp.GetPropertyModifierX(j,nx);
                  this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
                 }
               //--- Get the number of coordinate points of the base object for setting the Y coordinate
               int numy=pp.GetBasePivotsNumY(j);
               //--- In the loop by each coordinate point for setting the Y coordinate,
               for(int ny=0;ny<numy;ny++)
                 {
                  //--- get the property for setting the Y coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyY(j,ny);
                  int modifier_from=pp.GetPropertyModifierY(j,ny);
                  this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
                 }
              }
            dep.PropertiesCopyToPrevData();
           }
         //--- Move reference control points to new coordinates
         if(ExtToolkit!=NULL)
           {
            for(int i=0;i<this.Pivots();i++)
              {
               ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
              }
            ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
            long   lparam=0;
            double dparam=0;
            string sparam="";
            ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }
      //--- Save the current properties as the previous ones
      this.PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

Wenn wir einen der Pivotpunkte des grafischen Objekts oder das gesamte Objekt verschieben, ändern sich die Bildschirmkoordinaten seiner Drehpunkte. Das bedeutet, dass wir die Formularobjekte der Hilfsklasse ebenfalls zu den neuen Bildschirmkoordinaten verschieben müssen. Daher übergeben wir zunächst die neuen Koordinaten des grafischen Objekts an das Hilfsobjekt (in der Schleife durch die Anzahl der Pivotpunkte für die Preis/Zeit-Koordinaten und die Koordinaten in Pixel trennen). Dann rufen wir den Handler der Hilfsobjekt-Ereignisse auf, indem wir ihm die ID des Chart-Change-Ereignisses übergebe. Dies veranlasst den Handler der Hilfsobjekt-Ereignisse, die Bildschirmkoordinaten aller Formulare neu zu berechnen und sie entsprechend den neuen Preis- und Zeitkoordinaten des grafischen Objekts an eine neue Stelle zu verschieben.

In der Methode, die ein untergeordnetes Standard-Grafikobjekt zur Liste hinzufügt, beheben wir einen kleinen Fehler — ein untergeordnetes Grafikobjekt ändert seine eigenen Eigenschaften, daher sollten die neuen Eigenschaften sofort als die vorherigen gespeichert werden, um zu vermeiden, dass neue Ereignisse zum Ändern dieser Grafikobjekte erzeugt werden, wenn man auf sie klickt:

//+------------------------------------------------------------------+
//| Add a subordinate standard graphical object to the list          |
//+------------------------------------------------------------------+
bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj)
  {
   //--- If the current object is not an extended one, inform of that and return 'false'
   if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ);
      return false;
     }
   //--- If failed to add the pointer to the passed object into the list, inform of that and return 'false'
   if(!this.m_list.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST);
      return false;
     }
   //--- Object added to the list - set its number in the list,
   //--- name and ID of the current object as the base one,
   //--- set the flags of object availability and selection
   //--- and the graphical element type - standard extended graphical object
   obj.SetNumber(this.m_list.Total()-1);
   obj.SetBaseName(this.Name());
   obj.SetBaseObjectID(this.ObjectID());
   obj.SetFlagSelected(false,false);
   obj.SetFlagSelectable(false,false);
   obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED);
   obj.PropertiesCopyToPrevData();
   return true;
  }
//+------------------------------------------------------------------+


Der Handler für die abstrakten Standard-Grafikobjekt-Ereignisse:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(GraphElementType()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
      return;
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      if(ExtToolkit==NULL)
         return;
      for(int i=0;i<this.Pivots();i++)
        {
         ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
        }
      ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
      ExtToolkit.OnChartEvent(id,lparam,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+

Im Moment verarbeitet der Handler nur das Chart-Änderungsereignis.

Wenn das Objekt nicht erweitert ist, verlassen wir die Ereignisbehandlung. Wenn das Chart-Änderungsereignis erkannt wurde, prüfen wir Gültigkeit des Zeigers auf das Hilfsobjekt des erweiterten Standard-Grafikobjekts. Wenn kein Hilfsmittel erstellt wurde, kehren wir zurück. Als Nächstes setzen wir in der Schleife durch die Anzahl der Drehpunkte des grafischen Objekts die neuen Preis/Zeit-Koordinaten des grafischen Objekts auf das Hilfsobjekt. Danach setzen wir seine neuen Bildschirmkoordinaten und ruft die Ereignisbehandlung für die Ereignisse des Hilfsobjekts auf, in dem alle Formulare auf der Grundlage der neu an das Hilfsobjekt übergebenen Preis/Zeit-Koordinaten auf die neuen Bildschirmkoordinaten verschoben werden.


Wenn wir ein erweitertes Standard-Grafikobjekt aus dem Chart entfernen, müssen wir auch die Formularobjekte seines Hilfsobjekts entfernen, falls ein solches Objekt für das Grafikobjekt erstellt wurde. Das Entfernen von grafischen Objekten aus dem Chart wird in der Sammelklasse für grafische Elemente in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh behandelt.

In der Methode fügen wir den Codeblock zum Entfernen aller Formularobjekte aus dem Hilfsobjekt hinzu, in dem das Entfernen von erweiterten grafischen Objekten behandelt wird:

//+------------------------------------------------------------------+
//| Handle the removal of extended graphical objects                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj)
  {
   if(obj==NULL)
      return;
   //--- Save the ID of the graphical object chart and the number of subordinate objects in its list
   long chart_id=obj.ChartID();
   int total=obj.GetNumDependentObj();
   //--- If the list of subordinate objects is not empty (this is the base object)
   if(total>0)
     {
      CGStdGraphObjExtToolkit *toolkit=obj.GetExtToolkit();
      if(toolkit!=NULL)
        {
         toolkit.DeleteAllControlPointForm();
        }
      //--- In the loop, move along all dependent objects and remove them
      for(int n=total-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=obj.GetDependentObj(n);
         if(dep==NULL)
            continue;
         //--- If failed to remove it from the chart, display the appropriate message in the journal
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
        }
      //--- Upon the loop completion, update the chart to display the changes and exit the method
      ::ChartRedraw(chart_id);
      return;
     }
   //--- If this is a subordinate object
   else if(obj.BaseObjectID()>0)
     {
      //--- Get the base object name and its ID
      string base_name=obj.BaseName();
      long base_id=obj.BaseObjectID();
      //--- Get the base object from the graphical object collection list
      CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id);
      if(base==NULL)
         return;
      //--- get the number of dependent objects in its list
      int count=base.GetNumDependentObj();
      //--- In the loop, move along all its dependent objects and remove them
      for(int n=count-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=base.GetDependentObj(n);
         //--- If failed to get the pointer or the object has already been removed from the chart, move on to the next one
         if(dep==NULL || !this.IsPresentGraphObjOnChart(dep.ChartID(),dep.Name()))
            continue;
         //--- If failed to delete the graphical object from the chart,
         //--- display the appropriate message in the journal and move on to the next one
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
           {
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
            continue;
           }
        }
      //--- Remove the base object from the chart and from the list
      if(!::ObjectDelete(base.ChartID(),base.Name()))
         CMessage::ToLog(DFUN+base.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
     }
   //--- Update the chart for displaying the changes
   ::ChartRedraw(chart_id);
  }
//+------------------------------------------------------------------+

Hier rufen wir einfach den Zeiger auf das Hilfsobjekt des erweiterten grafischen Objekts ab. Wenn der Zeiger gültig ist, rufen wir die Methode zum Entfernen aller erstellten Formen des Hilfsobjekts des erweiterten Standard-Grafikobjekts auf, die ich zuvor betrachtet habe.

In der Ereignisbehandlung der Klasse für grafische Elemente fügen wir die Behandlung der Chart-Änderungen für erweiterte Standard-Grafikobjekte hinzu:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         if(obj==NULL)
            return;
         //--- Get the name of the renamed graphical object on the chart, which is not in the collection list
         string name_new=this.FindExtraObj(chart_id);
         //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- and send an event with the new name of the object to the control program chart
         if(obj.SetNamePrev(obj.Name()) && obj.SetName(name_new))
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),obj.TimeCreate(),obj.Name());
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj.PropertiesRefresh();
      obj.PropertiesCheckChanged();
     }
//--- Handle chart changes for extended standard objects
   if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE)
     {
      CArrayObj *list=this.GetListStdGraphObjectExt();
      if(list!=NULL)
        {
         for(int i=0;i<list.Total();i++)
           {
            obj=list.At(i);
            if(obj==NULL)
               continue;
            obj.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Wenn das Chart-Änderungsereignis erkannt wurde, holen wir hier die Liste aller erweiterten Standard-Grafikobjekte. In der Schleife holen wir uns gemäß ihrer Nummer das nächste Objekt, rufen seine Ereignisbehandlung auf und übergeben den Wert des Ereignisses "Chart geändert".


Test

Für den Test verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part95\ als TestDoEasyPart95.mq5.

Die einzigen Änderungen sind leicht unterschiedliche Namen der Preisetiketten-Objekte, die an das Basis-Trendlinien-Objekt angehängt sind. Die Namen der untergeordneten Objekte im Block zur Erstellung eines zusammengesetzten grafischen Objekts des EA-Ereignishandlers sind jetzt mit dem Zusatz "Ext" versehen, so dass die Namen dem erweiterten grafischen Objekttyp entsprechen:

   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      //--- Get the chart click coordinates
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         //--- Get the right point coordinates for a trend line
         datetime time2=iTime(Symbol(),PERIOD_CURRENT,1);
         double price2=iOpen(Symbol(),PERIOD_CURRENT,1);
         
         //--- Create the "Trend line" object
         string name_base="TrendLineExt";
         engine.CreateLineTrend(name_base,0,true,time,price,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and pass its properties to the journal
         CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID());
         
         //--- Create the "Left price label" object
         string name_dep="PriceLeftExt";
         engine.CreatePriceLabelLeft(name_dep,0,false,time,price);
         //--- Get the object from the list of graphical objects by chart name and ID and
         CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line left point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0);
         
         //--- Create the "Right price label" object
         name_dep="PriceRightExt";
         engine.CreatePriceLabelRight(name_dep,0,false,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and
         dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line right point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);

Erstellen wir ein zusammengesetztes grafisches Objekt. Die Formularobjekte sollen bei der Erstellung auf ihre Drehpunkte gesetzt werden.
Diese Formularobjekte haben die Koordinaten in Pixeln von der linken oberen Bildschirmecke. Wenn wir also das Chart verschieben, sollten diese Bildschirmkoordinaten neu berechnet werden, damit die Objekte auf die entsprechenden Pivot-Punkte des grafischen Objekts gesetzt werden. Dies werden wir überprüfen.

Kompilieren Sie den EA und starten Sie ihn auf dem Chart:


Wir können sehen, dass die Objekte ihren Platz einnehmen, wenn sich der Chart ändert. Dies geschieht jedoch mit einer Verzögerung.
Wenn ein grafisches Objekt entfernt wird, werden auch die entsprechenden Formularobjekte entfernt.

Was können wir mit der Verzögerung anfangen? Eigentlich brauchen wir die Bewegungen nicht live zu sehen — diese Bewegungen werden beim Verschieben des Charts immer ausgeblendet bleiben (sie werden jetzt angezeigt, um eine Ereignisreaktion zu verarbeiten). Die Linie des grafischen Objekts selbst wird bewegt, wenn die Formularobjekte mit der Maus gezogen werden. Jegliche Interaktion mit den Formularen wird auf einem festen Chart ausgeführt. Dieses Ergebnis ist also völlig ausreichend, vor allem wenn man bedenkt, dass das Chart erst nach Abschluss der Schleife und nicht bei jeder Schleifeniteration aktualisiert wird. Um die Belastung zu verringern, können wir den Abschluss der Chart-Änderung steuern, indem wir die Änderungen anzeigen und das Objekt anschließend einblenden (dies ist nur möglich, wenn der Mauszeiger über den aktiven Bereich des Formularobjekts bewegt wird, wenn es sichtbar werden soll).

Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit an zusammengesetzten grafischen Objektereignissen fortsetzen.

Alle Dateien der aktuellen Bibliotheksversion, des Test-EA und des Chart-Event-Control-Indikators für MQL5 sind unten zum Testen und Herunterladen angehängt. Stellen Sie Ihre Fragen, Kommentare und Vorschläge bitte im Kommentarteil.

Zurück zum Inhalt

*Frühere Artikel dieser Serie:

Grafiken in der Bibliothek DoEasy (Teil 93): Vorbereiten der Funktionen zur Erstellung zusammengesetzter grafischer Objekte
Grafiken in der Bibliothek DoEasy (Teil 94): Bewegen und Löschen zusammengesetzter grafischer Objekte

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

Beigefügte Dateien |
MQL5.zip (4207.98 KB)
Lernen Sie, wie man ein Handelssystem mit Momentum-Indikator entwickelt Lernen Sie, wie man ein Handelssystem mit Momentum-Indikator entwickelt
In diesem Artikel werde ich versuchen, eines der wichtigsten Konzepte und Indikatoren zu erläutern, nämlich den Momentum-Indikator, und ich werde erklären, wie man ein Handelssystem mit diesem Momentum-Indikator entwickelt.
Datenwissenschaft und maschinelles Lernen (Teil 01): Lineare Regression Datenwissenschaft und maschinelles Lernen (Teil 01): Lineare Regression
Es ist an der Zeit, dass wir als Händler unsere Systeme und uns selbst darauf trainieren, Entscheidungen auf der Grundlage von Zahlen zu treffen. Nicht unsere Augen oder wenn unser Bauchgefühl uns glauben macht, dass die Welt sich in diese Richtung bewegt, also lassen Sie uns senkrecht zur Richtung der Welle gehen.
Grafiken in der DoEasy-Bibliothek (Teil 96): Grafiken in Formularobjekten und Behandlung von Mausereignissen Grafiken in der DoEasy-Bibliothek (Teil 96): Grafiken in Formularobjekten und Behandlung von Mausereignissen
In diesem Artikel beginne ich mit dem Erstellen der Funktionsweise für die Behandlung von Mausereignissen in Formularobjekten und füge neue Eigenschaften und deren Verfolgung zu einem Symbolobjekt hinzu. Außerdem werde ich die Klasse der Symbolobjekte verbessern, da die Chart-Symbole jetzt neue Eigenschaften haben, die berücksichtigt und verfolgt werden müssen.
Lernen Sie, wie man ein Handelssystem mit dem RSI entwickelt Lernen Sie, wie man ein Handelssystem mit dem RSI entwickelt
In diesem Artikel werde ich Ihnen einen der beliebtesten und am häufigsten verwendeten Indikatoren in der Welt des Handels vorstellen: den RSI. Sie werden lernen, wie Sie ein Handelssystem mit diesem Indikator entwerfen können.