English Русский 中文 Español 日本語 Português
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

MetaTrader 5Beispiele | 26 November 2021, 09:34
304 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Wir sind bereits in der Lage, Änderungen der Eigenschaften von Standard-Grafikobjekten vorzunehmen, die in das Chart-Fenster eingebaut sind, mit dem das bibliotheksbasierte Programm arbeitet. Um einige Ereignisse zu kontrollieren, habe ich mich entschieden, das Ereignismodell aus dem vorherigen Artikel zu verwenden. Die Ereignisse von grafischen Objekten werden in OnChartEvent() behandelt. Dies hat den Code stark vereinfacht (obwohl sich die Ereignisbehandlung jetzt in zwei verschiedenen Bibliothekscodeblöcken befindet) und das Problem des unvollständigen Füllens der Eigenschaften von Klassenobjekten sofort beim Erstellen eines grafischen Objekts im Chart behoben. Ich habe dies im vorherigen Artikel erwähnt.

Alles scheint gut zu sein, aber jetzt sind wir nicht mehr in der Lage, Ereignisse von grafischen Objekten aus anderen Charts direkt zu empfangen. Alle Ereignisse, die auf einem Chart auftreten, kommen in OnChartEvent() des Programms an, das auf diesem speziellen Chart arbeitet. Um festzustellen, welches Ereignis auf dem Chart ohne Programm eingetreten ist, müssen wir also ein Ereignis an den Chart senden, auf dem das Programm gestartet ist.

Mit der Funktion EventChartCustom() können wir ein nutzerdefiniertes Ereignis an das Chart des Programms senden.
Die Funktion kann ein nutzerdefiniertes Ereignis für ein in ihr angegebenes Chart erzeugen. Gleichzeitig mit dem Senden der Ereignis-ID an das angegebene Chart fügt die Funktion automatisch die Konstante CHARTEVENT_CUSTOM zu ihrem Wert hinzu. Nach dem Empfang eines solchen Ereignisses in der Bibliothek müssen wir nur noch diesen hinzugefügten Wert davon abziehen, um festzustellen, welche Art von Ereignis in einem anderen Chart aufgetreten ist. Um herauszufinden, in welchem Chart ein Ereignis aufgetreten ist, können wir die Chart ID im long Parameter des lparam Ereignisses angeben. Nachdem wir gesehen haben, dass lparam einen Wert hat (standardmäßig sind lparam und dparam bei grafischen Objekten gleich Null), können wir jetzt schon feststellen, dass das Ereignis von einem anderen Chart gekommen ist — subtrahieren wir den CHARTEVENT_CUSTOM-Wert vom ID-Parameter (ebenfalls im Ereignis übergeben) und erhalten die Ereignis-ID. Das Objekt, bei dem das Ereignis aufgetreten ist, kann über den Parameter sparam ermittelt werden, da der Objektname darin übergeben wird.

So können wir Ereignisse aus anderen Charts an das Programm-Chart senden. Das Ereignis kann durch die Ereignis-ID (id), das Chart durch den Parameter lparam und der Objektname durch den Parameter sparam definiert werden. Nun müssen wir herausfinden, wie diese Ereignisse auf anderen Charts verwaltet werden sollen, da das Programm auf einem einzigen Chart gestartet wird, während wir Ereignisse von anderen Charts empfangen und an die Bibliothek und den Programm-Chart senden sollen. Das Programm sollte auch auf diesen Charts arbeiten können, während die Bibliothek davon Kenntnis haben und es ausführen können sollte.

Wie Sie sich vielleicht erinnern, haben wir eine kleine Klasse zur Steuerung von Ereignissen auf verschiedenen Charts (CChartObjectsControl). In der Kollektion der grafischen Objekte erstellen wir die Listen aller geöffneten Charts des Client-Terminals, die in den genannten Klassenparametern festgelegt sind. Darüber hinaus kontrolliert die Klasse auch Änderungen in der Anzahl der grafischen Objekte auf dem Chart, das von dem Klassenobjekt verwaltet wird. Dementsprechend können wir innerhalb der Klasse ein Programm für den von dem Objekt kontrollierten Chart erstellen und das Programm auf diesem Chart platzieren. Es ist möglich, einen Indikator als Programm zu verwenden. MQL5 ermöglicht es, einen nutzerdefinierten Indikator direkt in den Programmressourcen zu erstellen (so dass der Indikator nach der Kompilierung ein integraler Bestandteil bleibt), ein Indikator-Handle für jeden im Terminal geöffneten Chart zu erstellen und, was am angenehmsten ist, einen neu erstellten Indikator programmatisch auf dem Chart zu platzieren.

