Grafiken in der Bibliothek DoEasy (Teil 86): Grafische Objektkollektion - Verwaltung der Eigenschaftsänderungen
Inhalt
- Konzept
- Kontrolle der Änderung von Eigenschaften grafischer Objekte
- Kontrolle des Löschens von grafischen Objekten
- Test
- Was kommt als Nächstes?
Konzept
Im vorherigen Artikel habe ich die Möglichkeit implementiert, neu erstellte grafische Standardobjekte in der Kollektionsklasse für grafische Objekte zu speichern - wir erkennen neue Diagrammobjekte, erstellen Objekte von Klassen, die dem Objekttyp auf dem Diagramm entsprechen, und fügen sie der Kollektionsliste hinzu. Für eine umfassende Verwaltung der grafischen Objekte reicht dies jedoch nicht aus. Wir müssen alle Änderungen der Eigenschaften von grafischen Objekten in einem Chart sowie deren Löschung oder Umbenennung kontrollieren.
Da die Funktionen aus der Reihe ObjectGetXXX zum Auslesen der Eigenschaften grafischer Objekte verwendet werden, können wir nicht ständig die Werte jedes grafischen Objekts im Timer überprüfen, da diese Funktionen synchron sind. Das bedeutet, dass sie auf die Ausführung eines Befehls warten. Dies kann bei einer großen Anzahl von grafischen Objekten sehr ressourcenintensiv sein.
Hier stehen wir vor einer Wahl. Wir können entweder einen Timer verwenden, um jede Eigenschaft jedes grafischen Objekts zu überwachen, mit allen sich daraus ergebenden Konsequenzen für die Synchronizität der Funktionen zur Abfrage der Eigenschaften, oder wir können das Ereignismodell anwenden, indem wir in OnChartEvent() reagieren, was im Strategietester leider nicht funktioniert (wie Sie sich vielleicht erinnern, wird die Timer-Operation im Tester in der Bibliothek über die Funktionen OnTick() und OnCalculate() abgewickelt).
Nach Abwägung aller Vor- und Nachteile habe ich mich dafür entschieden, Änderungen an den Eigenschaften des grafischen Objekts im Diagramm-Event-Handler zu verfolgen. Mit anderen Worten, ich werde das Ereignismodell verwenden, was den Code vereinfacht, aber Einschränkungen für die Arbeit im Testprogramm mit sich bringt. Der Tester erlaubt es uns nicht, mit grafischen Objekten zu arbeiten (zumindest im Moment). Wir können sie nicht dem Fenster des Testers hinzufügen und ihre Eigenschaften nachträglich ändern. Das bedeutet, dass wir grafische Objekte nur auf "Live"-Charts behandeln sollten, auf denen die Ereignisbehandlung arbeitet.
In diesem Artikel werde ich eine Testversion der Behandlung von Ereignissen für grafische Objekte nur für das aktuelle Chart implementieren (dasjenige, auf dem das Programm gestartet wird). Sobald ich sicher bin, dass alles korrekt funktioniert, werde ich die vollwertige Ereignisbehandlung für jedes geöffnete Chart entwickeln. Die Ereignisbehandlung wird die Ereignisse an das Hauptchart des Programms senden, wo die Bibliothek sie sammelt und in ihrer grafischen Objektkollektion verarbeitet.
Kontrolle der Änderung von Eigenschaften grafischer Objekte
In OnChartEvent() sind wir an den folgenden Ereignissen interessiert:
- CHARTEVENT_OBJECT_CREATE — erstellt ein grafisches Objekt (wenn CHART_EVENT_OBJECT_CREATE=true auf einem Chart);
- CHARTEVENT_OBJECT_CHANGE - Objekteigenschaften über den Eigenschaftsdialog ändern;
- CHARTEVENT_OBJECT_DELETE — löscht ein grafisches Objekt (wenn CHART_EVENT_OBJECT_DELETE=true auf einem Chart);
- CHARTEVENT_OBJECT_DRAG — Ziehen eines grafischen Objekts mit der Maus.
Ich habe das Ereignis zur Erstellung eines grafischen Objekts im vorherigen Artikel vorbereitet, ohne OnChartEvent() aufzurufen.
Wir brauchen das Ereignis zum Ändern der Eigenschaften des grafischen Objekts über seinen Eigenschaftsdialog am Terminal, um die Änderungen der Objekteigenschaften manuell zu steuern.
Wir haben bereits das Ereignis zum Löschen von grafischen Objekten — die Bibliothek verfolgt die Anzahl der grafischen Objekte auf allen Terminal-Charts und hat Ereignisflags für jeden geöffneten Chart — wenn die Anzahl der Chart-Objekte abnimmt, können wir die Anzahl der aus dem Chart entfernten Objekte herausfinden und die Situation behandeln.
Das Ereignis "Verschieben des grafischen Objekts" benötigen wir, um Änderungen der Position des grafischen Objekts insgesamt und seiner einzelnen Ankerpunkte im Besonderen zu steuern.
Das Ereignis "Verschieben" wird auch aktiviert, wenn ein Objekt manuell erstellt wird. In dem Moment, in dem wir auf das Chart klicken, um ein Objekt zu setzen, und die Maustaste noch nicht losgelassen wurde, ist das Objekt bereits erstellt und die Bibliothek kann es erkennen, indem sie das entsprechende Klassenobjekt erstellt und es der Kollektion hinzufügt. Nicht alle Eigenschaften des Objekts werden korrekt gesetzt. Die Maustaste ist noch nicht losgelassen worden, und wir können das Objekt verschieben oder seine verbleibenden Ankerpunkte setzen, wenn das Objekt mehrere Punkte verwendet. Aber wenn ich die Maustaste loslasse, wird das grafische Objektbewegungsereignis erzeugt, vorausgesetzt, dass alle Objektankerpunkte bereits gesetzt sind. Indem wir das Ereignis verfolgen und die Eigenschaftswerte des bereits erstellten Klassenobjekts entsprechend den vollständig gesetzten Parametern eines erstellten grafischen Objekts ändern, setzen wir die korrekten Werte aller Eigenschaften des neu erstellten Objekts.
Die Änderung des Objektnamens bringt drei Ereignisse auf einmal mit sich — das Entfernen des Objekts, die Erstellung des Objekts und die Änderung der Eigenschaften des Objekts. Diese drei Ereignisse können nachverfolgt werden, um die Änderung des Namens eines der vorhandenen Objekte zu erkennen. Ich werde jedoch einen einfacheren Ansatz verwenden. Wenn wir einen Objektnamen ändern, wird das Ereignis CHARTEVENT_OBJECT_CHANGE immer als letztes behandelt. Da alle Terminalobjekte anhand ihres Namens und ihrer Chart-ID ausgewählt werden, können wir prüfen, welches der in einem Chart vorhandenen Objekte nicht mehr in der Kollektion enthalten ist. Dann suchen wir den Namen des Objekts auf dem Chart, für das das Klassenobjekt in der Kollektion fehlt (1), suchen das Objekt, für das es kein Chart-Objekt mit entsprechendem Namen gibt (2) und fügen diesen Namen dem in der Kollektion gefundenen Klassenobjekt (1) hinzu. Dies mag ziemlich kompliziert erscheinen. Aber eigentlich ist alles ganz einfach.
Um zu verstehen, welche Objekteigenschaft geändert wurde (da wir das Ereignis mit dem Namen des geänderten Objekts, aber ohne Angabe der geänderten Eigenschaft erhalten), müssen wir die aktuellen Werte aller Objekteigenschaften mit denen vergleichen, die vor dem Empfang des Ereignisses zur Änderung der Objekteigenschaften vorhanden waren. Daher müssen wir drei weitere Arrays erstellen, um "frühere" Objekteigenschaften zu speichern.
Wir öffnen die Datei \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh der abstrakten grafischen Standard-Objektklasse und fügen im privaten Abschnitt neue Arrays zum Speichern "vorheriger" Objekteigenschaften hinzu:
//+------------------------------------------------------------------+ //| The class of the abstract standard graphical object | //+------------------------------------------------------------------+ class CGStdGraphObj : public CGBaseObj { private: long m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL]; // String properties long m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL]; // Integer properties before change double m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL]; // Real properties before change string m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL]; // String properties before change //--- Return the index of the array the (1) double and (2) string properties are actually located at int IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property) const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_DOUBLE_TOTAL; } public:
Im privaten Abschnitt der Klasse legen wir die Methoden zum Setzen und Zurückgeben von "vorherigen" Objekteigenschaften fest:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Set object's previous (1) integer, (2) real and (3) string properties void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop_prev[property]=value; } void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value){ this.m_double_prop_prev[this.IndexProp(property)]=value;} void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,string value){ this.m_string_prop_prev[this.IndexProp(property)]=value;} //--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array long GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property) const { return this.m_long_prop_prev[property]; } double GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return this.m_double_prop_prev[this.IndexProp(property)]; } string GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property) const { return this.m_string_prop_prev[this.IndexProp(property)]; } //--- Return itself CGStdGraphObj *GetObject(void) { return &this;}
Wir haben die Eigenschaft "Objekt-ID" für das Klassenobjekt, das ein grafisches Objekt beschreibt. Mit dieser Eigenschaft können wir eine eindeutige Objektbezeichnung festlegen, die es uns ermöglicht, das Objekt zu identifizieren.
Diese Eigenschaft sollte nicht in die Definition der Objektereignisse einbezogen werden. Deshalb werden wir in der Methode zum Setzen der Objekt-ID den Wert, der der Methode übergeben wird, in beide Eigenschaften auf einmal setzen — die aktuelle (bereits entwickelte) und die vorherige (jetzt hinzuzufügende):
public: //+--------------------------------------------------------------------+ //|Methods of simplified access and setting graphical object properties| //+--------------------------------------------------------------------+ //--- Object index in the list int Number(void) const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM); } void SetNumber(const int number) { this.SetProperty(GRAPH_OBJ_PROP_NUM,number); } //--- Object ID long ObjectID(void) const { return this.GetProperty(GRAPH_OBJ_PROP_ID); } void SetObjectID(const long obj_id) { CGBaseObj::SetObjectID(obj_id); this.SetProperty(GRAPH_OBJ_PROP_ID,obj_id); this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,obj_id); } //--- Graphical object type
Der Algorithmus für die Reaktion auf ein Ereignis sieht folgendermaßen aus: Nach dem Empfang eines Ereignisses müssen wir alle Daten des Klassenobjekts, das das grafische Objekt beschreibt, aktualisieren, damit alle seine Eigenschaften die entsprechenden Werte haben. Da wir nicht wissen, welche Eigenschaft sich geändert hat, aktualisieren wir alle Objekteigenschaften und vergleichen die aktuellen Eigenschaften mit denen, die das Objekt vor dem Empfang des Objektänderungsereignisses hatte. Ich werde alle drei Arrays der Objekteigenschaften mit den entsprechenden Arrays der vorherigen Eigenschaften in drei Schleifen vergleichen. Beim Vergleich wird die Nachricht über die Änderung der Eigenschaft vorübergehend (da es sich bisher um eine Testversion handelt) an das Journal gesendet, wenn der aktuelle Eigenschaftswert nicht gleich dem vorherigen ist. Dasselbe gilt für jede festgestellte Differenz der verglichenen Werte in jedem Array der Objekteigenschaften. Später, bei der Implementierung der Kontrolle über die Änderungen der Eigenschaften für jeden der geöffneten Charts, werde ich eine weitere Methode einführen. Alle geänderten Eigenschaften werden an das Ereignisobjekt gesendet. Jedes dieser Objekte wird in die Kollektion aufgenommen, um die Änderungen der Eigenschaften der einzelnen Objekte in jedem geöffneten Chart zu melden.
Im öffentlichen Teil der Klasse deklarieren wir die Methode zum Überschreiben aller Eigenschaften des Objekts. Sie wird benötigt, um das Eigenschaftsänderungsereignis zu erkennen, um alle Eigenschaften eines grafischen Objekts auf einmal durchzugehen und sie in die Objekteigenschaften der Klasse einzutragen.
Die Methode, die die Änderungen in den Objekteigenschaften überprüft, vergleicht alle aktuellen Objekteigenschaften mit ihrem vorherigen Zustand.
Im privaten Teil der Klasse deklarieren wir drei Methoden, um alle Eigenschaften vom grafischen Objekt zu erhalten und in die Klassenobjekteigenschaften zu setzen.
Die Methode Kopieren der aktuellen Eigenschaften in die vorherigen ermöglicht den Vergleich der Eigenschaften mit den geänderten bei der nächsten Prüfung, wenn ein Ereignis erkannt wird:
//--- Return the description of the object visibility on timeframes string VisibleOnTimeframeDescription(void); //--- Re-write all graphical object properties void PropertiesRefresh(void); //--- Check object property changes void PropertiesCheckChanged(void); private: //--- Get and save (1) integer, (2) real and (3) string properties void GetAndSaveINT(void); void GetAndSaveDBL(void); void GetAndSaveSTR(void); //--- Copy the current data to the previous one void PropertiesCopyToPrevData(void); }; //+------------------------------------------------------------------+
Lassen Sie uns den geschützten parametrischen Konstruktor vereinfachen. Zuvor erhielt er alle Eigenschaften, die allen grafischen Objekten eigen sind, vom Objekt und setzte sie im Klassenobjekt:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_GROUP group, const long chart_id,const string name) { //--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits this.m_type=obj_type; CGBaseObj::SetChartID(chart_id); CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type)); CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD); CGBaseObj::SetBelong(belong); CGBaseObj::SetGroup(group); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save integer properties //--- properties inherent in all graphical objects but not present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID] = CGBaseObj::ChartID(); // Chart ID this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM] = CGBaseObj::SubWindow(); // Chart subwindow index this.m_long_prop[GRAPH_OBJ_PROP_TYPE] = CGBaseObj::TypeGraphObject(); // Graphical object type (ENUM_OBJECT) this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.m_long_prop[GRAPH_OBJ_PROP_BELONG] = CGBaseObj::Belong(); // Graphical object affiliation this.m_long_prop[GRAPH_OBJ_PROP_GROUP] = CGBaseObj::Group(); // Graphical object group this.m_long_prop[GRAPH_OBJ_PROP_ID] = 0; // Object ID this.m_long_prop[GRAPH_OBJ_PROP_NUM] = 0; // Object index in the list //--- Properties inherent in all graphical objects and present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME] = ::ObjectGetInteger(chart_id,name,OBJPROP_CREATETIME); // Object creation time this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES] = ::ObjectGetInteger(chart_id,name,OBJPROP_TIMEFRAMES); // Object visibility on timeframes this.m_long_prop[GRAPH_OBJ_PROP_BACK] = ::ObjectGetInteger(chart_id,name,OBJPROP_BACK); // Background object this.m_long_prop[GRAPH_OBJ_PROP_ZORDER] = ::ObjectGetInteger(chart_id,name,OBJPROP_ZORDER); // Priority of a graphical object for receiving the event of clicking on a chart this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN] = ::ObjectGetInteger(chart_id,name,OBJPROP_HIDDEN); // Disable displaying the name of a graphical object in the terminal object list this.m_long_prop[GRAPH_OBJ_PROP_SELECTED] = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTED); // Object selection this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE] = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTABLE); // Object availability this.m_long_prop[GRAPH_OBJ_PROP_TIME] = ::ObjectGetInteger(chart_id,name,OBJPROP_TIME); // First point time coordinate this.m_long_prop[GRAPH_OBJ_PROP_COLOR] = ::ObjectGetInteger(chart_id,name,OBJPROP_COLOR); // Color this.m_long_prop[GRAPH_OBJ_PROP_STYLE] = ::ObjectGetInteger(chart_id,name,OBJPROP_STYLE); // Style this.m_long_prop[GRAPH_OBJ_PROP_WIDTH] = ::ObjectGetInteger(chart_id,name,OBJPROP_WIDTH); // Line width //--- Properties belonging to different graphical objects this.m_long_prop[GRAPH_OBJ_PROP_FILL] = 0; // Object color filling this.m_long_prop[GRAPH_OBJ_PROP_READONLY] = 0; // Ability to edit text in the Edit object this.m_long_prop[GRAPH_OBJ_PROP_LEVELS] = 0; // Number of levels this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR] = 0; // Level line color this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE] = 0; // Level line style this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH] = 0; // Level line width this.m_long_prop[GRAPH_OBJ_PROP_ALIGN] = 0; // Horizontal text alignment in the Edit object (OBJ_EDIT) this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE] = 0; // Font size this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT] = 0; // Ray goes to the left this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT] = 0; // Ray goes to the right this.m_long_prop[GRAPH_OBJ_PROP_RAY] = 0; // Vertical line goes through all windows of a chart this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE] = 0; // Display the full ellipse of the Fibonacci Arc object this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE] = 0; // Arrow code for the "Arrow" object this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR] = 0; // Position of the binding point of the graphical object this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE] = 0; // Distance from the base corner along the X axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE] = 0; // Distance from the base corner along the Y axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION] = 0; // Gann object trend this.m_long_prop[GRAPH_OBJ_PROP_DEGREE] = 0; // Elliott wave marking level this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES] = 0; // Display lines for Elliott wave marking this.m_long_prop[GRAPH_OBJ_PROP_STATE] = 0; // Button state (pressed/released) this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID] = 0; // Chart object ID (OBJ_CHART). this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD] = 0; // Chart object period< this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = 0; // Time scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE] = 0; // Price scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE] = 0; // Chart object scale this.m_long_prop[GRAPH_OBJ_PROP_XSIZE] = 0; // Object width along the X axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_YSIZE] = 0; // Object height along the Y axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET] = 0; // X coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET] = 0; // Y coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR] = 0; // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL this.m_long_prop[GRAPH_OBJ_PROP_CORNER] = 0; // Chart corner for binding a graphical object this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = 0; // Border type for "Rectangle border" this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR] = 0; // Border color for OBJ_EDIT and OBJ_BUTTON //--- Save real properties this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)] = ::ObjectGetDouble(chart_id,name,OBJPROP_PRICE); // Price coordinate this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)] = 0; // Level value this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)] = 0; // Scale (property of Gann objects and Fibonacci Arcs objects) this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)] = 0; // Angle this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)] = 0; // Deviation of the standard deviation channel //--- Save string properties this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_NAME)] = name; // Object name this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)] = ::ObjectGetString(chart_id,name,OBJPROP_TEXT); // Object description (the text contained in the object) this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)] = ::ObjectGetString(chart_id,name,OBJPROP_TOOLTIP);// Tooltip text this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)] = ""; // Level description this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)] = ""; // Font this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)] = ""; // BMP file name for the "Bitmap Level" object this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]= ""; // Chart object symbol //--- Save basic properties in the parent object this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN); this.m_name=this.GetProperty(GRAPH_OBJ_PROP_NAME); } //+-------------------------------------------------------------------+
Nun werden alle diese Zeilen in separate Methoden verschoben, die alle von der Methode PropertiesRefresh() aufgerufen werden sollen.
Daher entfernen wir diese Zeilen. Der Konstruktor sieht nun also wie folgt aus:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_GROUP group, const long chart_id,const string name) { //--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits this.m_type=obj_type; this.SetName(name); CGBaseObj::SetChartID(chart_id); CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type)); CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD); CGBaseObj::SetBelong(belong); CGBaseObj::SetGroup(group); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save integer properties //--- properties inherent in all graphical objects but not present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID] = CGBaseObj::ChartID(); // Chart ID this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM] = CGBaseObj::SubWindow(); // Chart subwindow index this.m_long_prop[GRAPH_OBJ_PROP_TYPE] = CGBaseObj::TypeGraphObject(); // Graphical object type (ENUM_OBJECT) this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.m_long_prop[GRAPH_OBJ_PROP_BELONG] = CGBaseObj::Belong(); // Graphical object affiliation this.m_long_prop[GRAPH_OBJ_PROP_GROUP] = CGBaseObj::Group(); // Graphical object group this.m_long_prop[GRAPH_OBJ_PROP_ID] = 0; // Object ID this.m_long_prop[GRAPH_OBJ_PROP_NUM] = 0; // Object index in the list //--- Save the properties inherent in all graphical objects and present in a graphical object this.PropertiesRefresh(); //--- Save basic properties in the parent object this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN); //--- Save the current properties to the previous ones this.PropertiesCopyToPrevData(); } //+-------------------------------------------------------------------+
Damit die Methode PropertiesRefresh() korrekt funktioniert, sollte sie den Namen des grafischen Objekts kennen, von dem sie die Daten erhalten soll. Bisher wurde der Name fast ganz am Ende geschrieben — im Block zum Lesen von String-Parametern. Jetzt wird der Name des grafischen Objekts gelesen und sofort beim Eintritt in den Konstruktor in die Eigenschaften des Klassenobjekts gesetzt. Nach dem Hinzufügen aller Eigenschaften zum Klassenobjekt ist die Methode PropertiesCopyToPrevData() aufzurufen, die bereits alle gespeicherten Objekteigenschaften auf die Arrays der "vorherigen" Eigenschaften setzt, um deren Änderungen zu kontrollieren.
Die Methoden empfangen die Eigenschaften der Typen integer, double und string vom grafischen Objekt und speichern sie im Klassenobjekt:
//+------------------------------------------------------------------+ //| Get and save the integer properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveINT(void) { //--- Properties inherent in all graphical objects and present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME); // Object creation time this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES); // Object visibility on timeframes this.m_long_prop[GRAPH_OBJ_PROP_BACK] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK); // Background object this.m_long_prop[GRAPH_OBJ_PROP_ZORDER] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER); // Priority of a graphical object for receiving the event of clicking on a chart this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN); // Disable displaying the name of a graphical object in the terminal object list this.m_long_prop[GRAPH_OBJ_PROP_SELECTED] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED); // Object selection this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE); // Object availability this.m_long_prop[GRAPH_OBJ_PROP_TIME] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME); // First point time coordinate this.m_long_prop[GRAPH_OBJ_PROP_COLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR); // Color this.m_long_prop[GRAPH_OBJ_PROP_STYLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE); // Style this.m_long_prop[GRAPH_OBJ_PROP_WIDTH] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH); // Line width //--- Properties belonging to different graphical objects this.m_long_prop[GRAPH_OBJ_PROP_FILL] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL); // Fill an object with color this.m_long_prop[GRAPH_OBJ_PROP_READONLY] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY); // Ability to edit text in the Edit object this.m_long_prop[GRAPH_OBJ_PROP_LEVELS] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS); // Number of levels this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR); // Level line color this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE); // Level line style this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH); // Level line width this.m_long_prop[GRAPH_OBJ_PROP_ALIGN] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN); // Horizontal text alignment in the Edit object (OBJ_EDIT) this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE); // Font size this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT); // Ray goes to the left this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT); // Ray goes to the right this.m_long_prop[GRAPH_OBJ_PROP_RAY] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY); // Vertical line goes through all windows of a chart this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE); // Display the full ellipse of the Fibonacci Arc object this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE); // Arrow code for the "Arrow" object this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR); // Position of the binding point of the graphical object this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE); // Distance from the base corner along the X axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE); // Distance from the base corner along the Y axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION); // Gann object trend this.m_long_prop[GRAPH_OBJ_PROP_DEGREE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE); // Elliott wave marking level this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES); // Display lines for Elliott wave marking this.m_long_prop[GRAPH_OBJ_PROP_STATE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE); // Button state (pressed/released) this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID); // Chart object ID (OBJ_CHART). this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD); // Chart object period this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE); // Time scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE);// Price scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE);// Chart object scale this.m_long_prop[GRAPH_OBJ_PROP_XSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE); // Object width along the X axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_YSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE); // Object height along the Y axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET); // X coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET); // Y coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR); // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL this.m_long_prop[GRAPH_OBJ_PROP_CORNER] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER); // Chart corner for binding a graphical object this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE);// Border type for "Rectangle border" this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR);// Border color for OBJ_EDIT and OBJ_BUTTON } //+------------------------------------------------------------------+ //| Get and save the real properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveDBL(void) { this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE); // Price coordinate this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE); // Level value this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE); // Scale (property of Gann objects and Fibonacci Arcs objects) this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE); // Corner this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION); // Deviation of the standard deviation channel } //+------------------------------------------------------------------+ //| Get and save the string properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveSTR(void) { this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT); // Object description (the text contained in the object) this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP); // Tooltip text this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT);// Level description this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT); // Font this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE); // BMP file name for the "Bitmap Level" object this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL); // Chart object symbol } //+------------------------------------------------------------------+
Die Zeilen im Klassenkonstruktor, die durch die Methode zum Überschreiben aller grafischen Objekteigenschaften ersetzt wurden, sind in diese drei Methoden verschoben worden:
//+------------------------------------------------------------------+ //| Overwrite all graphical object properties | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesRefresh(void) { this.GetAndSaveINT(); this.GetAndSaveDBL(); this.GetAndSaveSTR(); } //+------------------------------------------------------------------+
Alle drei oben genannten Methoden werden hier nacheinander aufgerufen.
Die Methode kopiert die Eigenschaften des aktuellen Klassenobjekts in das vorherige:
//+------------------------------------------------------------------+ //| Copy the current data to the previous one | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCopyToPrevData(void) { ::ArrayCopy(this.m_long_prop_prev,this.m_long_prop); ::ArrayCopy(this.m_double_prop_prev,this.m_double_prop); ::ArrayCopy(this.m_string_prop_prev,this.m_string_prop); } //+------------------------------------------------------------------+
Hier kopieren wir die Arrays mit den Eigenschaftstypen integer, double und string nacheinander in die entsprechenden Arrays der vorherigen Eigenschaften, indem wir die Array-Kopierfunktion verwenden.
Die Methode prüft die Änderungen der Eigenschaften des Objekts:
//+------------------------------------------------------------------+ //| Check object property changes | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCheckChanged(void) { bool changed=false; int beg=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } beg=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } beg=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } if(changed) PropertiesCopyToPrevData(); } //+------------------------------------------------------------------+
Hier wird in drei Schleifen (für die Eigenschaften integer, double und string getrennt) die nächste Eigenschaft aus dem entsprechenden Array geholt und mit der gleichen Eigenschaft im Array der vorherigen Eigenschaften verglichen. Wenn die verglichenen Werte der aktuellen und vorherigen Eigenschaften nicht gleich sind, hat sich die Eigenschaft geändert. Wir setzen das Flag für Änderungen in den Objekteigenschaften und zeigen die Meldung im Journal an.
Dies ist eine Testmethode, die nur dazu dient, das Konzept der Suche nach Änderungen von Objekteigenschaften zu überprüfen. In den kommenden Artikeln werde ich das Konzept verbessern, um es voll funktionsfähig zu machen. Ich werde Änderungen an allen grafischen Objekten auf allen geöffneten Charts verfolgen und Ereignisse zur Änderung von Objekteigenschaften an das Kontrollprogramm Chart senden, damit sie von der Bibliothek weiterverarbeitet werden.
Kontrolle des Löschens von grafischen Objekten
Die Änderung von grafischen Objekteigenschaften wird in der Objektklasse verfolgt, da die grafischen Objekteigenschaften zum Objekt gehören. Sie werden in der Objektklasse gesetzt und können dort überprüft werden. Alle Methoden zur Änderung von Eigenschaften eines Objekts und zur Überprüfung von Änderungen, die oben beschrieben wurden, werden jedoch von der Klasse der grafischen Objekte in der Kollektion aufgerufen. Im Gegensatz dazu kann das Hinzufügen und Entfernen von grafischen Objekten zu einem Chart nur in der Klasse der grafischen Objektkollektion nachverfolgt werden. Diese Klasse verwaltet die vollständige Liste aller Objekte auf allen geöffneten Charts und führt sie in ihrer eigenen Kollektion auf.
Wir öffnen die Datei der grafischen Objektsammlungsklasse \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh und nehmen darin alle notwendigen Verbesserungen vor.
In der Chart-Objektverwaltungsklasse, die sich in derselben Datei befindet, deklarieren wir die private Methode, die die Berechtigung zum Verfolgen von Maus- und Grafikobjekt-Ereignissen festlegt. Damit kann die Berechtigung zur Verfolgung solcher Ereignisse für jedes der geöffneten Charts festgelegt werden.
In den Klassenkonstruktoren rufen wir diese Methoden zum Setzen von Berechtigungen für Charts auf, für die die Steuerobjekte erstellt werden sollen. Am Ende des Klassenlistings deklarieren wir die Ereignisbehandlung (ich werde ihn im nächsten Artikel implementieren):
//+------------------------------------------------------------------+ //| Chart object management class | //+------------------------------------------------------------------+ class CChartObjectsControl : public CObject { private: CArrayObj m_list_new_graph_obj; // List of added graphical objects ENUM_TIMEFRAMES m_chart_timeframe; // Chart timeframe long m_chart_id; // Chart ID string m_chart_symbol; // Chart symbol bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_last_objects; // Number of graphical objects during the previous check int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check //--- Return the name of the last graphical object added to the chart string LastAddedGraphObjName(void); //--- Set the permission to track mouse events and graphical objects void SetMouseEvent(void); public: //--- Return the variable values ENUM_TIMEFRAMES Timeframe(void) const { return this.m_chart_timeframe; } long ChartID(void) const { return this.m_chart_id; } string Symbol(void) const { return this.m_chart_symbol; } bool IsEvent(void) const { return this.m_is_graph_obj_event; } int TotalObjects(void) const { return this.m_total_objects; } int Delta(void) const { return this.m_delta_graph_obj; } //--- Create a new standard graphical object CGStdGraphObj *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name); //--- Return the list of newly added objects CArrayObj *GetListNewAddedObj(void) { return &this.m_list_new_graph_obj;} //--- Check the chart objects void Refresh(void); //--- Constructors CChartObjectsControl(void) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=::ChartID(); this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.SetMouseEvent(); } CChartObjectsControl(const long chart_id) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=chart_id; this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.SetMouseEvent(); } //--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property) virtual int Compare(const CObject *node,const int mode=0) const { const CChartObjectsControl *obj_compared=node; return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0); } //--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+
Über den Klassenkörper hinaus implementieren wir die Methode zum Festlegen von Berechtigungen zum Verfolgen von Maus- und Grafikobjekt-Ereignissen:
//+------------------------------------------------------------------+ //| Set the permission | //| to track mouse and graphical object events for the chart | //+------------------------------------------------------------------+ void CChartObjectsControl::SetMouseEvent(void) { ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_MOVE,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_WHEEL,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_CREATE,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_DELETE,true); } //+------------------------------------------------------------------+
Konzentrieren wir uns nun auf die Klasse der grafischen Kollektion von Objekten.
Im privaten Bereich der Klasse implementieren wir neue Methoden, deren Funktionen in der Beschreibung klar angegeben sind:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ class CGraphElementsCollection : public CBaseObj { private: CArrayObj m_list_charts_control; // List of chart management objects CListObj m_list_all_canv_elm_obj; // List of all graphical elements on canvas CListObj m_list_all_graph_obj; // List of all graphical objects bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check //--- Return the flag indicating the graphical element object presence in the collection list of graphical elements bool IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj); //--- Return the flag indicating the graphical element object presence in the collection list of graphical objects bool IsPresentGraphObjInList(const long chart_id,const string name); //--- Return the flag indicating the presence of a graphical object on a chart by name bool IsPresentGraphObjOnChart(const long chart_id,const string name); //--- Return the pointer to the object of managing objects of the specified chart CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id); //--- Create a new object of managing graphical objects of a specified chart and add it to the list CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id); //--- Update the list of graphical objects by chart ID CChartObjectsControl *RefreshByChartID(const long chart_id); //--- Return the first free ID of the graphical (1) object and (2) element on canvas long GetFreeGraphObjID(void); long GetFreeCanvElmID(void); //--- Add a graphical object to the collection bool AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control); //--- Find an object present in the collection but not on a chart CGStdGraphObj *FindMissingObj(const long chart_id); //--- Find the graphical object present on a chart but not in the collection string FindExtraObj(const long chart_id); //--- Remove the graphical object from the graphical object collection list bool DeleteGraphObjFromList(CGStdGraphObj *obj); public:
Im öffentlichen Abschnitt der Klasse deklarieren wir die Methode, die ein grafisches Objekt mit dem Namen und der ID des Charts zurückgibt und den Chart Event Handler:
public: //--- Return itself CGraphElementsCollection *GetObject(void) { return &this; } //--- Return the full collection list of standard graphical objects "as is" CArrayObj *GetListGraphObj(void) { return &this.m_list_all_graph_obj; } //--- Return the full collection list of graphical elements on canvas "as is" CArrayObj *GetListCanvElm(void) { return &this.m_list_all_canv_elm_obj;} //--- Return the list of graphical elements by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } //--- Return the list of graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } //--- Return the number of new graphical objects, (3) the flag of the occurred change in the list of graphical objects int NewObjects(void) const { return this.m_delta_graph_obj; } bool IsEvent(void) const { return this.m_is_graph_obj_event; } //--- Return a graphical object by chart name and ID CGStdGraphObj *GetStdGraphObject(const string name,const long chart_id); //--- Constructor CGraphElementsCollection(); //--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes) virtual void Print(const bool full_prop=false,const bool dash=false); //--- Display a short description of the object in the journal virtual void PrintShort(const bool dash=false,const bool symbol=false); //--- Create the list of chart management objects and return the number of charts int CreateChartControlList(void); //--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag void Refresh(void); void Refresh(const long chart_id); //--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+
In der Methode, die die Liste aller grafischen Objekte aktualisiert, fügen wir den Block hinzu, der das Entfernen eines grafischen Objekts aus einem Chart behandelt:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { //--- Declare variables to search for charts long chart_id=0; int i=0; //--- In the loop by all open charts in the terminal (no more than 100) while(i<CHARTS_MAX) { //--- Get the chart ID chart_id=::ChartNext(chart_id); if(chart_id<0) break; //--- Get the pointer to the object for managing graphical objects //--- and update the list of graphical objects by chart ID CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); //--- If failed to get the pointer, move on to the next chart if(obj_ctrl==NULL) continue; //--- If the number of objects on the chart changes if(obj_ctrl.IsEvent()) { //--- If a graphical object is added to the chart if(obj_ctrl.Delta()>0) { //--- Get the list of added graphical objects and move them to the collection list //--- (if failed to move the object to the collection, move on to the next object) if(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id); if(obj!=NULL) { //--- Display a short description of a detected object deleted from a chart in the journal obj.PrintShort(); //--- Remove the class object of a removed graphical object from the collection list if(!this.DeleteGraphObjFromList(obj)) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST); } } //--- otherwise else { } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
In dieser Methode wird das Vorhandensein eines Ereignisses in jedem Kontrollobjekt in einer Schleife anhand der Gesamtzahl der Chart-Kontrollobjekte überprüft. Wenn das Ereignis vorhanden ist, prüfen wir den Wert, um den sich die Anzahl der Objekte auf dem Chart (verwaltet durch das Chart-Kontrollobjekt) geändert hat. Die Behandlung des Hinzufügens eines Objekts habe ich im vorherigen Artikel implementiert. Hier habe ich den Umgang mit dem negativen Wert einer Änderung der Anzahl der Chart-Objekte eingeführt.
Hier ist alles ganz einfach: Zuerst suchen wir nach einem Objekt in der Kollektion, das kein grafisches Objekt in einem Chart hat, und entfernen es aus der Kollektion.
Die Methode, die nach einem Objekt sucht, das in der Kollektion, aber nicht in einem Chart vorhanden ist:
//+------------------------------------------------------------------+ //|Find an object present in the collection but not on a chart | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id) { CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL); if(list==NULL) return NULL; for(int i=0;i<list.Total();i++) { CGStdGraphObj *obj=list.At(i); if(obj==NULL) continue; if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name())) return obj; } return NULL; } //+------------------------------------------------------------------+
Hier erhalten wir die Liste aller Objekte, deren Chart IDs gleich der in den Methodenparametern angegebenen sind.
In der Schleife durch die erhaltene Liste, holen wir das nächste Objekt der Standard-Grafikobjektklasse. Wenn das Chart kein Objekt mit einem solchen Namen hat, gib den Zeiger auf das Objekt zurück.
Nach Beendigung der Schleife Rückgabe von NULL.
Die Methode sucht nach einem Objekt, das in einem Chart vorhanden ist, aber nicht in der Kollektion:
//+------------------------------------------------------------------+ //|Find an object present on a chart but not in the collection | //+------------------------------------------------------------------+ string CGraphElementsCollection::FindExtraObj(const long chart_id) { int total=::ObjectsTotal(chart_id); for(int i=0;i<total;i++) { string name=::ObjectName(chart_id,i); if(!this.IsPresentGraphObjInList(chart_id,name)) return name; } return NULL; } //+------------------------------------------------------------------+
Hier, in der Schleife durch alle Objekte in der Terminal-Liste, erhalten wir den Namen des nächsten Objekts. Wenn es kein Objekt mit einem solchen Namen und einer solchen Chart-ID in der Kollektion-Liste gibt, gib den Namen des grafischen Objekts zurück. Nach Beendigung der Schleife wird NULL zurückgegeben.
Die Methode gibt das Flag zurück, das das Vorhandensein der grafischen Objektklasse in der Liste der grafischen Objekte der Kollektion anzeigt:
//+------------------------------------------------------------------------------+ //| Return the flag indicating the presence of the graphical object class object | //| in the graphical object collection list | //+------------------------------------------------------------------------------+ bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name) { CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL); return(list==NULL || list.Total()==0 ? false : true); } //+------------------------------------------------------------------+
Rufen Sie die Liste der Objekte mit der angegebenen Diagramm-ID ab. Wir holen uns den Zeiger auf das Objekt, dessen Name mit dem erforderlichen übereinstimmt, aus der erhaltenen Liste. Wenn die Liste nicht abgerufen werden konnte oder sie leer ist, geben wir false zurück — Objekt wurde nicht gefunden, andernfalls true .
Die Methode, die das Flag zurückgibt, das das Vorhandensein eines grafischen Objekts in einem Diagramm namentlich anzeigt:
//+----------------------------------------------------------------------------------+ //| Return the flag indicating the presence of a graphical object on a chart by name | //+----------------------------------------------------------------------------------+ bool CGraphElementsCollection::IsPresentGraphObjOnChart(const long chart_id,const string name) { int total=::ObjectsTotal(chart_id); for(int i=0;i<total;i++) if(::ObjectName(chart_id,i)==name) return true; return false; } //+-------------------------------------------------------------------+
In der Schleife durch alle grafischen Objekte, rufen wir den Namen des nächsten Objekts auf einem durch ID angegebenen Diagramm ab. Wenn der Name mit dem erforderlichen übereinstimmt, geben wir true zurück. Nach Beendigung der Schleife wird false zurückgegeben.
Die Methode zum Entfernen des grafischen Objekts aus der Liste der grafischen Objektsammlungen:
//+---------------------------------------------------------------------+ //|Remove the graphical object from the graphical object collection list| //+---------------------------------------------------------------------+ bool CGraphElementsCollection::DeleteGraphObjFromList(CGStdGraphObj *obj) { this.m_list_all_graph_obj.Sort(); int index=this.m_list_all_graph_obj.Search(obj); return(index==WRONG_VALUE ? false : this.m_list_all_graph_obj.Delete(index)); } //+------------------------------------------------------------------+
Die Methode erhält den Zeiger auf das Objekt, das aus der Liste entfernt werden soll.
Es wird das Flag für sortierte Listen auf die Liste gesetzt (die Suche wird nur in sortierten Listen durchgeführt) und es wird der Objektindex mit der Methode Search() der Standardbibliothek abgerufen.
Wenn der Objektindex nicht gefunden wird, wird false zurückgegeben, andernfalls wird das Ergebnis des Löschens des Objekts aus der Liste mit der Methode Delete() der Standardbibliothek zurückgegeben.
Die Methode gibt den Zeiger auf das grafische Objekt nach Chart-Name und ID zurück:
//+------------------------------------------------------------------+ //| Return a graphical object by chart name and ID | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id) { CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,chart_id); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL); return(list!=NULL && list.Total()>0 ? list.At(0) : NULL); } //+------------------------------------------------------------------+
Ermittelt die Liste der Objekte, deren Chart IDs mit der an die Methode übergebenen übereinstimmen. Wir holen uns die Liste, die das Objekt enthält, dessen Name mit dem erforderlichen übereinstimmt, aus der erhaltenen Liste (es sollte nur ein solches Objekt geben). Wenn es nicht gelingt, die Liste zu erhalten und sie nicht leer ist, gib den Zeiger auf das erste (und einzige) Objekt in der Liste zurück. Andernfalls wird NULL zurückgegeben.
Ereignisbehandlung.
In diesem Artikel werde ich die Testversion der Ereignisbehandlung implementieren, die nur die Ereignisse von grafischen Objekten im aktuellen Chart behandelt. Die Ereignisbehandlung wird auf die Ereignisse des Wechsels oder der Verschiebung von grafischen Objekten reagieren. Diese Methode ist ausreichend, um die oben genannten Ereignisse zu definieren. Außerdem beheben wir zusätzlich das Problem des unvollständigen Ausfüllens von Objekteigenschaften, die nicht mit einem Mausklick erstellt werden. Ich habe bereits erwähnt, dass der erste Klick auf ein Chart ein Objekterzeugungsereignis erzeugt und die Bibliothek sofort das entsprechende Ereignis erzeugt.
Gleichzeitig werden darin nicht alle Eigenschaften korrekt gesetzt, da das Objekt in mehreren Mausklicks aufgebaut wird. Nach Abschluss des Objektaufbaus wird das Bewegungsereignis erzeugt. Als Reaktion darauf werden die Eigenschaften des Objekts neu geschrieben (da wir das Ereignis in Echtzeit verfolgen wollen, aber bei der Erstellung eines Objekts mit mehreren Ankerpunkten erhalten wir auch ein Bewegungsereignis, das zur Aktualisierung der Eigenschaften des Objekts führt, so dass sie mit den richtigen Daten neu geschrieben werden).
Die gesamte Logik ist in den Kommentaren zum Methodencode beschrieben. Betrachten wir nun die Methode:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj=NULL; if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG) { //--- If the object, whose properties were changed or which was relocated, //--- is successfully received from the collection list by its name set in sparam obj=this.GetStdGraphObject(sparam,::ChartID()); if(obj!=NULL) { //--- Update the properties of the obtained object //--- and check their change obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed else { //--- Let's search the list for the object that is not on the chart obj=this.FindMissingObj(::ChartID()); if(obj==NULL) return; //--- Get the name of the renamed graphical object on the chart, which is not in the collection list string name_new=this.FindExtraObj(::ChartID()); //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- update the chart properties and check their change obj.SetName(name_new); obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } } } //+------------------------------------------------------------------+
Außerdem müssen wir von bibliotheksbasierten Programmen Zugriff auf die grafische Kollektion erhalten.
Dazu erstellen wir im Hauptobjekt der Bibliothek innerhalb von \MQL5\Include\DoEasy\Engine.mqh die Methode, die den Zeiger auf die Klasse der Kollektion der grafischen Objekte der Bibliothek zurückgibt:
//--- Launch the new pause countdown void Pause(const ulong pause_msc,const datetime time_start=0) { this.PauseSetWaitingMSC(pause_msc); this.PauseSetTimeBegin(time_start*1000); while(!this.PauseIsCompleted() && !::IsStopped()){} } //--- Return the graphical object collection CGraphElementsCollection *GetGraphicObjCollection(void) { return &this.m_graph_objects; } //--- Constructor/destructor CEngine(); ~CEngine(); private:
Test
Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\Part86\ als TestDoEasyPart86.mq5.
Aus OnInit() entfernen wir die Berechtigungen zum Festlegen von Zeichenfolgen zum Verfolgen von Mausereignissen:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'246,244,244'; // Original ≈pale gray array_clr[1]=C'249,251,250'; // Final ≈pale gray-green //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Jetzt werden die Berechtigungen zur Kontrolle von Maus- und Grafikobjektereignissen in der Verwaltungsklasse des Charts festgelegt. Die Berechtigungen werden für alle geöffneten Charts gesetzt.
Ganz am Ende von OnChartEvent() fügen wir den Aufruf des Handlers der Klasse Events der grafischen Objektsammlung hinzu:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) return; //--- If the mouse is moved if(id==CHARTEVENT_MOUSE_MOVE) { CForm *form=NULL; datetime time=0; double price=0; int wnd=0; //--- If Ctrl is not pressed, if(!IsCtrlKeyPressed()) { //--- clear the list of created form objects, allow scrolling a chart with the mouse and show the context menu list_forms.Clear(); ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true); ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,true); return; } //--- If X and Y chart coordinates are successfully converted into time and price, if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- get the bar index the cursor is hovered over int index=iBarShift(Symbol(),PERIOD_CURRENT,time); if(index==WRONG_VALUE) return; //--- Get the bar index by index CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index); if(bar==NULL) return; //--- Convert the coordinates of a chart from the time/price representation of the bar object to the X and Y coordinates int x=(int)lparam,y=(int)dparam; if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y)) return; //--- Disable moving a chart with the mouse and showing the context menu ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false); ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,false); //--- Create the form object name and hide all objects except one having such a name string name="FormBar_"+(string)index; HideFormAllExceptOne(name); //--- If the form object with such a name does not exist yet, if(!IsPresentForm(name)) { //--- create a new form object form=bar.CreateForm(index,name,x,y,114,16); if(form==NULL) return; //--- Set activity and unmoveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(C'47,70,59'); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(2,2,clr,200,3); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity()); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); //--- If failed to add the form object to the list, remove the form and exit the handler if(!list_forms.Add(form)) { delete form; return; } //--- Capture the form appearance form.Done(); } //--- If the form object exists, if(form!=NULL) { //--- draw a text with the bar type description on it and show the form. The description corresponds to the mouse cursor position form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21'); form.Show(); } //--- Redraw the chart ChartRedraw(); } } engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Das sind alle Verbesserungen. Kompilieren Sie den EA und starten Sie ihn auf dem Chart. Beim Anlegen/Löschen eines Objekts oder beim Ändern seiner Eigenschaften werden die entsprechenden Ereigniseinträge im Client-Terminal-Journal angezeigt:
Bisher sind dies nur eine Einträge im Journal, aber das wird sich später ändern.
Was kommt als Nächstes?
Im nächsten Artikel werde ich die Objektereignishandler für jedes geöffnete Diagramm erstellen und das Senden dieser Ereignisse an das Steuerprogrammdiagramm implementieren, damit das Programm sie vollständig verarbeiten kann.
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
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/10018
- 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.