English Русский 中文 Español 日本語 Português
Grafiken in der Bibliothek DoEasy (Teil 86): Grafische Objektkollektion - Verwaltung der Eigenschaftsänderungen

Grafiken in der Bibliothek DoEasy (Teil 86): Grafische Objektkollektion - Verwaltung der Eigenschaftsänderungen

MetaTrader 5Beispiele | 19 November 2021, 10:17
347 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Im vorherigen Artikel habe ich die Möglichkeit implementiert, neu erstellte grafische Standardobjekte in der Kollektionsklasse für grafische Objekte zu speichern - wir erkennen neue Diagrammobjekte, erstellen Objekte von Klassen, die dem Objekttyp auf dem Diagramm entsprechen, und fügen sie der Kollektionsliste hinzu. Für eine umfassende Verwaltung der grafischen Objekte reicht dies jedoch nicht aus. Wir müssen alle Änderungen der Eigenschaften von grafischen Objekten in einem Chart sowie deren Löschung oder Umbenennung kontrollieren.

Da die Funktionen aus der Reihe ObjectGetXXX zum Auslesen der Eigenschaften grafischer Objekte verwendet werden, können wir nicht ständig die Werte jedes grafischen Objekts im Timer überprüfen, da diese Funktionen synchron sind. Das bedeutet, dass sie auf die Ausführung eines Befehls warten. Dies kann bei einer großen Anzahl von grafischen Objekten sehr ressourcenintensiv sein.

Hier stehen wir vor einer Wahl. Wir können entweder einen Timer verwenden, um jede Eigenschaft jedes grafischen Objekts zu überwachen, mit allen sich daraus ergebenden Konsequenzen für die Synchronizität der Funktionen zur Abfrage der Eigenschaften, oder wir können das Ereignismodell anwenden, indem wir in OnChartEvent() reagieren, was im Strategietester leider nicht funktioniert (wie Sie sich vielleicht erinnern, wird die Timer-Operation im Tester in der Bibliothek über die Funktionen OnTick() und OnCalculate() abgewickelt).

Nach Abwägung aller Vor- und Nachteile habe ich mich dafür entschieden, Änderungen an den Eigenschaften des grafischen Objekts im Diagramm-Event-Handler zu verfolgen. Mit anderen Worten, ich werde das Ereignismodell verwenden, was den Code vereinfacht, aber Einschränkungen für die Arbeit im Testprogramm mit sich bringt. Der Tester erlaubt es uns nicht, mit grafischen Objekten zu arbeiten (zumindest im Moment). Wir können sie nicht dem Fenster des Testers hinzufügen und ihre Eigenschaften nachträglich ändern. Das bedeutet, dass wir grafische Objekte nur auf "Live"-Charts behandeln sollten, auf denen die Ereignisbehandlung arbeitet.

In diesem Artikel werde ich eine Testversion der Behandlung von Ereignissen für grafische Objekte nur für das aktuelle Chart implementieren (dasjenige, auf dem das Programm gestartet wird). Sobald ich sicher bin, dass alles korrekt funktioniert, werde ich die vollwertige Ereignisbehandlung für jedes geöffnete Chart entwickeln. Die Ereignisbehandlung wird die Ereignisse an das Hauptchart des Programms senden, wo die Bibliothek sie sammelt und in ihrer grafischen Objektkollektion verarbeitet.


Kontrolle der Änderung von Eigenschaften grafischer Objekte

In OnChartEvent() sind wir an den folgenden Ereignissen interessiert:

  • CHARTEVENT_OBJECT_CREATE — erstellt ein grafisches Objekt (wenn CHART_EVENT_OBJECT_CREATE=true auf einem Chart);
  • CHARTEVENT_OBJECT_CHANGE - Objekteigenschaften über den Eigenschaftsdialog ändern;
  • CHARTEVENT_OBJECT_DELETE — löscht ein grafisches Objekt (wenn CHART_EVENT_OBJECT_DELETE=true auf einem Chart);
  • CHARTEVENT_OBJECT_DRAG — Ziehen eines grafischen Objekts mit der Maus.

Ich habe das Ereignis zur Erstellung eines grafischen Objekts im vorherigen Artikel vorbereitet, ohne OnChartEvent() aufzurufen.
Wir brauchen das Ereignis zum Ändern der Eigenschaften des grafischen Objekts über seinen Eigenschaftsdialog am Terminal, um die Änderungen der Objekteigenschaften manuell zu steuern.
Wir haben bereits das Ereignis zum Löschen von grafischen Objekten — die Bibliothek verfolgt die Anzahl der grafischen Objekte auf allen Terminal-Charts und hat Ereignisflags für jeden geöffneten Chart — wenn die Anzahl der Chart-Objekte abnimmt, können wir die Anzahl der aus dem Chart entfernten Objekte herausfinden und die Situation behandeln.
Das Ereignis "Verschieben des grafischen Objekts" benötigen wir, um Änderungen der Position des grafischen Objekts insgesamt und seiner einzelnen Ankerpunkte im Besonderen zu steuern.

