English Русский 中文 Español 日本語 Português
Grafiken in der DoEasy-Bibliothek (Teil 91): Standard-Ereignisse für grafische Objekte. Geschichte der Objektnamensänderung

Grafiken in der DoEasy-Bibliothek (Teil 91): Standard-Ereignisse für grafische Objekte. Geschichte der Objektnamensänderung

MetaTrader 5Beispiele | 17 Februar 2022, 08:36
196 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Die Bibliothek ist bereits in der Lage, Ereignisse zu definieren, die bei grafischen Standardobjekten auftreten. In diesem Artikel möchte ich die Funktionsweisen implementieren, die es Nutzern, die in einem bibliotheksbasierten Programm arbeiten, ermöglicht, beim Empfang eines Ereignisses genau zu definieren, welche Eigenschaft um wie viel geändert wurde. Die Idee ist einfach und basiert auf der Übergabe einer Ereignis-ID und des Namens eines Objekts, in dem das Ereignis aufgetreten ist, der ID des Charts, in dem sich das geänderte grafische Objekt befindet, und eines Namens einer Eigenschaft, deren Wert geändert wurde. Wenn grafische Objekte aus dem Chart entfernt werden, werden Objekte der Klassen, die diese entfernten grafischen Objekte beschreiben, in die Liste der entfernten Objekte aufgenommen. Auf diese Weise können wir programmatisch festlegen, welches Objekt entfernt wurde, und alle seine Eigenschaften erhalten oder ein versehentlich gelöschtes Objekt wiederherstellen.

Wenn wir das Konzept der Speicherung entfernter grafischer Objekte auf die Speicherung geänderter Objekteigenschaften ausweiten, können wir eine attraktive Funktionalität schaffen, die es uns ermöglicht, die gesamte Änderungshistorie von Objekten zu wiederholen. Mit anderen Worten, wenn wir die Liste aller nacheinander geänderten Objekteigenschaften haben, können wir jederzeit jeden Zustand reproduzieren, den das Objekt vor dem aktuellen Zustand hatte. Auf diese Weise ist es möglich, verschiedene Objektpositionen relativ zu einem Chart "festzulegen" und sie anschließend nacheinander zu reproduzieren (oder auch nicht). Dadurch wird es möglich, technische Analysewerkzeuge mit einem eigenen "Gedächtnis" zu entwickeln, einschließlich zusammengesetzter grafischer Objekte, die ich in den nächsten Artikeln behandeln werde.

Ich werde dieses Konzept hier nicht in vollem Umfang vorstellen, sondern seine Funktionalität am Beispiel der Änderung der Eigenschaft "Objektname" testen. In den folgenden Artikeln werde ich nach und nach die Möglichkeit schaffen, alle Objekteigenschaften zu erfassen und zu reproduzieren.

Dieses Konzept entstand aus dem Bedürfnis heraus, dem Programm mitzuteilen, welcher Name eines grafischen Objekts geändert wurde, um dessen vorherigen und aktuellen Namen im Journal anzuzeigen. Ich beschloss, dass es gut wäre, die Funktionalität der Bibliothek und der bibliotheksbasierten grafischen Objekte zu erweitern. So habe ich mir das aktuelle Konzept ausgedacht.


Verbesserung der Klassenbibliothek

Wie üblich beginne ich mit der Implementierung neuer Bibliotheksmeldungen in \MQL5\Include\DoEasy\Data.mqh.

Hinzufügen der neuen Nachrichtenindizes:

//--- 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_FAILED_ADD_OBJ_TO_DEL_LIST,          // Failed to set a graphical object to the list of removed objects
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST,          // Failed to set a graphical object to the list of renamed objects
   
   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_ADDED_EVN_CTRL_INDICATOR,            // Indicator for controlling and sending events successfully added to the chart
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR,       // Failed to add the indicator for controlling and sending events
   MSG_GRAPH_OBJ_ALREADY_EXIST_EVN_CTRL_INDICATOR,    // Indicator for controlling and sending events is already present on the chart
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Chart windows closed:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // Objects removed together with charts:
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_OBJ,               // Failed to create the event object for a graphical object
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_OBJ,                  // Failed to add the event object to the list
   MSG_GRAPH_OBJ_GRAPH_OBJ_PROP_CHANGE_HISTORY,       // Property change history: 
   
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE,                // New graphical object created
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE,                // Changed the graphical object property
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME,                // Graphical object renamed
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE,                // Graphical object removed
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART,             // Graphical object removed together with the chart
   
  };
