![DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize](https://c.mql5.com/2/49/doeasy_005_600x314.jpg)
DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize
Inhalt
- Konzept
- Verbesserung der Bibliotheksklassen
- Basis-Objektklasse von WinForms
- Test
- Was kommt als Nächstes?
Konzept
Bevor ich die Eigenschaften von AutoSize und AutoSizeMode des Paneel-Objekts implementiere, erstelle ich die Basisklasse aller Bibliotheksobjekte von WinForms. Da viele der Eigenschaften solcher Objekte voneinander vererbt werden, können die Eigenschaften des Paneels (engl. panel), an dem ich gerade arbeite, auch für andere WinForms-Objekte verwendet werden. Um zu vermeiden, dass für jedes der Objekte ähnliche Eigenschaften festgelegt werden, erstelle ich ein Basis-WinForms-Objekt, von dem alle anderen Objekte dieses Typs geerbt werden. Das Basisobjekt selbst wird von der Formularobjektklasse geerbt, in der die Interaktion mit der Maus implementiert wird.
Wenn für das im Paneel befindliche Objekt die Dock-Eigenschaft (die im vorherigen Artikel betrachtet wurde) aktiviert ist, „klebt“ ein solches Objekt an den Rändern seines Containers. Der Containerrahmen wird in der DockMode-Eigenschaft angegeben. Wenn in diesem Fall jedes nachfolgende Objekt, das innerhalb des Paneels platziert wird, die gleiche Bindung an die Seite seines Containers (Paneel) hat wie das vorherige Objekt, das innerhalb des Paneels platziert wurde, dann wird es an die nächste Seite des vorherigen Objekts und nicht an die gebunden angegebene Seite des Behälters. Somit werden alle Objekte, die innerhalb des Paneels platziert und beispielsweise an den linken Rand des Containers gebunden sind, in einer Reihe von links nach rechts angeordnet. Wenn das Paneel den AutoSize-Modus aktiviert hat, wird der Container automatisch in der Breite vergrößert, damit alle Objekte, die sich darin befinden und in einer Reihe angeordnet sind, nicht über ihren Container hinausragen. Genauso sollte sich der Container verhalten, wenn ein Objekt, das über die Kanten des Paneels hinausragt, darin platziert wird. Wenn für das Paneel der AutoSize-Modus aktiviert ist, muss es die Größe seiner Seiten anpassen, damit das Objekt seine Grenzen nicht überschreitet.
Gleichzeitig gibt es einen Unterschied, ob das Objekt an einer der Seiten seines Containers befestigt ist und der Container selbst die AutoSize-Eigenschaft aktiv hat oder nicht.
Alle Objekte, die in einem Container mit aktiver automatischer Größenanpassung platziert werden, werden in der Reihenfolge ihrer Priorität platziert, die durch die Seriennummer des Objekts in der Liste der an den Container angehängten Objekte angegeben ist. Dadurch können wir die Position von Objekten innerhalb des Containers vorbestimmen, dessen Größe sich automatisch an die Gesamtgröße aller daran gebundenen Elemente anpasst. Ich werde dies in den folgenden Artikeln berücksichtigen. Heute werde ich die Bibliotheksklassen gemäß der Aufgabe verbessern, ein neues Basis-WinForms-Objekt zu erstellen und die Autosize-Eigenschaft zu implementieren, wenn ein direkt daran angehängtes Element aus dem Paneel erstellt wird.
Lassen Sie uns zusätzlich zu den angegebenen Aufgaben eine kleine Optimierung der Konstruktion der grafischen Elemente innerhalb des Paneels durchführen. Da wir zuerst alle an das Paneel angehängten Elemente nach ihren Seriennummern und nach ihren Bindungswerten anordnen müssen (Dock) und dann die Größe des Paneels ändern müssen, falls die automatische Größenanpassung so eingestellt ist, dass sie zu seinem internen Inhalt passt (und wenn es wirklich notwendig), dann müssen wir zuerst alle Elemente „virtuell“ innerhalb des Paneels platzieren, dann sehen, wie sehr wir die Größe des Paneels ändern müssen, und es bei Bedarf ändern. Erst danach sollten wir alle Elemente innerhalb des Paneels neu zeichnen. Wenn wir die Elemente stattdessen sofort zeichnen, werden die Änderungen in ihrer Größe und ihrem Bindungspunkt in Echtzeit sichtbar und verursachen verschiedene Rendering-Artefakte rund um das Paneel. Daher werden wir zuerst die Elemente in der richtigen Reihenfolge anordnen, ihre Größe bei Bedarf ändern (dies hängt von der Methode zum Binden des Elements und seiner Nummer in der Liste der gebundenen Elemente ab) und dann berechnen, wie stark die Größe des Paneels geändert werden muss. Danach zeichnen wir das Paneel und die darin befindlichen Elemente erneut in einer neuen Reihenfolge.
Verbesserung des Bibliotheksklassen
Da ich hier ein neues Bibliotheksobjekt implementieren werde, muss ich seinen Typ zur Liste der Bibliotheksobjekttypen hinzufügen. Außerdem müssen wir den Objekttyp kennen, wenn wir auf das Paneel-Unterlagenobjekt zugreifen.
Geben wir in \MQL5\Include\DoEasy\Defines.mqh, nämlich in der Liste der Bibliotheksobjekttypen, den neuen Typ — Basis-WinForms-Objekt ein:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_BASE, // WinForms Base object type (base abstract WinForms object) OBJECT_DE_TYPE_GWF_PANEL, // WinForms Panel object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type //--- ... //--- ... }
Fügen wir der Liste der grafischen Elementtypen zwei neue Typen hinzu – grafisches Unterlageelement und Basis-WinForms-Objekt. Benennen wir außerdem die Makroersetzung GRAPH_ELEMENT_TYPE_PANEL in GRAPH_ELEMENT_TYPE_WF_PANEL um:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
Dieser Typ ermöglicht es uns zu wissen, welches Objekt gerade ausgewählt ist und was damit zu tun ist, wenn dies der notwendige Typ ist.
In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:
MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Underlay of the Panel WinForms control object MSG_GRAPH_ELEMENT_TYPE_WF_BASE, // WinForms base control MSG_GRAPH_ELEMENT_TYPE_WF_PANEL, // Panel control
und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
{"Окно","Window"}, {"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""}, {"Базовый элемент управления WinForms","Base WinForms control"}, {"Элемент управления \"Panel\"","Control element \"Panel\""},
Alle grafischen Bibliotheksobjekte werden von der grafischen Basisobjektklasse der CGBaseObj-Bibliothek abgeleitet. Die Klasse enthält alle Basismethoden zum Handhaben beliebiger grafischer Bibliotheksobjekte. Fügen wir in \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh die Anzeige der Beschreibung von zwei neuen Arten von grafischen Bibliothekselementen zur Methode hinzu, die die Beschreibung eines Typs von grafischen Elementen zurückgibt:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(void) { return ( this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : "Unknown" ); } //+------------------------------------------------------------------+
Machen wir noch einige Methoden in der Klasse des grafischen Elementobjekts auf der Leinwand virtuell, von der die verbleibenden Canvas-Objekte der Bibliotheksgrafiken abgeleitet sind. Die Klasse befindet sich in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
Die Implementierung einiger Methoden in der Klasse kann sich als ungeeignet für einige untergeordnete Objekte der Klasse herausstellen. Wenn wir sie virtuell machen, können wir diese Methoden in den abgeleiteten Klassen ändern, in denen sich die Implementierung der Methoden von der in der Klasse erstellten unterscheiden sollte.
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge, virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); void SetRightEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); } void SetBottomEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); }
...
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); }
Nun werden wir in den abgeleiteten Klassen, in denen eine andere Implementierung dieser Methoden benötigt wird, einfach genau die gleichen virtuellen Methoden schreiben, allerdings mit einer anderen Implementierung als der hier gezeigten. Genau diese virtuellen Methoden der abgeleiteten Klassen werden aufgerufen, wenn auf die virtuelle Methode der Elternklasse zugegriffen wird.
Ich muss einigen Methoden, die ich gerade verbessere, das Flag hinzufügen, das anzeigt, dass das grafische Objekt neu gezeichnet werden muss. Dadurch wird die Handhabung von Objektlisten optimiert, um das ständige Neuzeichnen jedes Objekts aus der Liste zu vermeiden, sondern zuerst alle Objekte in der Liste zu behandeln (z. B. die Größe jedes Objekts zu ändern und es an eine neue Position zu verschieben). Zeichnen wir nach Abschluss der Bearbeitung einer gesamten Liste einfach jedes Objekt mit seinen neuen Koordinaten oder mit seiner neuen Größe neu. Dies ist visuell schneller, als jedes Objekt in der Liste sofort nach dem Ändern seiner Abmessungen und Koordinaten neu zu zeichnen.
Ändern wir in der Klassendatei des Schattenobjekts \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqhdie Namen der Variablen und Methoden und entfernen das Wort „shadow““ (Schatten) aus ihnen. Ich glaube, das ist hier überflüssig.
Da wir den Schatten mit neuer Größe komplett neu zeichnen müssen, wenn auch mit den gleichen Schattenparametern, fügen wir noch eine weitere Variable, die den Schattenunschärferadius speichert, zwei Methoden für den Zugriff auf eine neue Variable sowie das Flag für die Notwendigkeit, das Ganze neu zu zeichnen Shadow-Objekt in die Methode zum Neuzeichnen der Schatten:
//+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color; // Shadow color uchar m_opacity; // Shadow opacity uchar m_blur; // Blur //--- Gaussian blur bool GaussianBlur(const uint radius); //--- Return the array of weight ratios bool GetQuadratureWeights(const double mu0,const int n,double &weights[]); //--- Draw the object shadow form void DrawShadowFigureRect(const int w,const int h); public: //--- Constructor CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); //--- Supported object properties (1) integer and (2) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Draw an object shadow void Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw); //+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) return the shadow color void SetColor(const color colour) { this.m_color=colour; } color Color(void) const { return this.m_color; } //--- (1) Set and (2) return the shadow opacity void SetOpacity(const uchar opacity) { this.m_opacity=opacity; } uchar Opacity(void) const { return this.m_opacity; } //--- (1) Set and (2) return the shadow blur void SetBlur(const uchar blur) { this.m_blur=blur; } uchar Blur(void) const { return this.m_blur; } }; //+------------------------------------------------------------------+
Fügen wir im Klassenkonstruktor die standardmäßige Schattenopazität hinzu, die durch die Makroersetzung CLR_DEF_SHADOW_OPACITY in der Bibliotheksdatei Defines.mqh angegeben ist, und geben die standardmäßige Schattenunschärfe mit der Makroersetzung DEF_SHADOW_BLUR ebenfalls aus derselben Datei an:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CShadowObj::CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetColorBackground(clrNONE); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.m_visible=true; CGCnvElement::Erase(); } //+------------------------------------------------------------------+
Bei der Implementierung der Methode zum Zeichnen eines Schattenobjekts geben wir nun eindeutig das Flag zum Neuzeichnen des Schattens an. Anstelle der lokalen Variablen 'radius' werde ich die neue Variable m_blur verwenden. Dadurch können wir den Schattenunschärfewert für das spätere Neuzeichnen des Schattenobjekts mit den Parametern speichern, mit denen es ursprünglich gezeichnet wurde:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw) { //--- Set the shadow shift values to the variables by X and Y axes this.SetCoordXRelative(shift_x); this.SetCoordYRelative(shift_y); //--- Calculate the height and width of the drawn rectangle int w=this.Width()-OUTER_AREA_SIZE*2; int h=this.Height()-OUTER_AREA_SIZE*2; //--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); //--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value); //--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(this.m_blur)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw); CGCnvElement::Update(redraw); } //+------------------------------------------------------------------+
Da ich das Basisobjekt aller Bibliotheksobjekte von WinForms etwas später implementieren werde, sollten einige Variablen und Methoden in den implementierten WinForms-Objektklassen und ihren übergeordneten Klassen entweder an eine neue Basisklasse oder an ihre übergeordnete Klasse — die CForm-Klasse — übergeben werden. Dadurch bleiben alle Variablen und Methoden auf den Ebenen der Klassen der Vererbungshierarchie verfügbar, wo sie benötigt werden.
Beispielsweise werden beim Erstellen eines Objekts seine Koordinaten und seine Größe in den Klassenvariablen von CPanel gespeichert. Wir benötigen die gleichen Daten in anderen WinForms-Objekten. Daher verschieben wir sie in die übergeordnete Klasse der WinForms-Objekte in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Auf den ersten Blick wäre es logisch, diese Variablen und Methoden in die Basisklasse von WinForms-Objekten zu verschieben. Die Daten können sich aber auch für Formularobjekte als nützlich erweisen, deren Klasse die übergeordnete Klasse für die Klasse des Basisobjekts aller WinForms-Objekte ist. Verschieben wir also diese Variablen dorthin:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor int m_init_x; // Newly created form X coordinate int m_init_y; // Newly created form Y coordinate int m_init_w; // Newly created form width int m_init_h; // Newly created form height //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Create a new graphical object
Lassen Sie uns die CreateNewElement()-Methode in zwei Teile teilen, um den Code zu optimieren – erstellen Sie noch eine weitere CreateAndAddNewElement()-Methode, bei der ein neues Element erstellt und der Liste hinzugefügt wird. Deklarieren wir die Methode im geschützten Abschnitt der Klasse. Verschieben wir die Methoden zum Umgang mit den Variablen, die die Anfangskoordinaten und die Objektgröße speichern, aus der CPanel-Klasse in den öffentlichen Abschnitt:
protected: CArrayObj m_list_tmp; // List for storing the pointers int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); void Deinitialize(void); //--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Update coordinates of bound objects virtual bool MoveDependentObj(const int x,const int y,const bool redraw=false); //--- Create a new bound element and add it to the list of bound objects CGCnvElement *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity); public: //--- Return the initial (1) X and (2) Y coordinate, (3) form width and (4) height int GetCoordXInit(void) const { return this.m_init_x; } int GetCoordYInit(void) const { return this.m_init_y; } int GetWidthInit(void) const { return this.m_init_w; } int GetHeightInit(void) const { return this.m_init_h; } //--- Set the initial (1) X and (2) Y coordinate, (3) form width and (4) height void SetCoordXInit(const int value) { this.m_init_x=value; } void SetCoordYInit(const int value) { this.m_init_y=value; } void SetWidthInit(const int value) { this.m_init_w=value; } void SetHeightInit(const int value) { this.m_init_h=value; } //--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam); int MouseCursorX(void) const { return this.m_mouse.CoordX(); } int MouseCursorY(void) const { return this.m_mouse.CoordY(); }
Wir machen die Methode CreateNewElement() virtuell und fügen das Flag hinzu, das die Notwendigkeit angibt, ein neu erstelltes Objekt zu rendern:
//--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y);
Die Methode wurde virtuell gemacht, damit wir dieselbe Methode mit ihrer eigenen Implementierung in jeder geerbten Klasse erstellen können. Das Flag zur Objekt-Neuzeichnung wird benötigt, um zu vermeiden, dass jedes Objekt direkt nach der Erstellung einzeln angezeigt wird. Stattdessen erstellen wir zuerst alle Objekte, passen dann die Größe des Paneels an, auf dem es erstellt wird, und rendern schließlich alles danach. Dies geht viel schneller, da wir visuelle Artefakte vermeiden können, die beim Anzeigen der geänderten Paneelgröße entstehen, während wir jedes nachfolgende Objekt aus allen daran angehängten und gleichzeitig in einer Schleife erstellten Objekten erstellen.
Deklarieren wir im Methodenblock für einen vereinfachten Zugriff auf die Objekteigenschaften zwei Methoden zur Behandlung der Eigenschaft „Schattenunschärfe“:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) get the form frame color void SetColorFrame(const color colour) { this.m_color_frame=colour; } color ColorFrame(void) const { return this.m_color_frame; } //--- (1) Set and (2) return the form shadow color void SetColorShadow(const color colour); color ColorShadow(void) const; //--- (1) Set and (2) return the form shadow opacity void SetOpacityShadow(const uchar opacity); uchar OpacityShadow(void) const; //--- (1) Set and (2) return the form shadow blur void SetBlurShadow(const uchar blur); uchar BlurShadow(void) const; }; //+------------------------------------------------------------------+
Fügen wir nach dem Aufrufen der Eigenschaftsinitialisierungsmethode den Aufruf der Methoden zur Initialisierung von Eigenschaften hinzu, die die anfängliche Größe und die Koordinaten des Objekts für alle parametrischen Konstruktoren speichern:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CForm::CForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CForm::CForm(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CForm::CForm(const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
Setzen wir in der Initialisierungsmethode selbst Nullen in diese Werte:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CForm::Initialize(void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE; this.m_gradient_v=true; this.m_gradient_c=false; this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; this.m_init_x=0; this.m_init_y=0; this.m_init_w=0; this.m_init_h=0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } //+------------------------------------------------------------------+
Die Methode, die ein neues angehängtes Element erstellt und es zur Liste der angehängten Objekte hinzufügt:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- Create a graphical element name string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns; //--- Get the screen coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return NULL; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return NULL; } //--- Set the minimum properties for a bound graphical element obj.SetColorBackground(colour); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(main); obj.SetBase(this.GetObject()); obj.SetID(this.ID()); obj.SetNumber(num); obj.SetCoordXRelative(x); obj.SetCoordYRelative(y); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(x); obj.SetCoordYRelativeInit(y); return obj; } //+------------------------------------------------------------------+
Die Methodenlogik wiederholt im Wesentlichen die Methodenlogik von CreateNewElement(), obwohl es keine bedingungslose Wiedergabe des erstellten Elements gibt. Die Methode gibt einfach den Zeiger auf ein erfolgreich erstelltes grafisches Element oder NULL zurück, falls beim Erstellen oder Hinzufügen des Elements zur Liste ein Fehler aufgetreten ist. Außerdem habe ich hier einen kleinen Fehler behoben. Die Liste der gebundenen Objekte enthält jetzt einen Objektindex für jedes Objekt.
Die Methode, die ein neues angehängtes Element erstellt, sieht jetzt anders aus – der gesamte Code zum Hinzufügen eines neuen Objekts und Hinzufügen zur Liste wurde in die oben betrachtete Methode verschoben und hier aufgerufen. Wenn das Objekt nicht erstellt oder nicht zur Liste hinzugefügt wird, gibt die Methode false zurück, andernfalls wird das Objekt mit dem Flag gerendert, das die Notwendigkeit seiner physischen Wiedergabe angibt, und die Methode gibt true zurück.
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- Create a new graphical element CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); //--- If the object has been created, draw the added object and return 'true' if(obj==NULL) return false; obj.Erase(colour,opacity,redraw); return true; } //+------------------------------------------------------------------+
In der Methode zum Zeichnen des Schattens übergeben wir nun das Flag, das die Notwendigkeit anzeigt, den Schatten zu zeichnen, in der Draw()-Methode der Schattenobjektklasse:
//+------------------------------------------------------------------+ //| Draw the shadow | //+------------------------------------------------------------------+ void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- If there is no shadow object, create it if(this.m_shadow_obj==NULL) this.CreateShadowObj(colour,opacity); //--- If the shadow object exists, draw the shadow on it, //--- set the shadow object visibility flag and //--- move the form object to the foreground if(this.m_shadow_obj!=NULL) { this.m_shadow_obj.Draw(shift_x,shift_y,blur,true); this.m_shadow_obj.SetVisible(true,false); this.BringToTop(); } } //+------------------------------------------------------------------+
Hier geben wir immer true zurück, um den Schatten zu zeichnen. Falls der Schatten nicht benötigt wird, wird dies in den aufrufenden Methoden durch einen bedingten Operator überprüft. Dies ist einfacher, als das Rendern eines nicht unscharfen Schattenrechtecks zu steuern, um zu kontrollieren, ob es gezeichnet wird oder nicht, bevor die Unschärfe dieses Rechtecks verwendet wird, sowie andere damit verbundene Fallstricke. Stattdessen ist es viel einfacher, einfach zu prüfen, ob der Schatten gerendert werden muss, und die Methode aufzurufen.
Die Methode zum Festlegen der Formschattenunschärfe:
//+------------------------------------------------------------------+ //| Set the form shadow blur | //+------------------------------------------------------------------+ void CForm::SetBlurShadow(const uchar blur) { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return; } this.m_shadow_obj.SetBlur(blur); } //+------------------------------------------------------------------+
Wenn kein Schattenobjekt vorhanden ist, zeigen wir die Meldung an, die angibt, dass das Schattenobjekt zuerst erstellt werden soll. Andernfalls wird das Verfahren zum Einstellen des Unschärfebetrags in das Schattenobjekt aufgerufen.
Die Methode, die die Schattenunschärfe des Formulars zurückgibt:
//+------------------------------------------------------------------+ //| Return the form shadow blur | //+------------------------------------------------------------------+ uchar CForm::BlurShadow(void) const { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return 0; } return this.m_shadow_obj.Blur(); } //+------------------------------------------------------------------+
Wenn kein Schattenobjekt vorhanden ist, zeigen wir die Meldung an, die angibt, dass das Schattenobjekt zuerst erstellt werden soll. Andernfalls wird die Methode zum Festlegen des Unschärfebetrags des Schattenobjekts zurückgegeben.
Abgesehen von den oben erwähnten Änderungen hat die Klasse einige kleinere Verbesserungen erhalten, die ihre Logik nicht beeinflussen. Es hat keinen Sinn, sie hier zu beschreiben. Sie können alle Änderungen in den unten angehängten Dateien einsehen.
Basisobjektklasse von WinForms
Erstellen wir in \MQL5\Include\DoEasy\Objects\Graph\WForms\eine neue Datei WinFormBase.mqh der Klasse CWinFormBase. Die Klasse sollte von der CForm-Formularobjektklasse geerbt werden. Damit die Basisklasse in der Datei sichtbar ist, fügen wir ihr die Klassendatei des Formularobjekts hinzu:
//+------------------------------------------------------------------+ //| WinFormBase.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Form.mqh" //+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { }
Wir verschieben die Variablen und Methoden zu ihrer Handhabung aus der Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh von Paneel WinForms in diese Klasse. Es soll alle Variablen und Methoden speichern, die wir für das Paneel-Objekt implementiert haben. Natürlich werden auch die neuen hinzugefügt.
Platzieren wir die folgenden Variablen im geschützten Abschnitt der Klasse:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { protected: color m_fore_color; // Default text color for all control objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Control frame style bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding control borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls private:
Im privaten Abschnitt platzieren wir die Methode, die die Schriftart-Flags zurückgibt:
private: //--- Return the font flags uint GetFontFlags(void); public:
deklarieren wir im öffentlichen Abschnitt die virtuellen Methoden zum Löschen des Elements, Neuzeichnen und Ändern der Größe sowie der Klassenkonstruktoren und verschieben solche Methoden aus der CPanel-Klassendatei:
public: //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Redraw the object virtual void Redraw(bool redraw); //--- Set the new size for the (1) current object and (2) the object specified by index virtual bool Resize(const int w,const int h,const bool redraw); virtual bool Resize(const int index,const int w,const int h,const bool redraw); //--- Constructors CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { this.m_type=OBJECT_DE_TYPE_GWF_BASE; } //--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the Bold font flag void SetBold(const bool flag); bool Bold(void); //--- (1) Set and (2) return the Italic font flag void SetItalic(const bool flag); bool Italic(void); //--- (1) Set and (2) return the Strikeout font flag void SetStrikeout(const bool flag); bool Strikeout(void); //--- (1) Set and (2) return the Underline font flag void SetUnderline(const bool flag); bool Underline(void); //--- (1) Set and (2) return the font style void SetFontDrawStyle(ENUM_FONT_STYLE style); ENUM_FONT_STYLE FontDrawStyle(void); //--- (1) Set and (2) return the font width type void SetFontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(void) const { return this.m_bold_type; } //--- (1) Set and (2) return the frame style void SetBorderStyle(const ENUM_FRAME_STYLE style) { this.m_border_style=style; } ENUM_FRAME_STYLE BorderStyle(void) const { return this.m_border_style; } //--- (1) Set and (2) return the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { this.m_autosize=flag; } bool AutoSize(void) { return this.m_autosize; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { this.m_dock_mode=mode; } ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void) const { return this.m_dock_mode; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control void SetMarginLeft(const int value) { this.m_margin[0]=value; } void SetMarginTop(const int value) { this.m_margin[1]=value; } void SetMarginRight(const int value) { this.m_margin[2]=value; } void SetMarginBottom(const int value) { this.m_margin[3]=value; } void SetMarginAll(const int value) { this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control int MarginLeft(void) const { return this.m_margin[0]; } int MarginTop(void) const { return this.m_margin[1]; } int MarginRight(void) const { return this.m_margin[2]; } int MarginBottom(void) const { return this.m_margin[3]; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value); } virtual void SetPaddingTop(const uint value) { this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value); } virtual void SetPaddingRight(const uint value) { this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value); } virtual void SetPaddingBottom(const uint value) { this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value); } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- Set the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value;} virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Return the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom int FrameWidthLeft(void) const { return this.m_frame_width_left; } int FrameWidthTop(void) const { return this.m_frame_width_top; } int FrameWidthRight(void) const { return this.m_frame_width_right; } int FrameWidthBottom(void) const { return this.m_frame_width_bottom; } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } }; //+------------------------------------------------------------------+
Die meisten übertragenen Methoden werden virtuell gemacht, damit sie bei Bedarf in geerbten Klassen umdefiniert werden können. Alle Methoden zum Setzen der Eigenschaften weisen nun das Präfix „Set“ in ihren Namen auf. Dies zeigt eindeutig die Methodenzugehörigkeit an.
Im parametrischen Konstruktor legen wir die grafischen Element- und Bibliotheksobjekttypen sowie die Standardeigenschaftswerte oder die in den Parametern übergebenen Werte fest:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.m_fore_color=CLR_DEF_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.SetMarginAll(0); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_frame_width_right=0; this.m_frame_width_left=0; this.m_frame_width_top=0; this.m_frame_width_bottom=0; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
Die Methoden wurden aus der CPanel-Klasse verschoben:
//+------------------------------------------------------------------+ //| Return the font flags | //+------------------------------------------------------------------+ uint CWinFormBase::GetFontFlags(void) { string name; int size; uint flags; uint angle; CGCnvElement::GetFont(name,size,flags,angle); return flags; } //+------------------------------------------------------------------+ //| Set the Bold font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetBold(const bool flag) { uint flags=this.GetFontFlags(); if(flag) { this.m_bold_type=FW_TYPE_BOLD; CGCnvElement::SetFontFlags(flags | FW_BOLD); } else this.m_bold_type=FW_TYPE_NORMAL; } //+------------------------------------------------------------------+ //| Return the Bold font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Bold(void) { uint flags=this.GetFontFlags(); return(flags &FW_BOLD)==FW_BOLD; } //+------------------------------------------------------------------+ //| Set the Italic font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetItalic(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_ITALIC); } //+------------------------------------------------------------------+ //| Return the Italic font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Italic(void) { uint flags=this.GetFontFlags(); return(flags &FONT_ITALIC)==FONT_ITALIC; } //+------------------------------------------------------------------+ //| Set the Strikeout font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetStrikeout(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT); } //+------------------------------------------------------------------+ //| Return the Strikeout font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Strikeout(void) { uint flags=this.GetFontFlags(); return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT; } //+------------------------------------------------------------------+ //| Set the Underline font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetUnderline(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE); } //+------------------------------------------------------------------+ //| Return the Underline font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Underline(void) { uint flags=this.GetFontFlags(); return(flags &FONT_UNDERLINE)==FONT_UNDERLINE; } //+------------------------------------------------------------------+ //| Set the font style | //+------------------------------------------------------------------+ void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style) { switch(style) { case FONT_STYLE_ITALIC : this.SetItalic(true); break; case FONT_STYLE_UNDERLINE : this.SetUnderline(true); break; case FONT_STYLE_STRIKEOUT : this.SetStrikeout(true); break; default: break; } } //+------------------------------------------------------------------+ //| Return the font style | //+------------------------------------------------------------------+ ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void) { return ( this.Italic() ? FONT_STYLE_ITALIC : this.Underline() ? FONT_STYLE_UNDERLINE : this.Strikeout() ? FONT_STYLE_UNDERLINE : FONT_STYLE_NORMAL ); } //+------------------------------------------------------------------+ //| Set the font width type | //+------------------------------------------------------------------+ void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type) { this.m_bold_type=type; uint flags=this.GetFontFlags(); switch(type) { case FW_TYPE_DONTCARE : CGCnvElement::SetFontFlags(flags | FW_DONTCARE); break; case FW_TYPE_THIN : CGCnvElement::SetFontFlags(flags | FW_THIN); break; case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT); break; case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT); break; case FW_TYPE_LIGHT : CGCnvElement::SetFontFlags(flags | FW_LIGHT); break; case FW_TYPE_REGULAR : CGCnvElement::SetFontFlags(flags | FW_REGULAR); break; case FW_TYPE_MEDIUM : CGCnvElement::SetFontFlags(flags | FW_MEDIUM); break; case FW_TYPE_SEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD); break; case FW_TYPE_DEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD); break; case FW_TYPE_BOLD : CGCnvElement::SetFontFlags(flags | FW_BOLD); break; case FW_TYPE_EXTRABOLD : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD); break; case FW_TYPE_ULTRABOLD : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD); break; case FW_TYPE_HEAVY : CGCnvElement::SetFontFlags(flags | FW_HEAVY); break; case FW_TYPE_BLACK : CGCnvElement::SetFontFlags(flags | FW_BLACK); break; default : CGCnvElement::SetFontFlags(flags | FW_NORMAL); break; } } //+------------------------------------------------------------------+
Ich habe alle diese Methoden in den vorherigen Artikeln zum Erstellen des Paneel-Objekts betrachtet.
Die virtuellen Methoden zum Löschen des Elements (Füllung mit Farbe):
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const bool redraw=false) { //--- Fully clear the element with the redrawing flag CGCnvElement::Erase(redraw); } //+------------------------------------------------------------------+
Diese virtuellen Methoden überschreiben dieselben Methoden der übergeordneten Klasse. Hier wird zuerst die Elternklasse aufgerufen, um das Element mit Farbe zu füllen, das Objektrahmen-Flag wird dann geprüft. Wenn der Rahmen vorhanden sein sollte und das Flag zur Objekt-Neuzeichnung gesetzt ist, wird der Rahmen gezeichnet. Als nächstes wird das Objekt aktualisiert.
Die Methode zum Festlegen der neuen Größe für das aktuelle Objekt:
//+------------------------------------------------------------------+ //| Set the new size for the current object | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int w,const int h,const bool redraw) { //--- If the object width and height are equal to the passed ones, return 'true' if(this.Width()==w && this.Height()==h) return true; //--- Declare the variable with the property change result bool res=true; //--- Save the panel initial size int prev_w=this.Width(); int prev_h=this.Height(); //--- Set the property change result to the 'res' variable //--- (if the property value is not equal to the passed value) if(this.Width()!=w) res &=this.SetWidth(w); if(this.Height()!=h) res &=this.SetHeight(h); if(!res) return false; //--- Calculate the value, by which the size should be changed int excess_w=this.Width()-prev_w; int excess_h=this.Height()-prev_h; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object has been received, if(this.IsShadow() && shadow!=NULL) { //--- save shadow shifts by X and Y, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- set the shadow new width and height res &=shadow.SetWidth(shadow.Width()+excess_w); res &=shadow.SetHeight(shadow.Height()+excess_h); if(!res) return false; //--- If there is no need to redraw, remove the shadow if(!redraw) shadow.Erase(); //--- Save the previously set shadow shift values relative to the panel shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- Redraw the element with new size if(redraw) this.Redraw(true); //--- All is successful - return 'true' return true; } //+------------------------------------------------------------------+
Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben. Kurz gesagt, wenn die vorhandene Objektgröße an die Methode übergeben wird, besteht keine Notwendigkeit, etwas zu ändern – geben Sie sofort true zurück. Wenn die an die Methode übergebene Größe nicht mit der Größe des Objekts übereinstimmt, ändern wir sie. Wenn es einen Schatten gibt, ändern wir seine Größe und zeichnen das gesamte Objekt neu, wenn das Neuzeichnen-Flag gesetzt ist. Mit anderen Worten, wenn das Neuzeichnen-Flag nicht gesetzt ist, ändert sich die Objektgröße, während das Objekt selbst nicht neu gezeichnet wird. Dies ist erforderlich, um das Neuzeichnen mehrerer Objekte zu beschleunigen, die an ein anderes Objekt gebunden sind. Nachdem die Größe aller gebundenen Objekte geändert wurde, sollten sie neu gezeichnet werden.
Die Methode, die die neue Größe für das durch den Index angegebene Objekt festlegt:
//+------------------------------------------------------------------+ //| Set the new size for the object specified by object index | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw) { CWinFormBase *obj=this.GetElement(index); return(obj!=NULL ? obj.Resize(w,h,redraw) : false); } //+------------------------------------------------------------------+
Die Methode erhält den Objektindex in der Liste der gebundenen Objekte, deren Größe geändert werden muss (die Größe wird auch an die Methode übergeben).
Wir holen uns das Objekt gemäß seines Index in der Liste und geben das Ergebnis des Aufrufs seiner oben betrachteten Methode Resize() zurück.
Die Methode zum Neuzeichnen eines Objekts:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CWinFormBase::Redraw(bool redraw) { //--- If the object type is less than the "Base WinForms object", exit if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE) return; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object exists, redraw it if(this.IsShadow() && shadow!=NULL) { //--- remove the previously drawn shadow, shadow.Erase(); //--- save the relative shadow coordinates, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- redraw the shadow, if(redraw) shadow.Draw(0,0,shadow.Blur(),redraw); //--- restore relative shadow coordinates shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- If the redraw flag is set, if(redraw) { //--- completely redraw the object and save its new initial look this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw); this.Done(); } //--- otherwise, remove the object else this.Erase(); //--- Redraw all bound objects with the redraw flag for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; element.Redraw(redraw); } //--- If the redraw flag is set and if this is the main object the rest are bound to, //--- redraw the chart to display changes immediately if(redraw && this.GetMain()==NULL) ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Die Methodenlogik ähnelt der Logik der Methode zum Ändern der Objektgröße. Zuerst zeichnen wir das Schattenobjekt neu, da es niedriger sein sollte als andere Objekte. Als Nächstes, wenn das Neuzeichnen-Flag gesetzt ist, zeichnen wir das Objekt vollständig neu und legen sein aktuelles Erscheinungsbild als „Referenz“ fest. Wenn das Neuzeichnen-Flag nicht gesetzt ist, entfernen wir das gezeichnete Schattenobjekt und das Objekt selbst. Als Nächstes gehen wir durch die Liste der angehängten Objekte und zeichnen jedes von ihnen gemäß dem Neuzeichen-Flag neu — entweder entfernen oder alles zeichnen.
Schließlich aktualisieren wir das Chart für die sofortige Anzeige der implementierten Änderungen, falls das Neuzeichnen-Flag gesetzt ist und das Flag das Hauptobjekt in der gesamten Hierarchie der gebundenen Objekte ist (es hat kein Hauptobjekt und seine GetMain() -Methode gibt NULL zurück).
Die Basisklasse aller Bibliotheks-WinForms-Objekte ist fertig.
Verbessern wir die Paneel-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Wenn die Dock-Eigenschaft für die an das Paneel gebundenen Objekte gesetzt ist, sollten diese Objekte automatisch in der Reihenfolge ihrer Indizes in der Liste der gebundenen Objekte platziert und gemäß den Regeln für die Bindungsart festgelegt werden. Mit anderen Worten, wenn sich Dock auf der linken Seite befindet, wird das Objekt in der Höhe auf die gesamte Höhe des Paneels gestreckt, an dem es befestigt ist, während seine linke Koordinate die linke Kante einer der Paneel-Unterlagen ist (wenn das Objekt das erste ist eines in der Liste) oder der rechte Rand des vorherigen Objekts aus der Liste der gebundenen Objekte. Diese Regel wird auf jedes Objekt aus der Liste angewendet. Außerdem sollten wir auch das Flag für die automatische Größenänderung des Bedienfelds und den Änderungsmodus für die Größenänderung berücksichtigen — nur vergrößern oder vergrößern und verkleinern.
Im aktuellen Artikel werde ich die Handhabung der Dock-Eigenschaft für gebundene Objekte nur im deaktivierten Modus zur automatischen Größenänderung des Bedienfelds implementieren. Um die Objektkante zu definieren, an der das aktuelle Objekt aus der Liste der gebundenen Objekte befestigt ist, erstellen wir vier Objekte nach der Anzahl der Kanten – oben, unten, links und rechts. Jedes Objekt, für das die Dock-Eigenschaft festgelegt ist, passt sich selbst in eines dieser Objekte ein, sodass die restlichen Objekte aus der Liste die Koordinaten des Objekts „kennen“ können, an das sie gebunden werden sollen (da sie an den Kanten angehängt sind des vorherigen Objekts in der Liste, wenn die Dock-Eigenschaft auch dafür festgelegt ist).
Da jetzt alle WinForms-Objekte vom Basis-WinForms-Objekt abgleitet werden, dann anstelle der enthaltenen Formularobjektdatei:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Form.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm
werden wir die Datei des Basis-WinForms-Objekts einbinden:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
Im privaten Abschnitt der Klasse deklarieren wir die Zeiger auf vier Objekte, um die Zeiger auf die Objekte zu speichern, deren Koordinaten wir zum Binden von Dock-Objekten benötigen:
class CPanel : public CWinFormBase { private: CGCnvElement *m_obj_top; // Pointer to the object whose coordinates the current upper object is bound to CGCnvElement *m_obj_bottom; // Pointer to the object whose coordinates the current bottom object is bound to CGCnvElement *m_obj_left; // Pointer to the object whose coordinates the current left object is bound to CGCnvElement *m_obj_right; // Pointer to the object whose coordinates the current right object is bound to CGCnvElement *m_underlay; // Underlay for placing elements bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the initial coordinates of a bound object virtual void GetCoords(int &x,int &y); //--- Create the underlay object bool CreateUnderlayObj(void); //--- Set the underlay as a coordinate system zero void SetUnderlayAsBase(void); protected:
Deklarieren wir anstelle der Methode SetDockingToContainer() die Methode SetUnderlayAsBase(), die das Unterlagenobjekt allen vier oben deklarierten Zeiger zuweist. Unmittelbar nach dem Erstellen eines Objekts wird seine Unterlage als Ursprung der Bindungskoordinaten des ersten gebundenen Objekts verwendet. Wenn Dock-Objekte platziert werden, werden die Zeiger auf die entsprechenden Objekte, die bereits an die erforderlichen Kanten gebunden sind, in den Variablen gesetzt. Allerdings dient die Unterlage gleich zu Beginn als Bindeobjekt.
Deklarieren wir im geschützten Abschnitt der Klasse zwei Methoden, die den Maximalwert zurückgeben, mit dem alle Dock-Objekte über das Objekt hinausgehen, an das sie gebunden sind, deklarieren die Methode, die die automatische Größenanpassung der Containergröße an die darin gespeicherten Objekte anpasst, und schreiben vier Methoden, die der Zeiger auf Objekte zurückgeben, deren Kanten von einem Dock-Objekt aus der Liste der an den Container gebundenen Objekte verwendet werden:
protected: //--- Return the maximum value of Dock object borders going beyond the container by width and (2) height int GetExcessMaxX(void); int GetExcessMaxY(void); //--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters bool SetCoordXUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false); } bool SetCoordYUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false); } bool SetWidthUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value) : false); } bool SetHeightUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false); } bool SetUnderlayParams(void); //--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height int GetCoordXUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordX() : 0); } int GetCoordYUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordY() : 0); } int GetWidthUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Width() : 0); } int GetHeightUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Height() : 0); } //--- Return the underlay (1) X and (2) Y coordinate relative to the panel int GetCoordXUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordXRelative() : 0); } int GetCoordYUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordYRelative() : 0); } //--- Return the object whose coordinates the current (1) top, (2) bottom, (3) left or (4) right object is bound to CGCnvElement *GetTopObj(void) { return this.m_obj_top; } CGCnvElement *GetBottomObj(void) { return this.m_obj_bottom; } CGCnvElement *GetLeftObj(void) { return this.m_obj_left; } CGCnvElement *GetRightObj(void) { return this.m_obj_right; } //--- Adjust the element size to fit its content bool AutoSizeProcess(const bool redraw); public:
Die restlichen Variablen und Methoden wurden bereits in die Basisklasse des WinForms-Objekts verschoben. Erfahren Sie mehr über solche Änderungen in den Dateien, die dem Artikel beigefügt sind.
Unter Berücksichtigung aller verschobenen Methoden, Anpassungen und neuen Methoden sieht der öffentliche Teil der Klasse nun wie folgt aus:
public: //--- Return the (1) underlay and (2) the object the current object is bound to CGCnvElement *GetUnderlay(void) { return this.m_underlay; } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=false); //--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); //--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Redraw the object virtual void Redraw(bool redraw); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); } //--- Reset the size of all bound objects to the initial ones bool ResetSizeAllToInit(void); //--- Place bound objects in the order of their Dock binding bool ArrangeObjects(const bool redraw); //--- (1) Set and (2) return the auto scrollbar flag void SetAutoScroll(const bool flag) { this.m_autoscroll=flag; } bool AutoScroll(void) { return this.m_autoscroll; } //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void SetAutoScrollMarginWidth(const int value) { this.m_autoscroll_margin[0]=value; } void SetAutoScrollMarginHeight(const int value) { this.m_autoscroll_margin[1]=value; } void SetAutoScrollMarginAll(const int value) { this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return this.m_autoscroll_margin[0]; } int AutoScrollMarginHeight(void) const { return this.m_autoscroll_margin[1]; } //--- (1) Set the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode) return; this.m_autosize_mode=mode; if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return this.m_autosize_mode; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { if(this.DockMode()==mode) return; CWinFormBase::SetDockMode(mode,redraw); CPanel *base=this.GetBase(); if(base!=NULL) base.ArrangeObjects(redraw); } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { CWinFormBase::SetPaddingLeft(value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the X axis this.m_underlay.SetCoordXRelative(this.PaddingLeft()); //--- Set the X coordinate and the underlay width this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } virtual void SetPaddingTop(const uint value) { CWinFormBase::SetPaddingTop(value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the Y axis this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the Y coordinate and underlay height this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } virtual void SetPaddingRight(const uint value) { CWinFormBase::SetPaddingRight(value); if(this.m_underlay!=NULL) { //--- Set the underlay width this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } virtual void SetPaddingBottom(const uint value) { CWinFormBase::SetPaddingBottom(value); if(this.m_underlay!=NULL) { //--- Set the underlay height this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; if(this.PaddingLeft()<this.FrameWidthLeft()) this.SetPaddingLeft(this.FrameWidthLeft()); } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; if(this.PaddingTop()<this.FrameWidthTop()) this.SetPaddingTop(this.FrameWidthTop()); } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; if(this.PaddingRight()<this.FrameWidthRight()) this.SetPaddingRight(this.FrameWidthRight()); } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value; if(this.PaddingBottom()<this.FrameWidthBottom()) this.SetPaddingBottom(this.FrameWidthBottom()); } virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_BEVEL); this.SetAutoScroll(false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
Alle Set-Methoden haben jetzt das Präfix „set“ in ihren Namen.
Die Methoden zum Setzen der Auto-Resizing, des Auto-Resizing-Modus und die Methoden zum Binden von Dock-Objekten an den Container rufen zunächst die Methode der Basisklasse zum Setzen der Eigenschaft auf. Dann rufen sie die Methode zur Größenänderung des Containers auf:
virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); }
Die Größe des Containers sollte nur geändert werden, wenn mindestens ein Objekt an den Container gebunden ist.
Andere ähnliche Verfahren werden mit ähnlicher Logik durchgeführt. Sie können sie auf eigene Faust erkunden.
Wenn Sie Fragen haben, können Sie diese gerne im Kommentarteil stellen.
In der Methode, die die Einstellung wir alle Unterlagenparameter festlegt, geben wir den Objekttyp „underlay“ (Unterlage) an:
//+------------------------------------------------------------------+ //| Set all underlay parameters | //+------------------------------------------------------------------+ bool CPanel::SetUnderlayParams(void) { //--- Set the object type this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY); //--- Set the underlay shift values to the variables by X and Y axes this.m_underlay.SetCoordXRelative(this.PaddingLeft()); this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the underlay coordinates and size bool res=true; res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); return res; } //+------------------------------------------------------------------+
Die Kenntnis eines ausgewählten Objekts vom Typ „underlay“ ist erforderlich, wenn Dock-Objekte innerhalb des Containers platziert werden. Wenn ein Objekt, dessen Kanten verwendet werden sollen, um das aktuelle Objekt aus der Liste der gebundenen Objekte anzuhängen, eine Unterlage ist, dann ist die Bindungskante beispielsweise links eine Unterlagen-X-Koordinate. Wenn es sich nicht um eine Unterlage, sondern um ein anderes Dock-Objekt handelt, wird die rechte Kante dieses Dock-Objekts als Bindungskante betrachtet.
Bei den Methoden zum Setzen der Paneelkoordinaten und ihrer Größe rufen wir zunächst die Methode zum Setzen der Basisobjekteigenschaften auf. Dann ändern wir die Koordinaten und die Größe des Unterlageobjekts:
//+------------------------------------------------------------------+ //| Set the panel X coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordX(const int coord_x) { if(!CGCnvElement::SetCoordX(coord_x)) return false; return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true); } //+------------------------------------------------------------------+ //| Set the panel Y coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordY(const int coord_y) { if(!CGCnvElement::SetCoordY(coord_y)) return false; return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true); } //+------------------------------------------------------------------+ //| Set the panel width | //+------------------------------------------------------------------+ bool CPanel::SetWidth(const int width) { if(!CGCnvElement::SetWidth(width)) return false; return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true); } //+------------------------------------------------------------------+ //| Set the panel height | //+------------------------------------------------------------------+ bool CPanel::SetHeight(const int height) { if(!CGCnvElement::SetHeight(height)) return false; return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true); } //+------------------------------------------------------------------+
Die Methode, die die Größe aller gebundenen Objekte auf die ursprünglichen zurücksetzt:
//+------------------------------------------------------------------+ //| Reset the size of all bound objects to the initial ones | //+------------------------------------------------------------------+ bool CPanel::ResetSizeAllToInit(void) { bool res=true; for(int i=0;i<this.ElementsTotal();i++) { CPanel *obj=this.GetElement(i); if(obj==NULL) { res &=false; continue; } res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit()); } return res; } //+------------------------------------------------------------------+
In der Schleife aller an den Container gebundenen Objekte rufen wir das nächste Objekt ab und setzen das Ergebnis der Größenänderung des Objekts auf seine ursprüngliche Größe in der Variablen res. Nach dem Schleifenende wird das Gesamtergebnis der Größenänderung in der Variablen res gesetzt. Wenn die Größenänderung mindestens eines Objekts mit einem Fehler endet, wird die Variable res auf false gesetzt, andernfalls auf true.
Die Methode, die ein neues angehängtes Element erstellt:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CPanel::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If failed to create a new graphical element, return 'false' CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- If the object type is a base WinForms object and higher, if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { //--- declare the pointer with the base WinForms object type and assign the pointer to the newly created object to it, //--- set the frame color equal to the background color CWinFormBase *wf=obj; wf.SetColorFrame(wf.ColorBackground()); } //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Redraw the panel and all added objects, and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
Die Methodenlogik wird in den Codekommentaren beschrieben und ist recht einfach.
Die Methode, die den maximalen Wert von Dock-Objektgrenzen zurückgibt, die in der Breite über den Container hinausgehen:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by width | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxX(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.RightEdge()>value) value=element.RightEdge(); } return value-this.m_underlay.RightEdge(); } //+------------------------------------------------------------------+
Die Methode, die den maximalen Wert von Dock-Objektgrenzen zurückgibt, die in der Höhe über den Container hinausgehen:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by height | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxY(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.BottomEdge()>value) value=element.BottomEdge(); } return value-this.m_underlay.BottomEdge(); } //+------------------------------------------------------------------+
Die Logik hinter beiden Methoden ist identisch: Wir holen uns in der Schleife aller an den Container gebundenen Objekte das nächste Objekt, und wenn seine rechte (untere) Kante den in der Wertvariablen festgelegten Wert überschreitet, wird der Kantenwert dieser Variablen zugewiesen. Nach Schleifenende weist der Variablenwert den maximalen Wert der rechten (unteren) Kante aller Objekte auf, und die Grenze zwischen dem gefundenen Wert und der rechten (unteren) Unterlageobjektkante wird zurückgegeben. Somit erhalten wir einen positiven oder negativen Wert in Pixel, um den wir die Größe des Containers ändern müssen.
Die Methode, die gebundene Objekte in der Reihenfolge ihrer Dock-Bindung platziert:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CPanel::ArrangeObjects(const bool redraw) { CWinFormBase *prev=NULL, *obj=NULL; //--- If auto resizing mode is enabled if(this.AutoSize()) { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the previous element from the list prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- Get the current element obj=GetElement(i); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { } } this.Resize(this.GetWidthInit(),this.GetHeightInit(),false); } //--- If auto resizing mode disabled else { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the current and previous elements from the list obj=this.GetElement(i); prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the top whose edges are used to bind the current one CGCnvElement *coord_base=this.GetTopObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the top one whose edges will be used to bind the next one this.m_obj_top=obj; } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the bottom whose edges are used to bind the current one CGCnvElement *coord_base=this.GetBottomObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the bottom one whose edges will be used to bind the next one this.m_obj_bottom=obj; } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the left whose edges are used to bind the current one CGCnvElement *coord_base=this.GetLeftObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the left one whose edges will be used to bind the next one this.m_obj_left=obj; } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the right whose edges are used to bind the current one CGCnvElement *coord_base=this.GetRightObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the right one whose edges will be used to bind the next one this.m_obj_right=obj; } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { //--- If failed to change the object size (for the entire underlay width and height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false)) continue; //--- Set the underlay as a binding object this.SetUnderlayAsBase(); //--- Get the object binding coordinates x=this.GetLeftObj().CoordX(); y=this.GetTopObj().CoordY(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { //--- Reset the object size obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false); //--- Get the initial object location coordinates x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit(); y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Calculate and set the relative object coordinates obj.SetCoordXRelative(x-this.m_underlay.CoordX()); obj.SetCoordYRelative(y-this.m_underlay.CoordY()); } } //--- Redraw the object with the redraw flag and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben. Falls das Paneel das Auto-Resize-Flag hat, werde ich die Objekte noch nicht im Container anordnen (es gibt nur Platzhalter, die durch Routinen ersetzt werden müssen), da es eine andere Logik haben wird als die, die für das Paneel ohne Auto-Resize implementiert ist .
Die Methode, die die Unterlage als Nullpunkt des Koordinatensystems für gebundene Dock-Objekte festlegt:
//+------------------------------------------------------------------+ //| Set the underlay as a coordinate system zero | //+------------------------------------------------------------------+ void CPanel::SetUnderlayAsBase(void) { this.m_obj_left=this.m_underlay; this.m_obj_right=this.m_underlay; this.m_obj_top=this.m_underlay; this.m_obj_bottom=this.m_underlay; } //+------------------------------------------------------------------+
Weisen wir den Zeiger für alle vier Bindungsobjekte der Unterlage zu.
Die Methode zum Anpassen der Elementgröße an den Inhalt:
//+------------------------------------------------------------------+ //| Adjust the element size to fit its content | //+------------------------------------------------------------------+ bool CPanel::AutoSizeProcess(const bool redraw) { //--- Get values along X and Y axes, by which the panel size is to be changed int excess_w=this.GetExcessMaxX(); int excess_h=this.GetExcessMaxY(); //--- If failed to change the size, return 'true' if(excess_w==0 && excess_h==0) return true; //--- If failed to change the panel size, return the result of its adjustment return ( //--- if only a size increase this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? this.Resize(this.Width()+(excess_w>0 ? excess_w : 0),this.Height()+(excess_h>0 ? excess_h : 0),redraw) : //--- if both increase and decrease this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw) ); } //+------------------------------------------------------------------+
Die Methodenlogik wird in den Codekommentaren beschrieben. Kurz gesagt, wir erhalten die Maximalwerte entlang der X- und Y-Achse, um die die gebundenen Objekte über die Grenzen der Unterlage hinausgehen. Ein positiver Wert zeigt an, dass die Objekte über die Unterlage hinausragen, während ein negativer Wert anzeigt, dass die Unterlage zu groß ist und verkleinert werden kann. Geben wir auch das Ergebnis der Änderung der Paneelgröße unter Berücksichtigung des automatischen Größenänderungsmodus zurück (entweder nur vergrößern oder vergrößern und verkleinern).
In der Datei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh der Kollektionsklasse der grafischen Elemente müssen wir die Namen der Methoden ersetzen, die die Eigenschaften BorderStyle() und FrameWidthAll() in der Paneel-Erstellungsmethode festlegen, durch die neuen: SetBorderStyle() und SetFrameWidthAll(). In den angehängten Dateien wurden die Methoden bereits umbenannt.
Ersetzen wir in \MQL5\Include\DoEasy\Engine.mqh, nämlich in den Methoden, die das WForm Paneel-Objekt zurückgeben, den Substitutionsnamen des Makros GRAPH_ELEMENT_TYPE_PANEL durch GRAPH_ELEMENT_TYPE_WF_PANEL:
//--- Return the WForm Element object by object ID CForm *GetWFForm(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object name on the current chart CPanel *GetWFPanel(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by chart ID and object name CPanel *GetWFPanel(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object ID CPanel *GetWFPanel(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Create the WinForm Element object
Das sind alle Änderungen und Verbesserungen, die ich für den aktuellen Artikel geplant habe.
Test
Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\Part105\ als TestDoEasyPart105.mq5.
Lassen Sie uns das Flag für die automatische Größenänderung des Bedienfelds zum Anpassen an den Inhalt des Bedienfelds sowie den Modus für die automatische Größenänderung hinzufügen.
Fügen wir außerdem einen neuen Schlüssel zum Platzieren von Objekten im Container hinzu. Wenn Sie Q drücken, werden alle Objekte, die sich innerhalb des Paneels befinden, gemäß ihrem Bindungsmodus platziert. Wir haben 6 solcher Objekte entsprechend der Anzahl der Bindungsmodi:
//+------------------------------------------------------------------+ //| Control borders bound to the container | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DOCK_MODE { CANV_ELEMENT_DOCK_MODE_NONE, // Attached to the specified coordinates, size does not change CANV_ELEMENT_DOCK_MODE_TOP, // Attaching to the top and stretching along the container width CANV_ELEMENT_DOCK_MODE_BOTTOM, // Attaching to the bottom and stretching along the container width CANV_ELEMENT_DOCK_MODE_LEFT, // Attaching to the left and stretching along the container height CANV_ELEMENT_DOCK_MODE_RIGHT, // Attaching to the right and stretching along the container height CANV_ELEMENT_DOCK_MODE_FILL, // Stretching along the entire container width and height }; //+------------------------------------------------------------------+
Dementsprechend erhält jedes dieser Objekte einen Bindungsmodus, der seiner Nummer in der Liste der Paneel-Objekte entspricht. Das erste Objekt in der Liste hat keine Bindung (CANV_ELEMENT_DOCK_MODE_NONE), das zweite den Modus CANV_ELEMENT_DOCK_MODE_TOP, das dritte — MODE_BOTTOM usw.
Fügen wir im globalen Bereich die Makrosubstitution für die Taste Q und Enumeration für Eingaben in Englisch und der Landessprache des Nutzerssowie neue Eingaben hinzu:
//+------------------------------------------------------------------+ //| TestDoEasyPart105.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (3) // Number of created forms #define START_X (4) // Initial X coordinate of the shape #define START_Y (4) // Initial Y coordinate of the shape #define KEY_LEFT (65) // (A) Left #define KEY_RIGHT (68) // (D) Right #define KEY_UP (87) // (W) Up #define KEY_DOWN (88) // (X) Down #define KEY_FILL (83) // (S) Filling #define KEY_ORIGIN (90) // (Z) Default #define KEY_INDEX (81) // (Q) By index //--- enumerations by compilation language #ifdef COMPILE_EN enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Grow AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Grow and Shrink }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; #endif //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Autosize Mode //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
Entfernen wir den Code zum Erstellen von Formularen und Elementen aus OnInit(). Belassen wir nur die Erstellung eines Paneels. Erstellen wir in der Schleife im Paneel selbst sechs gebundene Paneel-Objekte mit dem Neuzeichen-Flag false. In diesem Fall ist die Höhe des Paneels etwas geringer als die Gesamthöhe aller darin eingebauten Objekte, und die Breite ist im Gegenteil größer. So können wir sehen, wie sich das Paneel an die Größe der darin eingebauten Objekte anpasst:
//+------------------------------------------------------------------+ //| 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 WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 6 bound panel objects for(int i=0;i<6;i++) { //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 50 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20); int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16)); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false); } pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Nachdem wir alle Elemente innerhalb des Paneels erstellt haben, zeichnen wir das gesamte Paneel zusammen mit den darin erstellten Elementen vollständig neu (Neuzeichen-Flag true)
Fügen wir in der Ereignisbehandlung von OnChartEvent(), nämlich im Block der Tastenanschläge, die Routine für Q hinzu:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { CPanel *panel=engine.GetWFPanel(0); if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX)) { for(int i=0;i<panel.ElementsTotal();i++) { CPanel *obj=panel.GetElement(i); if(obj!=NULL) { if(lparam==KEY_UP) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false); else if(lparam==KEY_DOWN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false); else if(lparam==KEY_LEFT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false); else if(lparam==KEY_RIGHT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false); else if(lparam==KEY_FILL) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false); else if(lparam==KEY_ORIGIN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); else if(lparam==KEY_INDEX) { obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true); Sleep(i>0 ? 500 : 0); } } } panel.Redraw(true); } }
Um deutlich zu sehen, wie sich Objekte gemäß dem Bindungsmodus zu ihren Plätzen bewegen, nehmen wir nach jeder nächsten Bewegung des Objekts zu neuen Koordinaten eine Verzögerung von einer halben Sekunde vor.
Kompilieren Sie den EA und starten Sie ihn auf dem Chart:
Wie wir sehen können, sind die Objekte korrekt an jede der Paneelseiten gebunden. Wenn Sie Q drücken, wird jedes Objekt an der entsprechenden Paneelseite befestigt. Beim Ändern der Modi für die automatische Größenänderung des Bedienfelds passt es sich gemäß dem Modus für die automatische Größenanpassung an seinen internen Inhalt an.
Was kommt als Nächstes?
Im nächsten Artikel werde ich meine Arbeit an WinForms-Objekten fortsetzen.
*Vorherige Artikel in dieser Reihe:
DoEasy. Steuerung (Teil 1): Erste Schritte
DoEasy. Steuerung (Teil 2): Arbeiten an der Klasse CPanel
DoEasy. Steuerung (Teil 3): Erstellen gebundener Steuerelemente
DoEasy. Steuerung (Teil 4): Paneel-Steuerung, Parameter für Padding und Dock
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/10794
![Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 12): Times and Trade (I)](https://c.mql5.com/2/46/development__3.png)
![Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 11): System von Kreuzaufträgen](https://c.mql5.com/2/46/development__2.png)
![Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 13): Times and Trade (II)](https://c.mql5.com/2/46/development__4.png)
![Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 10): Zugriff auf nutzerdefinierte Indikatoren](https://c.mql5.com/2/46/development.png)
![MQL5 - Sprache von Handelsstrategien, eingebaut ins Kundenterminal MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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.