Das Ereignis "Verschieben" wird auch aktiviert, wenn ein Objekt manuell erstellt wird. In dem Moment, in dem wir auf das Chart klicken, um ein Objekt zu setzen, und die Maustaste noch nicht losgelassen wurde, ist das Objekt bereits erstellt und die Bibliothek kann es erkennen, indem sie das entsprechende Klassenobjekt erstellt und es der Kollektion hinzufügt. Nicht alle Eigenschaften des Objekts werden korrekt gesetzt. Die Maustaste ist noch nicht losgelassen worden, und wir können das Objekt verschieben oder seine verbleibenden Ankerpunkte setzen, wenn das Objekt mehrere Punkte verwendet. Aber wenn ich die Maustaste loslasse, wird das grafische Objektbewegungsereignis erzeugt, vorausgesetzt, dass alle Objektankerpunkte bereits gesetzt sind. Indem wir das Ereignis verfolgen und die Eigenschaftswerte des bereits erstellten Klassenobjekts entsprechend den vollständig gesetzten Parametern eines erstellten grafischen Objekts ändern, setzen wir die korrekten Werte aller Eigenschaften des neu erstellten Objekts.

Die Änderung des Objektnamens bringt drei Ereignisse auf einmal mit sich — das Entfernen des Objekts, die Erstellung des Objekts und die Änderung der Eigenschaften des Objekts. Diese drei Ereignisse können nachverfolgt werden, um die Änderung des Namens eines der vorhandenen Objekte zu erkennen. Ich werde jedoch einen einfacheren Ansatz verwenden. Wenn wir einen Objektnamen ändern, wird das Ereignis CHARTEVENT_OBJECT_CHANGE immer als letztes behandelt. Da alle Terminalobjekte anhand ihres Namens und ihrer Chart-ID ausgewählt werden, können wir prüfen, welches der in einem Chart vorhandenen Objekte nicht mehr in der Kollektion enthalten ist. Dann suchen wir den Namen des Objekts auf dem Chart, für das das Klassenobjekt in der Kollektion fehlt (1), suchen das Objekt, für das es kein Chart-Objekt mit entsprechendem Namen gibt (2) und fügen diesen Namen dem in der Kollektion gefundenen Klassenobjekt (1) hinzu. Dies mag ziemlich kompliziert erscheinen. Aber eigentlich ist alles ganz einfach.

Um zu verstehen, welche Objekteigenschaft geändert wurde (da wir das Ereignis mit dem Namen des geänderten Objekts, aber ohne Angabe der geänderten Eigenschaft erhalten), müssen wir die aktuellen Werte aller Objekteigenschaften mit denen vergleichen, die vor dem Empfang des Ereignisses zur Änderung der Objekteigenschaften vorhanden waren. Daher müssen wir drei weitere Arrays erstellen, um "frühere" Objekteigenschaften zu speichern.

Wir öffnen die Datei \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh der abstrakten grafischen Standard-Objektklasse und fügen im privaten Abschnitt neue Arrays zum Speichern "vorheriger" Objekteigenschaften hinzu:

//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   long              m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL];         // Integer properties
   double            m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL];        // Real properties
   string            m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL];        // String properties
   
   long              m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL];    // Integer properties before change
   double            m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL];   // Real properties before change
   string            m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL];   // String properties before change

//--- Return the index of the array the (1) double and (2) string properties are actually located at
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL;                              }
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_DOUBLE_TOTAL;  }

public:

Im privaten Abschnitt der Klasse legen wir die Methoden zum Setzen und Zurückgeben von "vorherigen" Objekteigenschaften fest:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                         }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;       }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;       }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property)        const { return this.m_long_prop[property];                        }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];      }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];      }
   
//--- Set object's previous (1) integer, (2) real and (3) string properties
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop_prev[property]=value;                  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value){ this.m_double_prop_prev[this.IndexProp(property)]=value;}
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,string value){ this.m_string_prop_prev[this.IndexProp(property)]=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)    const { return this.m_long_prop_prev[property];                   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property)     const { return this.m_double_prop_prev[this.IndexProp(property)]; }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property)     const { return this.m_string_prop_prev[this.IndexProp(property)]; }
   
//--- Return itself
   CGStdGraphObj    *GetObject(void)                                                { return &this;}


Wir haben die Eigenschaft "Objekt-ID" für das Klassenobjekt, das ein grafisches Objekt beschreibt. Mit dieser Eigenschaft können wir eine eindeutige Objektbezeichnung festlegen, die es uns ermöglicht, das Objekt zu identifizieren.
Diese Eigenschaft sollte nicht in die Definition der Objektereignisse einbezogen werden. Deshalb werden wir in der Methode zum Setzen der Objekt-ID den Wert, der der Methode übergeben wird, in beide Eigenschaften auf einmal setzen — die aktuelle (bereits entwickelte) und die vorherige (jetzt hinzuzufügende):

public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+
//--- Object index in the list
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM);                              }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,number);                                   }
//--- Object ID
   long              ObjectID(void)                const { return this.GetProperty(GRAPH_OBJ_PROP_ID);                                    }
   void              SetObjectID(const long obj_id)
                       {
                        CGBaseObj::SetObjectID(obj_id);
                        this.SetProperty(GRAPH_OBJ_PROP_ID,obj_id);
                        this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,obj_id);
                       }
//--- Graphical object type