//+------------------------------------------------------------------+

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

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"},
   {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"},
   
   {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
   {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
   {"Индикатор контроля и отправки событий успешно добавлен на график ","The indicator for monitoring and sending events has been successfully added to the chart "},
   {"Не удалось добавить индикатор контроля и отправки событий на график ","Failed to add the indicator of monitoring and sending events to the chart "},
   {"Индикатор контроля и отправки событий уже есть на графике","The indicator for monitoring and sending events is already on the chart"},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   {"Не удалось создать объект-событие для графического объекта","Failed to create event object for graphic object"},
   {"Не удалось добавить объект-событие в список","Failed to add event object to list"},
   {"История изменения свойства: ","Property change history: "},
   
   {"Создан новый графический объект","New graphic object created"},
   {"Изменено свойство графического объекта","Changed graphic object property"},
   {"Графический объект переименован","Graphic object renamed"},
   {"Удалён графический объект","Graphic object deleted"},
   {"Графический объект удалён вместе с графиком","The graphic object has been removed along with the chart"},
   
  };
//+---------------------------------------------------------------------+


Im öffentlichen Abschnitt der abstrakten grafischen Standardobjektklasse fügen wir in \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh die neue Methode zur Rückgabe des Zeigers auf das Eigenschaftsobjekt hinzu und deklarieren die Methode zur Anzeige der Historie der Umbenennung des grafischen Objekts im Journal:

//--- Return (1) itself and (2) the properties
   CGStdGraphObj    *GetObject(void)                                       { return &this;      }
   CProperties      *Properties(void)                                      { return this.Prop;  }
//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true;       }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  { return true;       }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property)  { return true;       }

//--- Get description of (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_STRING property);

//--- Return the description of the graphical object anchor point position
   virtual string    AnchorDescription(void)                                  const { return (string)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0); }

//--- 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);
//--- Return the object short name
   virtual string    Header(const bool symbol=false);
//--- Display the history of renaming the object to the journal
   void              PrintRenameHistory(void);

//--- Return the description of the (1) (ENUM_OBJECT) graphical object type
   virtual string    TypeDescription(void)                                    const { return CMessage::Text(MSG_GRAPH_STD_OBJ_ANY);          }


Bei der Umbenennung eines Objekts können wir seinen vorherigen Namen nicht kennen, wenn er nirgendwo im Voraus festgelegt wurde. Wir verfügen bereits über einen Mechanismus zur Definition einer Objektumbenennung. Die Kollektion der grafische Objekte speichert das Objekt der grafischen Standardobjektklasse zusammen mit der Beschreibung seiner Parameter für jedes physische Objekt in einem Chart. Sobald das Ereignis der Objektumbenennung definiert ist, ändern wir den Namen des Objekts in dem entsprechenden Klassenobjekt. Bevor wir den Namen ändern, können wir den vorherigen Namen des Objekts erfahren und ihn für die Historie speichern. Das Gleiche kann mit den übrigen Eigenschaften gemacht werden.

Ich habe mich entschlossen, den vorherigen Objektnamen direkt in den Objekteigenschaften zu speichern, da die Objekte der grafischen Objektklasse jetzt auf mehrdimensionalen dynamischen Arrays basieren, was es uns ermöglicht, die erforderliche Array-Größe in jeder ihrer Dimensionen jederzeit festzulegen. Lassen Sie uns das nutzen. Ich werde das Array der Objektnamen-Eigenschaft bei jeder nachfolgenden Umbenennung des grafischen Objekts vergrößern und seinen vorherigen Namen an das Ende des Arrays setzen. So wird die Zelle 0 des Objektnamen-Eigenschaftsarrays seinen aktuellen Namen speichern, während die anderen Array-Zellen seine vorherigen Namen speichern. In diesem Fall sind wir immer in der Lage, die gesamte Geschichte der Objektumbenennung zu kennen. Wenn wir das Gleiche für die anderen Eigenschaften tun, erhalten wir ein interessantes grafisches Objekt, das in der Lage ist, seine Zustände zu schreiben und zu speichern. Durch die Angabe der notwendigen Zelle, aus der die Parameter des Objekts gelesen werden sollen, können wir schnell alle seine Zustände reproduzieren, was für die Erstellung von Analysetools mit Speicher nützlich sein kann.

Andere Eigenschaften (nicht der Objektname) werden später verbessert. Hier werde ich nur das Konzept der Speicherung der Geschichte der Ersetzung von Objekteigenschaften prüfen. Dann werde ich die Struktur des mehrdimensionalen Arrays, in dem die Objekteigenschaften gespeichert werden, leicht verändern, so dass sie zum Speichern der Mehrfacheigenschaften des Objekts (z. B. seiner Ebenen) und der Historie der Ebenenänderungen verwendet werden können. Derzeit werden die Ebenen in denselben Array-Zellen gespeichert wie die Historie der Änderungen der Eigenschaftswerte des Objekts Name. Um Eigenschaftskollisionen zu vermeiden, muss ich die Struktur des multidimensionalen Eigenschaftsarrays leicht ändern. Dies werde ich im nächsten Artikel tun.

Im öffentlichen Abschnitt der Klasse legen wir die Methode zum Setzen des vorherigen Namens des Objekts fest:

//--- Compare CGStdGraphObj objects by a specified property (to sort the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CGStdGraphObj objects with each other by all properties (to search for equal objects)
   bool              IsEqual(CGStdGraphObj* compared_req) const;
   
//--- Set the object previous name
   bool              SetNamePrev(const string name)
                       {
                        if(!this.Prop.SetSizeRange(GRAPH_OBJ_PROP_NAME,this.Prop.CurrSize(GRAPH_OBJ_PROP_NAME)+1))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_NAME,this.Prop.CurrSize(GRAPH_OBJ_PROP_NAME)-1,name);
                        return true;
                       }
  
//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; m_group=WRONG_VALUE; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                       }
protected:

Die Methode erhält den vorherigen Namen des Objekts, die Größe des Eigenschafts-Arrays, das den Namen enthält, wird um 1 erhöht, während die neue Array-Zelle den vorherigen Namen des an die Methode übergebenen grafischen Objekts erhält und true zurückgegeben wird. Wenn das Ändern der Arraygröße oder das Setzen des Namens fehlschlägt, wird die entsprechende Journalmeldung angezeigt und false zurückgegeben.