Der Indikator hat keine gezeichneten Puffer. Sein einziger Zweck ist es, zwei grafische Objektereignisse (CHARTEVENT_OBJECT_CHANGE und CHARTEVENT_OBJECT_DRAG) in OnChartEvent() zu verfolgen und sie als nutzerdefiniertes Ereignis, das wir in der Bibliothek definieren und behandeln müssen, an den Programm-Chart zu senden.

Bevor ich dies implementiere, möchte ich darauf hinweisen, dass die Namen der lokalen Variablen zur Angabe des Beginns von Schleifen über Eigenschaften von Bibliotheksobjekten in vielen Bibliotheksdateien geändert wurden. Der Variablenname "beg" (Abkürzung für "begin") sah für englischsprachige Nutzer falsch aus... Deshalb habe ich beschlossen, ihn in allen Dateien durch "begin" zu ersetzen.

Nachfolgend sind die Bibliotheksdateien aufgeführt, in denen die Variable umbenannt wurde:

DataTick.mqh, Symbol.mqh, Bar.mqh, PendRequest.mqh, Order.mqh, MQLSignal.mqh, IndicatorDE.mqh, DataInd.mqh, Buffer.mqh, GCnvElement.mqh, Event.mqh, ChartWnd.mqh, ChartObj.mqh, MarketBookOrd.mqh, Account.mqh und GStdGraphObj.mqh.


Verbesserung der Klassenbibliothek

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

   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_UPPER,              // Anchor point at the upper left corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT,                    // Anchor point at the left center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_LOWER,              // Anchor point at the lower left corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LOWER,                   // Anchor point at the bottom center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_LOWER,             // Anchor point at the lower right corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT,                   // Anchor point at the right center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_UPPER,             // Anchor point at the upper right corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_UPPER,                   // Anchor point at the upper center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_CENTER,                  // Anchor point at the very center of the object

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   
   MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,           // Indicator for controlling and sending events created
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,    // Failed to create the indicator for controlling and sending events
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Chart window closed:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // Objects removed together with charts:
   
  };
//+------------------------------------------------------------------+

und die Nachrichtentexte, die den neu hinzugefügten Indizes entsprechen:

   {"Точка привязки в левом верхнем углу","Anchor point at the upper left corner"},
   {"Точка привязки слева по центру","Anchor point to the left in the center"},
   {"Точка привязки в левом нижнем углу","Anchor point at the lower left corner"},
   {"Точка привязки снизу по центру","Anchor point below in the center"},
   {"Точка привязки в правом нижнем углу","Anchor point at the lower right corner"},
   {"Точка привязки справа по центру","Anchor point to the right in the center"},
   {"Точка привязки в правом верхнем углу","Anchor point at the upper right corner"},
   {"Точка привязки сверху по центру","Anchor point above in the center"},
   {"Точка привязки строго по центру объекта","Anchor point strictly in the center of the object"},

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   
   {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
   {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   
  };
//+---------------------------------------------------------------------+


Die Methode Symbol() in \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh von der Klasse des abstrakten grafischen Standardobjekts gibt das Symbol des Charts zurück:

//--- Symbol for the Chart object 
   string            Symbol(void)                  const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL);                      }
   void              SetChartObjSymbol(const string symbol)
                       {
                        if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol);
                       }

Da alle Methoden, die das grafische Chart-Objekt behandeln, das Präfix ChartObj in ihrem Namen haben, benennen wir diese Methode ebenfalls um, um die Konsistenz zu gewährleisten:

//--- Symbol for the Chart object 
   string            ChartObjSymbol(void)          const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL);                      }
   void              SetChartObjSymbol(const string symbol)
                       {
                        if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol);
                       }
//--- Return the flags indicating object visibility on timeframes