Der Algorithmus für die Reaktion auf ein Ereignis sieht folgendermaßen aus: Nach dem Empfang eines Ereignisses müssen wir alle Daten des Klassenobjekts, das das grafische Objekt beschreibt, aktualisieren, damit alle seine Eigenschaften die entsprechenden Werte haben. Da wir nicht wissen, welche Eigenschaft sich geändert hat, aktualisieren wir alle Objekteigenschaften und vergleichen die aktuellen Eigenschaften mit denen, die das Objekt vor dem Empfang des Objektänderungsereignisses hatte. Ich werde alle drei Arrays der Objekteigenschaften mit den entsprechenden Arrays der vorherigen Eigenschaften in drei Schleifen vergleichen. Beim Vergleich wird die Nachricht über die Änderung der Eigenschaft vorübergehend (da es sich bisher um eine Testversion handelt) an das Journal gesendet, wenn der aktuelle Eigenschaftswert nicht gleich dem vorherigen ist. Dasselbe gilt für jede festgestellte Differenz der verglichenen Werte in jedem Array der Objekteigenschaften. Später, bei der Implementierung der Kontrolle über die Änderungen der Eigenschaften für jeden der geöffneten Charts, werde ich eine weitere Methode einführen. Alle geänderten Eigenschaften werden an das Ereignisobjekt gesendet. Jedes dieser Objekte wird in die Kollektion aufgenommen, um die Änderungen der Eigenschaften der einzelnen Objekte in jedem geöffneten Chart zu melden.

Im öffentlichen Teil der Klasse deklarieren wir die Methode zum Überschreiben aller Eigenschaften des Objekts. Sie wird benötigt, um das Eigenschaftsänderungsereignis zu erkennen, um alle Eigenschaften eines grafischen Objekts auf einmal durchzugehen und sie in die Objekteigenschaften der Klasse einzutragen.
Die Methode, die die Änderungen in den Objekteigenschaften überprüft, vergleicht alle aktuellen Objekteigenschaften mit ihrem vorherigen Zustand.

Im privaten Teil der Klasse deklarieren wir drei Methoden, um alle Eigenschaften vom grafischen Objekt zu erhalten und in die Klassenobjekteigenschaften zu setzen.
Die Methode Kopieren der aktuellen Eigenschaften in die vorherigen ermöglicht den Vergleich der Eigenschaften mit den geänderten bei der nächsten Prüfung, wenn ein Ereignis erkannt wird:

//--- Return the description of the object visibility on timeframes
   string            VisibleOnTimeframeDescription(void);

//--- Re-write all graphical object properties
   void              PropertiesRefresh(void);
//--- Check object property changes
   void              PropertiesCheckChanged(void);
   
private:
//--- Get and save (1) integer, (2) real and (3) string properties
   void              GetAndSaveINT(void);
   void              GetAndSaveDBL(void);
   void              GetAndSaveSTR(void);
//--- Copy the current data to the previous one
   void              PropertiesCopyToPrevData(void);
   
  };
//+------------------------------------------------------------------+

Lassen Sie uns den geschützten parametrischen Konstruktor vereinfachen. Zuvor erhielt er alle Eigenschaften, die allen grafischen Objekten eigen sind, vom Objekt und setzte sie im Klassenobjekt:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- 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;
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save integer properties
   //--- properties inherent in all graphical objects but not present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Chart ID
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Chart subwindow index
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Graphical object type (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Graphical object affiliation
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Graphical object group
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Object ID
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Object index in the list
   //--- Properties inherent in all graphical objects and present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(chart_id,name,OBJPROP_CREATETIME);  // Object creation time
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(chart_id,name,OBJPROP_TIMEFRAMES);  // Object visibility on timeframes
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(chart_id,name,OBJPROP_BACK);        // Background object
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(chart_id,name,OBJPROP_ZORDER);      // Priority of a graphical object for receiving the event of clicking on a chart
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(chart_id,name,OBJPROP_HIDDEN);      // Disable displaying the name of a graphical object in the terminal object list
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTED);    // Object selection
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTABLE);  // Object availability
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(chart_id,name,OBJPROP_TIME);        // First point time coordinate
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(chart_id,name,OBJPROP_COLOR);       // Color
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(chart_id,name,OBJPROP_STYLE);       // Style
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(chart_id,name,OBJPROP_WIDTH);       // Line width
   //--- Properties belonging to different graphical objects
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]                          = 0;  // Object color filling
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]                      = 0;  // Ability to edit text in the Edit object
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]                        = 0;  // Number of levels
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]                    = 0;  // Level line color
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]                    = 0;  // Level line style
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]                    = 0;  // Level line width
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]                         = 0;  // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]                      = 0;  // Font size
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]                      = 0;  // Ray goes to the left
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]                     = 0;  // Ray goes to the right
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]                           = 0;  // Vertical line goes through all windows of a chart
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]                       = 0;  // Display the full ellipse of the Fibonacci Arc object
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]                     = 0;  // Arrow code for the "Arrow" object
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]                        = 0;  // Position of the binding point of the graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]                     = 0;  // Distance from the base corner along the X axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]                     = 0;  // Distance from the base corner along the Y axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]                     = 0;  // Gann object trend
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]                        = 0;  // Elliott wave marking level
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]                     = 0;  // Display lines for Elliott wave marking
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]                         = 0;  // Button state (pressed/released)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]            = 0;  // Chart object ID (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]              = 0;  // Chart object period<
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE]          = 0;  // Time scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]         = 0;  // Price scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]         = 0;  // Chart object scale
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]                         = 0;  // Object width along the X axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]                         = 0;  // Object height along the Y axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]                       = 0;  // X coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]                       = 0;  // Y coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]                       = 0;  // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]                        = 0;  // Chart corner for binding a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE]                   = 0;  // Border type for "Rectangle border"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]                  = 0;  // Border color for OBJ_EDIT and OBJ_BUTTON
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(chart_id,name,OBJPROP_PRICE);  // Price coordinate
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = 0;                                               // Level value
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = 0;                                               // Scale (property of Gann objects and Fibonacci Arcs objects)
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = 0;                                               // Angle
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = 0;                                               // Deviation of the standard deviation channel
   