Außerhalb des Klassenkörpers implementieren wir die Methode, die den Verlauf der Objektumbenennung im Journal anzeigt:

//+------------------------------------------------------------------+
//| Display the history of renaming the object to the journal        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PrintRenameHistory(void)
  {
   ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_GRAPH_OBJ_PROP_CHANGE_HISTORY),this.GetPropertyDescription(GRAPH_OBJ_PROP_NAME),":");
   int size=this.Properties().CurrSize(GRAPH_OBJ_PROP_NAME);
   ::Print(DFUN,"0: ",this.GetProperty(GRAPH_OBJ_PROP_NAME,0));
   for(int i=size-1;i>0;i--)
      ::Print(DFUN,size-i,": ",this.GetProperty(GRAPH_OBJ_PROP_NAME,i));
  }
//+------------------------------------------------------------------+

Hier wird zuerst die Kopfzeile mit dem neuen Objektnamen angezeigt, gefolgt von seinem aktuellen Namen mit Index 0.
Als Nächstes wird in der Rückwärtsschleife bis zur Array-Zelle mit dem Index 1 einschließlich die Beschreibung der Objekteigenschaft Name angezeigt, die zu den Array-Zellen des Objektnamens hinzugefügt wurde, in denen seine Umbenennungsgeschichte gespeichert ist nach dem Schleifenindex. Da der aktuelle Objektname immer in der Nullzelle gespeichert wird, während der vorherige in der letzten Zelle gespeichert wird, werden wir diesen Wert berechnen, anstatt den Schleifenindex für die korrekte Anzeige des Objektumbenennungsreihenindexes zu verwenden.

Für ein dreimal umbenanntes Objekt erscheint zum Beispiel folgender Eintrag:

CGStdGraphObj::PrintRenameHistory: Property change history: Name: "M30 Vertical Line 24264_3":
CGStdGraphObj::PrintRenameHistory: 0: M30 Vertical Line 24264_3
CGStdGraphObj::PrintRenameHistory: 1: M30 Vertical Line 24264_2
CGStdGraphObj::PrintRenameHistory: 2: M30 Vertical Line 24264_1
CGStdGraphObj::PrintRenameHistory: 3: M30 Vertical Line 24264

Hier sehen wir deutlich die Reihenfolge der Umbenennung eines grafischen Objekts. (Wahrscheinlich werde ich die Nummerierung der Reihenfolge so ändern, dass 0 immer dem ursprünglichen Namen des Objekts entspricht, während der letzte Index dem aktuellen Namen entspricht).


Die Hauptarbeit bei der Fertigstellung der Verfolgung von Ereignissen für grafische Objekte wird in der Kollektionsklasse der grafischen Objekte geleistet.

Wir öffnen \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh und nehmen die erforderlichen Änderungen vor.

In der aktuellen Implementierung der Behandlung des Ereignisses des Hinzufügens von Objekten, holen wir das letzte hinzugefügte Objekt aus der Terminal-Liste. Dies funktioniert nur, wenn dem Chart ein einzelnes neues Objekt hinzugefügt wurde. Wenn mehrere Objekte gleichzeitig hinzugefügt werden, erhalten wir immer noch ein einzelnes Ereignis in der Bibliothek — das Hinzufügen eines einzelnen Objekts zu einem Chart aus mehreren. Um dies zu vermeiden, müssen wir prüfen, ob ein Objekt zum Chart hinzugefügt wurde, und wenn ja, das zuletzt hinzugefügte Objekt abrufen. Wenn mehrere Objekte gleichzeitig hinzugefügt wurden, müssen wir jedes einzelne in der Schleife durch alle Chartobjekte auswählen und ein Ereignis über seine Hinzufügung senden.

Ich werde diese Struktur jedoch leicht abändern. Zunächst werde ich die Liste aller hinzugefügten Objekte erstellen. Von dieser Liste aus werden dann Ereignisse an die Kontrollprogrammkarte gesendet. Neu hinzugefügte Objekte werden in den Methoden Refresh() der CChartObjectsControl-Chartverwaltungsklassenobjekte überprüft, die wir für jede der geöffneten Karten haben. In den Methoden erstellen wir Klassenobjekte für jedes hinzugefügte Objekt, fügen sie dann der Liste hinzu und senden in dieser Schleife Nachrichten über die Erstellung von Objekten an das Kontrollprogramm in der Liste. Die Ereignisbehandlung der Bibliothek wird vom Programm aus aufgerufen. Sie empfängt das notwendige Chartverwaltungsobjekt, von dem die Nachrichten gekommen sind, holt die Klassenobjekte der grafischen Standardobjekte ab und fügt sie in die Sammelliste ein.