Hier können wir die bereits umbenannten Variablen sehen, über die ich ganz am Anfang gesprochen habe:

//+------------------------------------------------------------------+
//| Compare CGStdGraphObj objects by all properties                  |
//+------------------------------------------------------------------+
bool CGStdGraphObj::IsEqual(CGStdGraphObj *compared_obj) const
  {
   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.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   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.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   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.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Display object properties in the journal                         |
//+------------------------------------------------------------------+
void CGStdGraphObj::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") =============");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n");
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   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;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   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;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   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;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+


Indikator sendet Nachrichten über Änderungen von Objekteigenschaften auf allen Charts

Definieren wir nun die Parameter des Indikators, der die Ereignisse der grafischen Objekte des Charts kontrollieren soll, an den er angehängt werden soll.

Der Indikator sollte wissen:

  1. die ID des Charts, auf dem er gestartet wird (nennen wir sie SourseID) und
  2. die ID des Charts, an den er nutzerdefinierte Ereignisse senden soll (DestinationID).
Die DestinationID sollte einfach in den Eingaben des Indikators angegeben werden, während die SourseID etwas mehr Arbeit erfordert.

Wenn wir den Indikator einfach manuell auf dem Chart starten (d.h. ihn im Navigator finden und auf das Chart-Symbol ziehen), gibt die Funktion ChartID(), die die ID des aktuellen Charts zurückgibt, die ID des Charts zurück, auf dem der Indikator gestartet wird. Auf den ersten Blick ist das genau das, was wir brauchen. Aber ... Wenn sich der Indikator in den Programmressourcen befindet (er wird während der Kompilierung in den Programmcode eingebaut und von der eingebauten Ressource aus gestartet), gibt die Funktion ChartID() die ID des Charts zurück, auf dem das Programm gestartet wird, und nicht die Instanz des Indikators. Mit anderen Worten, wir können die ID des Charts, auf dem der Indikator gestartet wurde, nicht herausfinden, indem wir den Indikator programmatisch auf verschiedenen Charts starten, wenn der Indikator aus einem anderen Ordner als Indicators\ gestartet wird. Die Lösung besteht darin, die ID des aktuellen Charts in den Einstellungen des Indikators zu übergeben, da wir die Listen der IDs aller Charts haben, die im Client-Terminal geöffnet sind.

Im Editor-Navigator (in \MQL5\Indikatoren\) erstellen wir einen neuen Ordner DoEasy\


mit einem neuen nutzerdefinierten Indikator EventControl.mq5.



Beim Erstellen geben wir zwei Eingabeparameter vom Typ long mit dem Anfangswert 0 an:


Im nächsten Schritt des Assistenten legen wir fest, dass OnChartEvent() in den Code des Indikators aufgenommen werden muss, indem wir das entsprechende Kontrollkästchen aktivieren:


Im nächsten Schritt lassen wir alle Felder und Kontrollkästchen leer und klicken auf Fertig stellen:


Der Indikator ist erstellt.

Wenn wir ihn jetzt kompilieren, werden wir eine Warnung erhalten, dass der Indikator keinen einzigen gezeichneten Puffer hat:


Um diese Warnung zu vermeiden, müssen wir im Code des Indikators ausdrücklich angeben, dass wir keine gezeichneten Puffer benötigen:

//+------------------------------------------------------------------+
//|                                                 EventControl.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0
//--- input parameters
input long     InpChartSRC = 0;
input long     InpChartDST = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

Beim Deinitialisieren der Bibliotheksklassen müssen wir den gestarteten Indikator aus allen geöffneten Charts entfernen. Da wir den Indikator über seinen Kurznamen finden können, müssen wir den Namen explizit in OnInit() des Indikators angeben, um den Indikator zu finden und aus dem Chart zu entfernen:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator shortname
   IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(string)InpChartSRC+"_To#"+(string)InpChartDST);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Der Kurzname wird enthalten:

  • Indikatorname: "EventSend",
  • die ID des Charts, von dem die Nachricht gesendet wird: "_From#"+(string)InpChartSRC und
  • die ID des Charts, an den die Nachricht gesendet wird: "_To#"+(string)InpChartDST.

