Grafiken in der DoEasy-Bibliothek (Teil 95): Steuerelemente für zusammengesetzte grafische Objekte
Inhalt
- Konzept
- Verbesserung der Klassenbibliothek
- Erweiterte Hilfsklasse für Standard-Grafikobjekt
- Test
- Was kommt als Nächstes?
Konzept
In diesem Artikel werde ich die Entwicklung von zusammengesetzten grafischen Objekten fortsetzen. Dabei handelt es sich um Standard-Grafikobjekte, die aus mehreren Objekten bestehen und zu einem einzigen Grafikobjekt zusammengefasst werden. In der Bibliothek werden grafische Objekte, die in einem zusammengesetzten Objekt enthalten sind, als erweiterte Standard-Grafikobjekte definiert. Solche Objekte haben einige zusätzliche Eigenschaften und Funktionen, die es ihnen ermöglichen, andere grafische Objekte einzubinden und zu integrieren.
Das Konzept eines zusammengesetzten grafischen Objekts erfordert die Funktionsweise, das Objekt an dem Punkt zu halten, an dem es mit einem anderen Objekt verbunden ist, und seine Position anzupassen, wenn ein übergeordnetes Objekt geändert oder verschoben wird.
Im vorangegangenen Artikel habe ich mit der Erstellung der Handler für Ereignisse für zusammengesetzte grafische Objekte begonnen, die Handhabung des Entfernens eines zusammengesetzten grafischen Objekts implementiert und mit der Entwicklung des Handlers für die Verschiebung des Objekts begonnen.
Heute werde ich ein wenig vom Verschieben eines zusammengesetzten grafischen Objekts abweichen und den Handler für Änderungsereignisse in einem Chart mit einem zusammengesetzten grafischen Objekt implementieren. Außerdem werde ich mich auf die Steuerelemente für die Verwaltung eines zusammengesetzten grafischen Objekts konzentrieren.
Warum wohl? Ich werde die Echtzeit-Erstellung von zusammengesetzten grafischen Objekten implementieren, indem ich ein untergeordnetes Objekt an ein Basisobjekt anhänge, indem ich das untergeordnete Objekt auf das Basisobjekt ziehe. Das grafische Basisobjekt erkennt, wenn ein anderes Objekt mit der Maus auf es gezogen wird. Der Mechanismus zum Anhängen von Objekten wird in einem bestimmten Abstand von einem seiner Chart-Ankerpunkte aktiviert. Die Linien, die den Ankerpunkt des angehängten Objekts mit dem Ankerpunkt des Basisobjekts verbinden, werden visuell angezeigt, um darauf hinzuweisen, dass das gezogene Objekt bereit ist, an das Basisobjekt angehängt zu werden. Um dies zu erreichen, sollte jeder Ankerpunkt des grafischen Objekts ein Formularobjekt einer bestimmten Größe enthalten. Die Linien, die anzeigen, dass die Objekte zur Interaktion bereit sind, werden auf dem Formular selbst angezeigt. Solche Formulare sind unsichtbar an jedem Drehpunkt des grafischen Objekts vorhanden. Die Größe des Bereichs ist nur zu Debugging-Zwecken sichtbar, indem das Zeichnen eines Rechtecks entlang der Formularränder aktiviert wird:
Außerdem zeigt das Formular die Ankerpunkte des grafischen Objekts an, die nur angezeigt werden, wenn der Mauszeiger über den aktiven Bereich des Formulars bewegt wird. Auf diese Weise können wir das erweiterte grafische Objekt verschieben und verändern, indem wir den Mauszeiger über den Formularbereich bewegen, anstatt es durch einen Mausklick zu markieren. Sobald wir den Mauszeiger über den aktiven Bereich des Formulars bewegen (in der Abbildung oben durch Rechtecke gekennzeichnet), erscheinen die Beschriftungen im Ankerpunkt des grafischen Objekts (der blaue Punkt in der Mitte des Kreises). Wenn wir beginnen, das Formular mit der Maus zu ziehen, wird der entsprechende Drehpunkt des grafischen Objekts dem Cursor folgen und das Objekt selbst zusammen mit dem zusammengesetzten grafischen Objekt ändern.
Wenn der Mauszeiger bei gedrückter Maustaste in den aktiven Bereich des Formulars eintritt, bedeutet dies (sofern verifiziert), dass wir ein weiteres grafisches Objekt auf das Formular anwenden und damit den Mechanismus der Bindung eines Objekts an ein anderes aktivieren. Die Formulare ermöglichen es uns also, mehrere Ziele auf einmal zu erreichen.
Ich werde das Anhängen eines Objekts an ein anderes hier nicht implementieren, da die Vorbereitungen noch nicht abgeschlossen sind. Stattdessen werde ich die Formulare erstellen, sie an die Ankerpunkte des grafischen Objekts anhängen und den Mechanismus implementieren, mit dem sie entlang der Koordinaten des Objektdrehpunkts verschoben werden können, wenn das Chart verändert wird, d. h. wenn es verschoben oder sein Anzeigemaßstab geändert wird. Dies sollte möglich sein, da das Formularobjekt die Koordinaten in Bildschirmpixeln hat, während die meisten grafischen Objekte in Zeit-/Preiswerten angezeigt werden.
Verbesserung der Klassenbibliothek
In \MQL5\Include\DoEasy\Data.mqh fügen wir die neue Nachrichtenindizes hinzu:
//--- CLinkedPivotPoint MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X, // Not a single pivot point is set for the object along the X axis MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y, // Not a single pivot point is set for the object along the Y axis MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE, // The object is not attached to the basic graphical object MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ, // Failed to create a data object for the X and Y pivot points MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X, // Number of base object pivot points for calculating the X coordinate: MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y, // Number of base object pivot points for calculating the Y coordinate: //--- CGStdGraphObjExtToolkit MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA, // Failed to change the size of the pivot point time data array MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA, // Failed to change the size of the pivot point price data array MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM, // Failed to create a form object to manage a pivot point }; //+------------------------------------------------------------------+
und die Nachrichtentexte entsprechend den neu hinzugefügten Indizes:
//--- CLinkedPivotPoint {"Для объекта не установлено ни одной опорной точки по оси X","The object does not have any pivot points set along the x-axis"}, {"Для объекта не установлено ни одной опорной точки по оси Y","The object does not have any pivot points set along the y-axis"}, {"Объект не привязан к базовому графическому объекту","The object is not attached to the base graphical object"}, {"Не удалось создать объект данных опорной точки X и Y.","Failed to create X and Y reference point data object"}, {"Количество опорных точек базового объекта для расчёта координаты X: ","Number of reference points of the base object to set the X coordinate: "}, {"Количество опорных точек базового объекта для расчёта координаты Y: ","Number of reference points of the base object to set the Y coordinate: "}, //--- CGStdGraphObjExtToolkit {"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"}, {"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"}, {"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"}, }; //+---------------------------------------------------------------------+
Wir ersetzen die Makrosubstitution in \MQL5\Include\DoEasy\Defines.mqh
#define CLR_DEFAULT (0xFF000000) // Default symbol background color in the navigator
mit besser verständlichen
#define CLR_MW_DEFAULT (0xFF000000) // Default symbol background color in the Market Watch
und die Makro-Substitution
#define NULL_COLOR (0x00FFFFFF) // Zero for the canvas with the alpha channel
mit besser verständlichen
#define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel
und wir fügen die neuen Makrosubstitutionen zum Setzen der Standardwerte für die hier zu erstellenden Formularobjekte hinzu:
//--- Graphical object parameters #define PROGRAM_OBJ_MAX_ID (10000) // Maximum value of an ID of a graphical object belonging to a program #define CTRL_POINT_SIZE (5) // Radius of the control point on the form for managing graphical object pivot points #define CTRL_FORM_SIZE (40) // Size of the control point form for managing graphical object pivot points //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+
Wir ersetzen die alten Makro-Ersatznamen in allen Dateien.
Drücken Sie einfach Strg+Umschalt+H, geben Sie die folgenden Werte ein und markieren Sie die unten stehenden Kästchen:
Klicken Sie auf "Ersetzen in Dateien". Der Editor nimmt Ersetzungen in allen Bibliotheksdateien vor.
NULL_COLOR wird auf die gleiche Weise durch CLR_CANV_NULL ersetzt.
Die neuen Makro-Ersatznamen sind anschaulicher und weisen auf ihre Funktion hin, wodurch sich die Anzahl möglicher Fehler verringert (ich habe z. B. CLR_DEFAULT eingegeben, um einen transparenten Leinwandhintergrund einzustellen, und viel Zeit damit verbracht, zu verstehen, warum es nicht funktionierte).
Außerdem habe ich einige kleine Änderungen in allen Klassendateien der grafischen Basisobjekte vorgenommen (durch einfaches Hinzufügen eines Kommas in den Text der Methode, die eine kurze Objektbeschreibung im Journal anzeigt):
//+------------------------------------------------------------------+ //| Display a short description of the object in the journal | //+------------------------------------------------------------------+ void CGStdArrowBuyObj::PrintShort(const bool dash=false,const bool symbol=false) { ::Print ( (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(string)this.GetProperty(GRAPH_OBJ_PROP_ID,0), ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ); } //+------------------------------------------------------------------+
Dies ist eine rein "kosmetische" Verbesserung.
Sie wurde in allen Dateien in \MQL5\Include\DoEasy\Objects\Graph\Standard\.
implementiert.
Erweiterte Hilfsklasse für Standard-Grafikobjekt
Beginnen wir mit der Erstellung eines Toolkits für den Umgang mit erweiterten grafischen Objekten. Dies wird eine Klasse sein, die alle notwendigen Methoden zur Erstellung von Formularobjekten und zur Arbeit mit ihnen enthält. Jedes erweiterte grafische Objekt soll einen Zeiger auf das Objekt der entsprechenden Klasse haben. Gegebenenfalls (wenn es sich um ein Basisobjekt innerhalb eines zusammengesetzten grafischen Objekts handelt) wird das Klassenobjekt beim Erstellen eines erweiterten Objekts dynamisch erzeugt und beim Löschen des letzteren gelöscht.
Das Objekt erhält die notwendigen Parameter des grafischen Basisobjekts (seine Koordinaten, Typ, Name usw.). Die Koordinaten des Basisobjekts werden verfolgt und die Koordinaten des Formularobjekts werden innerhalb des Objekts angepasst. Letztendlich werden die Formularobjekte die Verwaltung des Basisobjekts ermöglichen.
Aber das Wichtigste zuerst...
In \MQL5\Include\DoEasy\Objects\Graph\ erstellen wir den neuen Ordner Extend\, der die Datei CGStdGraphObjExtToolkit. mqh der Klasse CGStdGraphObjExtToolkit abgeleitet von der Basisklasse CObject zum Aufbau der MQL5 Standardbibliothek:
//+------------------------------------------------------------------+ //| CGStdGraphObjExtToolkit.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Graph\Form.mqh" //+------------------------------------------------------------------+ //| Extended standard graphical | //| object toolkit class | //+------------------------------------------------------------------+ class CGStdGraphObjExtToolkit : public CObject { }
Die Formularobjekt Klassendatei sollte in die Klassendatei aufgenommen werden.
Im privaten Teil der Klasse deklarieren wir die Variablen, die alle notwendigen Eigenschaften des Basisobjekts, die Eigenschaften für die Konstruktion von Formularen, die Liste für deren Speicherung und die Methoden für die Erstellung eines Formularobjekts und die Rückgabe seiner Bildschirmkoordinaten speichern:
//+------------------------------------------------------------------+ //| Extended standard graphical | //| object toolkit class | //+------------------------------------------------------------------+ class CGStdGraphObjExtToolkit : public CObject { private: long m_base_chart_id; // Base graphical object chart ID int m_base_subwindow; // Base graphical object chart subwindow ENUM_OBJECT m_base_type; // Base object type string m_base_name; // Base object name int m_base_pivots; // Number of base object reference points datetime m_base_time[]; // Time array of base object reference points double m_base_price[]; // Price array of base object reference points int m_base_x; // Base object X coordinate int m_base_y; // Base object Y coordinate int m_ctrl_form_size; // Size of forms for managing reference points int m_shift; // Shift coordinates for adjusting the form location CArrayObj m_list_forms; // List of form objects for managing reference points //--- Create a form object on a base object reference point CForm *CreateNewControlPointForm(const int index); //--- Return X and Y screen coordinates of the specified reference point of the graphical object bool GetControlPointCoordXY(const int index,int &x,int &y); public:
Wir verwenden die Arrays, um Preis- und Zeitkoordinaten zu speichern, da ein einzelnes grafisches Objekt mehrere Pivotpunkte haben kann. Die Koordinaten jedes Punktes werden also in den entsprechenden Array-Zellen gespeichert. Die Koordinate des ersten Punktes — durch den Index 0 des Arrays, die Koordinate des zweiten Punktes — durch den Index 1 des Arrays, die Koordinate des dritten Punktes — durch den Index 2, usw.
Die Verschiebung der Formularkoordinaten ermöglicht es uns, das Formular genau in der Mitte des Objektdrehpunkts zu positionieren. Diese Verschiebung deckt die Hälfte der Punktgröße ab. Wenn die Größe des Formulars ein Vielfaches von zwei ist, z. B. 10, wird sie durch Hinzufügen von 1, d. h. 11, angepasst. Auf diese Weise kann das Formular genau in der Mitte des Drehpunkts des grafischen Objekts platziert werden, so dass keine Seite eine andere auch nur um ein einziges Pixel überragt.
Die Liste der Formulare speichert alle erstellten Formulare. Der Zugriff auf sie wird durch den Zeiger gewährt. Die Methode zur Berechnung der Bildschirmkoordinaten ermöglicht es uns, die Koordinaten des Bildschirms zu kennen, auf dem das Formular platziert werden soll. Dies ermöglicht seine genaue Platzierung auf dem Drehpunkt des grafischen Objekts.
Im öffentlichen Teil der Klasse deklarieren wir alle notwendigen Methoden für die Handhabung der Klasse:
public: //--- Set the parameters of the base object of a composite graphical object void SetBaseObj(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]); //--- Set the base object (1) time, (2) price, (3) time and price coordinates void SetBaseObjTime(const datetime time,const int index); void SetBaseObjPrice(const double price,const int index); void SetBaseObjTimePrice(const datetime time,const double price,const int index); //--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates void SetBaseObjCoordX(const int value) { this.m_base_x=value; } void SetBaseObjCoordY(const int value) { this.m_base_y=value; } void SetBaseObjCoordXY(const int value_x,const int value_y) { this.m_base_x=value_x; this.m_base_y=value_y; } //--- (1) Set and (2) return the size of the form of pivot point management control points void SetControlFormSize(const int size); int GetControlFormSize(void) const { return this.m_ctrl_form_size; } //--- Return the pointer to the pivot point form by (1) index and (2) name CForm *GetControlPointForm(const int index) { return this.m_list_forms.At(index); } CForm *GetControlPointForm(const string name,int &index); //--- Return the number of the base object pivot points int GetNumPivotsBaseObj(void) const { return this.m_base_pivots; } //--- Create form objects on the base object pivot points bool CreateAllControlPointForm(void); //--- Remove all form objects from the list void DeleteAllControlPointForm(void); //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor/destructor CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]) { this.m_list_forms.Clear(); this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price); this.CreateAllControlPointForm(); } CGStdGraphObjExtToolkit(){;} ~CGStdGraphObjExtToolkit(){;} }; //+------------------------------------------------------------------+
Im Klassenkonstruktor leeren wir die Liste der Formularobjekte, alle notwendigen Werte des Basisobjekts (die in den Konstruktorparametern übergeben werden) auf die Klassenvariablen setzen und die Formularobjekte zur Verwaltung des Basisobjekts an jedem Drehpunkt des grafischen Basisobjekts erzeugen.
Die Methode setzt die Parameter des Basisobjekts eines zusammengesetzten grafischen Objekts:
//+------------------------------------------------------------------+ //| Set the base object parameters of the | //| composite graphical object | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObj(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]) { this.m_base_chart_id=base_chart_id; // Base graphical object chart ID this.m_base_subwindow=base_subwindow; // Base graphical object chart subwindow this.m_base_type=base_type; // Base object type this.m_base_name=base_name; // Base object name this.m_base_pivots=base_pivots; // Number of base object reference points this.m_base_x=base_x; // Base object X coordinate this.m_base_y=base_y; // Base object Y coordinate this.SetControlFormSize(ctrl_form_size); // Size of forms for managing reference points if(this.m_base_type==OBJ_LABEL || this.m_base_type==OBJ_BUTTON || this.m_base_type==OBJ_BITMAP_LABEL || this.m_base_type==OBJ_EDIT || this.m_base_type==OBJ_RECTANGLE_LABEL || this.m_base_type==OBJ_CHART) return; if(::ArraySize(base_time)==0) { CMessage::ToLog(DFUN+"base_time: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY); return; } if(::ArraySize(base_price)==0) { CMessage::ToLog(DFUN+"base_price: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY); return; } if(::ArrayResize(this.m_base_time,this.m_base_pivots)!=this.m_base_pivots) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA); return; } if(::ArrayResize(this.m_base_price,this.m_base_pivots)!=this.m_base_pivots) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA); return; } for(int i=0;i<this.m_base_pivots;i++) { this.m_base_time[i]=base_time[i]; // Time (i) of the base object pivot point this.m_base_price[i]=base_price[i]; // Price (i) of the base object pivot point } } //+------------------------------------------------------------------+
Die Methode erhält alle notwendigen Werte der Eigenschaften des Basisobjekts. Als nächstes wird der Typ des Basisobjekts überprüft. Wenn es sich um ein Objekt handelt, das nicht auf den Preis/Zeit-Koordinaten basiert, verlassen wir die Methode. Wir behandeln solche Objekte noch nicht. Als Nächstes überprüfen wir Größe der Arrays der Basisobjektkoordinaten, die an die Methode übergeben werden. Wenn sie gleich Null sind (die Methode hat ein leeres Array erhalten), informiere darüber und verlasse die Methode. Danach wird die Größe der internen Koordinaten-Arrays entsprechend den übergebenen Arrays geändert. Wenn das Array nicht geändert werden konnte, wird darüber informiert und die Methode verlassen. Am Ende kopiert man einfach die eingegebenen Arrays in die internen Arrays Element für Element.
Die Methode legt die Größe der Referenzpunkte für die Verwaltung der Pivotpunkte fest:
//+------------------------------------------------------------------+ //|Set the size of reference points for managing pivot points | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetControlFormSize(const int size) { this.m_ctrl_form_size=(size>254 ? 255 : size<5 ? 5 : size%2==0 ? size+1 : size); this.m_shift=(int)ceil(m_ctrl_form_size/2)+1; } //+------------------------------------------------------------------+
Die Methode erhält die erforderliche Formulargröße. Wenn die Größe 254 überschreitet, wird sie auf 255 gesetzt (ungerader Wert), wenn die übergebene Größe kleiner als 5 ist, wird sie auf 5 gesetzt (dies wird der minimale Wert für die Formulargröße sein). Andererseits, wenn die übergebene Größe gleich zwei ist, wird Eins dazu addiert und es kann benutzt werden. Wenn keiner der geprüften Werte wahr ist, wird die an die Methode übergebene Größe verwendet.
Als Nächstes wird die Koordinatenverschiebung berechnet, da die Form so eingestellt werden soll, dass der Drehpunkt des grafischen Objekts genau in der Mitte liegt. Um dies zu erreichen, müssen wir den Verschiebungswert vom Koordinatenwert subtrahieren. Die berechnete Formulargröße durch zwei teilen, den nächsthöheren ganzzahligen Wert nehmen und einen addieren.
Die Methode zum Setzen der Zeitkoordinate des Basisobjekts:
//+------------------------------------------------------------------+ //| Set the time coordinate of the base object | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObjTime(const datetime time,const int index) { if(index>this.m_base_pivots-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return; } this.m_base_time[index]=time; } //+------------------------------------------------------------------+
Die Methode erhält Zeit und Index des Pivotpunkts des Objekts. Wenn der Index die Anzahl der Pivotpunkte im Objekt übersteigt, wird die Anfrage außerhalb des Array-Bereichs gemeldet und verlassen. Als Ergebnis wird der Zeitwert, der an die Methode übergeben wird, in die Zelle gesetzt, die dem Index im Zeit-Array entspricht.
Die Methode ist notwendig, um die Zeit des Basisobjekts im Klassenobjekt festzulegen, wenn das Objekt geändert wird.
Die Methode setzt die Preiskoordinate des Basisobjekts:
//+------------------------------------------------------------------+ //| Set the coordinate of the base object price | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObjPrice(const double price,const int index) { if(index>this.m_base_pivots-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return; } this.m_base_price[index]=price; } //+------------------------------------------------------------------+
Die Methode ist identisch mit der oben beschriebenen, mit dem Unterschied, dass hier der Preis des Pivotpunkts des Basisobjekts, der durch den Index spezifiziert wird, zum Array Preis der Klasse hinzugefügt wird.
Die Methode setzt die Zeit- und Preiskoordinate des Basisobjekts:
//+------------------------------------------------------------------+ //| Set the time and price coordinates of the base object | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetBaseObjTimePrice(const datetime time,const double price,const int index) { if(index>this.m_base_pivots-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return; } this.m_base_time[index]=time; this.m_base_price[index]=price; } //+------------------------------------------------------------------+
Die Methode ist identisch mit den beiden oben genannten Methoden, mit dem Unterschied, dass sowohl der Preis als auch die Zeit in den Klassenarrays festgelegt werden.
Die Methode gibt die X- und Y-Koordinaten des angegebenen Pivotpunkts des grafischen Objekts in Bildschirmkoordinaten zurück:
//+------------------------------------------------------------------+ //| Return the X and Y coordinates of the specified pivot point | //| of the graphical object in screen coordinates | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y) { switch(this.m_base_type) { case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : x=this.m_base_x; y=this.m_base_y; break; default: if(!::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y)) { x=0; y=0; return false; } } return true; } //+------------------------------------------------------------------+
Die Methode erhält den Index des gewünschten Drehpunkts des grafischen Basisobjekts. Das ist der Pivotpunkt, für den die Bildschirmkoordinaten (in Pixeln von der linken oberen Bildschirmecke) und zwei Variablen (über den Link), die die Bildschirmkoordinaten des Formulars erhalten sollen, empfangen werden sollen. Wenn sich das Objekt bereits innerhalb der Bildschirmkoordinaten befindet, werden diese Koordinaten zurückgegeben.
Befindet sich das Objekt innerhalb der Preis/Zeit-Koordinaten, berechnen wir diese mit der Funktion ChartTimePriceToXY(). Wenn es nicht gelingt, die Koordinaten in die Bildschirmkoordinaten umzuwandeln, setzen wir die Koordinaten auf Null und geben false zurück.
Als Ergebnis wird true zurückgegeben.
Die Methode gibt den Zeiger auf das Formular des Pivotpunkts mit Namen zurück:
//+------------------------------------------------------------------+ //| Return the pointer to the pivot point form by name | //+------------------------------------------------------------------+ CForm *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index) { index=WRONG_VALUE; for(int i=0;i<this.m_list_forms.Total();i++) { CForm *form=this.m_list_forms.At(i); if(form==NULL) continue; if(form.Name()==name) { index=i; return form; } } return NULL; } //+------------------------------------------------------------------+
Die Methode erhält den Namen des gewünschten Formulars und die Variable über die Verknüpfung mit dem Index des gefundenen Formulars in der Liste der Formularobjekte.
In der Schleife durch die Liste der Formularobjekte, das nächste Objekt holen. Wenn dessen Name mit dem gewünschten übereinstimmt, schreiben wir den Schleifenindex in die Variable und geben den Zeiger auf das erkannte Objekt zurück. Am Ende der Schleife wird NULL zurückgegeben — das Formular wurde nicht gefunden, der Index ist gleich -1. Dieser Wert wurde vor dem Start der Schleife gesetzt.
Die Methode erstellt ein Formularobjekt auf einem Basisobjekt-Referenzpunkt:
//+------------------------------------------------------------------+ //| Create a form object on a base object reference point | //+------------------------------------------------------------------+ CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index) { string name=this.m_base_name+"_TKPP_"+(index<this.m_base_pivots ? (string)index : "X"); CForm *form=this.GetControlPointForm(index); if(form!=NULL) return NULL; int x=0, y=0; if(!this.GetControlPointCoordXY(index,x,y)) return NULL; return new CForm(this.m_base_chart_id,this.m_base_subwindow,name,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize()); } //+------------------------------------------------------------------+
Die Methode erhält den Index des gewünschten Drehpunktes, an dem das Formularobjekt erstellt werden soll.
Wir erstellen den Namen des Formularobjekts, bestehend aus dem Namen des Basisobjekts + der Abkürzung "ToolKit Pivot Point" (_TKPP) + dem Index des Pivotpunkts. Beim Erstellen der Indexbeschreibung wird der den Wert des Indexes überprüft. Ist er kleiner als die Anzahl der Pivotpunkte des Basisobjekts (die Berechnung beginnt bei Null), verwenden wir die String-Darstellung des an die Methode übergebenen Index. Andernfalls verwenden wir das X-Symbol. Warum brauchen wir das? Später wird es möglich sein, untergeordnete Objekte nicht nur an den Drehpunkten, sondern auch zwischen den Drehpunkten an das Basisobjekt anzuhängen. Außerdem müssen wir ein Kontrollformular in der Mitte der Basisobjektlinie erstellen, über die hinaus das gesamte Objekt verschoben wird, um es zu verschieben. Daher sollte der Formularname die Möglichkeit bieten, das Formular nicht nur für Pivotpunkte, sondern auch für andere Punkte zu erstellen.
Als Nächstes wird das Vorhandensein des Formulars in der Liste anhand des an die Methode übergebenen Index geprüft. Wenn das Formularobjekt bereits durch den Index in der Liste vorhanden ist (der Zeiger darauf ist nicht gleich NULL), wird NULL zurückgegeben.
Danach transformieren wir die Koordinaten des Pivotpunkts durch seinen Index in die Bildschirmkoordinaten und geben das Ergebnis der Erstellung eines Formularobjekts auf den erhaltenen Koordinaten zurück. Der Verschiebungswert wird von beiden Koordinaten subtrahiert, um den Mittelpunkt des Formulars präzise auf dem Pivotpunkt zu positionieren.
Ich könnte einfach den Wert für den Formularankerpunkt setzen, aber unsere Bibliothekskonvention besagt, dass der Ankerpunkt aller Formulare unverändert bleibt — in der oberen linken Ecke. Daher wird die Verschiebung für die Positionierung des Formularobjekts verwendet, wo immer es nötig ist.
Die Methode zur Erstellung von Formularobjekten auf den Pivotpunkten des Basisobjekts:
//+------------------------------------------------------------------+ //| Create form objects on the base object pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void) { bool res=true; //--- In the loop by the number of base object pivot points for(int i=0;i<this.m_base_pivots;i++) { //--- Create a new form object on the current pivot point corresponding to the loop index CForm *form=this.CreateNewControlPointForm(i); //--- If failed to create the form, inform of that and add 'false' to the final result if(form==NULL) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM); res &=false; } //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result if(!this.m_list_forms.Add(form)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); delete form; res &=false; } //--- Set all the necessary properties for the created form object form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM); // Object is created programmatically form.SetActive(true); // Form object is active form.SetMovable(true); // Movable object form.SetActiveAreaShift(0,0,0,0); // Object active area - the entire form form.SetFlagSelected(false,false); // Object is not selected form.SetFlagSelectable(false,false); // Object cannot be selected by mouse form.Erase(CLR_CANV_NULL,0); // Fill in the form with transparent color and set the full transparency //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver); // Draw an outlining rectangle for visual display of the form location form.DrawCircle((int)floor(form.Width()/2),(int)floor(form.Height()/2),CTRL_POINT_SIZE,clrDodgerBlue); // Draw a circle in the form center form.DrawCircleFill((int)floor(form.Width()/2),(int)floor(form.Height()/2),2,clrDodgerBlue); // Draw a point in the form center form.Done(); // Save the initial form object state (its appearance) } //--- Redraw the chart for displaying changes (if successful) and return the final result if(res) ::ChartRedraw(this.m_base_chart_id); return res; } //+------------------------------------------------------------------+
Die gesamte Logik ist in den Codekommentaren beschrieben. Kurz gesagt, erstellen wir in der Schleife nach der Anzahl der Drehpunkte des Basisobjekts ein neues Formularobjekt für jeden Drehpunkt, fügen Sie es der Liste der Formularobjekte hinzu und setzen Sie die Koordinaten des entsprechenden Drehpunkts des Basisobjekts auf jedes Formular. Jedes Formular enthält den Kreis in der Mitte und den Punkt, der angibt, dass es sich um das Objekt handelt, das den Drehpunkt des Basisobjekts verwaltet.
Diese Objekte sind zunächst unsichtbar und werden erst sichtbar, wenn der Mauszeiger über dem Formularbereich schwebt. Für den Moment werde ich sie sichtbar machen, um ihr Verhalten bei Änderungen im Chart zu testen. In späteren Artikeln werde ich an meinem Plan festhalten — die Objekte werden zunächst unsichtbar sein und erst erscheinen, wenn der Mauszeiger über ihren aktiven Bereich (d. h. die gesamte Größe des Formularobjekts) bewegt wird.
Die Methode entfernt alle Formularobjekte aus der Liste:
//+------------------------------------------------------------------+ //| Remove all form objects from the list | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::DeleteAllControlPointForm(void) { this.m_list_forms.Clear(); } //+------------------------------------------------------------------+
Wir verwenden einfach die Methode Clear(), um die gesamte Liste vollständig zu löschen.
In der Ereignisbehandlung, verarbeiten wir die Ereignisse des Formularobjekts entsprechend des aufgetretenen Ereignisses:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { if(id==CHARTEVENT_CHART_CHANGE) { for(int i=0;i<this.m_list_forms.Total();i++) { CForm *form=this.m_list_forms.At(i); if(form==NULL) continue; int x=0, y=0; if(!this.GetControlPointCoordXY(i,x,y)) continue; form.SetCoordX(x-this.m_shift); form.SetCoordY(y-this.m_shift); form.Update(); } ::ChartRedraw(this.m_base_chart_id); } } //+------------------------------------------------------------------+
Zurzeit behandeln wir nur das Chart-Änderungsereignis. In der Schleife durch alle Formularobjekte, holen wir uns das nächste Formular aus der Liste. Wenn wir die Bildschirmkoordinaten entsprechend dem Pivot-Punkt, auf dem es gezeichnet ist, nicht erhalten haben, gehen wir zum nächsten Formular über. Wir legen die neuen Bildschirmkoordinaten für das Formular fest und aktualisieren das Formular. Nach Abschluss der Schleife zeichnen wir das Chart neu, um die Änderungen anzuzeigen.
Da das Hilfsobjekt des erweiterten Standard-Grafikobjekts im Objekt der Klasse des Standard-Grafikobjekts gespeichert ist, müssen wir die Klasse in \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh verbessern.
Wir fügen zunächst die Dateien des Formularobjekts und des neu erstellten Toolkit-Objekts des erweiterten Standard-Grafikobjekts in die Datei ein:
//+------------------------------------------------------------------+ //| GStdGraphObj.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\GBaseObj.mqh" #include "..\..\..\Services\Properties.mqh" #include "..\..\Graph\Form.mqh" #include "..\..\Graph\Extend\CGStdGraphObjExtToolkit.mqh" //+------------------------------------------------------------------+ //| Class of the dependent object pivot point data | //+------------------------------------------------------------------+
Im privaten Bereich der Klasse des abstrakten Standard-Grafikobjekts deklarieren wir den Zeiger auf das Hilfsobjekt des erweiterten Standard-Grafikobjekts:
//+------------------------------------------------------------------+ //| The class of the abstract standard graphical object | //+------------------------------------------------------------------+ class CGStdGraphObj : public CGBaseObj { private: CArrayObj m_list; // List of subordinate graphical objects CProperties *Prop; // Pointer to the property object CLinkedPivotPoint m_linked_pivots; // Linked pivot points CGStdGraphObjExtToolkit *ExtToolkit; // Pointer to the extended graphical object toolkit int m_pivots; // Number of object reference points //--- Read and set (1) the time and (2) the price of the specified object pivot point void SetTimePivot(const int index); void SetPricePivot(const int index); //--- Read and set (1) color, (2) style, (3) width, (4) value, (5) text of the specified object level void SetLevelColor(const int index); void SetLevelStyle(const int index); void SetLevelWidth(const int index); void SetLevelValue(const int index); void SetLevelText(const int index); //--- Read and set the BMP file name for the "Bitmap Level" object. Index: 0 - ON, 1 - OFF void SetBMPFile(const int index); public:
Im öffentlichen Abschnitt schreiben wir die Methode, die den Zeiger auf das Toolkit-Objekt zurückgibt:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Curr.SetLong(property,index,value); } void SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value) { this.Prop.Curr.SetDouble(property,index,value); } void SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value) { this.Prop.Curr.SetString(property,index,value); } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index) const { return this.Prop.Curr.GetLong(property,index); } double GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index) const { return this.Prop.Curr.GetDouble(property,index); } string GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index) const { return this.Prop.Curr.GetString(property,index); } //--- Set object's previous (1) integer, (2) real and (3) string properties void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.SetLong(property,index,value); } void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.SetDouble(property,index,value); } void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.SetString(property,index,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,int index) const { return this.Prop.Prev.GetLong(property,index); } double GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index) const { return this.Prop.Prev.GetDouble(property,index); } string GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index) const { return this.Prop.Prev.GetString(property,index); } //--- Return (1) itself, (2) properties and (3) the change history CGStdGraphObj *GetObject(void) { return &this; } CProperties *Properties(void) { return this.Prop; } CChangeHistory *History(void) { return this.Prop.History;} CGStdGraphObjExtToolkit *GetExtToolkit(void) { return this.ExtToolkit; } //--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property) { return true; }
In der öffentlichen Sektion der Klasse deklarieren wir den Handler für grafische Objekt-Ereignisse. Im Konstruktor setzen wir den Standardwert NULL für den Zeiger auf das Toolkit-Objekt, während wir im Destruktor der Klasse die Gültigkeit des Zeigers prüfen und zuerst alle Formen aus dem Toolkit-Objekt entfernen und dann das Objekt selbst löschen:
private: //--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object void SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to); void SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to); //--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object void SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to); void SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to); //--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property void SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier); void SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier); void SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier); public: //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Default constructor CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; this.ExtToolkit=NULL; } //--- Destructor ~CGStdGraphObj() { if(this.Prop!=NULL) delete this.Prop; if(this.ExtToolkit!=NULL) { this.ExtToolkit.DeleteAllControlPointForm(); delete this.ExtToolkit; } } protected: //--- Protected parametric constructor CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_ELEMENT_TYPE elm_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_SPECIES species, const long chart_id, const int pivots, const string name); public: //+--------------------------------------------------------------------+ //|Methods of simplified access and setting graphical object properties| //+--------------------------------------------------------------------+
In den Methodenblock für einen vereinfachten Zugriff und das Setzen von grafischen Objekteigenschaften, schreiben wir die Methode, die die Anzahl der grafischen Objektdrehpunkte zurückgibt:
public: //+--------------------------------------------------------------------+ //|Methods of simplified access and setting graphical object properties| //+--------------------------------------------------------------------+ //--- Number of object reference points int Pivots(void) const { return this.m_pivots; } //--- Object index in the list int Number(void) const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM,0); } void SetNumber(const int number) { this.SetProperty(GRAPH_OBJ_PROP_NUM,0,number); }
Im geschützten des parametrischen Konstruktor prüfen wir den Typ des grafischen Elements. Wenn es sich um ein erweitertes grafisches Objekt handelt, erstellen wir ein neues Hilfsobjekt und speichern den Zeiger darauf in der Variablen ExtToolkit. Am Ende der Konstruktorauflistung initialisieren wir das Hilfsobjekt:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_ELEMENT_TYPE elm_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_SPECIES species, const long chart_id,const int pivots, const string name) { //--- Create the property object with the default values this.Prop=new CProperties(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL); this.ExtToolkit=(elm_type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? new CGStdGraphObjExtToolkit() : NULL); //--- Set the number of pivot points and object levels this.m_pivots=pivots; int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS); //--- Set the property array dimensionalities according to the number of pivot points and levels this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels); this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2); //--- 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(elm_type); CGBaseObj::SetBelong(belong); CGBaseObj::SetSpecies(species); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save the integer properties inherent in all graphical objects but not present in the current one this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID()); // Chart ID this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow()); // Chart subwindow index this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject()); // Graphical object type (ENUM_OBJECT) this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement()); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong()); // Graphical object affiliation this.SetProperty(GRAPH_OBJ_PROP_SPECIES,0,CGBaseObj::Species()); // Graphical object species this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,0); // Graphical object group this.SetProperty(GRAPH_OBJ_PROP_ID,0,0); // Object ID this.SetProperty(GRAPH_OBJ_PROP_BASE_ID,0,0); // Base object ID this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0); // Object index in the list this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,false); // Flag of storing the change history this.SetProperty(GRAPH_OBJ_PROP_BASE_NAME,0,this.Name()); // Base object name //--- 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,0); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0); //--- Initialize the extended graphical object toolkit if(this.GraphElementType()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { datetime times[]; double prices[]; if(::ArrayResize(times,this.Pivots())!=this.Pivots()) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA); if(::ArrayResize(prices,this.Pivots())!=this.Pivots()) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA); for(int i=0;i<this.Pivots();i++) { times[i]=this.Time(i); prices[i]=this.Price(i); } this.ExtToolkit.SetBaseObj(this.TypeGraphObject(),this.Name(),this.ChartID(),this.SubWindow(),this.Pivots(),CTRL_FORM_SIZE,this.XDistance(),this.YDistance(),times,prices); this.ExtToolkit.CreateAllControlPointForm(); this.SetFlagSelected(false,false); this.SetFlagSelectable(false,false); } //--- Save the current properties to the previous ones this.PropertiesCopyToPrevData(); } //+-------------------------------------------------------------------+
Bei der Initialisierung des Hilfsobjekts deklarieren wir zunächst die Arrays der Zeit- und Preiseigenschaften, passen ihre Größe an die Anzahl der grafischen Objektdrehpunkte an und setzen die Preis- und Zeitwerte von den Objektdrehpunkten, die dem Schleifenindex entsprechen.
Als Nächstes rufen wir die Methode zur Initialisierung des Hilfsobjekts auf und übergeben ihr die erforderlichen Parameter des grafischen Objekts zusammen mit den neu gefüllten Arrays der Preis- und Zeiteigenschaften. Nach der Initialisierung rufen wir die Methode zum Erstellen von Formularobjekten an den Pivotpunkten des grafischen Objekts auf. Schließlich setzen wir den Status des nicht ausgewählten Objekts für das grafische Objekt und deaktivieren Sie die Möglichkeit, es mit der Maus auszuwählen.
In der Methode, die die Änderungen der Objekteigenschaften prüft, d.h. in dem Codeblock, der das erweiterte Standard-Grafikobjekt behandelt, fügen wir den Codeblock für das Verschieben von Kontrollpunkten (Formularobjekten) zu den neuen Bildschirmkoordinaten hinzu, wenn wir die Position der Drehpunkte des erweiterten Standard-Grafikobjekts ändern:
//+------------------------------------------------------------------+ //| Check object property changes | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCheckChanged(void) { CGBaseObj::ClearEventsList(); 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; for(int j=0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j)) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } 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; for(int j=0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j)) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } 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; for(int j=0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } if(changed) { for(int i=0;i<this.m_list_events.Total();i++) { CGBaseEvent *event=this.m_list_events.At(i); if(event==NULL) continue; ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam()); } if(this.AllowChangeHistory()) { int total=HistoryChangesTotal(); if(this.CreateNewChangeHistoryObj(total<1)) ::Print ( DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total), ": ",this.HistoryChangedObjTimeChangedToString(total-1) ); } //--- If subordinate objects are attached to the base one (in a composite graphical object) if(this.m_list.Total()>0) { //--- In the loop by the number of added graphical objects, for(int i=0;i<this.m_list.Total();i++) { //--- get the next graphical object, CGStdGraphObj *dep=m_list.At(i); if(dep==NULL) continue; //--- get the data object of its pivot points, CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint(); if(pp==NULL) continue; //--- get the number of coordinate points the object is attached to int num=pp.GetNumLinkedCoords(); //--- In the loop by the object coordinate points, for(int j=0;j<num;j++) { //--- get the number of coordinate points of the base object for setting the X coordinate int numx=pp.GetBasePivotsNumX(j); //--- In the loop by each coordinate point for setting the X coordinate, for(int nx=0;nx<numx;nx++) { //--- get the property for setting the X coordinate, its modifier //--- and set it in the object selected as the current one in the main loop int prop_from=pp.GetPropertyX(j,nx); int modifier_from=pp.GetPropertyModifierX(j,nx); this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx); } //--- Get the number of coordinate points of the base object for setting the Y coordinate int numy=pp.GetBasePivotsNumY(j); //--- In the loop by each coordinate point for setting the Y coordinate, for(int ny=0;ny<numy;ny++) { //--- get the property for setting the Y coordinate, its modifier //--- and set it in the object selected as the current one in the main loop int prop_from=pp.GetPropertyY(j,ny); int modifier_from=pp.GetPropertyModifierY(j,ny); this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny); } } dep.PropertiesCopyToPrevData(); } //--- Move reference control points to new coordinates if(ExtToolkit!=NULL) { for(int i=0;i<this.Pivots();i++) { ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); long lparam=0; double dparam=0; string sparam=""; ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes ::ChartRedraw(m_chart_id); } //--- Save the current properties as the previous ones this.PropertiesCopyToPrevData(); } } //+------------------------------------------------------------------+
Wenn wir einen der Pivotpunkte des grafischen Objekts oder das gesamte Objekt verschieben, ändern sich die Bildschirmkoordinaten seiner Drehpunkte. Das bedeutet, dass wir die Formularobjekte der Hilfsklasse ebenfalls zu den neuen Bildschirmkoordinaten verschieben müssen. Daher übergeben wir zunächst die neuen Koordinaten des grafischen Objekts an das Hilfsobjekt (in der Schleife durch die Anzahl der Pivotpunkte für die Preis/Zeit-Koordinaten und die Koordinaten in Pixel trennen). Dann rufen wir den Handler der Hilfsobjekt-Ereignisse auf, indem wir ihm die ID des Chart-Change-Ereignisses übergebe. Dies veranlasst den Handler der Hilfsobjekt-Ereignisse, die Bildschirmkoordinaten aller Formulare neu zu berechnen und sie entsprechend den neuen Preis- und Zeitkoordinaten des grafischen Objekts an eine neue Stelle zu verschieben.
In der Methode, die ein untergeordnetes Standard-Grafikobjekt zur Liste hinzufügt, beheben wir einen kleinen Fehler — ein untergeordnetes Grafikobjekt ändert seine eigenen Eigenschaften, daher sollten die neuen Eigenschaften sofort als die vorherigen gespeichert werden, um zu vermeiden, dass neue Ereignisse zum Ändern dieser Grafikobjekte erzeugt werden, wenn man auf sie klickt:
//+------------------------------------------------------------------+ //| Add a subordinate standard graphical object to the list | //+------------------------------------------------------------------+ bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj) { //--- If the current object is not an extended one, inform of that and return 'false' if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ); return false; } //--- If failed to add the pointer to the passed object into the list, inform of that and return 'false' if(!this.m_list.Add(obj)) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST); return false; } //--- Object added to the list - set its number in the list, //--- name and ID of the current object as the base one, //--- set the flags of object availability and selection //--- and the graphical element type - standard extended graphical object obj.SetNumber(this.m_list.Total()-1); obj.SetBaseName(this.Name()); obj.SetBaseObjectID(this.ObjectID()); obj.SetFlagSelected(false,false); obj.SetFlagSelectable(false,false); obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED); obj.PropertiesCopyToPrevData(); return true; } //+------------------------------------------------------------------+
Der Handler für die abstrakten Standard-Grafikobjekt-Ereignisse:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGStdGraphObj::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(GraphElementType()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) return; if(id==CHARTEVENT_CHART_CHANGE) { if(ExtToolkit==NULL) return; for(int i=0;i<this.Pivots();i++) { ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); ExtToolkit.OnChartEvent(id,lparam,dparam,sparam); } } //+------------------------------------------------------------------+
Im Moment verarbeitet der Handler nur das Chart-Änderungsereignis.
Wenn das Objekt nicht erweitert ist, verlassen wir die Ereignisbehandlung. Wenn das Chart-Änderungsereignis erkannt wurde, prüfen wir Gültigkeit des Zeigers auf das Hilfsobjekt des erweiterten Standard-Grafikobjekts. Wenn kein Hilfsmittel erstellt wurde, kehren wir zurück. Als Nächstes setzen wir in der Schleife durch die Anzahl der Drehpunkte des grafischen Objekts die neuen Preis/Zeit-Koordinaten des grafischen Objekts auf das Hilfsobjekt. Danach setzen wir seine neuen Bildschirmkoordinaten und ruft die Ereignisbehandlung für die Ereignisse des Hilfsobjekts auf, in dem alle Formulare auf der Grundlage der neu an das Hilfsobjekt übergebenen Preis/Zeit-Koordinaten auf die neuen Bildschirmkoordinaten verschoben werden.
Wenn wir ein erweitertes Standard-Grafikobjekt aus dem Chart entfernen, müssen wir auch die Formularobjekte seines Hilfsobjekts entfernen, falls ein solches Objekt für das Grafikobjekt erstellt wurde. Das Entfernen von grafischen Objekten aus dem Chart wird in der Sammelklasse für grafische Elemente in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh behandelt.
In der Methode fügen wir den Codeblock zum Entfernen aller Formularobjekte aus dem Hilfsobjekt hinzu, in dem das Entfernen von erweiterten grafischen Objekten behandelt wird:
//+------------------------------------------------------------------+ //| Handle the removal of extended graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj) { if(obj==NULL) return; //--- Save the ID of the graphical object chart and the number of subordinate objects in its list long chart_id=obj.ChartID(); int total=obj.GetNumDependentObj(); //--- If the list of subordinate objects is not empty (this is the base object) if(total>0) { CGStdGraphObjExtToolkit *toolkit=obj.GetExtToolkit(); if(toolkit!=NULL) { toolkit.DeleteAllControlPointForm(); } //--- In the loop, move along all dependent objects and remove them for(int n=total-1;n>WRONG_VALUE;n--) { //--- Get the next graphical object CGStdGraphObj *dep=obj.GetDependentObj(n); if(dep==NULL) continue; //--- If failed to remove it from the chart, display the appropriate message in the journal if(!::ObjectDelete(dep.ChartID(),dep.Name())) CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); } //--- Upon the loop completion, update the chart to display the changes and exit the method ::ChartRedraw(chart_id); return; } //--- If this is a subordinate object else if(obj.BaseObjectID()>0) { //--- Get the base object name and its ID string base_name=obj.BaseName(); long base_id=obj.BaseObjectID(); //--- Get the base object from the graphical object collection list CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id); if(base==NULL) return; //--- get the number of dependent objects in its list int count=base.GetNumDependentObj(); //--- In the loop, move along all its dependent objects and remove them for(int n=count-1;n>WRONG_VALUE;n--) { //--- Get the next graphical object CGStdGraphObj *dep=base.GetDependentObj(n); //--- If failed to get the pointer or the object has already been removed from the chart, move on to the next one if(dep==NULL || !this.IsPresentGraphObjOnChart(dep.ChartID(),dep.Name())) continue; //--- If failed to delete the graphical object from the chart, //--- display the appropriate message in the journal and move on to the next one if(!::ObjectDelete(dep.ChartID(),dep.Name())) { CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); continue; } } //--- Remove the base object from the chart and from the list if(!::ObjectDelete(base.ChartID(),base.Name())) CMessage::ToLog(DFUN+base.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); } //--- Update the chart for displaying the changes ::ChartRedraw(chart_id); } //+------------------------------------------------------------------+
Hier rufen wir einfach den Zeiger auf das Hilfsobjekt des erweiterten grafischen Objekts ab. Wenn der Zeiger gültig ist, rufen wir die Methode zum Entfernen aller erstellten Formen des Hilfsobjekts des erweiterten Standard-Grafikobjekts auf, die ich zuvor betrachtet habe.
In der Ereignisbehandlung der Klasse für grafische Elemente fügen wir die Behandlung der Chart-Änderungen für erweiterte Standard-Grafikobjekte hinzu:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj=NULL; ushort idx=ushort(id-CHARTEVENT_CUSTOM); if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CLICK) { //--- Calculate the chart ID //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID //--- If the event ID corresponds to a user event, the chart ID is received from lparam //--- Otherwise, the chart ID is assigned to -1 long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE); long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param); //--- Get the object, whose properties were changed or which was relocated, //--- from the collection list by its name set in sparam obj=this.GetStdGraphObject(sparam,chart_id); //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed if(obj==NULL) { //--- Let's search the list for the object that is not on the chart obj=this.FindMissingObj(chart_id); //--- If failed to find the object here as well, exit if(obj==NULL) return; //--- Get the name of the renamed graphical object on the chart, which is not in the collection list string name_new=this.FindExtraObj(chart_id); //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- and send an event with the new name of the object to the control program chart if(obj.SetNamePrev(obj.Name()) && obj.SetName(name_new)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),obj.TimeCreate(),obj.Name()); } //--- Update the properties of the obtained object //--- and check their change obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } //--- Handle chart changes for extended standard objects if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE) { CArrayObj *list=this.GetListStdGraphObjectExt(); if(list!=NULL) { for(int i=0;i<list.Total();i++) { obj=list.At(i); if(obj==NULL) continue; obj.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
Wenn das Chart-Änderungsereignis erkannt wurde, holen wir hier die Liste aller erweiterten Standard-Grafikobjekte. In der Schleife holen wir uns gemäß ihrer Nummer das nächste Objekt, rufen seine Ereignisbehandlung auf und übergeben den Wert des Ereignisses "Chart geändert".
Test
Für den Test verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part95\ als TestDoEasyPart95.mq5.
Die einzigen Änderungen sind leicht unterschiedliche Namen der Preisetiketten-Objekte, die an das Basis-Trendlinien-Objekt angehängt sind. Die Namen der untergeordneten Objekte im Block zur Erstellung eines zusammengesetzten grafischen Objekts des EA-Ereignishandlers sind jetzt mit dem Zusatz "Ext" versehen, so dass die Namen dem erweiterten grafischen Objekttyp entsprechen:
if(id==CHARTEVENT_CLICK) { if(!IsCtrlKeyPressed()) return; //--- Get the chart click coordinates datetime time=0; double price=0; int sw=0; if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price)) { //--- Get the right point coordinates for a trend line datetime time2=iTime(Symbol(),PERIOD_CURRENT,1); double price2=iOpen(Symbol(),PERIOD_CURRENT,1); //--- Create the "Trend line" object string name_base="TrendLineExt"; engine.CreateLineTrend(name_base,0,true,time,price,time2,price2); //--- Get the object from the list of graphical objects by chart name and ID and pass its properties to the journal CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID()); //--- Create the "Left price label" object string name_dep="PriceLeftExt"; engine.CreatePriceLabelLeft(name_dep,0,false,time,price); //--- Get the object from the list of graphical objects by chart name and ID and CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); //--- add it to the list of graphical objects bound to the "Trend line" object obj.AddDependentObj(dep); //--- Set its pivot point by X and Y axis to the trend line left point dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0); //--- Create the "Right price label" object name_dep="PriceRightExt"; engine.CreatePriceLabelRight(name_dep,0,false,time2,price2); //--- Get the object from the list of graphical objects by chart name and ID and dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); //--- add it to the list of graphical objects bound to the "Trend line" object obj.AddDependentObj(dep); //--- Set its pivot point by X and Y axis to the trend line right point dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1); } } engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
Erstellen wir ein zusammengesetztes grafisches Objekt. Die Formularobjekte sollen bei der Erstellung auf ihre Drehpunkte gesetzt werden.
Diese Formularobjekte haben die Koordinaten in Pixeln von der linken oberen Bildschirmecke. Wenn wir also das Chart verschieben, sollten diese Bildschirmkoordinaten neu berechnet werden, damit die Objekte auf die entsprechenden Pivot-Punkte des grafischen Objekts gesetzt werden. Dies werden wir überprüfen.
Kompilieren Sie den EA und starten Sie ihn auf dem Chart:
Wir können sehen, dass die Objekte ihren Platz einnehmen, wenn sich der Chart ändert. Dies geschieht jedoch mit einer Verzögerung.
Wenn ein grafisches Objekt entfernt wird, werden auch die entsprechenden Formularobjekte entfernt.
Was können wir mit der Verzögerung anfangen? Eigentlich brauchen wir die Bewegungen nicht live zu sehen — diese Bewegungen werden beim Verschieben des Charts immer ausgeblendet bleiben (sie werden jetzt angezeigt, um eine Ereignisreaktion zu verarbeiten). Die Linie des grafischen Objekts selbst wird bewegt, wenn die Formularobjekte mit der Maus gezogen werden. Jegliche Interaktion mit den Formularen wird auf einem festen Chart ausgeführt. Dieses Ergebnis ist also völlig ausreichend, vor allem wenn man bedenkt, dass das Chart erst nach Abschluss der Schleife und nicht bei jeder Schleifeniteration aktualisiert wird. Um die Belastung zu verringern, können wir den Abschluss der Chart-Änderung steuern, indem wir die Änderungen anzeigen und das Objekt anschließend einblenden (dies ist nur möglich, wenn der Mauszeiger über den aktiven Bereich des Formularobjekts bewegt wird, wenn es sichtbar werden soll).
Was kommt als Nächstes?
Im nächsten Artikel werde ich meine Arbeit an zusammengesetzten grafischen Objektereignissen fortsetzen.
*Frühere Artikel dieser Serie:
Grafiken in der Bibliothek DoEasy (Teil 93): Vorbereiten der Funktionen zur Erstellung zusammengesetzter grafischer Objekte
Grafiken in der Bibliothek DoEasy (Teil 94): Bewegen und Löschen zusammengesetzter grafischer Objekte
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/10387
- 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.