//--- Save string properties
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_NAME)]        = name;                                            // Object name
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]        = ::ObjectGetString(chart_id,name,OBJPROP_TEXT);   // Object description (the text contained in the object)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]     = ::ObjectGetString(chart_id,name,OBJPROP_TOOLTIP);// Tooltip text
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]   = "";                                              // Level description
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]        = "";                                              // Font
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]     = "";                                              // BMP file name for the "Bitmap Level" object
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]= "";                                          // Chart object symbol 
   
//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);
   this.m_name=this.GetProperty(GRAPH_OBJ_PROP_NAME);

  }
//+-------------------------------------------------------------------+

Nun werden alle diese Zeilen in separate Methoden verschoben, die alle von der Methode PropertiesRefresh() aufgerufen werden sollen.
Daher entfernen wir diese Zeilen. Der Konstruktor sieht nun also wie folgt aus:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- 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(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save integer properties
   //--- properties inherent in all graphical objects but not present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Chart ID
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Chart subwindow index
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Graphical object type (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Graphical object affiliation
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Graphical object group
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Object ID
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Object index in the list

//--- 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);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);

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

Damit die Methode PropertiesRefresh() korrekt funktioniert, sollte sie den Namen des grafischen Objekts kennen, von dem sie die Daten erhalten soll. Bisher wurde der Name fast ganz am Ende geschrieben — im Block zum Lesen von String-Parametern. Jetzt wird der Name des grafischen Objekts gelesen und sofort beim Eintritt in den Konstruktor in die Eigenschaften des Klassenobjekts gesetzt. Nach dem Hinzufügen aller Eigenschaften zum Klassenobjekt ist die Methode PropertiesCopyToPrevData() aufzurufen, die bereits alle gespeicherten Objekteigenschaften auf die Arrays der "vorherigen" Eigenschaften setzt, um deren Änderungen zu kontrollieren.

Die Methoden empfangen die Eigenschaften der Typen integer, double und string vom grafischen Objekt und speichern sie im Klassenobjekt:

//+------------------------------------------------------------------+
//| Get and save the integer properties                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Properties inherent in all graphical objects and present in a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME); // Object creation time
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES); // Object visibility on timeframes
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK);       // Background object
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER);     // Priority of a graphical object for receiving the event of clicking on a chart
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN);     // Disable displaying the name of a graphical object in the terminal object list
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED);   // Object selection
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE); // Object availability
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME);       // First point time coordinate
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR);      // Color
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE);      // Style
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH);      // Line width
   //--- Properties belonging to different graphical objects
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL);       // Fill an object with color
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY);   // Ability to edit text in the Edit object
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS);     // Number of levels
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR); // Level line color
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE); // Level line style
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH); // Level line width
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN);      // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE);   // Font size
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT);   // Ray goes to the left
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT);  // Ray goes to the right
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]         = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY);        // Vertical line goes through all windows of a chart
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE);    // Display the full ellipse of the Fibonacci Arc object
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE);  // Arrow code for the "Arrow" object
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR);     // Position of the binding point of the graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE);  // Distance from the base corner along the X axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE);  // Distance from the base corner along the Y axis in pixels
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION);  // Gann object trend
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE);     // Elliott wave marking level
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES);  // Display lines for Elliott wave marking
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE);      // Button state (pressed/released)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID);   // Chart object ID (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD);     // Chart object period
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE); // Time scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE);// Price scale display flag for the Chart object
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE);// Chart object scale
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE);      // Object width along the X axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE);      // Object height along the Y axis in pixels.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET);    // X coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET);    // Y coordinate of the upper-left corner of the visibility area.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR);    // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER);     // Chart corner for binding a graphical object
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE);// Border type for "Rectangle border"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR);// Border color for OBJ_EDIT and OBJ_BUTTON
  }
//+------------------------------------------------------------------+
//| Get and save the real properties                                 |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveDBL(void)
  {
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE);       // Price coordinate
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE);  // Level value
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE);       // Scale (property of Gann objects and Fibonacci Arcs objects)
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE);       // Corner
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION);   // Deviation of the standard deviation channel
  }
//+------------------------------------------------------------------+
//| Get and save the string properties                               |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveSTR(void)
  {
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT);     // Object description (the text contained in the object)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP);  // Tooltip text
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]         = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT);// Level description
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT);     // Font
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE);  // BMP file name for the "Bitmap Level" object
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]  = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL);   // Chart object symbol 
  }
//+------------------------------------------------------------------+

Die Zeilen im Klassenkonstruktor, die durch die Methode zum Überschreiben aller grafischen Objekteigenschaften ersetzt wurden, sind in diese drei Methoden verschoben worden:

//+------------------------------------------------------------------+
//| Overwrite all graphical object properties                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesRefresh(void)
  {
   this.GetAndSaveINT();
   this.GetAndSaveDBL();
   this.GetAndSaveSTR();
  }
//+------------------------------------------------------------------+

Alle drei oben genannten Methoden werden hier nacheinander aufgerufen.

Die Methode kopiert die Eigenschaften des aktuellen Klassenobjekts in das vorherige:

//+------------------------------------------------------------------+
//| Copy the current data to the previous one                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCopyToPrevData(void)
  {
   ::ArrayCopy(this.m_long_prop_prev,this.m_long_prop);
   ::ArrayCopy(this.m_double_prop_prev,this.m_double_prop);
   ::ArrayCopy(this.m_string_prop_prev,this.m_string_prop);
  }
//+------------------------------------------------------------------+

