
Grafiken in der Bibliothek DoEasy (Teil 87): Grafische Kollektion - Verwaltung der Änderungen von Eigenschaften von Objekten auf allen offenen Charts
Inhalt
- Konzept
- Verbesserung der Klassenbibliothek
- Indikator sendet Nachrichten über Änderungen von Objekteigenschaften auf allen Charts
- Behandlung von Indikatorsignalen über die Änderung von Eigenschaften von Objekten
- Test
- Was kommt als Nächstes?
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:
- die ID des Charts, auf dem er gestartet wird (nennen wir sie SourseID) und
- die ID des Charts, an den er nutzerdefinierte Ereignisse senden soll (DestinationID).
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).
Stellen Sie Ihre Fragen, Kommentare und Vorschläge in den Kommentaren.
*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





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.