Nach den Verbesserungen soll die Methode Refresh() der Chartmanagement-Objektklasse wie folgt aussehen:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Check objects on a chart                   |
//+------------------------------------------------------------------+
void CChartObjectsControl::Refresh(void)
  {
//--- Clear the list of newly added objects
   this.m_list_new_graph_obj.Clear();
//--- Calculate the number of new objects on the chart
   this.m_total_objects=::ObjectsTotal(this.ChartID());
   this.m_delta_graph_obj=this.m_total_objects-this.m_last_objects;
   //--- If an object is added to the chart
   if(this.m_delta_graph_obj>0)
     {
      //--- Create the list of added graphical objects
      for(int i=0;i<this.m_delta_graph_obj;i++)
        {
         //--- Get the name of the last added object (if a single new object is added),
         //--- or a name from the terminal object list by index (if several objects have been added)
         string name=(this.m_delta_graph_obj==1 ? this.LastAddedGraphObjName() : ::ObjectName(this.m_chart_id,i));
         //--- Handle only non-programmatically created objects
         if(name==NULL || ::StringFind(name,this.m_name_program)>WRONG_VALUE)
            continue;
         //--- Create the object of the graphical object class corresponding to the added graphical object type
         ENUM_OBJECT type=(ENUM_OBJECT)::ObjectGetInteger(this.ChartID(),name,OBJPROP_TYPE);
         ENUM_OBJECT_DE_TYPE obj_type=ENUM_OBJECT_DE_TYPE(type+OBJECT_DE_TYPE_GSTD_OBJ+1);
         CGStdGraphObj *obj=this.CreateNewGraphObj(type,name);
         //--- If failed to create an object, inform of that and move on to the new iteration
         if(obj==NULL)
           {
            CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ);
            continue;
           }
         //--- Set the object affiliation and add the created object to the list of new objects
         obj.SetBelong(GRAPH_OBJ_BELONG_NO_PROGRAM); 
         //--- If failed to add the object to the list, inform of that, remove the object and move on to the next iteration
         if(!this.m_list_new_graph_obj.Add(obj))
           {
            CMessage::ToLog(DFUN_ERR_LINE,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
            delete obj;
            continue;
           }
        }
      //--- Send events to the control program chart from the created list
      for(int i=0;i<this.m_list_new_graph_obj.Total();i++)
        {
         CGStdGraphObj *obj=this.m_list_new_graph_obj.At(i);
         if(obj==NULL)
            continue;
         //--- Send an event to the control program chart
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_CREATE,this.ChartID(),obj.TimeCreate(),obj.Name());
        }
     }
//--- save the index of the last added graphical object and the difference with the last check
   this.m_last_objects=this.m_total_objects;
   this.m_is_graph_obj_event=(bool)this.m_delta_graph_obj;
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist in den Code-Kommentaren vollständig beschrieben. Der Parameter dparam der Funktion EventChartCustom() übergibt die Erstellungszeit des grafischen Objekts. Dies ist notwendig, um Objekte zu identifizieren, die sich auf einem wiederhergestellten Chart befinden, das zuvor gelöscht wurde. Mit anderen Worten, wenn wir grafische Objekte auf einem geöffneten Chart setzen, dieses Chart dann entfernen und wiederherstellen, werden die grafischen Objekte, die zum Zeitpunkt des Löschens auf dem Chart gesetzt waren, ebenfalls wiederhergestellt. Solche Objekte haben eine Erstellungszeit von Null. Diese Zeit können wir später für die Identifizierung von grafischen Objekten verwenden, die zusammen mit dem Symbol Chart wiederhergestellt wurden.

Vergewissern wir uns in der Methode zum Hinzufügen des Ereignissteuerungsindikators der Chart-Verwaltungsklasse auf dem Chart, dass ein solcher Indikator noch nicht auf dem Chart vorhanden ist, bevor wir ihn hinzufügen:

//+------------------------------------------------------------------+
//|CChartObjectsControl: Add the event control indicator to the chart|
//+------------------------------------------------------------------+
bool CChartObjectsControl::AddEventControlInd(void)
  {
   if(this.m_handle_ind==INVALID_HANDLE)
      return false;
   ::ResetLastError();
   string shortname="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main;
   int total=::ChartIndicatorsTotal(this.ChartID(),0);
   for(int i=0;i<total;i++)
      if(::ChartIndicatorName(this.ChartID(),0,i)==shortname)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_ALREADY_EXIST_EVN_CTRL_INDICATOR);
         return true;
        }
   return ::ChartIndicatorAdd(this.ChartID(),0,this.m_handle_ind);
  }
//+------------------------------------------------------------------+

Hier erstellen wir zunächst den kurzen Indikatornamen, der für die Suche nach Indikatoren im Chart verwendet wird. Als Nächstes erhalten wir in der Schleife die nächste Chart-ID. Wenn der Kurzname des erhaltenen Indikators mit dem erforderlichen Indikator übereinstimmt, wird mitgeteilt, dass ein solcher Indikator bereits im Chart vorhanden ist und true zurückgegeben. Nach Beendigung der Schleife wird das Ergebnis des Hinzufügens des Indikators zum Chart zurückgegeben.

Im privaten Teile der Kollektionsklasse der grafischen Objekte deklarieren wir die Liste der entfernten grafischen Objekte und wir die überladene Methode, die das Objekt findet (das in der Kollektion vorhanden ist, aber im Chart fehlt), die neben dem Zeiger auf das gefundene Objekt auch den Objektindex in der Liste zurückgibt. Deklaration der Methoden, die entfernte Objekte in die Liste der entfernten grafischen Objekte verschieben.

//+------------------------------------------------------------------+
//| 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
  {
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
   CArrayObj         m_list_deleted_obj;        // List of removed 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(bool program_object);
   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);
   CGStdGraphObj    *FindMissingObj(const long chart_id,int &index);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object class object from the graphical object collection list: (1) specified object, (2) by chart ID
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   void              DeleteGraphObjectsFromList(const long chart_id);
//--- Move the graphical object class object to the list of removed graphical objects: (1) specified object, (2) by index
   bool              MoveGraphObjToDeletedObjList(CGStdGraphObj *obj);
   bool              MoveGraphObjToDeletedObjList(const int index);
