Grafiken in der Bibliothek DoEasy (Teil 100): Verbesserungen im Umgang mit erweiterten grafischen Standardobjekten
Inhalt
Konzept
Im Laufe der letzten Artikel habe ich die Funktionsweise für die Handhabung von zusammengesetzten grafischen Objekten entwickelt, die auf der Grundlage von erweiterten grafischen Standardobjekten erstellt wurden. Wir haben uns schrittweise auf die Schaffung dieser Funktionsweise Artikel für Artikel zubewegt. Manchmal war ich gezwungen, von einem Thema zum anderen zu wechseln: Zuerst erstellte ich Objekte auf der Basis der Klasse CCanvas und begann dann mit der Erstellung von grafischen Objekten, da die geplante Funktionalität für die Einführung von grafischen Objekten in alle Bibliotheksobjekte das Vorhandensein einer zumindest teilweise funktionierenden Funktionalität von grafischen Standardobjekten erforderte. Danach kehrte ich zu Formularobjekten auf der Basis von CCanvas zurück, da fortgeschrittene grafische Objekte die Verbesserung von Klassen auf der Basis des Canvas erforderten. Jetzt müssen wir wieder weitergehen, um die Entwicklung von Objekten auf Canvas fortzusetzen.
Daher werde ich im aktuellen Artikel offensichtliche Mängel bei der gleichzeitigen Handhabung von erweiterten (und Standard-) grafischen Objekten und Formularobjekten auf Canvas beseitigen sowie Fehler beheben, die während des im vorherigen Artikel durchgeführten Tests entdeckt wurden. Der Artikel schließt diesen Teil der Bibliotheksbeschreibung ab. Der nächste Artikel wird einen neuen Abschnitt einleiten, in dem ich mit der Entwicklung von grafischen Objekten auf der Leinwand beginnen werde, die Windows Forms in MS Visual Studio imitieren. Ich werde diese Objekte benötigen, um die Entwicklung von erweiterten grafischen Standardobjekten sowie von darauf basierenden zusammengesetzten Objekten fortzusetzen.
Verbesserung der Klassenbibliothek
Wenn das Chart grafische Objekte auf dem Canvas zur Erstellung von GUI-Elementen — Element, Gestalt, Fenster (noch nicht implementiert) oder andere ähnliche Steuerelemente — enthält, führt das Platzieren von grafischen Standardobjekten sowie anderen darauf basierenden Bibliotheksobjekten auf dem Diagramm (entweder manuell oder programmatisch) dazu, dass diese Objekte über den Steuerelementen gezeichnet werden, was unpraktisch ist. Daher müssen wir einen Mechanismus entwickeln, um neue grafische Objekte im Diagramm zu verfolgen und alle GUI-Elemente in den Vordergrund zu bringen. Um dies zu erreichen, können wir die Eigenschaft ZOrder eines grafischen Objekts verwenden (Priorität eines grafischen Objekts für den Empfang des Ereignisses beim Klicken auf ein Chart (CHARTEVENT_CLICK)).
Unten finden Sie die Hilfeinformationen:
Der Standardwert ist Null, wenn ein Objekt erstellt wird, aber die Priorität kann bei Bedarf erhöht werden. Beim Übereinanderlegen von Objekten wird nur das Objekt mit der höchsten Priorität das Ereignis CHARTEVENT_CLICK erhalten.
Aber ich werde diese Eigenschaft breiter einsetzen — der Wert dieser Eigenschaft gibt die Reihenfolge an, in der GUI-Elemente relativ zueinander sowie relativ zu anderen grafischen Objekten angeordnet sind.
In der Enumeration der Integer-Eigenschaften eines grafischen Elements auf der Leinwand in der Datei \MQL5\Include\DoEasy\Defines.mqh fügen wir eine neue Eigenschaft hinzu und erhöhen die Anzahl der Integer-Eigenschaften von 23 auf 24:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type CANV_ELEMENT_PROP_BELONG, // Graphical element affiliation CANV_ELEMENT_PROP_NUM, // Element index in the list CANV_ELEMENT_PROP_CHART_ID, // Chart ID CANV_ELEMENT_PROP_WND_NUM, // Chart subwindow index CANV_ELEMENT_PROP_COORD_X, // Form's X coordinate on the chart CANV_ELEMENT_PROP_COORD_Y, // Form's Y coordinate on the chart CANV_ELEMENT_PROP_WIDTH, // Element width CANV_ELEMENT_PROP_HEIGHT, // Element height CANV_ELEMENT_PROP_RIGHT, // Element right border CANV_ELEMENT_PROP_BOTTOM, // Element bottom border CANV_ELEMENT_PROP_ACT_SHIFT_LEFT, // Active area offset from the left edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_TOP, // Active area offset from the upper edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT, // Active area offset from the right edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM, // Active area offset from the bottom edge of the element CANV_ELEMENT_PROP_MOVABLE, // Element moveability flag CANV_ELEMENT_PROP_ACTIVE, // Element activity flag CANV_ELEMENT_PROP_INTERACTION, // Flag of interaction with the outside environment CANV_ELEMENT_PROP_COORD_ACT_X, // X coordinate of the element active area CANV_ELEMENT_PROP_COORD_ACT_Y, // Y coordinate of the element active area CANV_ELEMENT_PROP_ACT_RIGHT, // Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Fügen wir die neue Eigenschaft zur Liste der möglichen Kriterien für die Sortierung von grafischen Elementen auf der Leinwand hinzu:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type SORT_BY_CANV_ELEMENT_BELONG, // Sort by a graphical element affiliation SORT_BY_CANV_ELEMENT_NUM, // Sort by form index in the list SORT_BY_CANV_ELEMENT_CHART_ID, // Sort by chart ID SORT_BY_CANV_ELEMENT_WND_NUM, // Sort by chart window index SORT_BY_CANV_ELEMENT_COORD_X, // Sort by the element X coordinate on the chart SORT_BY_CANV_ELEMENT_COORD_Y, // Sort by the element Y coordinate on the chart SORT_BY_CANV_ELEMENT_WIDTH, // Sort by the element width SORT_BY_CANV_ELEMENT_HEIGHT, // Sort by the element height SORT_BY_CANV_ELEMENT_RIGHT, // Sort by the element right border SORT_BY_CANV_ELEMENT_BOTTOM, // Sort by the element bottom border SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT, // Sort by the active area offset from the left edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP, // Sort by the active area offset from the top edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT, // Sort by the active area offset from the right edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM, // Sort by the active area offset from the bottom edge of the element SORT_BY_CANV_ELEMENT_MOVABLE, // Sort by the element moveability flag SORT_BY_CANV_ELEMENT_ACTIVE, // Sort by the element activity flag SORT_BY_CANV_ELEMENT_INTERACTION, // Sort by the flag of interaction with the outside environment SORT_BY_CANV_ELEMENT_COORD_ACT_X, // Sort by X coordinate of the element active area SORT_BY_CANV_ELEMENT_COORD_ACT_Y, // Sort by Y coordinate of the element active area SORT_BY_CANV_ELEMENT_ACT_RIGHT, // Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name }; //+------------------------------------------------------------------+
Jetzt können wir grafische Elemente auf der Leinwand anhand dieser Eigenschaft auswählen und sortieren.
Alle grafischen Objekte in der Bibliothek sind vom Basisobjekt aller grafischen Objekte der Bibliothek abgeleitet — standardmäßige grafische Objekte und grafische Elemente auf der Leinwand. In der Datei der grafischen Basisobjektklasse \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh werden die Methoden zur Handhabung der Eigenschaft ZOrder virtuell gemacht:
//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag bool SetFlagHidden(const bool flag,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop) { this.m_hidden=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set the priority of a graphical object for receiving the event of clicking on a chart virtual bool SetZorder(const long value,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop) { this.m_zorder=value; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set object visibility on all timeframes bool SetVisible(const bool flag,const bool only_prop) { long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop) { this.m_visible=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set visibility flags on timeframes specified as flags
...
//--- Return the values of class variables ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return this.m_type_element; } ENUM_GRAPH_OBJ_BELONG Belong(void) const { return this.m_belong; } ENUM_GRAPH_OBJ_SPECIES Species(void) const { return this.m_species; } ENUM_OBJECT TypeGraphObject(void) const { return this.m_type_graph_obj; } datetime TimeCreate(void) const { return this.m_create_time; } string Name(void) const { return this.m_name; } long ChartID(void) const { return this.m_chart_id; } long ObjectID(void) const { return this.m_object_id; } virtual long Zorder(void) const { return this.m_zorder; } int SubWindow(void) const { return this.m_subwindow; } int ShiftY(void) const { return this.m_shift_y; } int VisibleOnTimeframes(void) const { return this.m_timeframes_visible; } int Digits(void) const { return this.m_digits; } int Group(void) const { return this.m_group; } bool IsBack(void) const { return this.m_back; } bool IsSelected(void) const { return this.m_selected; } bool IsSelectable(void) const { return this.m_selectable; } bool IsHidden(void) const { return this.m_hidden; } bool IsVisible(void) const { return this.m_visible; } //--- Return the graphical object type (ENUM_OBJECT) calculated from the object type (ENUM_OBJECT_DE_TYPE) passed to the method
Die gleichen Methoden sind in der Basisobjektklasse aller grafischen Bibliotheksobjekte vorhanden.
Die Methoden sind in ihnen ebenfalls virtuell.
In der Datei der abstrakten grafischen Standardobjektklasse \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh:
//--- Background object bool Back(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0); } bool SetFlagBack(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagBack(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag); return true; } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value); return true; } //--- Disable displaying the name of a graphical object in the terminal object list bool Hidden(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0); } bool SetFlagHidden(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagHidden(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag); return true; } //--- Object selection
In der Datei der grafischen Elementobjektklasse \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh fügen wir die gleichen virtuellen Methoden hinzu:
//--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisible(true,false); } virtual void Hide(void) { CGBaseObj::SetVisible(false,false); } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value); return true; } //+------------------------------------------------------------------+ //| The methods of receiving raster data | //+------------------------------------------------------------------+
Da die Objektklasse des grafischen Elements bereits über die Struktur der Objekteigenschaften zum Speichern und Wiederherstellen verfügt, sollten wir das Feld für die neue Integer-Eigenschaft hinzufügen:
//--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type int number; // Element index in the list long chart_id; // Chart ID int subwindow; // Chart subwindow index int coord_x; // Form's X coordinate on the chart int coord_y; // Form's Y coordinate on the chart int width; // Element width int height; // Element height int edge_right; // Element right border int edge_bottom; // Element bottom border int act_shift_left; // Active area offset from the left edge of the element int act_shift_top; // Active area offset from the top edge of the element int act_shift_right; // Active area offset from the right edge of the element int act_shift_bottom; // Active area offset from the bottom edge of the element uchar opacity; // Element opacity color color_bg; // Element background color bool movable; // Element moveability flag bool active; // Element activity flag bool interaction; // Flag of interaction with the outside environment int coord_act_x; // X coordinate of the element active area int coord_act_y; // Y coordinate of the element active area int coord_act_right; // Right border of the element active area int coord_act_bottom; // Bottom border of the element active area long zorder; // Priority of a graphical object for receiving the event of clicking on a chart //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure
In der Methode zur Erstellung der Objektstruktur fügen wir das Speichern einer neuen Objekteigenschaft hinzu:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Eleemnt ID in the list this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); // Chart ID this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); // Chart subwindow index this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); // Form's X coordinate on the chart this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); // Form's Y coordinate on the chart this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); // Element width this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); // Element height this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT); // Element right edge this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM); // Element bottom edge this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); // Active area offset from the left edge of the element this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); // Active area offset from the top edge of the element this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); // Active area offset from the right edge of the element this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); // Element moveability flag this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); // Flag of interaction with the outside environment this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.color_bg=this.m_color_bg; // Element background color this.m_struct_obj.opacity=this.m_opacity; // Element opacity this.m_struct_obj.zorder=this.m_zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
In der Methode der Wiederherstellung eines Objekts aus der Struktur fügen wir das Lesen der Eigenschaft zu einem Objekt hinzu:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x); // Form's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y); // Form's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height); // Element height this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right); // Element right edge this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom); // Element bottom edge this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity this.m_zorder=this.m_struct_obj.zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
Wir brauchen all dies, um das Objekt korrekt auf der Festplatte zu speichern und es in Zukunft beim Lesen und Schreiben von Bibliotheksobjekten in Dateien von der Festplatte wiederherzustellen. Dies wird notwendig sein, damit die Bibliothek beim Neustart des Terminals den Zustand des Programms und seiner Daten wiederherstellen kann, um die Arbeit normal fortzusetzen. Aber all dies wird erst später implementiert werden. In der Zwischenzeit bereiten wir lediglich die notwendige Funktionsweise vor.
Kehren wir zu der Datei der abstrakten grafischen Standardobjektklasse \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh zurück.
Da wir in Zukunft die minimalen und maximalen Koordinaten des gesamten Objekts kennen müssen, wenn wir aus den erweiterten grafischen Standardobjekten zusammengesetzte grafische Objekte erstellen, ist es jetzt an der Zeit, Methoden hinzuzufügen, die die Werte der minimalen und maximalen X- und Y-Koordinaten des grafischen Standardobjekts ermitteln und zurückgeben. Später werden wir in der Lage sein, die minimalen/maximalen Koordinaten des gesamten zusammengesetzten grafischen Objekts auf der Grundlage dieser Methoden zu ermitteln.
Wir deklarieren zwei neue Methoden im öffentlichen Teil der Klasse:
//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects CArrayObj *GetListDependentObj(void) { return &this.m_list; } CGStdGraphObj *GetDependentObj(const int index) { return this.m_list.At(index); } int GetNumDependentObj(void) { return this.m_list.Total(); } //--- Return the name of the dependent object by index string NameDependent(const int index); //--- Add the dependent graphical object to the list bool AddDependentObj(CGStdGraphObj *obj); //--- Change X and Y coordinates of the current and all dependent objects bool ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false); //--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object bool SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj); //--- Return (1) the minimum and (2) maximum XY screen coordinate of the specified pivot point bool GetMinCoordXY(int &x,int &y); bool GetMaxCoordXY(int &x,int &y); //--- Return the pivot point data object CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; }
Schreiben wir ihre Implementierung außerhalb des Klassenkörpers.
Die Methode, die die minimale XY-Bildschirmkoordinate von allen Pivotpunkten zurückgibt:
//+------------------------------------------------------------------+ //| Return the minimum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMinCoordXY(int &x,int &y) { //--- Declare the variables storing the minimum time and maximum price datetime time_min=LONG_MAX; double price_max=0; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the minimum time if(this.Time(i)<time_min) time_min=this.Time(i); //--- Define the maximum price if(this.Price(i)>price_max) price_max=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_min,price_max,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
Bei Objekten, die in Bildschirmkoordinaten konstruiert wurden, ist es nicht notwendig, nach der Maximum/Minimum-Koordinate zu suchen, da solche Objekte einen einzigen Chart-Ankerpunkt haben, der bereits in den Bildschirmkoordinaten enthalten ist — wir sollten sie einfach zurückgeben.
Umgekehrt müssen wir bei Objekten, die in Zeit-/Preiskoordinaten konstruiert wurden, diese Koordinaten über die Funktion ChartTimePriceToXY() ermitteln. Zunächst muss jedoch die minimale Zeitkoordinate aus allen Pivotpunkten des Objekts und die maximale Preiskoordinate aus denselben Pivotpunkten ermittelt werden. Warum suchen wir nach der maximalen Preiskoordinate, wenn wir die minimale Koordinate im Chart benötigen? Die Antwort ist einfach: Der Preis auf dem Chart steigt von unten nach oben, während die Koordinaten in Pixeln auf dem Chart von der linken oberen Ecke aus gezählt werden. Je höher also der Preis ist, desto kleiner ist der Wert der Pixelkoordinate für diesen Preispunkt.
In der Schleife durch alle Pivotpunkte finden wir die minimale Zeit und den maximalen Preis, die wir schließlich an die Funktion ChartTimePriceToXY() übergeben, um Koordinaten in Chart-Pixeln zu erhalten.
Die ähnliche Methode wird verwendet, um die maximale Bildschirm-XY-Koordinate von allen Pivot-Punkten zu erhalten:
//+------------------------------------------------------------------+ //| Return the maximum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMaxCoordXY(int &x,int &y) { //--- Declare the variables that store the maximum time and minimum price datetime time_max=0; double price_min=DBL_MAX; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the maximum time if(this.Time(i)>time_max) time_max=this.Time(i); //--- Define the maximum price if(this.Price(i)<price_min) price_min=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_max,price_min,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
Die Methode ähnelt der vorherigen, nur dass wir hier nach der maximalen Zeit und dem minimalen Preis suchen.
Wie ich bereits erwähnt habe, werde ich diese Methoden später bei der Arbeit mit zusammengesetzten grafischen Objekten benötigen.
In der Klasse des erweiterten Standard-Toolkits für grafische Objekte \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh muss die Methode, die die X- und Y-Koordinaten des angegebenen Drehpunkts des grafischen Objekts in Bildschirmkoordinaten zurückgibt, leicht angepasst werden. Der Grund dafür ist, dass wir die grafischen Objekte durch die "switch"-Operator-Fälle so verteilen müssen, dass jeder Fall grafische Objekte nach der Anzahl der Pivot-Punkte für Objekte enthält, die mit Zeit-/Preiskoordinaten konstruiert wurden. Verteilen wir sie also in der richtigen Reihenfolge:
//+------------------------------------------------------------------+ //| 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) { //--- Declare form objects, from which we are to receive their screen coordinates CFormControl *form0=NULL, *form1=NULL; //--- Set X and Y to zero - these values will be received in case of a failure x=0; y=0; //--- Depending on the graphical object type switch(this.m_base_type) { //--- Objects drawn using screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write object screen coordinates and return 'true' x=this.m_base_x; y=this.m_base_y; return true; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- One reference point (time/price) case OBJ_TEXT : case OBJ_BITMAP : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate coordinates for forms on the line pivot points if(index<this.m_base_pivots) return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false); //--- Calculate the coordinates for the central form located between the line pivot points else { form0=this.GetControlPointForm(0); form1=this.GetControlPointForm(1); if(form0==NULL || form1==NULL) return false; x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2; y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2; return true; } //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- default : break; } return false; } //+------------------------------------------------------------------+
Objekte, die anfänglich in Bildschirmkoordinaten gezeichnet werden, geben einfach die Bildschirmkoordinaten ihres Basisobjekts zurück (das Objekt, an das sie angehängt sind).
Objekte mit implementierter Rückgabe von Bildschirmkoordinaten haben zwei Drehpunkte. Die Bildschirmkoordinaten werden für alle Objekt-Drehpunkte und den zentralen Punkt berechnet.
Die restlichen Objekte, die noch nicht so funktionieren, werden einfach in ihre Gruppen eingeteilt (einige Objekte werden in Gruppen nach Typen und nicht nach der Anzahl der Pivotpunkte zusammengefasst, ich werde sie später implementieren).
Jetzt müssen wir sicherstellen, dass alle GUI-Steuerelemente im Chart immer über den grafischen Objekten bleiben, die neu zum Chart hinzugefügt werden. Außerdem müssen wir dafür sorgen, dass die GUI-Elemente in der gleichen Reihenfolge angeordnet werden, in der sie sich befanden, bevor ein grafisches Objekt zum Chart hinzugefügt wurde.
Um ein grafisches Objekt in den Vordergrund zu bringen, müssen wir es ausblenden und nacheinander wieder einblenden. Dies geschieht, indem man das Sichtbarkeitsflag für grafische Objekte zurücksetzt und dann einbaut. Um das Objekt auszublenden, müssen wir die ObjectSetInteger() Funktion verwenden, um das OBJ_NO_PERIODS Flag für die OBJPROP_TIMEFRAMES Eigenschaft zu setzen. Das OBJ_ALL_PERIODS-Flag wird verwendet, um das Objekt anzuzeigen. Wenn wir dies für alle GUI-Objekte in der Reihenfolge tun, in der sie sich in der Liste der grafischen Elemente befinden, verlieren wir die Reihenfolge ihrer Position auf dem Chart. Mit anderen Worten, die Objekte werden so angeordnet, dass das allererste in der Liste auf dem Chart ganz unten steht und das letzte ganz oben. Diese Objekte können aber auch in einer völlig anderen Reihenfolge angeordnet sein, die beim Neuzeichnen geändert wird. Hier wenden wir uns der neuen Eigenschaft zu, die wir heute hinzugefügt haben — ZOrder. Wir müssen die Liste der grafischen Objekte in aufsteigender Reihenfolge der ZOrder-Eigenschaft sortieren. Dann werden die Objekte in der richtigen Reihenfolge neu gezeichnet.
Öffnen Sie die Datei der Sammlungsklasse für grafische Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh und nehmen wir darin alle notwendigen Verbesserungen vor.
Wir deklarieren drei neue Methoden im privaten Abschnitt der Klasse:
//--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); private: //--- Move all objects on canvas to the foreground void BringToTopAllCanvElm(void); //--- (1) Return the maximum ZOrder of all CCanvas elements, //--- (2) Set ZOrde to the specified element and adjust it in all the remaining elements long GetZOrderMax(void); bool SetZOrderMAX(CGCnvElement *obj); //--- Handle the removal of extended graphical objects void DeleteExtendedObj(CGStdGraphObj *obj);
Schreiben wir ihre Implementierung außerhalb des Klassenkörpers.
Die Methode verschiebt alle Objekte auf der Leinwand in den Vordergrund:
//+------------------------------------------------------------------+ //| Move all objects on canvas to the foreground | //+------------------------------------------------------------------+ void CGraphElementsCollection::BringToTopAllCanvElm(void) { //--- Create a new list, reset the flag of managing memory and sort by ZOrder CArrayObj *list=new CArrayObj(); list.FreeMode(false); list.Sort(SORT_BY_CANV_ELEMENT_ZORDER); //--- Declare the graphical element object CGCnvElement *obj=NULL; //--- In the loop by the total number of all graphical elements, for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++) { //--- get the next object obj=this.m_list_all_canv_elm_obj.At(i); if(obj==NULL) continue; //--- and insert it to the list in the order of sorting by ZOrder list.InsertSort(obj); } //--- In a loop by the created list sorted by ZOrder, for(int i=0;i<list.Total();i++) { //--- get the next object obj=list.At(i); if(obj==NULL) continue; //--- and move it to the foreground obj.BringToTop(); } //--- Remove the list sorted by 'new' delete list; } //+------------------------------------------------------------------+
Was wir hier also sehen. Wir deklarieren das neue Listenobjekt CArrayObj und setzen sein Speicherverwaltungsflag zurück. Als Nächstes setzen wir die Eigenschaft des Sortierflags der Liste für ZOrder. Warum tun wir das? Um die CObject-Objekte nach einer beliebigen Eigenschaft sortieren zu können, müssen wir den Eigenschaftswert als Sortierkennzeichen setzen. In diesem Fall gibt die virtuelle Methode Compare(), die in allen von der Klasse CObject abgeleiteten Objekten implementiert ist, den Wert zurück, der sich aus dem Vergleich ähnlicher Eigenschaften (die als Sortiermodus festgelegte Eigenschaft) zweier Objekte ergibt, was für die Operation der Methode Sort() erforderlich ist.
Das Zurücksetzen des Speichermanagement-Flags ist erforderlich, damit wir schließlich Kopien von Objekten aus der Kollektionsliste in dem erstellten Array erhalten und nicht nur Zeiger auf die Sammlungsobjekte. Das ist wichtig, denn wenn wir es mit den Zeigern zu tun haben, dann wird jede Änderung an ihnen in der neuen Liste automatisch das Objekt in der Kollektionsliste ändern, da es sich um die Zeiger auf dasselbe Objekt der Kollektionsliste handelt, die sich in zwei Listen befinden. Ich erstelle eine unabhängige Listenkopie, deren Änderung sich nicht auf das Original auswirkt, um solche "Überraschungen" zu vermeiden. Nach Abschluss der Methode sollten wir die erstellte Liste löschen, um Speicherlecks zu vermeiden. Dies wird auch in der Hilfe der Standardbibliothek (FreeMode) erwähnt:
Das Setzen des Speichermanagement-Flags ist ein wichtiger Bestandteil bei der Verwendung der Klasse CArrayObj. Da die Array-Elemente Zeiger auf dynamische Objekte sind, ist es wichtig zu bestimmen, was mit ihnen geschehen soll, wenn sie aus dem Array entfernt werden.
Wenn das Flag gesetzt ist und ein Element aus dem Array entfernt wird, wird das Element automatisch durch den delete-Operator gelöscht. Wenn das Flag nicht gesetzt ist, wird davon ausgegangen, dass ein Zeiger auf das gelöschte Objekt noch irgendwo im Benutzerprogramm vorhanden ist und anschließend vom Programm freigegeben wird.
Wenn das Nutzerprogramm das Flag der Speicherverwaltung zurücksetzt, muss der Benutzer seine Verantwortung für die Entfernung des Arrays vor Beendigung des Programms verstehen. Andernfalls wird der vom neuen Operator für Elemente zugewiesene Speicher nicht freigegeben.
Bei großen Datenmengen kann dies sogar zu einem Absturz des Terminals führen. Wenn der Nutzer das Speicherverwaltungsflag nicht zurücksetzt, gibt es einen weiteren Fallstrick.
Wenn Zeigerelemente in einem Array irgendwo in den lokalen Variablen gespeichert sind, dann führt das Entfernen des Arrays zu einem kritischen Fehler und zum Absturz des Nutzerprogramms. Standardmäßig ist das Speicherverwaltungsflag gesetzt, d. h. die Klasse des Arrays ist für das Freigeben der Speicherelemente verantwortlich.
Als Nächstes durchlaufen wir in einer Schleife die Kollektionsliste der grafischen Elemente und fügen das jeweils nächste Objekt in der Sortierreihenfolge in die neue Liste ein.
Nach Abschluss der Schleife erhalten wir die neue Liste sortiert in aufsteigender Reihenfolge der ZOrder-Eigenschaften aller grafischen Elemente.
In der Schleife durch die Liste holen wir das nächste Element und bringen es in den Vordergrund. Jedes nachfolgende Element wird immer höher sein als das vorherige.
Wenn die Schleife beendet ist, entferne die neu erstellte Liste.
Die Methode gibt die maximale ZOrder aller CCanvas-Elemente zurück:
//+------------------------------------------------------------------+ //| Return the maximum ZOrder of all CCanvas elements | //+------------------------------------------------------------------+ long CGraphElementsCollection::GetZOrderMax(void) { this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ZORDER); int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ZORDER); CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index); return(obj!=NULL ? obj.Zorder() : WRONG_VALUE); } //+------------------------------------------------------------------+
Hier sortieren wir die Liste nach der Eigenschaft ZOrder und erhalten den Elementindex in der Liste mit dem maximalen Eigenschaftswert.
Wir holen uns den Zeiger auf das Element über den erhaltenen Index und geben die Eigenschaft ZOrder des Elements zurück.
Wenn das Element nicht empfangen wurde, gibt die Methode -1 zurück.
Wenn wir zum Beispiel drei GUI-Objekte haben, wäre es sinnvoll, drei ZOrder-Werte für sie zu haben. Alle Objekte haben zunächst einen ZOrder-Wert von Null — sie befinden sich alle ganz unten. Sobald wir ein Objekt erfassen, wird seine ZOrder um 1 erhöht. Aber zuerst sollten wir sehen, ob irgendein anderes Objekt einen ZOrder-Wert hat, der gleich oder größer als 1 ist, denn das letzte ausgewählte Objekt sollte höher als alle anderen sein und die maximal verfügbare ZOrder um 1 überschreiten. Natürlich können wir die maximale ZOrder ermitteln und sie einfach um 1 erhöhen. Aber das ist nicht die eleganteste Lösung. Stattdessen werde ich dafür sorgen, dass von den drei Objekten nur ZOrder-Werte im Bereich von 0 - 2 möglich sind.
Daher müssen wir für das letzte ausgewählte Objekt entweder die ZOrder um 1 erhöhen oder sie so hoch wie möglich lassen (um die Anzahl aller Objekte, die bei Null beginnen), während wir die ZOrder für die übrigen Objekte um 1 verringern. Wenn sich das Objekt ganz unten befindet und seine ZOrder bereits Null ist, wird sie nicht verringert. Die Änderung der ZOrder-Werte erfolgt also "on loop" entsprechend der Anzahl der GUI-Objekte.
Die Methode setzt ZOrder auf das angegebene Element und passt es in anderen Elementen an:
//+------------------------------------------------------------------+ //| Set ZOrde to the specified element | //| and adjust it in other elements | //+------------------------------------------------------------------+ bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj) { //--- Get the maximum ZOrder of all graphical elements long max=this.GetZOrderMax(); //--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false' if(obj==NULL || max<0) return false; //--- Declare the variable for storing the method result bool res=true; //--- If the maximum ZOrder is zero, ZOrder is equal to 1, //--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1, //--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1 long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1); //--- If failed to set ZOrder for an object passed to the method, return 'false' if(!obj.SetZorder(value,false)) return false; //--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder CForm *form=obj; //--- and draw a text specifying ZOrder on the form form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Sort the list of graphical elements by an element ID this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID); //--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL); //--- If failed to obtain the list and the list size exceeds one, //--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false' if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1) return false; //--- In the loop by the obtained list of remaining graphical element objects for(int i=0;i<list.Total();i++) { //--- get the next object CGCnvElement *elm=list.At(i); //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0) continue; //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value if(!elm.SetZorder(elm.Zorder()-1,false)) res &=false; //--- Temporarily (for the test purpose), if the element is a form, if(elm.Type()==OBJECT_DE_TYPE_GFORM) { //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form form=elm; form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } } //--- Upon the loop completion, return the result set in 'res' return res; } //+------------------------------------------------------------------+
Jeder Methodenstring ist mit Kommentaren versehen, um die Logik der Methode zu verdeutlichen. Zu Testzwecken wurde eine Textanzeige auf dem Formular mit einem Hinweis auf den ZOrder-Wert hinzugefügt, damit wir die Änderung der Eigenschaften jedes Formularobjekts während der Tests visuell verfolgen können.
Es gibt zwei Fälle, in denen wir GUI-Objekte über die neu erstellten grafischen Objekte verschieben müssen:
- Manuelles Hinzufügen eines grafischen Standardobjekts zu einem Chart,
- Programmgesteuertes Hinzufügen eines grafischen Objekts (ein Standard- oder ein erweitertes zusammengesetztes Objekt).
Diese Fälle werden in der Bibliothek unabhängig voneinander behandelt.
Daher müssen wir an verschiedenen Stellen einen Aufruf einer Methode hinzufügen, die GUI-Objekte auf eine höhere Ebene hebt.
Wenn wir ein grafisches Objekt programmatisch erstellen, können wir einen Aufruf zum Neuzeichnen von GUI-Objekten direkt in einer privaten Methode platzieren, die das erstellte grafische Standardobjekt zur Liste hinzufügt. Dies ist genau die Stelle, an der der Erfolg der Erstellung eines grafischen Objekts letztendlich bestimmt wird, und wir können den Aufruf einer Methode zum Verschieben aller GUI-Objekte in den Vordergrund genau an das Ende dieser Methode setzen:
//--- Add a newly created standard graphical object to the list bool AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj) { if(!this.m_list_all_graph_obj.Add(obj)) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); ::ObjectDelete(chart_id,name); delete obj; return false; } //--- Move all the forms above the created chart object and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(chart_id); return true; }
Nach dem Aufruf der Methode müssen wir das Chart aktualisieren, damit die Änderungen sofort angezeigt werden.
Im Falle einer manuellen Deklaration von grafischen Objekten wird dieses Ereignis in der Methode zur Aktualisierung der Liste aller grafischen Objekte definiert.
In dem Code-Block, in dem das Hinzufügen eines neuen grafischen Objekts definiert ist, fügen wir den Aufruf der Methode hinzu, die GUI-Objekte in den Vordergrund bringt:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { this.RefreshForExtraObjects(); //--- Declare variables to search for charts long chart_id=0; int i=0; //--- In the loop by all open charts in the terminal (no more than 100) while(i<CHARTS_MAX) { //--- Get the chart ID chart_id=::ChartNext(chart_id); if(chart_id<0) break; //--- Get the pointer to the object for managing graphical objects //--- and update the list of graphical objects by chart ID CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); //--- If failed to get the pointer, move on to the next chart if(obj_ctrl==NULL) continue; //--- If the number of objects on the chart changes if(obj_ctrl.IsEvent()) { //--- If a graphical object is added to the chart if(obj_ctrl.Delta()>0) { //--- Get the list of added graphical objects and move them to the collection list //--- (if failed to move the object to the collection, move on to the next object) if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; //--- Move all the forms above the created chart object and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(obj_ctrl.ChartID()); } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { int index=WRONG_VALUE; //--- In the loop by the number of removed graphical objects for(int j=0;j<-obj_ctrl.Delta();j++) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id,index); if(obj!=NULL) { //--- Get the removed object parameters long lparam=obj.ChartID(); string sparam=obj.Name(); double dparam=(double)obj.TimeCreate(); //--- If this is an extended graphical object if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { this.DeleteExtendedObj(obj); } //--- Move the graphical object class object to the list of removed objects //--- and send the event to the control program chart if(this.MoveGraphObjToDeletedObjList(index)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam); } } } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
Bei der Durchführung des Tests im vorigen Artikel habe ich festgestellt, dass die Koordinaten eines zusammengesetzten grafischen Objekts nicht immer korrekt durch die Grenzen des Charts begrenzt werden. Bei unterschiedlichen Positionen der Objektdrehpunkte kann das Objekt "deformiert" werden, wenn es einen Chart-Rand erreicht.
Den Grund habe ich gleich nach dem Test aufgedeckt:
... die Verschiebungen der Pivotpunkte werden relativ zum zentralen Punkt berechnet, d.h. ein Punkt hat eine positive Verschiebung, der zweite eine negative. Der Fehler bei der Berechnung der Begrenzung tritt auf, wenn man die Lage der Angelpunkte relativ zum zentralen Punkt ändert.
Wir beheben dies, indem wir die notwendigen Änderungen vornehmen. Wir werden sofort die Koordinaten des zentralen Punktes sowie die Verschiebungen von den Drehpunkten der grafischen Objekte zu ihrem zentralen Punkt berechnen. Bei der Berechnung der Begrenzungen durch die Chart-Größe sollten Sie den Wert für die Verschiebung ohne Vorzeichen verwenden. In diesem Fall hat das Vorzeichen der Verschiebung keinen Einfluss auf die Berechnung.
In der Ereignisbehandlung des Blocks für die Behandlung eines erweiterten grafischen Standardobjekts fügen wir eine neue Berechnung der Begrenzungen hinzu und zeigen die Debugging-Daten im Chart an:
//--- If the form is central for managing all pivot points of a graphical object else { //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point)) { //--- In the loop by the number of object pivot points, for(int i=0;i<(int)this.m_data_pivot_point.Size();i++) { //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders //--- By X coordinate if(x+shift-::fabs(this.m_data_pivot_point[i].ShiftX)<0) x=-shift+::fabs(m_data_pivot_point[i].ShiftX); if(x+shift+::fabs(this.m_data_pivot_point[i].ShiftX)>chart_width) x=chart_width-shift-::fabs(this.m_data_pivot_point[i].ShiftX); //--- By Y coordinate if(y+shift-::fabs(this.m_data_pivot_point[i].ShiftY)<0) y=-shift+::fabs(this.m_data_pivot_point[i].ShiftY); if(y+shift+::fabs(this.m_data_pivot_point[i].ShiftY)>chart_height) y=chart_height-shift-::fabs(this.m_data_pivot_point[i].ShiftY); //--- set the calculated coordinates to the current object pivot point ext.ChangeCoordsExtendedObj(x+shift+this.m_data_pivot_point[i].ShiftX,y+shift+this.m_data_pivot_point[i].ShiftY,i); } } //--- Display debugging comments on the chart if(m_data_pivot_point.Size()>=2) { int max_x,min_x,max_y,min_y; if(ext.GetMinCoordXY(min_x,min_y) && ext.GetMaxCoordXY(max_x,max_y)) Comment ( "MinX=",min_x,", MaxX=",max_x,"\n", "MaxY=",min_y,", MaxY=",max_y ); } }
Sobald wir auf das GUI-Objekt klicken, müssen wir es in den Vordergrund bringen und seine maximale ZOrder setzen.
In der Ereignisbehandlung des Cursor-Handling-Blocks innerhalb eines aktiven Bereichs der gedrückten Maustaste fügen wir den Code-Block zum Setzen einer maximalen ZOrder für das Objekt ein:
//--- 'The cursor is inside the active area, any mouse button is clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; // the flag of holding the mouse button on the form //--- If the left mouse button is pressed if(this.m_mouse.IsPressedButtonLeft()) { //--- Set flags and form parameters move=true; // movement flag form.SetInteraction(true); // flag of the form interaction with the environment form.BringToTop(); // form on the background - above all others form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate this.ResetAllInteractionExeptOne(form); // Reset interaction flags for all forms except the current one //--- Get the maximum ZOrder long zmax=this.GetZOrderMax(); //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0)) { //--- If the form is not a control point for managing an extended standard graphical object, //--- set the form's ZOrder above all others if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL) this.SetZOrderMAX(form); } } }
Hier setze ich ZOrder für Formularobjekte, die keine erweiterten grafischen Standardobjektsteuerungen sind.
Die Behandlung der Klassenereignisse der grafischen Elementsammlung ist recht umfangreich. Es macht also keinen Sinn, den gesamten Code hier zu veröffentlichen. Sie finden alle Änderungen (siehe oben) in den angehängten Dateien.
Ändern wir die Berechnung der Verschiebung in der Methode, die die Bildschirmkoordinaten jedes Drehpunkts eines grafischen Objekts zurückgibt.
Jetzt berechnen wir sofort die Koordinaten des zentralen Drehpunktes (der zum Verschieben des Objekts verwendet wird) und berechnen die Verschiebungen auf der Grundlage dieses Punktes. Genauer gesagt werden wir die Verschiebungen von jedem Drehpunkt bis zum zentralen Punkt berechnen:
//+------------------------------------------------------------------+ //| Return screen coordinates | //| of each graphical object pivot point | //+------------------------------------------------------------------+ bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]) { //--- Central point coordinates int xc=0, yc=0; //--- If failed to increase the array of structures to match the number of pivot points, //--- inform of that in the journal and return 'false' if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots()) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); return false; } //--- In the loop by the number of graphical object pivot points for(int i=0;i<obj.Pivots();i++) { //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false' if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY); return false; } } //--- Depending on the graphical object type switch(obj.TypeGraphObject()) { //--- One pivot point in screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : break; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate the central point coordinates xc=(array_pivots[0].X+array_pivots[1].X)/2; yc=(array_pivots[0].Y+array_pivots[1].Y)/2; //--- Calculate the shifts of all pivot points from the central one and write them to the structure array array_pivots[0].ShiftX=array_pivots[0].X-xc; // X shift from 0 to the central point array_pivots[1].ShiftX=array_pivots[1].X-xc; // X shift from 1 to the central point array_pivots[0].ShiftY=array_pivots[0].Y-yc; // Y shift from 0 to the central point array_pivots[1].ShiftY=array_pivots[1].Y-yc; // Y shift from 1 to the central point return true; //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default: break; } return false; } //+------------------------------------------------------------------+
Alle Verschiebungen und Begrenzungen sollten nun korrekt berechnet werden. Führen wir den Test durch, um das zu überprüfen.
Test
Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn in \MQL5\Experts\TestDoEasy\Part100\ als TestDoEasyPart100.mq5.
Da wir während des Tests den Wert der ZOrder-Eigenschaft auf jedem Formular sofort sehen wollen, fügen wir in OnInit() den Nullwert der Eigenschaft für das Formular (das Objekt ganz unten) ein und fügen die Anzeige der ID des Formularobjekts und seinen ZOrder-Eigenschaftswert hinzu:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- 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 //--- Create form objects CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { //--- When creating an object, pass all the required parameters to it form=new CForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(true); //--- Set the form ID and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the opacity of 200 form.SetOpacity(245); //--- 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(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(false); //--- 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(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity(),true); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,TextByLanguage("Тест: ID ","Test: ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Add the form to the list if(!engine.GraphAddCanvElmToCollection(form)) delete form; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Kompilieren und starten wir den EA auf dem Chart:
Wie wir sehen können, ist der ZOrder-Wert jedes Formularobjekts unmittelbar nach der Erstellung gleich Null, aber grafische Objekte werden immer noch "unter ihnen" aufgebaut. Änderungen des ZOrder-Werts eines jeden Objekts werden "kreisförmig" durchgeführt — nicht mehr als die Anzahl der Formularobjekte auf dem Chart (von Null an gerechnet). Jedes konstruierte grafische Objekt erscheint immer "unter" den GUI-Objekten, und ihre relative Position bleibt unverändert, was bedeutet, dass sie in der Liste entsprechend den Werten ihrer ZOrder-Eigenschaft korrekt verteilt sind. Schließlich ist das zusammengesetzte grafische Objekt jetzt korrekt auf die Ränder des Bildschirms begrenzt und wird unabhängig von der Lage seiner Drehpunkte nicht verzerrt. Wie andere grafische Objekte wird es unterhalb der Formularobjekte gezeichnet.
Was kommt als Nächstes?
Der nächste Artikel wird einen großen Abschnitt über die Erstellung von GUI-Objekten im Stile von Windows Forms beginnen.
Das bedeutet aber nicht, dass erweiterte grafische Objekte und darauf basierende zusammengesetzte Objekte nicht weiterentwickelt werden. Wir brauchen einfach vollwertige Steuerelemente für ihre Weiterentwicklung. Im nächsten Abschnitt der Bibliothek werde ich die Entwicklung und Verfeinerung von erweiterten grafischen Objekten schrittweise 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
Grafiken in der DoEasy-Bibliothek (Teil 95): Steuerelemente für zusammengesetzte grafische Objekte
Grafiken in der DoEasy-Bibliothek (Teil 96): Grafiken in Formularobjekten und Behandlung von Mausereignissen
Grafiken in der DoEasy-Bibliothek (Teil 97): Unabhängige Handhabung der Bewegung von Formularobjekten
Grafiken in der Bibliothek DoEasy (Teil 98): Verschieben von Angelpunkten erweiterter grafischer Standardobjekte
Grafiken in der DoEasy-Bibliothek (Teil 99): Verschieben eines erweiterten grafischen Objekts mit einem einzigen Steuerpunkt
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/10634
- 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.