Im OnCalculate() machen wir nichts — wir geben einfach die Anzahl der Balken im Chart zurück:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return rates_total;
  }
//+------------------------------------------------------------------+

In OnChartEvent() des Indikators kontrollieren wir zwei Ereignisse für grafische Objekte (CHARTEVENT_OBJECT_CHANGE und CHARTEVENT_OBJECT_DRAG). Wenn sie erkannt werden, senden wir ein nutzerdefiniertes Ereignis an den Chart des Steuerungsprogramms:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG)
     {
      EventChartCustom(InpChartDST,(ushort)id,InpChartSRC,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+

In der Nachricht selbst geben wir den Chart an, an den das Ereignis gesendet wird, die Ereignis-ID (die Funktion EventChartCustom() fügt dem Ereigniswert automatisch den Wert CHARTEVENT_CUSTOM hinzu), die ID des Charts, von dem das Ereignis gesendet wird und zwei verbleibende Standardwerte — dparam wird gleich Null sein, während sparam den Namen des grafischen Objekts festlegt, bei dem die Änderungen eingetreten sind.

Wir kompilieren den Indikator und lassen ihn in seinem Ordner. Wir werden über die Bibliothek auf ihn zugreifen. Wir werden ihn nur beim Kompilieren der Bibliothek benötigen, die ihn in ihren Ressourcen ablegt und später auf die in den Ressourcen gespeicherte Indikatorinstanz zugreift.
Bei der Verteilung des kompilierten Programms ist es nicht mehr notwendig, den Quellcode oder die kompilierte Datei des Indikators zu übergeben, da der Indikatorcode bei der Kompilierung des Programms in den Programmcode eingebettet wird und das Programm bei Bedarf auf den Code zugreifen wird.

Die Indikator-Datei finden Sie in den Anhängen unten.

Nun müssen wir eine Ressource erstellen, in der der ausführbare Indikatorcode gespeichert werden soll.

In \MQL5\Include\DoEasy\Defines.mqh, definieren wir die Makro-Substitution zur Angabe des Pfades zur ausführbaren Indikator-Datei:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Data.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#define PATH_TO_EVENT_CTRL_IND         "Indicators\\DoEasy\\EventControl.ex5"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+

Mit dieser Makro-Substitution erhalten wir den Pfad zu der kompilierten Indikator-Datei in den Bibliotheksressourcen.

In dieselbe Datei fügen wir die Liste der möglichen Ereignisse des grafischen Objekts ein:

//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of possible graphical object events                         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// No event
   GRAPH_OBJ_EVENT_CREATE,                            // "Creating a new graphical object" event
   GRAPH_OBJ_EVENT_CHANGE,                            // "Changing graphical object properties" event
   GRAPH_OBJ_EVENT_MOVE,                              // "Moving graphical object" event
   GRAPH_OBJ_EVENT_RENAME,                            // "Renaming graphical object" event
   GRAPH_OBJ_EVENT_DELETE,                            // "Removing graphical object" event
  };
#define GRAPH_OBJ_EVENTS_NEXT_CODE  (GRAPH_OBJ_EVENT_DELETE+1)  // The code of the next event after the last graphical object event code
//+------------------------------------------------------------------+
//| List of anchoring methods                                        |
//| (horizontal and vertical text alignment)                         |
//+------------------------------------------------------------------+

Diese Enumeration enthält eine vorläufige Liste von Ereignissen, die an das Programm gesendet werden sollen, wenn eine einheitliche Funktionalität für die Steuerung von Ereignissen von grafischen Standardobjekten geschaffen wird, die ich in naher Zukunft einführen werde.


Behandlung von Indikatorsignalen über die Änderung von Eigenschaften von Objekten

Beim Öffnen neuer Chart-Fenster erzeugt die Bibliothek automatisch Instanzen von Objekten der Klasse zur Verwaltung von CChartObjectsControl Chart-Objekten und speichert sie in der Liste der Chart-Verwaltungsobjekte in der Klasse der grafischen Objekte der Kollektion.

Das Objekt zur Verwaltung der Chart-Objekte speichert die verwaltete Chart-ID in seinen Eigenschaften. Dementsprechend können wir bei der Erstellung des Chart-Fensters einen Indikator für die Steuerung der Ereignisse von grafischen Objekten in demselben Objekt erstellen. Auf diese Weise können wir unseren neuen Indikator in jedem neu geöffneten Chart platzieren (oder in bestehenden Charts beim ersten Start). Der Indikator kontrolliert die Ereignisse der grafischen Objekte und sendet sie an den Chart des Kontrollprogramms.

Im privaten Abschnitt der Klasse CChartObjectsControl in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, deklarieren wir drei neue Variablen:

//+------------------------------------------------------------------+
//| 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
   long              m_chart_id_main;           // Control program 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
   int               m_handle_ind;              // Event controller indicator handle
   string            m_name_ind;                // Short name of the event controller indicator
   
//--- 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:

Die Funktionen der Variablen sind aus ihren Beschreibungen ersichtlich.

Im öffentlichen Teil der Klasse deklarieren wir zwei neue Methoden, um einen Indikator zu erstellen und ihn dem Chart hinzuzufügen:

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;}
//--- Create the event control indicator
   bool              CreateEventControlInd(const long chart_id_main);