//--- Move all objects by chart ID to the list of removed graphical objects
   void              MoveGraphObjectsToDeletedObjList(const long chart_id);
//--- Remove the object of managing charts from the list
   bool              DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj);
//--- Create a new standard graphical object, return an object name
   bool              CreateNewStdGraphObject(const long chart_id,
                                             const string name,
                                             const ENUM_OBJECT type,
                                             const int subwindow,
                                             const datetime time1,
                                             const double price1,
                                             const datetime time2=0,
                                             const double price2=0,
                                             const datetime time3=0,
                                             const double price3=0,
                                             const datetime time4=0,
                                             const double price4=0,
                                             const datetime time5=0,
                                             const double price5=0);
public:


In den öffentlichen Abschnitt der Klasse schreiben wir die Methoden, die die Listen der entfernten grafischen Objekte nach ausgewählten Eigenschaften zurückgeben, deklarieren die Methode, die den Zeiger auf das entfernte grafische Objekt zurückgibt und schreiben die Methoden zurück, die die Liste der entfernten grafischen Objekte zurückgeben, den Zeiger auf das letzte entfernte grafische Objekt und die Methode zurück, die die Größe des Arrays der Eigenschaften des grafischen Objekts zurückgibt.
Schließlich deklarieren wir die Methode, die den Verlauf der Objektumbenennung im Journal anzeigt:

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 existing 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,int index,long value,ENUM_COMPARER_TYPE mode=EQUAL)      { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode);    }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value,ENUM_COMPARER_TYPE mode=EQUAL)     { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode);    }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value,ENUM_COMPARER_TYPE mode=EQUAL)     { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode);    }
//--- Return the list of removed graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetListDel(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListDeletedObj(),property,index,value,mode);  }
   CArrayObj        *GetListDel(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByGraphicStdObjectProperty(this.GetListDeletedObj(),property,index,value,mode);  }
   CArrayObj        *GetListDel(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByGraphicStdObjectProperty(this.GetListDeletedObj(),property,index,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 an (1) existing and (2) removed graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
   CGStdGraphObj    *GetStdDelGraphObject(const string name,const long chart_id);
//--- Return the list of (1) chart management objects and (2) removed graphical objects
   CArrayObj        *GetListChartsControl(void)                                                          { return &this.m_list_charts_control;  }
   CArrayObj        *GetListDeletedObj(void)                                                             { return &this.m_list_deleted_obj;     }
//--- Return (1) the last removed graphical object and (2) the array size of graphical object properties
   CGStdGraphObj    *GetLastDeletedGraphObj(void)                 const { return this.m_list_deleted_obj.At(this.m_list_deleted_obj.Total()-1); }
   int               GetSizeProperty(const string name,const long chart_id,const int prop)
                       {
                        CGStdGraphObj *obj=this.GetStdGraphObject(name,chart_id);
                        return(obj!=NULL ? obj.Properties().CurrSize(prop) : 0);
                       }
   
//--- 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);
//--- Display the history of renaming the object to the journal
   void              PrintRenameHistory(const string name,const long chart_id);

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

private:


Im Klassenkonstruktor löschen wir die Liste der entfernten grafischen Objekte und setzen das Flag für die sortierte Liste:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   this.m_type=COLLECTION_GRAPH_OBJ_ID;
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_charts_control.Sort();
   this.m_list_charts_control.Clear();
   this.m_total_objects=0;
   this.m_is_graph_obj_event=false;
   this.m_list_deleted_obj.Clear();
   this.m_list_deleted_obj.Sort();
  }
//+------------------------------------------------------------------+


In der Methode Refresh() der Kollektionsklasse der grafischen Objekte haben wir nur ein entferntes Objekt gefunden, als wir das Ereignis des Entfernens grafischer Objekte aus dem Chart behandelt haben. Jetzt müssen wir jedes entfernte Objekt finden und das entsprechende Ereignis an das Steuerprogramm Chart in der Schleife nach der Anzahl der entfernten Objekte senden. Dies geschieht im verbesserten Block zur Behandlung der Entfernung von grafischen Objekten:

//+------------------------------------------------------------------+
//| 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(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            int index=WRONG_VALUE;
            //--- In the loop by the number of removed graphical objects
            for(int j=0;j<-obj_ctrl.Delta();j++)
              {
               // Find an extra object in the list
               CGStdGraphObj *obj=this.FindMissingObj(chart_id,index);
               if(obj!=NULL)
                 {
                  //--- Get the removed object parameters
                  long   lparam=obj.ChartID();
                  string sparam=obj.Name();
                  double dparam=(double)obj.TimeCreate();
                  //--- Move the graphical object class object to the list of removed objects
                  //--- and send the event to the control program chart
                  if(this.MoveGraphObjToDeletedObjList(index))
                     ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam);
                 }
              }
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+


Zuvor habe ich die Objekte der Klassen, die grafische Objekte beschreiben, die zusammen mit dem Chart-Fenster entfernt wurden, in der Methode zum Entfernen eines Chart-Fensters entfernt. Nun werde ich sie in die Liste der entfernten grafischen Objekte verschieben. Diese Liste wird es uns später ermöglichen, alle Eigenschaften jedes mit dem Chart entfernten Objekts abzurufen:

//+------------------------------------------------------------------+
//| 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();
         string chart_symb=obj_ctrl.Symbol();
         int total_ctrl=this.m_list_charts_control.Total();
         this.DeleteGraphObjCtrlObjFromList(obj_ctrl);
         int total_obj=this.m_list_all_graph_obj.Total();
         this.MoveGraphObjectsToDeletedObjList(chart_id);
         int del_ctrl=total_ctrl-this.m_list_charts_control.Total();
         int del_obj=total_obj-this.m_list_all_graph_obj.Total();
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DEL_CHART,chart_id,del_obj,chart_symb);
        }
     }
  }
//+------------------------------------------------------------------+


Die Methode befindet sich in der Kollektion, aber nicht auf dem Chart und gibt den Zeiger auf das Objekt und seinen Index in der Liste zurück:

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

Diese Methode ist eine Überladung der zuvor implementierten gleichnamigen Methode. Im Gegensatz zu ihrem "Partner" gibt die Methode zusätzlich zu dem Zeiger auf ein erkanntes Objekt auch den Index zurück, was beim Zugriff auf das erkannte Objekt über seinen Index in der Liste notwendig sein kann.


Die Methode verschiebt alle Objekte nach Chart-ID in die Liste der entfernten grafischen Objekte:

//+------------------------------------------------------------------+
//| Move all objects (by chart ID)                                   |
//| to the list of removed graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::MoveGraphObjectsToDeletedObjList(const long chart_id)
  {
   CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
   if(list==NULL)
      return;
   for(int i=list.Total()-1;i>WRONG_VALUE;i--)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      this.MoveGraphObjToDeletedObjList(obj);
     }
  }
//+------------------------------------------------------------------+

Die Methode verschiebt alle Objekte mit der angegebenen Chart ID in die Liste der entfernten Objekte.
Hier erhalten wir die Liste der Objekte mit der angegebenen Chart ID. Außerdem holen wir in der Schleife durch die angegebene Liste das nächste Objekt und verschieben es in die Liste der entfernten grafischen Objekte mit der unten betrachteten Methode.


Die Methode verschiebt das Objekt der grafischen Objektklasse nach Index in die Liste der entfernten Objekte:

//+------------------------------------------------------------------+
//| Move the class object of the graphical object by index           |
//| to the list of removed graphical objects                         |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::MoveGraphObjToDeletedObjList(const int index)
  {
   CGStdGraphObj *obj=this.m_list_all_graph_obj.Detach(index);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
      return false;
     }
   if(!this.m_list_deleted_obj.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST);
      delete obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Die Methode verschiebt das Objekt, dessen Index an die Methode übergeben wurde, aus der Kollektionsliste in die Liste der entfernten Objekte.
Hier holen wir uns das Objekt aus der Liste mit dem angegebenen Index. Wenn das Abrufen des Objekts fehlgeschlagen ist, informieren wir darüber und geben falsezurück.
Wenn es nicht gelungen ist, das Objekt in die Liste der entfernten Objekte aufzunehmen, informiere darüber, entfernen wir das Objekt und geben false zurück.
Als Ergebnis wird true zurückgegeben.

Die Methode verschiebt das angegebene Objekt der grafischen Objektklasse in die Liste der entfernten grafischen Objekte:

//+------------------------------------------------------------------+
//| Move the specified graphical object class object                 |
//| to the list of removed graphical objects                         |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::MoveGraphObjToDeletedObjList(CGStdGraphObj *obj)
  {
   this.m_list_all_graph_obj.Sort();
   int index=this.m_list_all_graph_obj.Search(obj);
   return this.MoveGraphObjToDeletedObjList(index);
  }
//+------------------------------------------------------------------+

Die Methode verschiebt das durch den Zeiger angegebene Objekt in die Liste der entfernten grafischen Objekte.
Hier setzen wir das Flag für die sortierte Liste der Kollektion und erhalten den Objektindex in der Liste mit Hilfe der Methode Search().
Wenn man die Methode verwendet, die das Objekt nach seinem Index in der oben betrachteten Liste verschiebt, verschiebt man das Objekt in die Liste der entfernten grafischen Objekte.

Die Methode gibt ein entferntes grafisches Objekt nach Chart-Name und ID zurück:

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

Der Objektname und die Chart-ID werden der Methode übergeben. Als Nächstes erhalten wir die Liste der Objekte nach Chart ID. Aus der erhaltenen Liste die Liste der Objekte nach Namen erhalten (es sollte nur ein solches Objekt geben). Wenn die Liste empfangen wurde und ihre Größe größer als Null ist, gib den Zeiger auf das einzelne Objekt in der Liste zurück, das sich bei Index 0 befindet. Andernfalls wird NULL zurückgegeben — das Objekt konnte nicht erhalten werden.

Aus der Ereignisbehandlung entfernen wir den Codeblock, der für die Behandlung von grafischen Objektereignissen verantwortlich ist. Verschieben wir ihn in die Ereignisbehandlung des EA:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         if(obj==NULL)
            return;
         //--- Get the name of the renamed graphical object on the chart, which is not in the collection list
         string name_new=this.FindExtraObj(chart_id);
         //--- Send an event with the old name of an object to the control program chart and
         //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),0,obj.Name());
         obj.SetName(name_new);
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj.PropertiesRefresh();
      obj.PropertiesCheckChanged();
     }