Hier kopieren wir die Arrays mit den Eigenschaftstypen integer, double und string nacheinander in die entsprechenden Arrays der vorherigen Eigenschaften, indem wir die Array-Kopierfunktion verwenden.

Die Methode prüft die Änderungen der Eigenschaften des Objekts:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   bool changed=false;
   int beg=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+

Hier wird in drei Schleifen (für die Eigenschaften integer, double und string getrennt) die nächste Eigenschaft aus dem entsprechenden Array geholt und mit der gleichen Eigenschaft im Array der vorherigen Eigenschaften verglichen. Wenn die verglichenen Werte der aktuellen und vorherigen Eigenschaften nicht gleich sind, hat sich die Eigenschaft geändert. Wir setzen das Flag für Änderungen in den Objekteigenschaften und zeigen die Meldung im Journal an.

Dies ist eine Testmethode, die nur dazu dient, das Konzept der Suche nach Änderungen von Objekteigenschaften zu überprüfen. In den kommenden Artikeln werde ich das Konzept verbessern, um es voll funktionsfähig zu machen. Ich werde Änderungen an allen grafischen Objekten auf allen geöffneten Charts verfolgen und Ereignisse zur Änderung von Objekteigenschaften an das Kontrollprogramm Chart senden, damit sie von der Bibliothek weiterverarbeitet werden.


Kontrolle des Löschens von grafischen Objekten

Die Änderung von grafischen Objekteigenschaften wird in der Objektklasse verfolgt, da die grafischen Objekteigenschaften zum Objekt gehören. Sie werden in der Objektklasse gesetzt und können dort überprüft werden. Alle Methoden zur Änderung von Eigenschaften eines Objekts und zur Überprüfung von Änderungen, die oben beschrieben wurden, werden jedoch von der Klasse der grafischen Objekte in der Kollektion aufgerufen. Im Gegensatz dazu kann das Hinzufügen und Entfernen von grafischen Objekten zu einem Chart nur in der Klasse der grafischen Objektkollektion nachverfolgt werden. Diese Klasse verwaltet die vollständige Liste aller Objekte auf allen geöffneten Charts und führt sie in ihrer eigenen Kollektion auf.

Wir öffnen die Datei der grafischen Objektsammlungsklasse \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh und nehmen darin alle notwendigen Verbesserungen vor.

In der Chart-Objektverwaltungsklasse, die sich in derselben Datei befindet, deklarieren wir die private Methode, die die Berechtigung zum Verfolgen von Maus- und Grafikobjekt-Ereignissen festlegt. Damit kann die Berechtigung zur Verfolgung solcher Ereignisse für jedes der geöffneten Charts festgelegt werden.
In den Klassenkonstruktoren rufen wir diese Methoden zum Setzen von Berechtigungen für Charts auf, für die die Steuerobjekte erstellt werden sollen. Am Ende des Klassenlistings deklarieren wir die Ereignisbehandlung (ich werde ihn im nächsten Artikel implementieren):

//+------------------------------------------------------------------+
//| Chart object management class                                    |
//+------------------------------------------------------------------+
class CChartObjectsControl : public CObject
  {
private:
   CArrayObj         m_list_new_graph_obj;      // List of added graphical objects
   ENUM_TIMEFRAMES   m_chart_timeframe;         // Chart timeframe
   long              m_chart_id;                // Chart ID
   string            m_chart_symbol;            // Chart symbol
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_last_objects;            // Number of graphical objects during the previous check
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   
//--- Return the name of the last graphical object added to the chart
   string            LastAddedGraphObjName(void);
//--- Set the permission to track mouse events and graphical objects
   void              SetMouseEvent(void);
   
public:
//--- Return the variable values
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_chart_timeframe;    }
   long              ChartID(void)                             const { return this.m_chart_id;           }
   string            Symbol(void)                              const { return this.m_chart_symbol;       }
   bool              IsEvent(void)                             const { return this.m_is_graph_obj_event; }
   int               TotalObjects(void)                        const { return this.m_total_objects;      }
   int               Delta(void)                               const { return this.m_delta_graph_obj;    }
//--- Create a new standard graphical object
   CGStdGraphObj    *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name);
//--- Return the list of newly added objects
   CArrayObj        *GetListNewAddedObj(void)                        { return &this.m_list_new_graph_obj;}
//--- Check the chart objects
   void              Refresh(void);
//--- Constructors
                     CChartObjectsControl(void)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=::ChartID();
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     CChartObjectsControl(const long chart_id)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=chart_id;
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     
//--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CChartObjectsControl *obj_compared=node;
                        return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0);
                       }

//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

  };
//+------------------------------------------------------------------+

Über den Klassenkörper hinaus implementieren wir die Methode zum Festlegen von Berechtigungen zum Verfolgen von Maus- und Grafikobjekt-Ereignissen:

//+------------------------------------------------------------------+
//| Set the permission                                               |
//| to track mouse and graphical object events for the chart         |
//+------------------------------------------------------------------+
void CChartObjectsControl::SetMouseEvent(void)
  {
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_CREATE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_DELETE,true);
  }
//+------------------------------------------------------------------+


Konzentrieren wir uns nun auf die Klasse der grafischen Kollektion von Objekten.

Im privaten Bereich der Klasse implementieren wir neue Methoden, deren Funktionen in der Beschreibung klar angegeben sind:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
class CGraphElementsCollection : public CBaseObj
  {
private:
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of 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 presence in the collection list of graphical elements
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
//--- Return the flag indicating the graphical element object presence in the collection list of graphical objects
   bool              IsPresentGraphObjInList(const long chart_id,const string name);
//--- Return the flag indicating the presence of a graphical object on a chart by name
   bool              IsPresentGraphObjOnChart(const long chart_id,const string name);
//--- Return the pointer to the object of managing objects of the specified chart
   CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id);