//--- Add the event control indicator to the chart
   bool              AddEventControlInd(void);
//--- Check the chart objects
   void              Refresh(void);
//--- Constructors

In den Klassenkonstruktoren setzen wir die Standardwerte auf neue Variablen und fügen den Klassendestruktor hinzu, der den Indikator auf dem Chart und das Indikator-Handle entfernt, während er den Berechnungsteil des Indikators freigibt:

//--- 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_chart_id_main=::ChartID();
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.m_name_ind="";
                        this.m_handle_ind=INVALID_HANDLE;
                        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_chart_id_main=::ChartID();
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.m_name_ind="";
                        this.m_handle_ind=INVALID_HANDLE;
                        this.SetMouseEvent();
                       }
//--- Destructor
                     ~CChartObjectsControl()
                       {
                        ::ChartIndicatorDelete(this.ChartID(),0,this.m_name_ind);
                        ::IndicatorRelease(this.m_handle_ind);
                       }
                     
//--- 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);

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


Wir implementieren die deklarierten Methoden außerhalb des Klassenkörpers.

Die Methode, die das Ereignissteuerungskennzeichen erstellt:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Create the event control indicator         |
//+------------------------------------------------------------------+
bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main)
  {
   this.m_chart_id_main=chart_id_main;
   string name="::"+PATH_TO_EVENT_CTRL_IND;
   ::ResetLastError();
   this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main);
   if(this.m_handle_ind==INVALID_HANDLE)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR);
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main;
   Print
     (
      DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ",
      CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\""
     );
   return true;
  }
//+------------------------------------------------------------------+

Hier setzen wir die in den Methodenparametern übergebene Kontrollprogramm-ID, den Pfad zu unserem Indikator in den Ressourcen und erstellen das Indikator-Handle auf Basis eines Symbols und Zeitrahmens des von der Klasse kontrollierten Charts.
Es wird die ID des Charts, der durch das Klassenobjekt kontrolliert wird, und die ID des Kontrollprogramms der Eingabeparameter des Indikators
übergeben.
Wenn das Erstellen des Indikators fehlgeschlagen ist, wird dies im Terminaljournal unter Angabe des Fehlerindexes und der Beschreibung mitgeteilt und false
zurückgegeben.
Wenn das Indikator-Handle erstellt wurde, geben wir den Kurznamen an, der verwendet wird, um den Indikator im Klassen-Destruktor aus dem Chart zu entfernen, zeigen die Journalmeldung über die Erstellung des Indikators im Chart an und geben true zurück.

Beachten Sie, dass wir das Kontextauflösungszeichen "::" vor dem Pfadstring angeben, wenn wir den Pfad zum Indikator in den Bibliotheksressourcen angeben.
Im Gegensatz dazu setzen wir bei der Erstellung einer Ressource "\\".

Die Methode, die den Indikator für die Ereigniskontrolle zum Chart hinzufügt:

//+------------------------------------------------------------------+
//|CChartObjectsControl: Add the event control indicator to the chart|
//+------------------------------------------------------------------+
bool CChartObjectsControl::AddEventControlInd(void)
  {
   if(this.m_handle_ind==INVALID_HANDLE)
      return false;
   return ::ChartIndicatorAdd(this.ChartID(),0,this.m_handle_ind);
  }