//--- Handle standard graphical object events
   if(idx>GRAPH_OBJ_EVENT_NO_EVENT && idx<GRAPH_OBJ_EVENTS_NEXT_CODE)
     {
      //--- Depending on the event type, display an appropriate message in the journal
      switch(idx)
        {
         case GRAPH_OBJ_EVENT_CREATE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_CHANGE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_RENAME   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_DELETE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE));
           break;
         case GRAPH_OBJ_EVENT_DEL_CHART:
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART),": ChartID: ",lparam,", ChartSymbol: ",sparam);
           break;
         default:
           break;
        }
     }
  }
//+------------------------------------------------------------------+

In der Ereignisbehandlung selbst ist die Logik für das Senden der Nachricht zur Umbenennung des grafischen Objekts festzulegen. Wenn ein neuer Name für das Objekt festgelegt wurde, sende das Ereignis an das Kontrollprogramm Chart:

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


Die Methode, die den Verlauf der Objektumbenennung im Journal anzeigt:

//+------------------------------------------------------------------+
//| Display the history of renaming the object to the journal        |
//+------------------------------------------------------------------+
void CGraphElementsCollection::PrintRenameHistory(const string name,const long chart_id)
  {
   CGStdGraphObj *obj=this.GetStdGraphObject(name,chart_id);
   if(obj==NULL)
      return;
   obj.PrintRenameHistory();
  }
//+------------------------------------------------------------------+

Die Methode erhält den Namen des grafischen Objekts und die ID des Charts, auf dem es sich befindet.
Wir erhalten den Zeiger auf das Objekt anhand des Namens und der ID des Charts und rufen seine Methode, die den Verlauf der Objektumbenennung im Journal anzeigt, unter Verwendung der zuvor betrachteten Methode PrintRenameHistory() der abstrakten grafischen Standardobjektklasse.

Wir fügen der Hauptklasse der CEngine Bibliothek einige Methoden hinzu, um die Arbeit mit dem Programm zu erleichtern.

Wir öffnen \MQL5\Include\DoEasy\Engine.mqh und fügen die Methode hinzu, die die Liste der entfernten grafischen Objekte zurückgibt, die Methode, die die Anzahl der entfernten grafischen Objekte zurückgibt und die Methode, die die Größe des angegebenen Eigenschaftsarrays 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 (1) collection of graphical objects and (2) the list of removed objects
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects;                       }
   CArrayObj           *GetListDeletedObj(void)                         { return this.m_graph_objects.GetListDeletedObj();    }
//--- Return (1) the number of removed graphical objects and (2) the size of the property array
   int                  TotalDeletedGraphObjects(void)                  { return this.GetListDeletedObj().Total();            }
   int                  GraphGetSizeProperty(const string name,const long chart_id,const int prop)
                          {
                           return this.m_graph_objects.GetSizeProperty(name,chart_id,prop);
                          }
   
//--- Fill in the array with IDs of the charts opened in the terminal

Diese Methoden geben das Ergebnis zurück, das von den gleichnamigen Methoden der grafischen Objektkollektion geliefert wird. Ich glaube, mehr Details sind hier nicht nötig.


Test

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

Alles, was wir tun müssen, ist den Codeblock für die Behandlung von Ereignissen von grafischen Objekten, der aus dem Handler von Ereignissen der Klasse der grafischen Objekte der Kollektion entfernt wurde, zum Handler OnChartEvent() des EA hinzuzufügen:

//+------------------------------------------------------------------+
//| 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();
           }
         //--- Re-draw the chart
         ChartRedraw();
        }
     }
   */
   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         long array[];
         engine.GraphGetArrayChartsID(array);
         for(int i=0;i<ArraySize(array);i++)
            engine.CreateLineVertical(array[i],"LineVertical",0,time);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);

//--- Handle standard graphical object events
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   CGStdGraphObj *obj=NULL;
   if(idx>GRAPH_OBJ_EVENT_NO_EVENT && idx<GRAPH_OBJ_EVENTS_NEXT_CODE)
     {
      CChartObjectsControl *chart_ctrl=NULL;
      int end=0;
      string evn="";
      //--- Depending on the event type, display an appropriate message in the journal
      switch(idx)
        {
         //--- Graphical object creation event
         case GRAPH_OBJ_EVENT_CREATE   :
           //--- Display the message about creating a new graphical object
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE),":");
           //--- Get the pointer to the object by chart name and ID passed to sparam and lparam, respectively
           //--- and display the short description of a newly created object to the journal
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              obj.PrintShort();
             }
           break;
         //--- Event of changing the graphical object property
         case GRAPH_OBJ_EVENT_CHANGE   :
           //--- Display the message about changing the graphical object property
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE),":");
           //--- Get the pointer to the object by chart name and ID passed to sparam and lparam, respectively
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              //--- Display a short description of the changed object in the journal
              obj.PrintShort();
              //--- calculate the code of the changed property passed to dparam and get the property description
              if(dparam<GRAPH_OBJ_PROP_INTEGER_TOTAL)
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_INTEGER)dparam);
              else if(dparam<GRAPH_OBJ_PROP_DOUBLE_TOTAL)
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_DOUBLE)dparam);
              else
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_STRING)dparam);
              //--- Display the description of the graphical object's changed property in the journal
              Print(DFUN,evn);
             }
           break;
         //--- Graphical object renaming event
         case GRAPH_OBJ_EVENT_RENAME   :
           //--- Display the message about renaming the graphical object
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME));
           //--- Get the pointer to the object by chart name and ID passed to sparam and lparam, respectively
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              //--- Display the previous and new object name, as well as its entire renaming history, in the journal
              Print(DFUN,obj.GetProperty(GRAPH_OBJ_PROP_NAME,obj.Properties().CurrSize(GRAPH_OBJ_PROP_NAME)-1)," >>> ",obj.GetProperty(GRAPH_OBJ_PROP_NAME,0));
              obj.PrintRenameHistory();
             }
           break;
         //--- Graphical object deletion event
         case GRAPH_OBJ_EVENT_DELETE   :
           //--- Display the message about removing the graphical object
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE),":");
           //--- Get the pointer to the removed object by chart name and ID passed to sparam and lparam, respectively
           //--- and display a short description of the removed object in the journal
           obj=engine.GetGraphicObjCollection().GetStdDelGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              obj.PrintShort();
             }
           break;
         //--- Event of removing the graphical object together with the chart window
         case GRAPH_OBJ_EVENT_DEL_CHART:
           //--- Display the message about removing graphical objects together with the chart window, whose ID and symbol are passed to lparam and sparam
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART),": #",lparam,", ",sparam,":");
           //--- Calculate the end value for the loop by the list of removed graphical objects
           end=engine.TotalDeletedGraphObjects()-(int)dparam;
           if(end<0)
              end=0;
           //--- In the loop from the end of the removed graphical objects list up to the value calculated in the 'end' variable,
           for(int i=engine.TotalDeletedGraphObjects()-1;i>=end;i--)
             {
              //--- get the next removed graphical object from the list
              obj=engine.GetListDeletedObj().At(i);
              if(obj==NULL)
                 continue;
              //--- and display its brief description in the journal
              obj.PrintShort();
             }
           break;
         //---
         default:
           break;
        }
     }
  }