//--- Create a new object of managing graphical objects of a specified chart and add it to the list
   CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id);
//--- Update the list of graphical objects by chart ID
   CChartObjectsControl *RefreshByChartID(const long chart_id);
//--- Return the first free ID of the graphical (1) object and (2) element on canvas
   long              GetFreeGraphObjID(void);
   long              GetFreeCanvElmID(void);
//--- Add a graphical object to the collection
   bool              AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control);
//--- Find an object present in the collection but not on a chart
   CGStdGraphObj    *FindMissingObj(const long chart_id);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object from the graphical object collection list
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   
public:

Im öffentlichen Abschnitt der Klasse deklarieren wir die Methode, die ein grafisches Objekt mit dem Namen und der ID des Charts zurückgibt und den Chart Event Handler:

public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Return the full collection list of standard graphical objects "as is"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Return the full collection list of graphical elements on canvas "as is"
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
//--- Return the list of graphical elements by a 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.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
//--- Return the list of graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)    { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
//--- Return the number of new graphical objects, (3) the flag of the occurred change in the list of graphical objects
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   bool              IsEvent(void) const                                                                 { return this.m_is_graph_obj_event;    }
//--- Return a graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
//--- 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);

//--- Create the list of chart management objects and return the number of charts
   int               CreateChartControlList(void);
//--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag
   void              Refresh(void);
   void              Refresh(const long chart_id);
//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
  };
//+------------------------------------------------------------------+

In der Methode, die die Liste aller grafischen Objekte aktualisiert, fügen wir den Block hinzu, der das Entfernen eines grafischen Objekts aus einem Chart behandelt:

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
//--- Declare variables to search for charts
   long chart_id=0;
   int i=0;
//--- In the loop by all open charts in the terminal (no more than 100)
   while(i<CHARTS_MAX)
     {
      //--- Get the chart ID
      chart_id=::ChartNext(chart_id);
      if(chart_id<0)
         break;
      //--- Get the pointer to the object for managing graphical objects
      //--- and update the list of graphical objects by chart ID
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      //--- If failed to get the pointer, move on to the next chart
      if(obj_ctrl==NULL)
         continue;
      //--- If the number of objects on the chart changes
      if(obj_ctrl.IsEvent())
        {
         //--- If a graphical object is added to the chart
         if(obj_ctrl.Delta()>0)
           {
            //--- Get the list of added graphical objects and move them to the collection list
            //--- (if failed to move the object to the collection, move on to the next object)
            if(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            // Find an extra object in the list
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);
            if(obj!=NULL)
              {
               //--- Display a short description of a detected object deleted from a chart in the journal
               obj.PrintShort();
               //--- Remove the class object of a removed graphical object from the collection list
               if(!this.DeleteGraphObjFromList(obj))
                  CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
              }
           }
         //--- otherwise
         else
           {
            
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+

In dieser Methode wird das Vorhandensein eines Ereignisses in jedem Kontrollobjekt in einer Schleife anhand der Gesamtzahl der Chart-Kontrollobjekte überprüft. Wenn das Ereignis vorhanden ist, prüfen wir den Wert, um den sich die Anzahl der Objekte auf dem Chart (verwaltet durch das Chart-Kontrollobjekt) geändert hat. Die Behandlung des Hinzufügens eines Objekts habe ich im vorherigen Artikel implementiert. Hier habe ich den Umgang mit dem negativen Wert einer Änderung der Anzahl der Chart-Objekte eingeführt.
Hier ist alles ganz einfach: Zuerst suchen wir nach einem Objekt in der Kollektion, das kein grafisches Objekt in einem Chart hat, und entfernen es aus der Kollektion.

Die Methode, die nach einem Objekt sucht, das in der Kollektion, aber nicht in einem Chart vorhanden ist:

//+------------------------------------------------------------------+
//|Find an object present in the collection but not on a chart       |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   if(list==NULL)
      return NULL;
   for(int i=0;i<list.Total();i++)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name()))
         return obj;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Hier erhalten wir die Liste aller Objekte, deren Chart IDs gleich der in den Methodenparametern angegebenen sind.
In der Schleife durch die erhaltene Liste, holen wir das nächste Objekt der Standard-Grafikobjektklasse. Wenn das Chart kein Objekt mit einem solchen Namen hat, gib den Zeiger auf das Objekt zurück.
Nach Beendigung der Schleife Rückgabe von NULL.

Die Methode sucht nach einem Objekt, das in einem Chart vorhanden ist, aber nicht in der Kollektion:

//+------------------------------------------------------------------+
//|Find an object present on a chart but not in the collection       |
//+------------------------------------------------------------------+
string CGraphElementsCollection::FindExtraObj(const long chart_id)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
     {
      string name=::ObjectName(chart_id,i);
      if(!this.IsPresentGraphObjInList(chart_id,name))
         return name;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Hier, in der Schleife durch alle Objekte in der Terminal-Liste, erhalten wir den Namen des nächsten Objekts. Wenn es kein Objekt mit einem solchen Namen und einer solchen Chart-ID in der Kollektion-Liste gibt, gib den Namen des grafischen Objekts zurück. Nach Beendigung der Schleife wird NULL zurückgegeben.

Die Methode gibt das Flag zurück, das das Vorhandensein der grafischen Objektklasse in der Liste der grafischen Objekte der Kollektion anzeigt:

//+------------------------------------------------------------------------------+
//| Return the flag indicating the presence of the graphical object class object |
//| in the graphical object collection list                                      |
//+------------------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list==NULL || list.Total()==0 ? false : true);
  }
//+------------------------------------------------------------------+

Rufen Sie die Liste der Objekte mit der angegebenen Diagramm-ID ab. Wir holen uns den Zeiger auf das Objekt, dessen Name mit dem erforderlichen übereinstimmt, aus der erhaltenen Liste. Wenn die Liste nicht abgerufen werden konnte oder sie leer ist, geben wir false zurück — Objekt wurde nicht gefunden, andernfalls true .

Die Methode, die das Flag zurückgibt, das das Vorhandensein eines grafischen Objekts in einem Diagramm namentlich anzeigt:

//+----------------------------------------------------------------------------------+
//| Return the flag indicating the presence of a graphical object on a chart by name |
//+----------------------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjOnChart(const long chart_id,const string name)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
      if(::ObjectName(chart_id,i)==name)
         return true;
   return false;
  }