//+------------------------------------------------------------------+

Hier wird das Handle des Indikators überprüft. Wenn es ungültig ist, wird false zurückgegeben. Ansonsten wird das Ergebnis der Funktion zum Hinzufügen des Indikators zum Chart zurückgegeben.

Bevor wir die Klasse der grafischen Kollektion definieren, geben wir den Pfad zu der Ressource an, die über den Indikator verfügt:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
class CGraphElementsCollection : public CBaseObj
  {

Diese Zeile erstellt die Bibliotheksressource, die die kompilierte, ausführbare Indikatordatei zur Steuerung von Chart-Ereignissen enthält.

Im privaten Abschnitt der Klasse deklarieren wir vier neue Methoden:

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 class 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 presence of the graphical object class in the graphical object collection list
   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);
//--- Check if the chart window is present
   bool              IsPresentChartWindow(const long chart_id);
//--- Handle removing the chart window
   void              RefreshForExtraObjects(void);
//--- 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: (1) specified object, (2) by chart ID
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   void              DeleteGraphObjectsFromList(const long chart_id);
//--- Remove the object of managing charts from the list
   bool              DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj);
  
public:

Um nicht mehr benötigte Chart-Steuerungsobjekte aus der Klassenliste und Objekte, die entfernte grafische Objekte beschreiben, aus der Liste der Kollektionen zu entfernen, müssen wir wissen, dass ein bestimmtes Chart gelöscht wurde. Hierfür wird die Methode IsPresentChartWindow() verwendet. Die Methode RefreshForExtraObjects() behandelt das Vorhandensein unnötiger Objekte in den Listen der Kollektionsklasse, während die Methoden DeleteGraphObjectsFromList() und DeleteGraphObjCtrlObjFromList() die angegebenen Objekte aus den Listen der Kollektion grafischer Objekte löschen.

In der Methode, die ein neues Objekt zur Verwaltung der grafischen Objekte eines bestimmten Charts erstellt, fügen wir den Code zum Erstellen eines Indikators und zum Hinzufügen zum Chart hinzu:

//+------------------------------------------------------------------+
//| Create a new graphical object management object                  |
//| for a specified and add it to the list                           |
//+------------------------------------------------------------------+
CChartObjectsControl *CGraphElementsCollection::CreateChartObjectCtrlObj(const long chart_id)
  {
//--- Create a new object for managing chart objects by ID
   CChartObjectsControl *obj=new CChartObjectsControl(chart_id);
//--- If the object is not created, inform of the error and return NULL
   if(obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ),(string)chart_id);
      return NULL;
     }
//--- If failed to add the object to the list, inform of the error, remove the object and return NULL
   if(!this.m_list_charts_control.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      delete obj;
      return NULL;
     }
   if(obj.ChartID()!=CBaseObj::GetMainChartID() && obj.CreateEventControlInd(CBaseObj::GetMainChartID()))
      obj.AddEventControlInd();
//--- Return the pointer to the object that was created and added to the list
   return obj;
  }
//+------------------------------------------------------------------+

Hier vergewissern wir uns, dass das Kontrollobjekt nicht für den aktuellen Chart erstellt wird, auf dem das Programm gerade arbeitet und dass der Indikator erfolgreich für den Chart erstellt wird, der durch das Chart-Management-Objekt kontrolliert wird. Wenn alles korrekt ist, fügen wir den neu erstellten Indikator dem Chart hinzu.

In der Methode, die die Liste aller grafischen Objekte aktualisiert, muss man zuerst das Schließen von Charts im Terminal behandeln, um die Chart-Verwaltungsobjekte zu entfernen, die den entfernten Charts entsprechen, sowie die Klassenobjekte, die grafische Objekte beschreiben, die in der Liste der Kollektion nach dem Schließen des Charts überflüssig wurden und zusammen mit den Charts entfernt wurden:

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
   this.RefreshForExtraObjects();
//--- 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);
              }
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+


Die Methode zur Überprüfung des Vorhandenseins des Chart-Fensters:

//+------------------------------------------------------------------+
//| Check if the chart window is present                             |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentChartWindow(const long chart_id)
  {
   long chart=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=::ChartNext(chart);
      if(chart<0)
         break;
      if(chart==chart_id)
         return true;
      //--- Increase the loop index
      i++;
     }
   return false;
  }
//+------------------------------------------------------------------+

Hier erhalten wir die ID des nächsten Charts und vergleichen sie mit derjenigen, die der Methode in der Schleife von allen offenen Charts übergeben wurde.
Wenn die IDs übereinstimmen, dann ist der Chart vorhanden, und es wird true
zurückgegeben.
Nach Beendigung der Schleife wird false zurückgegeben, da kein Chart mit der angegebenen ID gefunden wurde.

Die Methode, die die Löschung des Chart-Fensters behandelt:

//+------------------------------------------------------------------+
//| Handle removing the chart window                                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::RefreshForExtraObjects(void)
  {
   for(int i=this.m_list_charts_control.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObjectsControl *obj_ctrl=this.m_list_charts_control.At(i);
      if(obj_ctrl==NULL)
         continue;
      if(!this.IsPresentChartWindow(obj_ctrl.ChartID()))
        {
         long chart_id=obj_ctrl.ChartID();
         int total_ctrl=m_list_charts_control.Total();
         this.DeleteGraphObjCtrlObjFromList(obj_ctrl);
         int total_obj=m_list_all_graph_obj.Total();
         this.DeleteGraphObjectsFromList(chart_id);
         int del_ctrl=total_ctrl-m_list_charts_control.Total();
         int del_obj=total_obj-m_list_all_graph_obj.Total();
         Print
           (
            DFUN,CMessage::Text(MSG_GRAPH_OBJ_CLOSED_CHARTS),(string)del_ctrl,". ",
            CMessage::Text(MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS),(string)del_obj
           );
        }
     }
  }
//+------------------------------------------------------------------+

Hier in der Schleife durch die Liste der Chart-Verwaltungsobjekte, holen wir uns das nächste Objekt. Wenn das Terminal kein dem Objekt entsprechendes Chart enthält, bedeutet dies, dass das Chart geschlossen wurde
. So erhalten wir die ID des geschlossenen Charts, sowie die Gesamtzahl der zuvor geöffneten Charts, und entfernen das Kontrollobjekt, das dem bereits geschlossenen Chart entspricht, aus der Liste.
Wir ermitteln die Gesamtzahl der grafischen Objekte, die vor dem Entfernen des Charts im Terminal vorhanden waren und entfernen die Objekte der grafischen Objektklassen, die auf dem jetzt geschlossenen Chart vorhanden waren, aus der Liste der Kollektion.
Als Nächstes berechnen wir die Anzahl der geschlossenen Charts und die Anzahl der mit den Charts entfernten grafischen Objekte und drucken die Journalmeldung bezüglich der Anzahl der geschlossenen Charts und der grafischen Objekte darauf aus.
Später wird diese Meldung durch die Erzeugung eines Ereignisses ersetzt und an das Kontrollprogramm Chart gesendet.

Die Methode entfernt das grafische Objekt aus der Liste der grafischen Objekte in der Kollektion anhand einer Chart-ID:

//+------------------------------------------------------------------+
//| Remove a graphical object by a chart ID                          |
//| from the graphical object collection list                        |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteGraphObjectsFromList(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   if(list==NULL)
      return;
   for(int i=list.Total();i>WRONG_VALUE;i--)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      this.DeleteGraphObjFromList(obj);
     }
  }
//+------------------------------------------------------------------+

Hier erhalten wir die Liste der grafischen Objekte mit der angegebenen Chart-ID. In der Schleife durch die erhaltene Liste, holen wir uns das nächste Objekt und entfernen es aus der Kollektionsliste.

Die Methode zum Entfernen des Verwaltungsobjekts des Charts aus der Liste:

//+------------------------------------------------------------------+
//| Remove the object of managing charts from the list               |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj)
  {
   this.m_list_charts_control.Sort();
   int index=this.m_list_charts_control.Search(obj);
   return(index==WRONG_VALUE ? false : m_list_charts_control.Delete(index));
  }
//+------------------------------------------------------------------+