//+------------------------------------------------------------------+

Die Logik des hinzugefügten Codeblocks ist sehr detailliert beschrieben. Ich glaube, es ist nicht nötig, hier darauf einzugehen.
Wenn Sie Fragen haben, können Sie diese gerne in den Kommentaren unten stellen.

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


Wie wir sehen können, wird die Umbenennung des Objekts in seinem "Speicher" gespeichert.

Auch das Löschen von grafischen Objekten im Paket wird korrekt gehandhabt:


Beachten Sie, dass nicht alle grafischen Objekte, die zusammen mit dem Chart entfernt wurden, in der Liste der grafischen Kollektion korrekt wiederhergestellt werden, nachdem das zuvor entfernte Chart-Fenster wiederhergestellt wurde. Ich habe den Grund für das Überspringen von Objekten noch nicht gefunden. Ich werde dies aber auf jeden Fall beheben.

Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit an grafischen Objektereignissen fortsetzen und mit der Implementierung der Funktionalität zum Speichern der Änderungshistorie von Objekteigenschaften in den Objekteigenschaften beginnen.

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

Zurück zum Inhalt

*Frühere Artikel dieser Serie:

Grafiken in der Bibliothek DoEasy (Teil 86): Grafische Objektkollektion - Verwaltung der Eigenschaftsänderungen
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 88): Grafische Objektkollektion — zweidimensionales dynamisches Array zur Speicherung der sich dynamisch ändernden Objekteigenschaften
Grafiken in der Bibliothek DoEasy (Teil 89): Programmieren von grafischen Standardobjekten, grundlegende Funktionsweise
Grafiken in der DoEasy-Bibliothek (Teil 90): Standard-Ereignisse für grafische Objekte. grundlegende Funktionsweise

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

Beigefügte Dateien |
MQL5.zip (4181.64 KB)
Lernen Sie, wie man verschiedene Systeme mit gleitenden Durchschnitten entwirft Lernen Sie, wie man verschiedene Systeme mit gleitenden Durchschnitten entwirft
Kurzbeschreibung: In diesem Artikel lernen wir, wie man verschiedene Systeme des gleitenden Durchschnitts nach unterschiedlichen Strategien des gleitenden Durchschnitts entwickelt.
Verbesserte Erkennung von Kerzenmustern am Beispiel des Doji Verbesserte Erkennung von Kerzenmustern am Beispiel des Doji
Wie kann man mehr Kerzenmuster als üblich finden? Hinter der Einfachheit von Kerzenmustern verbirgt sich auch ein schwerwiegender Nachteil, der durch die Nutzung der erheblich erweiterten Möglichkeiten moderner Handelsautomatisierungs-Tools beseitigt werden kann.
Lernen Sie, wie man ein Handelssystem mit Hilfe der Bollinger Bänder entwickelt Lernen Sie, wie man ein Handelssystem mit Hilfe der Bollinger Bänder entwickelt
In diesem Artikel lernen wir die Bollinger Bänder kennen, einen der beliebtesten Indikatoren in der Handelswelt. Wir werden die technische Analyse betrachten und sehen, wie man ein algorithmisches Handelssystem auf der Grundlage des Bollinger Bänder Indikators entwickelt.
Matrizen und Vektoren in MQL5 Matrizen und Vektoren in MQL5
Durch die Verwendung der speziellen Datentypen 'matrix' und 'vector' ist es möglich, Code zu erstellen, der der mathematischen Notation sehr nahe kommt. Mit diesen Methoden müssen Sie keine verschachtelten Schleifen erstellen oder auf die korrekte Indizierung von Arrays in Berechnungen achten. Die Verwendung von Matrix- und Vektormethoden erhöht daher die Zuverlässigkeit und Geschwindigkeit bei der Entwicklung komplexer Programme.