//+-------------------------------------------------------------------+

In der Schleife durch alle grafischen Objekte, rufen wir den Namen des nächsten Objekts auf einem durch ID angegebenen Diagramm ab. Wenn der Name mit dem erforderlichen übereinstimmt, geben wir true zurück. Nach Beendigung der Schleife wird false zurückgegeben.

Die Methode zum Entfernen des grafischen Objekts aus der Liste der grafischen Objektsammlungen:

//+---------------------------------------------------------------------+
//|Remove the graphical object from the graphical object collection list|
//+---------------------------------------------------------------------+
bool CGraphElementsCollection::DeleteGraphObjFromList(CGStdGraphObj *obj)
  {
   this.m_list_all_graph_obj.Sort();
   int index=this.m_list_all_graph_obj.Search(obj);
   return(index==WRONG_VALUE ? false : this.m_list_all_graph_obj.Delete(index));
  }
//+------------------------------------------------------------------+

Die Methode erhält den Zeiger auf das Objekt, das aus der Liste entfernt werden soll.
Es wird das Flag für sortierte Listen auf die Liste gesetzt
(die Suche wird nur in sortierten Listen durchgeführt) und es wird der Objektindex mit der Methode Search() der Standardbibliothek abgerufen.
Wenn der Objektindex nicht gefunden wird, wird false
zurückgegeben, andernfalls wird das Ergebnis des Löschens des Objekts aus der Liste mit der Methode Delete() der Standardbibliothek zurückgegeben.

Die Methode gibt den Zeiger auf das grafische Objekt nach Chart-Name und ID zurück:

//+------------------------------------------------------------------+
//| Return a graphical object by chart name and ID                   |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id)
  {
   CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,chart_id);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Ermittelt die Liste der Objekte, deren Chart IDs mit der an die Methode übergebenen übereinstimmen. Wir holen uns die Liste, die das Objekt enthält, dessen Name mit dem erforderlichen übereinstimmt, aus der erhaltenen Liste (es sollte nur ein solches Objekt geben). Wenn es nicht gelingt, die Liste zu erhalten und sie nicht leer ist, gib den Zeiger auf das erste (und einzige) Objekt in der Liste zurück. Andernfalls wird NULL zurückgegeben.

Ereignisbehandlung.

In diesem Artikel werde ich die Testversion der Ereignisbehandlung implementieren, die nur die Ereignisse von grafischen Objekten im aktuellen Chart behandelt. Die Ereignisbehandlung wird auf die Ereignisse des Wechsels oder der Verschiebung von grafischen Objekten reagieren. Diese Methode ist ausreichend, um die oben genannten Ereignisse zu definieren. Außerdem beheben wir zusätzlich das Problem des unvollständigen Ausfüllens von Objekteigenschaften, die nicht mit einem Mausklick erstellt werden. Ich habe bereits erwähnt, dass der erste Klick auf ein Chart ein Objekterzeugungsereignis erzeugt und die Bibliothek sofort das entsprechende Ereignis erzeugt.

Gleichzeitig werden darin nicht alle Eigenschaften korrekt gesetzt, da das Objekt in mehreren Mausklicks aufgebaut wird. Nach Abschluss des Objektaufbaus wird das Bewegungsereignis erzeugt. Als Reaktion darauf werden die Eigenschaften des Objekts neu geschrieben (da wir das Ereignis in Echtzeit verfolgen wollen, aber bei der Erstellung eines Objekts mit mehreren Ankerpunkten erhalten wir auch ein Bewegungsereignis, das zur Aktualisierung der Eigenschaften des Objekts führt, so dass sie mit den richtigen Daten neu geschrieben werden).

Die gesamte Logik ist in den Kommentaren zum Methodencode beschrieben. Betrachten wir nun die Methode:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- If the object, whose properties were changed or which was relocated,
      //--- is successfully received from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,::ChartID());
      if(obj!=NULL)
        {
         //--- Update the properties of the obtained object
         //--- and check their change
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      else
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(::ChartID());
         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(::ChartID());
         //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- update the chart properties and check their change
         obj.SetName(name_new);
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
     }
  }
//+------------------------------------------------------------------+


Außerdem müssen wir von bibliotheksbasierten Programmen Zugriff auf die grafische Kollektion erhalten.
Dazu erstellen wir im Hauptobjekt der Bibliothek innerhalb von \MQL5\Include\DoEasy\Engine.mqh die Methode, die den Zeiger auf die Klasse der Kollektion der grafischen Objekte der Bibliothek zurückgibt:

//--- Launch the new pause countdown
   void                 Pause(const ulong pause_msc,const datetime time_start=0)
                          {
                           this.PauseSetWaitingMSC(pause_msc);
                           this.PauseSetTimeBegin(time_start*1000);
                           while(!this.PauseIsCompleted() && !::IsStopped()){}
                          }

//--- Return the graphical object collection
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects; }

//--- Constructor/destructor
                        CEngine();
                       ~CEngine();

private:


Test

Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\Part86\ als TestDoEasyPart86.mq5.

Aus OnInit() entfernen wir die Berechtigungen zum Festlegen von Zeichenfolgen zum Verfolgen von Mausereignissen:

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

Jetzt werden die Berechtigungen zur Kontrolle von Maus- und Grafikobjektereignissen in der Verwaltungsklasse des Charts festgelegt. Die Berechtigungen werden für alle geöffneten Charts gesetzt.

Ganz am Ende von OnChartEvent() fügen wir den Aufruf des Handlers der Klasse Events der grafischen Objektsammlung hinzu:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If working in the tester, exit
   if(MQLInfoInteger(MQL_TESTER))
      return;
//--- 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, allow scrolling a chart with the mouse and show the context menu
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,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 and showing the context menu
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,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,114,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();
        }
     }
   
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
   
  }
//+------------------------------------------------------------------+

Das sind alle Verbesserungen. Kompilieren Sie den EA und starten Sie ihn auf dem Chart. Beim Anlegen/Löschen eines Objekts oder beim Ändern seiner Eigenschaften werden die entsprechenden Ereigniseinträge im Client-Terminal-Journal angezeigt:


Bisher sind dies nur eine Einträge im Journal, aber das wird sich später ändern.

Was kommt als Nächstes?

Im nächsten Artikel werde ich die Objektereignishandler für jedes geöffnete Diagramm erstellen und das Senden dieser Ereignisse an das Steuerprogrammdiagramm implementieren, damit das Programm sie vollständig verarbeiten kann.

Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit der Test-EA-Datei für MQL5 zum Testen und Herunterladen angehängt.

Stellen Sie Ihre Fragen, Kommentare und Vorschläge in den Kommentaren.

Zurück zum Inhalt

*Frühere Artikel dieser Serie:

Grafiken in der Bibliothek DoEasy (Teil 83): Die Klasse des abstrakten grafischen Standardobjekts
Grafiken in der Bibliothek DoEasy (Teil 84): Abgeleitete Klassen des abstrakten grafischen Standardobjekts
Grafiken in der Bibliothek DoEasy (Teil 85): Grafische Objektkollektion - Hinzufügen neu erstellter Objekte

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

Beigefügte Dateien |
MQL5.zip (4153.33 KB)
Grafiken in der Bibliothek DoEasy (Teil 87): Grafische Kollektion - Verwaltung der Änderungen von Eigenschaften von Objekten auf allen offenen Charts Grafiken in der Bibliothek DoEasy (Teil 87): Grafische Kollektion - Verwaltung der Änderungen von Eigenschaften von Objekten auf allen offenen Charts
In diesem Artikel werde ich meine Arbeit an der Kontrolle von Standardereignissen für grafische Objekte fortsetzen und eine Funktionalität schaffen, die es den Nutzern ermöglicht, Änderungen der Eigenschaften von grafischen Objekten zu kontrollieren, die auf beliebigen im Terminal geöffneten Charts platziert sind.
Multilayer-Perzeptron und Backpropagation-Algorithmus (Teil II): Implementierung in Python und Integration mit MQL5 Multilayer-Perzeptron und Backpropagation-Algorithmus (Teil II): Implementierung in Python und Integration mit MQL5
Für die Entwicklung von Integrationen mit MQL steht ein Python-Paket zur Verfügung, das eine Fülle von Möglichkeiten wie Datenexploration, Erstellung und Nutzung von maschinellen Lernmodellen ermöglicht. Die eingebaute Python-Integration in MQL5 ermöglicht die Erstellung verschiedener Lösungen, von der einfachen linearen Regression bis hin zu Deep-Learning-Modellen. Werfen wir einen Blick darauf, wie man eine Entwicklungsumgebung einrichtet und vorbereitet und wie man einige der Bibliotheken für maschinelles Lernen verwendet.
Fester PriceAction Stoploss oder fester RSI (Smart Stop-Loss) Fester PriceAction Stoploss oder fester RSI (Smart Stop-Loss)
Der Stop-Loss ist ein wichtiges Instrument für das Geldmanagement beim Handel. Ein effektiver Einsatz von Stop-Loss, Take-Profit und der Losgröße kann einen Händler beim Handel beständiger und insgesamt profitabler machen. Obwohl der Stop-Loss ein großartiges Instrument ist, gibt es bei seiner Verwendung einige Probleme. Die größte davon ist die Stop-Loss-Jagd. Dieser Artikel befasst sich mit der Frage, wie die Stop-Loss-Hatz im Handel reduziert werden kann, und vergleicht sie mit der klassischen Stop-Loss-Nutzung, um ihre Rentabilität zu bestimmen.
Besser Programmieren (Teil 07): Tipps, um ein erfolgreicher freiberuflicher Entwickler zu werden Besser Programmieren (Teil 07): Tipps, um ein erfolgreicher freiberuflicher Entwickler zu werden
Möchten Sie ein erfolgreicher Freelance-Entwickler auf MQL5 werden? Wenn die Antwort ja lautet, ist dieser Artikel genau das Richtige für Sie.