Hier setzen wir das Flag für eine sortierte Liste für die Liste der Chart-Management-Objekte und nutzen die Methode Search(), um den Index des angegebenen Objekts in der Liste zu finden. Wenn das Objekt nicht in der Liste ist, wird false zurückgegeben. Ansonsten wird das Ergebnis der Operation der Methode Delete() zurückgegeben.

In der Behandlung von Ereignissen der grafischen Objektkollektion ersetzen wir die Arbeit mit dem aktuellen Chart durch die Handhabung des Charts nach seiner ID. Um dies zu erreichen, verwenden wir den Wert von lparam eines Ereignisses. Er ist Null, wenn ein Ereignis vom aktuellen Chart empfangen wird, und er ist gleich der ID des Charts sein, von dem das Ereignis kommt, wenn ein nutzerdefiniertes Ereignis vom Indikator empfangen wird.

Um die Ereignis-ID des grafischen Objekts aus dem nutzerdefinierten Ereignis zu erhalten, müssen wir CHARTEVENT_CUSTOM von dem erhaltenen ID-Wert subtrahieren und die berechnete Ereignis-ID in idx zusammen mit der ID-Prüfung überprüfen.

Wenn lparam ungleich Null ist, wurde das Ereignis nicht aus dem aktuellen Chart empfangen. Andernfalls wurde es von dem aktuellen Chart empfangen.
Jetzt müssen nur noch alle Instanzen von ::ChartID() durch die erhaltene chart_id im Code ersetzt werden:

//+------------------------------------------------------------------+
//| 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 || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG)
     {
      //--- Get the chart ID. If lparam is zero,
      //--- the event is from the current chart,
      //--- otherwise, this is a custom event from an indicator
      long chart_id=(lparam==0 ? ::ChartID() : lparam);
      //--- 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,chart_id);
      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(chart_id);
         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,
         //--- update the chart properties and check their change
         obj.SetName(name_new);
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
     }
  }
//+------------------------------------------------------------------+

Das sind alle Verbesserungen, die wir derzeit benötigen. Testen wir das erzielte Ergebnis.


Test

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

Der EA bleibt unverändert. Kompilieren Sie ihn einfach und starten Sie ihn auf dem Chart, während Sie vorher zwei weitere Charts im Terminal öffnen. Beim Erstellen und Ändern von Objekten in weiteren Charts werden alle Ereignisse, die an grafischen Objekten auftreten, von der Bibliothek aufgezeichnet und entsprechende Meldungen im Journal angezeigt. Beim Öffnen eines weiteren Charts wird auch für diesen das Chart-Kontrollobjekt erstellt und der Indikator registriert die Objektänderungsereignisse und sendet sie an die Bibliothek. Beim Entfernen weiterer Charts werden die entsprechenden Einträge im Journal angezeigt:



Was kommt als Nächstes?

Im nächsten Artikel werde ich damit beginnen, den Umgang mit grafischen Objekten so zu gestalten, dass alle Ereignisse, die an grafischen Objekten auf offenen Charts auftreten, an den Chart des Kontrollprogramms gesendet werden (derzeit werden registrierte Ereignisse einfach im Journal angezeigt, aber das von der Bibliothek kontrollierte Programm bekommt davon nichts mit).

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
Grafiken in der Bibliothek DoEasy (Teil 86): Grafische Objektkollektion - Verwaltung der Eigenschaftsänderungen

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

Beigefügte Dateien |
MQL5.zip (4155.98 KB)
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.
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
In diesem Artikel geht es um die Kontrolle der Änderung von Eigenschaften sowie um das Entfernen und Umbenennen grafischer Objekte in der Bibliothek.
Die Verwendung von Kanälen und Gruppenchats der MQL5.community Die Verwendung von Kanälen und Gruppenchats der MQL5.community
Die Website MQL5.com bringt Händler aus der ganzen Welt zusammen. Die Nutzer veröffentlichen Artikel, teilen kostenlose Codes, verkaufen Produkte auf dem Markt, führen Freelance-Aufträge aus und kopieren Handelssignale. Sie können mit ihnen im Forum, in Händler-Chats und in MetaTrader-Kanälen kommunizieren.
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.