English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 8): Objektkategorien von Basis-WinForms zur Steuerung von GroupBox- und CheckBox

DoEasy. Steuerung (Teil 8): Objektkategorien von Basis-WinForms zur Steuerung von GroupBox- und CheckBox

MetaTrader 5Beispiele | 11 August 2022, 11:04
96 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

WinForms-Objekte, die wir erstellen, sind ähnlich wie MS Visual Studio kategorisiert:

  • Standardsteuerung
  • Container
  • Menüs und Symbolleisten
  • Daten
  • Komponenten
  • Drucken
  • Dialogfenster

Objekte aller Kategorien werden von dem gemeinsamen Basisobjekt CWinFormBase geerbt. Außerdem haben Objekte, die zur gleichen Kategorie gehören, innerhalb ihrer Kategorie gleichartige Funktionen. Daher lohnt es sich, alle ähnlichen Eigenschaften und Methoden von Objekten, die zu einer Kategorie gehören, in einer gemeinsamen Klasse derselben Kategorie zusammenzufassen. Mit anderen Worten, wir müssen für jede Kategorie von WinForms-Objekten eine eigene Basisklasse erstellen, die von der Basisklasse aller WinForms-Objekte geerbt wird. Dadurch wird das Schreiben des Codes für neue Objekte in jeder Kategorie vereinfacht.

In diesem Artikel werden wir zwei solcher Objekte erstellen — für Container und Standardsteuerungsobjekte. Um zu verstehen, welche Eigenschaften und Methoden Objekte einer Kategorie gemeinsam haben können, müssen wir im Allgemeinen mindestens zwei Objekte dieser Kategorie erstellen.

Erstellen wir die Objektklasse GroupBox in der Kategorie Container — dies ist ein Container, der visuell mehrere Objekte in sich vereint.
Im Gegensatz zum Paneel-Objekt, das auch ein Container ist, hat die GroupBox weniger Funktionalität, da sie nur ein Container für die visuelle Kombination von Objekten in einer gemeinsamen Gruppe ist.

In der Kategorie Standard-Steuerelemente legen wir das Objekt CheckBox an. Dies ist ein Kontrollkästchen mit einer Beschriftung, das drei Zustände annehmen kann: markiert, nicht markiert und undefiniert. Da das Objekt eine Textbeschriftung hat, wird diese vom Label-Objekt geerbt. Es wird jedoch um die Möglichkeit erweitert, ein Kontrollkästchen (Prüfzeichen) in verschiedenen Zuständen zu zeichnen.

Alle erstellten Objekte sind noch statisch, d.h. sie können nicht mit der Maus interagieren. Ich werde die notwendige Funktionalität implementieren, nachdem ich die meisten der für die Entwicklung geplanten WinForms-Objekte erstellt habe.


Verbesserung des Bibliotheksklassen

Bei erstellten Objekten müssen wir die Werte einiger Eigenschaften hinzufügen, die ihnen bei der Erstellung standardmäßig zugewiesen werden.

In \MQL5\Include\DoEasy\Defines.mqh fügen wir neue Makrosubstitutionen für diese Werte hinzu:

//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define CLR_DEF_FORE_COLOR             (C'0x2D,0x43,0x48')        // Default color for texts of objects on canvas
#define CLR_DEF_FORE_COLOR_OPACITY     (255)                      // Default color non-transparency for canvas object texts
#define CLR_DEF_FRAME_COLOR            (C'0x66,0x6C,0x6F')        // Default color for object frames on canvas
#define CLR_DEF_FRAME_COLOR_OPACITY    (255)                      // Default color non-transparency for canvas object frames
#define CLR_DEF_FRAME_COLOR_DARKNESS   (-2.0)                     // Default color opacity for canvas object frames (when using the background color)
#define CLR_DEF_FRAME_GBOX_COLOR       (C'0xDC,0xDC,0xDC')        // Default color for GroupBox object frames on canvas
#define CLR_DEF_OPACITY                (200)                      // Default color non-transparency for canvas objects
#define CLR_DEF_SHADOW_COLOR           (C'0x6B,0x6B,0x6B')        // Default color for canvas object shadows
#define CLR_DEF_SHADOW_OPACITY         (127)                      // Default color non-transparency for canvas objects
#define DEF_SHADOW_BLUR                (4)                        // Default blur for canvas object shadows
#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define DEF_CHECK_SIZE                 (12)                       // Verification flag default size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Default form/panel/window frame width
//--- Graphical object parameters

Das Kontrollkästchen-Flag hat die Standardgröße von 12x12 Pixeln im CheckBox-Objekt.

Derzeit gibt es die Typen Base, Paneel und Label im WinForms-Abschnitt der Objekttypenliste der Bibliothek. Sie sind nicht notwendig, da die gleichen Typen in einer anderen Enumeration festgelegt sind. Aber hier können wir sie als Hinweis auf die WinForms-Objektkategorie verwenden. Also ändern wir die Namen in der Enumeration, dass sie nur Objektkategorien anzeigen:

//+------------------------------------------------------------------+
//| 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_CONTAINER,                                  // WinForms container object type
   OBJECT_DE_TYPE_GWF_COMMON,                                     // WinForms standard control object type
//--- Animation

Im Falle von WinForms-Objekten können wir nun den Bibliotheksobjekttyp als Typ und als WinForms-Objektkategorie verwenden. In der Enumeration der grafischen Elementtypen wird der Typ in seiner Kategorie angegeben:

//+------------------------------------------------------------------+
//| 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_CONTAINER,                   // Windows Forms container base object
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
   GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                    // Windows Forms GroupBox
   GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                 // Windows Forms base standard control
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
   GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                    // Windows Forms ChackBox
  };
//+------------------------------------------------------------------+


Das Kontrollkästchen zur Überprüfung des Objekts kann einen der drei Zustände annehmen. Erstellen wir eine Enumeration, um sie zu spezifizieren:

//+------------------------------------------------------------------+
//| Control flag status                                              |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_CHEK_STATE
  {
   CANV_ELEMENT_CHEK_STATE_UNCHECKED,                 // Unchecked
   CANV_ELEMENT_CHEK_STATE_CHECKED,                   // Checked
   CANV_ELEMENT_CHEK_STATE_INDETERMINATE,             // Undefined
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+


Wir fügen neue Eigenschaften ganz am Ende der Enumeration der ganzzahligen Eigenschaften eines Canvas-basierten grafischen Elements sowie Erhöhung der Anzahl der ganzzahligen Eigenschaften von 44 auf 48:

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type
   
   //--- ...
   //--- ...

   CANV_ELEMENT_PROP_PADDING_LEFT,                    // Left margin inside the control
   CANV_ELEMENT_PROP_PADDING_RIGHT,                   // Right margin inside the control
   CANV_ELEMENT_PROP_TEXT_ALIGN,                      // Text position within text label boundaries
   CANV_ELEMENT_PROP_CHECK_ALIGN,                     // Position of the verification flag within control borders
   CANV_ELEMENT_PROP_CHECKED,                         // Control verification flag status
   CANV_ELEMENT_PROP_CHECK_STATE,                     // Status of a control having a verification flag
   CANV_ELEMENT_PROP_AUTOCHECK,                       // Auto change flag status when it is selected
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (48)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Wir sortieren nach neuer Eigenschaft zur Enumeration möglicher Kriterien zum Sortieren von grafischen Elementen auf der Leinwand hinzufügen:

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical elements on the canvas    |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                         // Sort by graphical element type
   
   //--- ...
   //--- ...

   SORT_BY_CANV_ELEMENT_PADDING_LEFT,                 // Sort by left margin inside the control
   SORT_BY_CANV_ELEMENT_PADDING_RIGHT,                // Sort by right margin inside the control
   SORT_BY_CANV_ELEMENT_TEXT_ALIGN,                   // Sort by text position within text label boundaries
   SORT_BY_CANV_ELEMENT_CHECK_ALIGN,                  // Sort by position of the verification flag within control borders
   SORT_BY_CANV_ELEMENT_CHECKED,                      // Sort by control verification flag status
   SORT_BY_CANV_ELEMENT_CHECK_STATE,                  // Sort by status of a control having a verification flag
   SORT_BY_CANV_ELEMENT_AUTOCHECK,                    // Sort by auto change flag status when it is selected
//--- Sort by real properties

//--- Sort by string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Sort by the graphical resource name
   SORT_BY_CANV_ELEMENT_TEXT,                         // Sort by graphical element text
  };
//+------------------------------------------------------------------+

Jetzt können wir die Listen sortieren und Objekte anhand der neuen Eigenschaften auswählen.


Die BorderStyle-Eigenschaft hat verschiedene Zwecke für verschiedene WinForms-Objekte. Im Falle eines Paneel-Objekts gibt die Eigenschaft den Stil des Objektrahmens an. Im Fall von GroupBox wird der Typ des Rahmens festgelegt, der um die Gruppe von Objekten gezogen wird (das Objekt selbst hat keinen Rahmen). Dementsprechend können wir die gleiche Methode in verschiedenen Objekten für unterschiedliche Zwecke verwenden.
Um dies zu erreichen, müssen wir sie in der Basisklassendatei aller WinForms-Objekte \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh virtuell machen:

//--- (1) Set and (2) return the font width type
   void              SetFontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return (ENUM_FW_TYPE)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE);               }
//--- (1) Set and (2) return the frame style
   virtual void      SetBorderStyle(const ENUM_FRAME_STYLE style)    { this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style);                           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return (ENUM_FRAME_STYLE)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE);        }

Durch die Neudefinition der Methode in den geerbten Klassen können wir für jede von ihnen eine eigene Methodenimplementierung erstellen.


Basisobjekt in der Kategorie Container

Bei der Erstellung des zweiten WinForms-Objekts in der Kategorie „Container“ wurde deutlich, dass viele Eigenschaften und Methoden gleich sind und sich von Objekt zu Objekt wiederholen. Um Überschneidungen und gleichartige Methoden in verschiedenen Objekten zu vermeiden, müssen wir alle in eine einzige gemeinsame Klasse verschieben, von der Objekte dieser Kategorie geerbt werden. Somit erhält jedes der nachgeordneten Objekte alle diese Methoden von seinem Elternteil. Die Methoden, die sich in ihrer Implementierung für verschiedene Objekte der gleichen Kategorie unterscheiden sollten, sollten virtuell gemacht und in geerbten Klassen neu definiert werden.

Wir erstellen in \MQL5\Include\DoEasy\Objects\Graph\WFormsContainers\ die neue Datei Container.mqh der Klasse CContainer. Die Klasse sollte von der Basisklasse aller WinForms-Objekte der Bibliothek geerbt werden, deren Datei in die erstellte Klasse aufgenommen werden soll:

//+------------------------------------------------------------------+
//|                                                    Container.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, 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"
//+------------------------------------------------------------------+
//| Class of the base container object of WForms controls            |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase
  {
  }


Wir sollten viele Methoden aus der bereits vorhandenen Paneel-Containerklasse in die neue Klasse verschieben, da diese Methoden auch in anderen Klassen der Kategorie benötigt werden. Da ich die meisten dieser Methoden im sechsten Artikel bei der Beschreibung der Paneel-Klasse berücksichtigt habe, werden wir hier nur den Klassenkörper und die Implementierung seiner Methoden betrachten.

Da das Klassenobjekt (im Gegensatz zum Paneel-Objekt) nicht über das Underlay-Objekt verfügt, müssen wir den Arbeitsbereich des Objekts irgendwie festlegen. Der Objektbereich, in dem Sie andere an den Container gebundene Objekte platzieren können, gilt als Arbeitsbereich. Wir wollen neue Methoden einführen, um die Grenzen des Arbeitsbereichs aufzuzeigen:

//+------------------------------------------------------------------+
//|                                                    Container.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, 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"
#include "..\..\WForms\Common Controls\CheckBox.mqh"
//+------------------------------------------------------------------+
//| Class of the base container object of WForms controls            |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase
  {
private:
//--- 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);

//--- Calculate Dock objects' binding coordinates
   void              CalculateCoords(CArrayObj *list);

protected:
//--- Adjust the element size to fit its content
   bool              AutoSizeProcess(const bool redraw);
   
public:
//--- Return the size and coordinates of the working area
   int               GetWidthWorkspace(void)       const
                       {
                        return this.Width()-::fmax(this.FrameWidthLeft(),this.PaddingLeft())-::fmax(this.FrameWidthRight(),this.PaddingRight());
                       }
   int               GetHeightWorkspace(void)      const
                       {
                        return this.Height()-::fmax(this.FrameWidthTop(),this.PaddingTop())-::fmax(this.FrameWidthBottom(),this.PaddingBottom());
                       }
   int               GetCoordXWorkspace(void)      const
                       {
                        return this.CoordX()+::fmax(this.FrameWidthLeft(),this.PaddingLeft());
                       }
   int               GetCoordYWorkspace(void)      const
                       {
                        return this.CoordY()+::fmax(this.FrameWidthTop(),this.PaddingTop());
                       }
   int               GetRightEdgeWorkspace(void)   const
                       {
                        return this.RightEdge()-::fmax(this.FrameWidthRight(),this.PaddingRight());
                       }
   int               GetBottomEdgeWorkspace(void)  const
                       {
                        return this.BottomEdge()-::fmax(this.FrameWidthBottom(),this.PaddingBottom());
                       }

//--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)
   CArrayObj        *GetListWinFormsObj(void);
   CArrayObj        *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type);
//--- Return the pointer to the specified WinForms object with the specified type by index
   CWinFormBase     *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height
   virtual bool      SetCoordX(const int coord_x)              { return CGCnvElement::SetCoordX(coord_x);   }
   virtual bool      SetCoordY(const int coord_y)              { return CGCnvElement::SetCoordY(coord_y);   }
   virtual bool      SetWidth(const int width)                 { return CGCnvElement::SetWidth(width);      }
   virtual bool      SetHeight(const int height)               { return CGCnvElement::SetHeight(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)                             { CWinFormBase::Redraw(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
   virtual bool      ArrangeObjects(const bool redraw);
   
//--- 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.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,value);  }
   void              SetAutoScrollMarginHeight(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,value);  }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
   void              SetAutoScrollMargin(const int width,const int height)
                       {
                        this.SetAutoScrollMarginWidth(width); this.SetAutoScrollMarginHeight(height);
                       }
//--- Return the (1) field width and (2) height around the control during auto scrolling
   int               AutoScrollMarginWidth(void)               const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W); }
   int               AutoScrollMarginHeight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H); }
  
//--- 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.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode);
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)   const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_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);
                        CContainer *base=this.GetBase();
                        if(base!=NULL)
                           base.ArrangeObjects(redraw);
                       }

//--- 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
                     CContainer(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);
                     CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; 
                        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_NONE);
                        this.SetAutoScroll(false,false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                       }
//--- Destructor
                    ~CContainer();
  };
//+------------------------------------------------------------------+

Wir kennen andere Methoden aus dem vorangegangenen Artikel zur Erstellung des Paneel-Objekts.

Im Klassenkonstruktor initialisieren wir alle Variablen mit Standardwerten:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CContainer::CContainer(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   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_NONE);
   this.SetAutoScroll(false,false);
   this.SetAutoScrollMarginAll(0);
   this.SetAutoSize(false,false);
   this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


Rufen wir im Destruktor der Klasse die Deinitialisierungsmethode auf, die sich in der Klasse des Formularobjekts befindet, die eine übergeordnete Klasse für die Klasse des Basisobjekts für die WinForms-Objekte der Bibliothek ist:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CContainer::~CContainer()
  {
   CForm::Deinitialize();
  }
//+------------------------------------------------------------------+


Die Methode zum Erstellen eines neuen grafischen Objekts enthält die Zeichenketten zum Erstellen aller vorhandenen (und etwas später besprochene) WinForms-Objekte (und mehr) , die nicht aus der Kategorie der Container-Objekte stammen, mit Ausnahme eines Objekts der gleichen Klasse:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CContainer::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                           const int obj_num,
                                           const string obj_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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER :
         element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Warum können wir keine Objekte aus der Kategorie Container erstellen? Der Grund dafür ist, dass es sich um Nachkommen dieser Klasse handelt, von denen sie noch nichts weiß. Es ist jedoch möglich, hier das Objekt einer eigenen Klasse (CContainer) zu erstellen. Da es sich um eine virtuelle Methode handelt, können wir die Erstellung von Objekten der Nachfolgeklassen dieser Klasse in den abgeleiteten Klassen festlegen.


Die Methode zum Anlegen eines neuen gebundenen Elements wird ebenfalls aus der Klasse der Paneel-Objekte übernommen und lediglich um neue Zeichenketten zum Setzen der Parameter für neu angelegte Objekte ergänzt:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CContainer::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 the object type is less than the base WinForms object
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- report the error and return 'false'
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- If failed to create a new graphical element, return 'false'
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Set the text color of the created object as that of the base panel
   obj.SetForeColor(this.ForeColor());
//--- If the object type is a container
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CONTAINER)
     {
      //--- set the frame color equal to the background color 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- If the object type is a panel
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL)
     {
      //--- set the frame color equal to the background color 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- If the object type is GroupBox
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
     {
      //--- set the frame color equal to the background color 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- If the object type is a text label,
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_LABEL)
     {
      //--- set the object text color depending on the one passed to the method
      //--- or the panel text color or the one passed to the method and the frame color equal to the text color 
      obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour);
      obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor());
     }
//--- If the object type is CheckBox
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CHECKBOX)
     {
      //--- set the object text color depending on the one passed to the method
      //--- or the object text color or the one passed to the method and the frame color equal to the text color 
      obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour);
      obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor());
     }
//--- 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;
  }
//+------------------------------------------------------------------+

In dieser Methode können wir alle bestehenden und zukünftigen (aber bereits in der Datei Defines.mqh angegebenen) Objekte einstellen, da der Basisobjekttyp CWinFormBase hier erstellt und bekannt ist, da er eine übergeordnete Klasse ist. Alle Parameter, die in Codeblöcken gesetzt werden, die die Einstellung von Parametern für das erstellte Objekt definieren, werden aus der CWinFormBase-Klassenliste verwendet, was weder zu Kollisionen noch zu Fehlern führt.

Diese Methode wird weiterhin durch Codeblöcke für neu erstellte Objekte ergänzt. Wenn ihre Initialisierung das Setzen von in der Klasse unbekannten Parametern erfordert, ist die Methode virtuell. Die Behandlung von unbekannten Eigenschaften neuer Objekte wird in der redefinierten Methode dieser neuen Klassen festgelegt.

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 CContainer::ResetSizeAllToInit(void)
  {
   bool res=true;
   CArrayObj *list=this.GetListWinFormsObj();
   if(list==NULL)
      return false;
   for(int i=0;i<list.Total();i++)
     {
      CWinFormBase *obj=list.At(i);
      if(obj==NULL)
        {
         res &=false;
         continue;
        }
      res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit());
     }
   return res;
  }
//+------------------------------------------------------------------+

Hier erhalten wir nur die Liste der WinForms-Objekte. Anschließend wird in der Schleife über die erhaltene Liste für jedes Objekt die Anfangsgröße festgelegt.

Die Methode zum Anpassen der Elementgröße an den Inhalt:

//+------------------------------------------------------------------+
//| Adjust the element size to fit its content                       |
//+------------------------------------------------------------------+
bool CContainer::AutoSizeProcess(const bool redraw)
  {
//--- Get the list of bound objects with WinForms type basic and higher
   CArrayObj *list=this.GetListWinFormsObj();
//--- Get objects with the maximum and minimum X and Y coordinates from the list by their indices
   CWinFormBase *maxx=list.At(CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_X));
   CWinFormBase *minx=list.At(CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_X));
   CWinFormBase *maxy=list.At(CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_Y));
   CWinFormBase *miny=list.At(CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_Y));
//--- If at least one of the four objects is not received, return 'false'
   if(maxx==NULL || minx==NULL || maxy==NULL || miny==NULL)
      return false;

//--- Get the minimum X and Y coordinate
   int min_x=minx.CoordX();
   int min_y=fmin(miny.CoordY(),maxy.BottomEdge());
//--- Calculate the total width and height of all bound objects
   int w=maxx.RightEdge()-min_x;
   int h=int(::fmax(miny.CoordY(),maxy.BottomEdge())-min_y);
//--- Calculate the number of pixels, by which we need to resize the container in width and height
   int excess_x=w-this.GetWidthWorkspace();
   int excess_y=h-this.GetHeightWorkspace();
//--- Calculate the offset, by which the bound objects are to be moved
   int shift_x=this.GetCoordXWorkspace()-min_x;
   int shift_y=this.GetCoordYWorkspace()-min_y;
//--- If failed to change the container size, return 'true'
   if(excess_x==0 && excess_y==0)
      return true;
//--- If it is necessary to move the attached objects inside the container along the X or Y coordinate
   bool res=true;
   if(shift_x>0 || shift_y>0)
     {
      //--- In the loop by all attached objects,
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object
         CWinFormBase *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- If the object needs to be shifted horizontally, write the shift result to 'res'
         if(shift_x>0)
            res &=obj.Move(obj.CoordX()+shift_x,obj.CoordY());
         //--- If the object needs to be shifted vertically, write the shift result to 'res'
         if(shift_y>0)
            res &=obj.Move(obj.CoordX(),obj.CoordY()+shift_y);
         //--- Set new relative object X and Y coordinates
         obj.SetCoordXRelative(obj.CoordX()-this.GetCoordXWorkspace());
         obj.SetCoordYRelative(obj.CoordY()-this.GetCoordYWorkspace());
        }
     }
//--- Return the result of resizing the container
   return
     (
      //--- If we failed to move at least one bound object, return 'false'
      !res ? false :
      //--- Otherwise, if only a size increase
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_x>0  ? excess_x : 0),this.Height()+(excess_y>0  ? excess_y : 0),redraw) :
      //--- if both increase and decrease
      this.Resize(this.Width()+(excess_x!=0 ? excess_x : 0),this.Height()+(excess_y!=0 ? excess_y : 0),redraw)
     );
  }
//+------------------------------------------------------------------+

Die Methode wurde auch aus dem Paneel-Objekt ausgelagert. Da die Klasse jedoch kein Underlay (Unterlage) hat, verwenden wir hier die Werte der Parameter des Arbeitsbereichs des Objekts anstelle seiner Eigenschaftswerte.

Derzeit funktioniert die Methode nicht korrekt. Ich werde seine Verbesserung zusammen mit der Verbesserung der Methode zur Anordnung von Objekten im Container in der Reihenfolge, in der sie indiziert werden, in späteren Artikeln behandeln:

//+------------------------------------------------------------------+
//| Place bound objects in the order of their Dock binding           |
//+------------------------------------------------------------------+
bool CContainer::ArrangeObjects(const bool redraw)
  {
//--- Get the list of bound objects with WinForms type basic and higher
   CArrayObj *list=this.GetListWinFormsObj();
   CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL;
//--- In the loop by all bound objects,
   for(int i=0;i<list.Total();i++)
     {
      //--- Get the current and previous elements from the list
      obj=list.At(i);
      prev=list.At(i-1);
      //--- If the object is not received, move on
      if(obj==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 working area width and by the initial object height), move on to the next one
         if(!obj.Resize(this.GetWidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Get the object binding coordinates
         x=this.GetCoordXWorkspace();
         y=(prev!=NULL ? prev.BottomEdge()+1 : this.GetCoordYWorkspace());
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Bottom
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
        {
         //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one
         if(!obj.Resize(this.GetWidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Get the object binding coordinates
         x=this.GetCoordXWorkspace();
         y=(prev!=NULL ? prev.CoordY()-obj.Height()-1 : this.GetBottomEdgeWorkspace()-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;
        }
      //--- Left
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
        {
         //--- If failed to change the object size (for the initial object width and the entire working area height), move on to the next one
         if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false))
            continue;
         //--- Get the object binding coordinates
         x=(prev!=NULL ? prev.RightEdge()+1 : this.GetCoordXWorkspace());
         y=this.GetCoordYWorkspace();
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Right
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
        {
         //--- If failed to change the object size (for the initial object width and the entire working area height), move on to the next one
         if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false))
            continue;
         //--- Get the object binding coordinates
         x=(prev!=NULL ? prev.CoordX()-obj.Width()-1 : this.GetRightEdgeWorkspace()-obj.Width());
         y=this.GetCoordYWorkspace();
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Binding with filling
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
        {
         //--- If failed to change the object size (for the entire working area width and height), move on to the next one
         if(!obj.Resize(this.GetWidthWorkspace(),this.GetHeightWorkspace(),false))
            continue;
         //--- Get the object binding coordinates
         x=this.GetCoordXWorkspace();
         y=this.GetCoordYWorkspace();
         //--- 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.GetCoordXWorkspace()+obj.CoordXRelativeInit();
         y=this.GetCoordYWorkspace()+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.GetCoordXWorkspace());
      obj.SetCoordYRelative(y-this.GetCoordYWorkspace());
     }

//--- If auto resizing mode is enabled
   if(this.AutoSize())
      this.AutoSizeProcess(false);

//--- Redraw the object with the redraw flag and return 'true'
   this.Redraw(redraw); 
   return true;
  }
//+------------------------------------------------------------------+

Beide oben genannten Methoden müssen überarbeitet werden. Sie sind hier nur vorhanden, weil sie in der Paneel-Objektklasse vorhanden waren. Ich werde sie später überarbeiten.

Die folgenden Methoden wurden ebenfalls aus der Paneel-Objektklasse übernommen. Sie wurden ohne Änderungen verschoben:

//+------------------------------------------------------------------+
//| Return the list of bound objects                                 |
//| of any WinForms base type and higher                             |
//+------------------------------------------------------------------+
CArrayObj *CContainer::GetListWinFormsObj(void)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE,EQUAL_OR_MORE);
  }
//+------------------------------------------------------------------+
//| Return the list of bound objects                                 |
//| with the specified WinForms object type                          |
//+------------------------------------------------------------------+
CArrayObj *CContainer::GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,type,EQUAL);
  }
//+------------------------------------------------------------------+
//| Return the pointer to the specified WinForms object              |
//| with the specified type by index                                 |
//+------------------------------------------------------------------+
CWinFormBase *CContainer::GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CArrayObj *list=this.GetListWinFormsObjByType(type);
   return(list!=NULL ? list.At(index) : NULL);
  }
//+------------------------------------------------------------------+
//| Calculate Dock objects' binding coordinates                      |
//+------------------------------------------------------------------+
void CContainer::CalculateCoords(CArrayObj *list)
  {
   
  }
//+------------------------------------------------------------------+

Damit ist das Thema der Basisklasse der Containerobjekte abgeschlossen.

Erstellen wir nun die neue Klasse des GroupBox-Objekts, das auch als Container für andere WinForms-Objekte dient.


WinForms-Objekte ‚GroupBox‘

Wir erstellen in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ die neue Datei GroupBox.mqh der Klasse CGroupBox. Die Klasse sollte von der neu erstellten Basisklasse der Container-Objekte abgeleitet werden, und ihre Datei sowie die Datei der Paneel-Objektklasse sollten in die Klassendatei aufgenommen werden:

//+------------------------------------------------------------------+
//|                                                     GroupBox.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 "Container.mqh"
#include "Panel.mqh"
//+------------------------------------------------------------------+
//| GroupBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CGroupBox : public CContainer
  {
  }

Fügen wir in den Abschnitten private, protected und public (privat, geschützt und öffentlich) der Klasse die bereits bekannten Methoden hinzu.
Da das GroupBox-Objekt über einen Rahmen verfügt, der eine Gruppe von Objekten umschließt, deklarieren wir die Methode, die einen solchen Rahmen zeichnet, im privaten Abschnitt:

//+------------------------------------------------------------------+
//| GroupBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CGroupBox : public CContainer
  {
private:
//--- Draw a frame
   void              DrawFrame(void);
//--- 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);
                                          
protected:
//--- Initialize the variables
   virtual void      Initialize(void);

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);
//--- Set a frame style
   virtual void      SetBorderStyle(const ENUM_FRAME_STYLE style)
                       {
                        if((this.FrameWidthTop()<2 || this.FrameWidthBottom()<2 || this.FrameWidthLeft()<2 || this.FrameWidthRight()<2) && 
                            style>FRAME_STYLE_FLAT)
                           this.SetFrameWidthAll(2);
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style);
                       }
   
//--- Constructors
                     CGroupBox(const long chart_id,
                               const int subwindow,
                               const string name,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
                     CGroupBox(const string name) : CContainer(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
                        this.Initialize();
                       }
//--- Destructor
                    ~CGroupBox(){ CForm::Deinitialize(); }
  };
//+------------------------------------------------------------------+

In der Methode zum Einstellen des Rahmenstils überprüfen wir den übergebenen Stil. Wenn der Rahmen nicht flach ist, sollte seine Breite mindestens zwei Pixel betragen. Ist dies der Fall, wird die Breite von 2 Pixeln für alle Rahmenseiten festgelegt.


Im parametrischen Konstruktor legen wir die anfängliche Objektgröße und die Koordinaten fest und rufen die Initialisierungsmethode auf:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CGroupBox::CGroupBox(const long chart_id,
                     const int subwindow,
                     const string name,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
   this.Initialize();
  }
//+------------------------------------------------------------------+

Wie Sie sehen können, ist der WinForms-Objekttyp als GroupBox eingestellt, während der Bibliotheksobjekttyp als Container eingestellt ist.


In der Methode zur Erstellung eines neuen grafischen Objekts können wir Paneel-Objekte, Klassen-Objekte und CheckBox-Klassen-Objekte erstellen, die später in diesem Artikel implementiert werden.

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string obj_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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+


Initialisierungsmethode der Variablen

//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CGroupBox::Initialize(void)
  {
//--- Clear all object lists and set sorted list flags for them
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
//--- GroupBox has no shadow object
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
//--- The width of the object frame on each side is 1 pixel by default
   this.SetFrameWidth(1,1,1,1);
//--- The object does not have a gradient filling (neither vertical, nor horizontal)
   this.m_gradient_v=false;
   this.m_gradient_c=false;
//--- Reset all "working" flags and variables
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::SetInteraction(false);
//--- Create an animation object and add it to the list for storing such objects
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(this.m_animations);
//--- Set a transparent background for the object background and the default color for the frame
   this.SetColorBackground(CLR_CANV_NULL);
   this.SetOpacity(0);
   this.SetColorFrame(CLR_DEF_FRAME_GBOX_COLOR);
//--- Set the default color and text opacity, as well as the absence of the object frame
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
//--- Set the default text parameters
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.SetText("GroupBox");
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
//--- Set the default object parameters
   this.SetAutoSize(false,false);
   this.SetMarginAll(3);
   this.SetPaddingAll(3);
   this.SetEnabled(true);
   this.SetVisible(true,false);
  }
//+------------------------------------------------------------------+

Alle Klassenvariablen werden in der Methode mit Standardwerten initialisiert. Die Initialisierungswerte einiger Variablen weichen von den in der übergeordneten Klasse festgelegten Werten ab.

Die Methoden zum Löschen des Objekts:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CGroupBox::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);
//--- Draw a frame encasing a group of objects
   this.DrawFrame();
//--- Draw a header above the frame
   CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CGroupBox::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);
//--- Draw a frame encasing a group of objects
   this.DrawFrame();
//--- Draw a header above the frame
   CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CGroupBox::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

Die Methoden sind virtuell. Abgesehen davon, dass das gesamte Objekt mit einer Hintergrundfarbe gefüllt wird (die Standardeinstellung ist transparent), werden auch der Rahmen und der darüber liegende Objektgruppen-Kopftext angezeigt.

Die Methode, die den Rahmen zeichnet:

//+------------------------------------------------------------------+
//| Draw the frame                                                   |
//+------------------------------------------------------------------+
void CGroupBox::DrawFrame(void)
  {
//--- Get half of the text height
   int w=0;
   int h=0;
   this.TextSize(Text(),w,h);
   int height=this.Height()-h/2;
//--- Depending on the frame style, draw its necessary type
   switch(this.BorderStyle())
     {
      case FRAME_STYLE_FLAT :
        this.DrawFrameFlat(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
      case FRAME_STYLE_BEVEL :
        this.DrawFrameBevel(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
      case FRAME_STYLE_STAMP :
        this.DrawFrameStamp(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
      //--- FRAME_STYLE_SIMPLE
      default:
        this.DrawFrameSimple(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
     }
//--- If the text set for an object is not an empty string, erase the frame area where a text should be located using the transparent color
   if(this.Text()!="")
      this.DrawRectangleFill(5,h/2-1,w+7,h/2+this.FrameWidthTop()+1,CLR_CANV_NULL,0);
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Kurz gesagt, wir müssen einen Rahmen zeichnen, der die Gruppe von Objekten im Container umschließt. Der Rahmen sollte entlang der Kanten des gesamten Objekts gezeichnet werden und den Stil und die Farbe haben, die in seinen Eigenschaften angegeben sind. Die obere Kante des Rahmens sollte nicht an der Oberkante des Objekts, sondern in der Mitte der Kopfzeile verlaufen. Um die anfängliche Y-Koordinate des Rahmens zu berechnen, nehmen Sie die Schrifthöhe und teilen Sie sie durch 2. Dies ist die Y-Koordinate des Rahmens. Der Kopfzeilentext sollte nicht direkt auf den Rahmen gezeichnet werden, d.h. es sollte keine Rahmenlinie an der Stelle des Textes vorhanden sein.
Um dies zu erreichen, löschen wir einfach die Linie an der richtigen Stelle, indem wir ein Rechteck mit einer transparenten Farbe über die Linie zeichnen. Die Größe des Rechtecks sollte die Größe des Etiketts auf jeder Seite um ein Pixel überschreiten.

Das WinForms-Objekt ‚GroupBox‘ ist fertig.

Wir öffnen die Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh der Paneel-Objektklasse und entfernen alle in die Basisklasse verschobenen Methoden aus ihr. Außerdem fügen wir die Datei der Basisklasse der Containerobjekte und die Datei der GroupBox-Klasse in die Klassendatei ein. Die Klasse wird nun vom Basis-Containerobjekt abgeleitet:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CContainer

Aus dem privaten Teil der Klasse entfernen wir die Variablen der Zeiger auf die Objekte, an deren Koordinaten das Dock-Objekt gebunden ist, und die Methode, die die Unterlage mit Hilfe eines solchen Objekts einstellt, da wir jetzt die Methoden der Basisklasse CContainer dafür verwenden werden:

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

//--- 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);


Entfernen wir unnötige Zeilen aus den Klassenkonstruktoren, da sie nun in der übergeordneten Klasse festgelegt werden:

//--- 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_NONE);
                        this.SetAutoScroll(false,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();
  };
//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   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_NONE);
   this.SetAutoScroll(false,false);
   this.SetAutoScrollMarginAll(0);
   this.SetAutoSize(false,false);
   this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
   this.Initialize();
   if(this.CreateUnderlayObj())
      this.SetUnderlayAsBase();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+

Außerdem setzen wir in den Konstruktoren einen neuen Objekttyp und die Initialisierung der Klasse CContainer anstelle der bisherigen CWinFormBase in der Initialisierungsliste:

//--- 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) : CContainer(::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_CONTAINER; 
                        this.CreateUnderlayObj();
                       }
//--- Destructor
                    ~CPanel(){ CForm::Deinitialize(); }
  };
//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   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_CONTAINER;
   this.CreateUnderlayObj();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


In der Methode zur Erstellung eines neuen grafischen Objekts stellen wir die Erstellung neuer Objekte ein:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string obj_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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Ich werde im Folgenden die Erstellung des CheckBox-Objekts besprechen.

Die übrigen Änderungen in der Klasse sind unbedeutend. Es hat keinen Sinn, sie hier zu erwähnen. Sie finden sie in den Dateien im Anhang zu diesem Artikel. Die Hauptsache ist die Entfernung der Methoden, die in die Basisklasse verschoben wurden.

Erstellen wir nun das Basisobjekt für die WinForms-Standardsteuerungsobjekte auf dieselbe Weise.


Basisobjekt in der Kategorie Standardsteuerung

Dieses Objekt speichert die Methoden des Textlabel-Objekts, das ich im vorherigen Artikel besprochen habe. Die meisten Methoden dieses Objekts werden sich in anderen Objekten dieser Kategorie wiederholen, sodass wir auch hier (und in anderen Kategorien) ein Basisobjekt benötigen.

Erstellen wir in der Bibliotheksdatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ eine neue Datei CommonBase.mqh der Klasse CCommonBase.
Die Klasse sollte von der Basisklasse der Bibliothek WinForms Objekte
abgeleitet werden, und die Klassendatei sollte in die Datei aufgenommen werden:

//+------------------------------------------------------------------+
//|                                                   CommonBase.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, 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"
//+------------------------------------------------------------------+
//| Class of the base WForms standard control object                 |
//+------------------------------------------------------------------+
class CCommonBase : public CWinFormBase
  {
  }


Die Klasse ist recht klein. Schauen wir sie uns in ihrer Gesamtheit an:

//+------------------------------------------------------------------+
//| Class of the base WForms standard control object                 |
//+------------------------------------------------------------------+
class CCommonBase : public CWinFormBase
  {
private:

protected:
//--- Set the element width and height automatically
   virtual void      AutoSetWH(void)   { return;   }
//--- Initialize the variables
   virtual void      Initialize(void);
   
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);

//--- Constructor
                     CCommonBase(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+

Hier haben wir eine leere virtuelle Methode für die automatische Größenanpassung von Objekten deklariert. Die Methode sollte in geerbten Klassen implementiert werden, da jede Klasse ihre eigenen Größenkriterien haben kann, um die Objektgröße festzulegen.

Der Klassenkonstruktor legt die Objekttypen und alle Standard-Eigenschaftswerte fest.
Nachdem alle Werte eingestellt wurden, wird das Objekt neu gezeichnet:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCommonBase::CCommonBase(const long chart_id,
                         const int subwindow,
                         const string name,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_COMMON_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_COMMON_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   if(this.AutoSize())
      this.AutoSetWH();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+


Initialisierungsverfahren

//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CCommonBase::Initialize(void)
  {
//--- Clear all object lists and set sorted list flags for them
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
//--- Standard control has no shadow object
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
//--- The width of the object frame on each side is 1 pixel by default
   this.m_frame_width_right=1;
   this.m_frame_width_left=1;
   this.m_frame_width_top=1;
   this.m_frame_width_bottom=1;
//--- The object does not have a gradient filling (neither vertical, nor horizontal)
   this.m_gradient_v=false;
   this.m_gradient_c=false;
//--- Reset all "working" flags and variables
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::SetInteraction(false);
//--- Create an animation object and add it to the list for storing such objects
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(this.m_animations);
//--- Set the transparent color for the object background
   this.SetColorBackground(CLR_CANV_NULL);
   this.SetOpacity(0);
//--- Set the default color and text opacity, as well as the absence of the object frame
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetBorderStyle(FRAME_STYLE_NONE);
//--- Set the default text parameters
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.SetText("");
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
//--- Set the default object parameters
   this.SetAutoSize(false,false);
   this.SetMarginAll(3);
   this.SetPaddingAll(0);
   this.SetEnabled(true);
   this.SetVisible(true,false);
  }
//+------------------------------------------------------------------+


Die Methoden zum Löschen und Füllen des Objekthintergrunds:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CCommonBase::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(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CCommonBase::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(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CCommonBase::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

All diese Methoden haben wir bereits in anderen Bibliotheksobjekten kennengelernt. Sie sind alle im Code kommentiert und bedürfen keiner Erklärung. Initialisierung und Bereinigung für jedes Objekt sind ähnlich, aber es gibt leichte Unterschiede in den Standardwerten der Variablen und in den Prinzipien und der Reihenfolge des Malens des Hintergrunds und des Zeichnens einiger Elemente darauf. Aus diesem Grund kann jede Klasse ihre eigene Implementierung dieser Methoden haben.

Da ich einige Methoden aus der Objektklasse „Textlabel“ in die Klasse der Basisobjekte von Standardsteuerelementen verschoben habe, müssen wir die Klasse CLabel in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh korrigieren.

Anstelle von WinFormBase.mqh wird die Klasse der Basisobjektdatei in die Klasse eingefügt und wird von dieser Klasse abgeleitet:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "CommonBase.mqh"
//+------------------------------------------------------------------+
//| Label object class of WForms controls                            |
//+------------------------------------------------------------------+
class CLabel : public CCommonBase


Im Klassenkonstruktor, d. h. in der Initialisierungsliste, legen wir die Initialisierung einer neuen übergeordneten Klasse fest und setzen einen neuen Bibliotheksobjekttyp:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLabel::CLabel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CCommonBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LABEL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_LABEL);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   this.SetMargin(3,0,3,0);
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

Die übrigen Änderungen in der Klasse sind unbedeutend und haben mit der Entfernung der Methoden zu tun, die von der aktuellen Klasse in die übergeordnete Klasse verschoben wurden.
Sie finden sie in den unten angehängten Dateien.


WinForms-Objekt ‚CheckBox‘

Das CheckBox-Objekt ist ein Kontrollkästchen mit einer Beschriftung. Die Beschriftung und das Flag können sich jeweils an neun Positionen relativ zu den Objektgrenzen befinden:

  • Oben links
  • Mitte links
  • Unten links
  • Unten Mitte
  • Unten rechts
  • Mitte rechts
  • Oben rechts
  • Oben Mitte
  • Zentrum

Bei verschiedenen Kombinationen von Ankreuzfeld und Textposition ist es notwendig, die Textposition so anzupassen, dass sie das Ankreuzfeld möglichst nicht überlappt. Der Rahmen des Objekts kann angezeigt werden.

Da das Objekt einen Text hat und die Größe des Textes (und das Flag) verwendet wird, um die Größe des Objekts automatisch zu ändern, ist es sinnvoll, vom Objekt „Text label“ abzuleiten und die Funktionalität hinzuzufügen, um das Ankreuzfeld in drei Kombinationen anzuzeigen:

  • Ungeprüft
  • Geprüft
  • Unbestimmt

Ein unbestimmter Zustand tritt auf, wenn CheckBox den Status von Gruppen gleicher Objekte anzeigt, von denen einige ausgewählt sind, während andere nicht ausgewählt sind.

Wir erstellen in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ eine neue Datei Label.mqh mit der Klasse CLabel. Die Klassendatei CLabel sollte in die Datei eingebunden werden und die Klasse sollte von ihr geerbt werden:

//+------------------------------------------------------------------+
//|                                                     CheckBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Label.mqh"
//+------------------------------------------------------------------+
//| CheckBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CCheckBox : public CLabel
  {
  }


Im privaten Abschnitt der Klasse fügen wir die Variablen zum Speichern der Textbeschriftung und der Koordinaten und Größe des Kontrollkästchens hinzu sowie die Methoden zum Ändern der Objektgröße und zur Handhabung der Text- und Kontrollkästchenkoordinaten:

class CCheckBox : public CLabel
  {
private:
   int               m_text_x;                                       // Text X coordinate
   int               m_text_y;                                       // Text Y coordinate
   int               m_check_x;                                      // Checkbox X coordinate
   int               m_check_y;                                      // Checkbox Y coordinate
   int               m_check_w;                                      // Checkbox width
   int               m_check_h;                                      // Checkbox height
//--- Set the element width and height automatically
   virtual void      AutoSetWH(void);
//--- Set X and Y coordinates(1) of the checkbox and (2) the text depending on the alignment type
   void              SetCheckFlagCoords(int &x,int &y);
   void              SetTextCoords(int &x,int &y);
//--- Set the corrected text coordinates depending on the text alignment and checkbox
   void              SetCorrectTextCoords(void);

protected:

Deklarieren wir in den Abschnitten protected und public die Methoden zur Behandlung der Klasse:

protected:
//--- Displays the checkbox for the specified state
   virtual void      ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state);
   
//--- (1) Set and (2) return the checkbox size on the element
   void              SetCheckWidth(const int width)                  { this.m_check_w=(width<5  ? 5 : width);  }
   void              SetCheckHeight(const int height)                { this.m_check_h=(height<5 ? 5 : height); }
   int               CheckWidth(void)                          const { return this.m_check_w;                  }
   int               CheckHeight(void)                         const { return this.m_check_h;                  }
   
public:
//--- Set the element (1) width and (2) height,
   virtual bool      SetWidth(const int width)                       { return CGCnvElement::SetWidth(width>this.m_check_w   ? width  : this.m_check_w);     }
   virtual bool      SetHeight(const int height)                     { return CGCnvElement::SetHeight(height>this.m_check_h ? height : this.m_check_h);     }
   
//--- (1) Set and (2) return the element checkbox location angle (alignment type)
   void              SetCheckAlign(const ENUM_ANCHOR_POINT anchor)   { this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,anchor);                              }
   ENUM_ANCHOR_POINT CheckAlign(void)                          const { return (ENUM_ANCHOR_POINT)this.GetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN);           }
   
//--- (1) Set and (2) return the checkbox status
   void              SetChecked(const bool flag)                     { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag);                                    }
   bool              Checked(void)                             const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED);                            }
   
//--- (1) Set and (2) return the control status
   void              SetCheckState(const ENUM_CANV_ELEMENT_CHEK_STATE state) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,state);                       }
   ENUM_CANV_ELEMENT_CHEK_STATE CheckState(void)               const { return (ENUM_CANV_ELEMENT_CHEK_STATE)this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE);}

//--- Redraw the object
   virtual void      Redraw(bool redraw);

//--- Constructor
                     CCheckBox(const long chart_id,
                               const int subwindow,
                               const string name,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
  };
//+------------------------------------------------------------------+

Alle diese Methoden werden in den Methodenbeschreibungen genannt. Schauen wir uns ihre Umsetzung an.

Im Klassenkonstruktor legen wir die Standardwerte für Eigenschaften fest:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCheckBox::CCheckBox(const long chart_id,
                     const int subwindow,
                     const string name,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CLabel(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetCheckWidth(DEF_CHECK_SIZE);
   this.SetCheckHeight(DEF_CHECK_SIZE);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetTextAlign(ANCHOR_LEFT);
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_check_x=0;
   this.m_check_y=0;
   this.Redraw(false);
  }
//+------------------------------------------------------------------+


Die Methode zum Neuzeichnen eines Objekts:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CCheckBox::Redraw(bool redraw)
  {
//--- Fill the object with the background color having full transparency
   this.Erase(this.ColorBackground(),0,true);
//--- Set corrected text coordinates relative to the checkbox
   this.SetCorrectTextCoords();
//--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object 
   this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.ShowControlFlag(this.CheckState());
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Hier ist alles ganz einfach. Löschen Sie zunächst das gesamte Objekt vollständig, indem Sie es mit der Hintergrundfarbe füllen (die standardmäßig transparent ist). Als Nächstes berechnen wir gültige Textkoordinaten in Bezug auf das Kontrollkästchen, zeichnen den Text und das Kontrollkästchen und aktualisieren das Objekt.


Die Methode zur Einstellung der X- und Y-Koordinaten das Flag in Abhängigkeit von der Ausrichtungsart:

//+------------------------------------------------------------------+
//| Set X and Y checkbox coordinates                                 |
//| depending on the alignment type                                  |
//+------------------------------------------------------------------+
void CCheckBox::SetCheckFlagCoords(int &x,int &y)
  {
//--- Depending on the checkbox location
   switch(this.CheckAlign())
     {
      //--- The checkbox is located vertically from the left side of the object in the center
      case ANCHOR_LEFT : 
        x=0;
        y=(this.Height()-this.CheckHeight())/2;
        break;
      //--- The checkbox is located in the lower left corner of the object
      case ANCHOR_LEFT_LOWER : 
        x=0;
        y=this.Height()-this.CheckHeight()-1;
        break;
      //--- The checkbox is located in the center of the bottom edge of the object
      case ANCHOR_LOWER : 
        x=(this.Width()-this.CheckWidth())/2;
        y=this.Height()-this.CheckHeight()-1;
        break;
      //--- The checkbox is located in the lower right corner of the object
      case ANCHOR_RIGHT_LOWER : 
        x=this.Width()-this.CheckWidth()-1;
        y=this.Height()-this.CheckHeight()-1;
        break;
      //--- The checkbox is located vertically from the right side of the object in the center
      case ANCHOR_RIGHT : 
        x=this.Width()-this.CheckWidth()-1;
        y=(this.Height()-this.CheckHeight())/2;
        break;
      //--- The checkbox is located in the upper right corner of the object
      case ANCHOR_RIGHT_UPPER : 
        x=this.Width()-this.CheckWidth()-1;
        y=0;
        break;
      //--- The checkbox is located in the center of the upper edge of the object
      case ANCHOR_UPPER : 
        x=(this.Width()-this.CheckWidth())/2;
        y=0;
        break;
      //--- The checkbox is located in the object center
      case ANCHOR_CENTER : 
        x=(this.Width()-this.CheckWidth())/2;
        y=(this.Height()-this.CheckHeight())/2;
        break;
      //--- The checkbox is located in the upper left corner of the object
      //---ANCHOR_LEFT_UPPER
      default:
        x=0;
        y=0;
        break;
     }
  }
//+------------------------------------------------------------------+

Die Methode erhält die Variablen, in die die berechneten Checkbox-Koordinaten gesetzt werden sollen. Je nach Ausrichtungsmethode (Position des Kontrollkästchens innerhalb der Objektgrenzen) werden seine Koordinaten berechnet und in den an die Methode übergebenen Variablen festgelegt.


Die Methode setzt die X- und Y-Koordinaten des Textes in Abhängigkeit von der Ausrichtungsart:

//+------------------------------------------------------------------+
//| Set X and Y text coordinates                                     |
//| depending on the alignment type                                  |
//+------------------------------------------------------------------+
void CCheckBox::SetTextCoords(int &x,int &y)
  {
//--- Depending on the element text alignment type
   switch(this.TextAlign())
     {
      //--- The text is displayed in the upper left corner of the object
      case ANCHOR_LEFT_UPPER : 
        //--- Set the text binding point at the top left
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
        //--- Set the text binding point coordinate
        x=this.FrameWidthLeft();
        y=this.FrameWidthTop();
        break;
      //--- The text is drawn vertically from the left side of the object in the center
      case ANCHOR_LEFT : 
        //--- Set the text binding point at the center left
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_CENTER);
        //--- Set the text binding point coordinate
        x=this.FrameWidthLeft();
        y=this.Height()/2;
        break;
      //--- The text is displayed in the lower left corner of the object
      case ANCHOR_LEFT_LOWER : 
        //--- Set the text binding point at the bottom left
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_BOTTOM);
        //--- Set the text binding point coordinate
        x=this.FrameWidthLeft();
        y=this.Height()-this.FrameWidthBottom();
        break;
      
      //--- The text is drawn at the center of the bottom edge of the object
      case ANCHOR_LOWER : 
        //--- Set the text anchor point at the bottom center
        this.SetTextAnchor(FRAME_ANCHOR_CENTER_BOTTOM);
        //--- Set the text binding point coordinate
        x=this.Width()/2;
        y=this.Height()-this.FrameWidthBottom();
        break;
      //--- The text is displayed in the lower right corner of the object
      case ANCHOR_RIGHT_LOWER : 
        //--- Set the text binding point at the bottom right
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_BOTTOM);
        //--- Set the text binding point coordinate
        x=this.Width()-this.FrameWidthRight();
        y=this.Height()-this.FrameWidthBottom();
        break;
      //--- The text is drawn vertically from the right side of the object in the center
      case ANCHOR_RIGHT : 
        //--- Set the text binding point at the center right
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_CENTER);
        //--- Set the text binding point coordinate
        x=this.Width()-this.FrameWidthRight();
        y=this.Height()/2;
        break;
      //--- The text is displayed in the upper right corner of the object
      case ANCHOR_RIGHT_UPPER : 
        //--- Set the text binding point at the top right
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_TOP);
        //--- Set the text binding point coordinate
        x=this.Width()-this.FrameWidthRight();
        y=this.FrameWidthTop();
        break;
      //--- The text is drawn at the center of the upper edge of the object
      case ANCHOR_UPPER : 
        //--- Set the text binding point at the center top
        this.SetTextAnchor(FRAME_ANCHOR_CENTER_TOP);
        //--- Set the text binding point coordinate
        x=this.Width()/2;
        y=this.FrameWidthTop();
        break;
      //--- The text is drawn at the object center
      //---ANCHOR_CENTER
      default:
        //--- Set the text binding point at the center
        this.SetTextAnchor(FRAME_ANCHOR_CENTER);
        //--- Set the text binding point coordinate
        x=this.Width()/2;
        y=this.Height()/2;
        break;
     }
  }
//+------------------------------------------------------------------+

Hier ist alles ähnlich wie bei der Berechnung der Koordinaten des Kontrollkästchens, die oben beschrieben wurde. Hier wird zusätzlich der Textankerpunkt geändert, was die Berechnung der neuen Textbeschriftungskoordinaten vereinfacht.


Die Methode setzt gültige Textkoordinaten in Abhängigkeit von der Textausrichtung und dem Ankreuzfeld:

//+------------------------------------------------------------------+
//| Set valid text coordinates depending on                          |
//| text alignment and checkbox                                      |
//+------------------------------------------------------------------+
void CCheckBox::SetCorrectTextCoords(void)
  {
//--- Set checkbox and text coordinates depending on their alignment method
   this.SetCheckFlagCoords(this.m_check_x,this.m_check_y);
   this.SetTextCoords(this.m_text_x,this.m_text_y);
//--- Get the text size
   int text_w=0, text_h=0;
   this.TextSize(this.Text(),text_w,text_h);
//--- Depending on the checkbox location within the object boundaries
   switch(this.CheckAlign())
     {
      //--- The checkbox is located in the upper left corner of the object
      //--- The checkbox is located vertically from the left side of the object in the center
      //--- The checkbox is located in the lower left corner of the object
      case ANCHOR_LEFT_UPPER  : 
      case ANCHOR_LEFT        : 
      case ANCHOR_LEFT_LOWER  : 
        //--- If the text is left-aligned, set the text X coordinate with the checkbox width + 2 pixels
        if(this.TextAlign()==ANCHOR_LEFT_UPPER || this.TextAlign()==ANCHOR_LEFT || this.TextAlign()==ANCHOR_LEFT_LOWER)
           this.m_text_x=this.CheckWidth()+2;
        break;

      //--- The checkbox is located in the upper right corner of the object
      //--- The checkbox is located vertically from the right side of the object in the center
      //--- The checkbox is located in the lower right corner of the object
      case ANCHOR_RIGHT_UPPER : 
      case ANCHOR_RIGHT       : 
      case ANCHOR_RIGHT_LOWER : 
        //--- If the text is right-aligned, set the text X coordinate with the checkbox width + 2 pixels
        if(this.TextAlign()==ANCHOR_RIGHT_UPPER || this.TextAlign()==ANCHOR_RIGHT || this.TextAlign()==ANCHOR_RIGHT_LOWER)
           this.m_text_x=this.Width()-this.CheckWidth()-2;
        break;

      //--- The checkbox is located in the center of the bottom edge of the object
      case ANCHOR_LOWER : 
        //--- If the text is bottom-aligned, set the text X coordinate with the checkbox height
        if(this.TextAlign()==ANCHOR_LEFT_LOWER || this.TextAlign()==ANCHOR_LOWER || this.TextAlign()==ANCHOR_RIGHT_LOWER)
           this.m_text_y=this.m_check_y;
        break;
      
      //--- The checkbox is located in the center of the upper edge of the object
      case ANCHOR_UPPER : 
        //--- If the text is top-aligned, set the text X coordinate with the checkbox height
        if(this.TextAlign()==ANCHOR_LEFT_UPPER || this.TextAlign()==ANCHOR_UPPER || this.TextAlign()==ANCHOR_RIGHT_UPPER)
           this.m_text_y=this.m_check_h;
        break;
      //--- The checkbox is located in the object center
      //---ANCHOR_CENTER
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Hier werden je nach der relativen Position des Ankreuzfeldes und der Textbeschriftung neue Textkoordinaten festgelegt, damit der Text das Ankreuzfeldsymbol nicht überlappt. Dies ist keine Universallösung, aber alle wichtigen Korrelationen werden hier berücksichtigt. Andere Kollisionen der beiden Objektkomponenten können durch Änderung der Größe des Objekts selbst oder durch Einstellung des richtigen Verhältnisses zwischen den Positionen von Text und Kontrollkästchen gelöst werden.

Die Methode, die das Ankreuzfeld für den angegebenen Zustand anzeigt:

//+------------------------------------------------------------------+
//| Display the checkbox for the specified state                     |
//+------------------------------------------------------------------+
void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state)
  {
//--- Draw the rectangle of checkbox boundaries
   this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.ColorFrame());
//--- Create X and Y coordinate arrays for drawing a polyline
   int array_x[]={m_check_x+2,m_check_x+m_check_w/2-1,m_check_x+m_check_w-2};
   int array_y[]={m_check_y+m_check_h/2,m_check_y+m_check_h-3,m_check_y+3};
//--- Depending on the checkbox status passed to the method
   switch(state)
     {
      //--- Set checkbox
      case CANV_ELEMENT_CHEK_STATE_CHECKED :
        //--- Draw a polyline in the form of a checkmark inside the checkbox boundaries
        this.DrawPolylineAA(array_x,array_y,ColorFrame());
        break;
      //--- Unchecked checkbox
      case CANV_ELEMENT_CHEK_STATE_INDETERMINATE :
        //--- Draw a filled rectangle inside the checkbox boundaries
        this.DrawRectangleFill(m_check_x+3,m_check_y+3,m_check_x+m_check_w-3,m_check_y+m_check_h-3,ColorFrame());
        break;
      //--- Undefined state
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Je nach Zustand des an die Methode übergebenen Kontrollkästchens wird entweder ein Häkchen oder ein ausgefülltes Rechteck innerhalb der Grenzen des Kontrollkästchens gezeichnet. Die Polylinienkoordinaten werden so berechnet, dass die Proportionen des gezeichneten Häkchens unabhängig von der Größe der Ränder des Kontrollkästchens immer gleich bleiben. Für einen undefinierten Zustand wird ein Rechteck gezeichnet. Bisher gibt es nur eine Art dieser Symbole - ein Häkchen und ein Rechteck mit einer Farbe. In Zukunft werden wir weitere Arten der Anzeige von Flag-Zuständen einführen.

Die Methode legt automatisch die Breite und Höhe des Elements fest:

//+------------------------------------------------------------------+
//| Set the element width and height automatically                   |
//+------------------------------------------------------------------+
void CCheckBox::AutoSetWH(void)
  {
//--- Define the variables for receiving the label width and height
   int w=0, h=0;
//--- Get the width and height depending on the object text
   CGCnvElement::TextSize(this.Text()!="" && this.Text()!=NULL ? this.Text() : " ",w,h);
//--- Add the Margin values of the object on the left and right to the resulting width, as well as the checkbox size
   w+=(this.MarginLeft()+this.MarginRight()+this.CheckWidth());
//--- If the width is equal to the size of the checkbox, set it to three pixels + checkbox size
   if(w==this.CheckWidth())
      w=this.CheckWidth()+3;
//--- Add the Margin values of the object on the top and bottom to the resulting height
   h+=(this.MarginTop()+this.MarginBottom());
//--- If failed to get the height, set it as "font size" * ratio
   if(h==0)
      h=(int)ceil(FontSize()*1.625);
//--- If the height is ultimately less than the size of the checkbox, set the height equal to the height of the checkbox
   if(h<this.CheckHeight())
      h=this.CheckHeight();
//--- Set the object width and height from the received values
   this.SetWidth(w);
   this.SetHeight(h);
  }
//+------------------------------------------------------------------+

Im Gegensatz zur Methode der übergeordneten Klasse berücksichtigt die virtuelle Methode die Größe des Kontrollkästchens bei der Änderung der Objektgröße.

Binden wir die Datei der Klasse in die CContainer-Klassendatei ein (Öffnen der Klassencontainerdatei und Einbinden der Klasse), damit wir die Klassenobjekte aus der Containerklasse erstellen und sie eingebunden werden:

//+------------------------------------------------------------------+
//|                                                    Container.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, 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"
#include "..\..\WForms\Common Controls\CheckBox.mqh"
//+------------------------------------------------------------------+
//| Class of the base container object of WForms controls            |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase


Damit alle neuen Klassen in der Kollektionsklasse der grafischen Elemente sichtbar bleiben, binden wir die Datei der CGroupBox-Klasse in die Sammlungsklasse in der Datei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh ein:

//+------------------------------------------------------------------+
//|                                      GraphElementsCollection.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"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\WForms\Containers\GroupBox.mqh"
#include "..\Objects\Graph\WForms\Containers\Panel.mqh"

Die übrigen Klassen werden in GroupBox.mqh aufgenommen und sind dort sichtbar.

Schreiben wir die Methode, die das WinForms-Grafikobjekt „GroupBox“ auf der Leinwand im angegebenen Diagramm und Unterfenster erstellt:

//--- Create the 'GroupBox' WinForms graphical object on canvas on the specified chart and subwindow
   int               CreateGroupBox(const long chart_id,
                                    const int subwindow,
                                    const string name,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h,
                                    const string text,
                                    const color text_color=clrNONE,
                                    const color frame_color=WRONG_VALUE,
                                    const int  frame_width=WRONG_VALUE,
                                    ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                    const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGroupBox *obj=new CGroupBox(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(true);
                        obj.SetMovable(false);
                        obj.SetText(text);
                        obj.SetForeColor(text_color==clrNONE ? CLR_DEF_FORE_COLOR : text_color);
                        obj.SetColorBackground(CLR_CANV_NULL);
                        obj.SetColorFrame(frame_color==clrNONE ? CLR_DEF_FRAME_GBOX_COLOR : frame_color);
                        obj.SetBorderStyle(frame_style!=FRAME_STYLE_NONE ? frame_style : FRAME_STYLE_SIMPLE);
                        obj.SetOpacity(0,false);
                        obj.SetFrameWidthAll(frame_width==WRONG_VALUE ? 1 : frame_width);
                        //--- Draw the shadow drawing flag
                        obj.SetShadow(false);
                        if(redraw)
                           obj.Erase(CLR_CANV_NULL,0,redraw);
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.Done();
                        return obj.ID();
                       }
 
//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow

Die Methode erhält die Parameter, die für die Erstellung eines Objekts erforderlich sind. Nach erfolgreicher Erstellung des Objekts werden diese Parameter auf die Objekteigenschaften gesetzt. Die Methode gibt die Objekt-ID zurück, die bei der Erstellung des Objekts ermittelt wurde.

Um Zugriff auf die Erstellung neuer Objekte aus nutzerdefinierten Programmen zu haben, schreiben wir in \MQL5\Include\DoEasy\Engine.mqh der Hauptobjektklasse der Bibliothek die Methoden zum Erstellen und Empfangen neuer Objekte.

Die Methoden, die das WForm-Objekt ‚GroupBox‘ zurückgeben:

//--- 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);
                          }
                          
//--- Return the GroupBox WForm object by object name on the current chart
   CGroupBox           *GetWFGroupBox(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           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 GroupBox WForm object by chart ID and object name
   CGroupBox           *GetWFGroupBox(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_GROUPBOX);
                           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 GroupBox object by object ID
   CGroupBox           *GetWFGroupBox(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

//--- Create the WinForm Element object

In jeder der Methoden holen wir uns die Liste der Objekte vom Typ GroupBox und sortieren die erhaltene Liste nach den Parametern, die an die Methode übergeben wurden. Wenn das gewünschte Objekt in der Liste enthalten ist, dann ist es das einzige in der Liste — der Zeiger darauf wird von der Methode zurückgegeben. Wenn das Objekt nicht in der Liste gefunden wird, gibt die Methode NULL zurück.

Fügen wir drei Methoden zur Erstellung des WinForm-Objekts „GroupBox“ hinzu:

//--- Create the 'GroupBox' WinForm object
   CGroupBox           *CreateWFGroupBox(const long chart_id,
                                         const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const string text,
                                         const color text_color=clrNONE,
                                         const color frame_color=clrNONE,
                                         const int frame_width=WRONG_VALUE,
                                         const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                         const bool redraw=false)
                          {
                           int obj_id=this.m_graph_objects.CreateGroupBox(chart_id,subwindow,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw);
                           return this.GetWFGroupBox(obj_id);
                          }
//--- Create the Groupbox WinForm object in the specified subwindow on the current chart
   CGroupBox           *CreateWFGroupBox(const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const string text,
                                         const color text_color=clrNONE,
                                         const color frame_color=clrNONE,
                                         const int frame_width=WRONG_VALUE,
                                         const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                         const bool redraw=false)
                          {
                           return this.CreateWFGroupBox(::ChartID(),subwindow,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw);
                          }
//--- Create the GroupBox WinForm object in the main window of the current chart
   CGroupBox           *CreateWFGroupBox(const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const string text,
                                         const color text_color=clrNONE,
                                         const color frame_color=clrNONE,
                                         const int frame_width=WRONG_VALUE,
                                         const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                         const bool redraw=false)
                          {
                           return this.CreateWFGroupBox(::ChartID(),0,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw);
                          }
   
//--- Fill in the array with IDs of the charts opened in the terminal

Die erste der Methoden erstellt das GroupBox-Objekt auf dem durch die ID angegebenen Diagramm im angegebenen Unterfenster mit den an die Methode übergebenen Parametern. Die Methode zur Erstellung eines solchen Objekts aus der oben betrachteten Kollektionsklasse wird aufgerufen, und der Zeiger auf das erstellte Objekt, der durch die ID eines neu erstellten Objekts erhalten wird, wird zurückgegeben. Alle anderen Methoden rufen diese (erste) Methode auf und geben in der Parameterzeile explizit die aktuelle Diagramm-ID und das Hauptdiagrammfenster an.

Damit ist die Erstellung neuer Bibliotheksobjekte abgeschlossen.


Test

Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part108\ als TstDE108.mq5.

Wir erstellen ein Paneel-Objekt und platzieren darauf gebundene Objekte: zwei Paneel-Objekte mit daran gebundenen Textlabel-Objekten. Erstellen wir das GroupBox-Objekt und fügen es unter diesen Feldern ein. Da ich keine Möglichkeit vorgesehen habe, das CheckBox-Objekt aus Container-Objekten zu erstellen und zu binden, erstellen wir ein solches Objekt getrennt vom HauptPaneel — einfach im Chart, um zu sehen, was wir davon haben. Installieren wir die Objektparameter in den EA-Einstellungen, um sie schnell und übersichtlich zu ändern, und sehen uns das Ergebnis an.

Im globalen Bereich erstellen wir zwei neue Enumerationen für englische und russische Kompilationsversionen sowie neue Eingaben:

//--- 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
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // None
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (bevel)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (stamp)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Indeterminate
  };
#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
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // No frame
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple frame
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat frame
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (convex)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (concave)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Undefined
  };
#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
sinput   ENUM_BORDER_STYLE             InpFrameStyle     =  BORDER_STYLE_NONE;      // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign      =  ANCHOR_LEFT_UPPER;      // Label text align
sinput   ENUM_ANCHOR_POINT             InpCheckAlign     =  ANCHOR_LEFT_UPPER;      // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign =  ANCHOR_LEFT_UPPER;      // Check label text align
sinput   ENUM_CHEK_STATE               InpCheckState     =  CHEK_STATE_UNCHECKED;   // Check flag state
sinput   ENUM_INPUT_YES_NO             InpCheckAutoSize  =  INPUT_YES;              // CheckBox autosize
sinput   ENUM_BORDER_STYLE             InpCheckFrameStyle=  BORDER_STYLE_NONE;      // CheckBox border style
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+


In OnInit() platzieren wir den Codeblock für die Erstellung der GroupBox- und CheckBox-Objekte und nehmen kleine Korrekturen vor, um die Anzahl der erstellten Objekte und deren Platzierungskoordinaten zu ändern:

//+------------------------------------------------------------------+
//| 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,false);
   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 2 bound panel objects
      CPanel *obj=NULL;
      for(int i=0;i<2;i++)
        {
         //--- create the panel object with calculated coordinates, width of 90 and height of 40
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(prev==NULL ? xb : xb+prev.Width()+20);
         int y=0;
         if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false))
           {
            obj=pnl.GetElement(i);
            if(obj==NULL)
               continue;
            obj.SetFrameWidthAll(3);
            obj.SetBorderStyle(FRAME_STYLE_BEVEL);
            obj.SetColorBackground(obj.ChangeColorLightness(obj.ColorBackground(),4*i));
            obj.SetForeColor(clrRed);
            //--- Calculate the width and height of the future text label object
            int w=obj.Width()-obj.FrameWidthLeft()-obj.FrameWidthRight()-4;
            int h=obj.Height()-obj.FrameWidthTop()-obj.FrameWidthBottom()-4;
            //--- Create a text label object
            obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,obj,2,2,w,h,clrNONE,255,false,false);
            //--- Get the pointer to a newly created object
            CLabel *lbl=obj.GetElement(0);
            if(lbl!=NULL)
              {
               //--- If the object has an even or zero index in the list, set the default text color for it
               if(i % 2==0)
                  lbl.SetForeColor(CLR_DEF_FORE_COLOR);
               //--- If the object index in the list is odd, set the object opacity to 127
               else
                  lbl.SetForeColorOpacity(127);
               //--- Set the font Black width type and
               //--- specify the text alignment from the EA settings
               lbl.SetFontBoldType(FW_TYPE_BLACK);
               lbl.SetTextAlign(InpTextAlign);
               //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol 
               lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK));
               //--- Set the frame width and type for a text label and update the modified object
               lbl.SetFrameWidthAll(1);
               lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle);
               lbl.Update(true);
              }
           }
        }
      //--- Create the 'GroupBox' WinForms object
      CGroupBox *gbox=NULL;
      //--- GroupBox width is a width of the main panel underlay,
      //--- while the Y coordinate is an indent from attached panels by 6 pixels
      int w=pnl.GetUnderlay().Width();
      int y=obj.BottomEdgeRelative()+6;
      //--- If the attached GroupBox object is created
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,pnl,0,y,w,100,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- get the pointer to the GroupBox object by its index in the list of bound objects
         gbox=pnl.GetElement(2);
         if(gbox!=NULL)
           {
            //--- set the "indented frame" type, the frame color matches the main panel background color,
            //--- while the text color is the background color of the last attached panel darkened by 1
            gbox.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox.SetColorFrame(pnl.ColorBackground());
            gbox.SetForeColor(gbox.ChangeColorLightness(obj.ColorBackground(),-1));
           }
        }
      //--- Create an independent CheckBox object separately from the main panel directly on the chart
      CCheckBox *cbox=new CCheckBox(ChartID(),0,"CBox",pnl.RightEdge()+20,pnl.CoordY()+10,100,60);
      //--- If the object has been created
      if(cbox!=NULL)
        {
         //--- Add the newly created object to the list of library objects temporarily to avoid memory leaks
         ListStorage.Add(cbox);
         //--- Set object parameters from EA inputs
         cbox.SetAutoSize((bool)InpCheckAutoSize,false);
         cbox.SetCheckAlign(InpCheckAlign);
         cbox.SetTextAlign(InpCheckTextAlign);
         //--- Set the displayed text, frame style and checkbox status
         cbox.SetText("CheckBox");
         cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
         cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState);
         //--- Redraw the object
         cbox.Redraw(true);
        }
      //--- Redraw all objects according to their hierarchy
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


In OnChartEvent() ändern wir den Objekttyp, um einen Cast-Fehler des Objekttyps zu vermeiden:

   //--- 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++)
           {
            CWinFormBase *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);
        }

Zuvor haben wir hier den spezifischen Typ des CPanel-Objekts erhalten und nicht die Basisklasse der WinForms-Objekte der Bibliothek. Es lag kein Fehler vor, da nur Objekte dieses Typs (Tafel) an dem Test teilnahmen.

Kompilieren Sie den EA und starten Sie ihn auf dem Chart:


Wie wir sehen, funktioniert die Positionierung der Komponenten des CheckBox-Objekts korrekt, das GroupBox-Objekt wird auf dem Paneel erstellt und an dieses gebunden.

Im Moment sind alle diese Objekte statisch — sie haben keine Möglichkeit, mit der Maus zu interagieren. Ich werde es später implementieren — für mehrere WinForms-Objekte auf einmal.

Was kommt als Nächstes?

Im nächsten Artikel werde ich die Entwicklung von WinForms-Objekten fortsetzen.

Alle Dateien der aktuellen Bibliotheksversion, des Test-EA und des Chartereignis-Kontrollindikators für MQL5 sind unten angehängt, damit Sie sie testen und herunterladen können. Hinterlassen Sie Ihre Fragen, Kommentare und Vorschläge im Kommentarteil.

Zurück zum Inhalt

*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
DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize
DoEasy. Steuerung (Teil 6): Paneel-Steuerung, automatische Größenanpassung des Containers an den inneren Inhalt
DoEasy. Steuerung (Teil 7): Steuerung der Text Label



Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11075

Beigefügte Dateien |
MQL5.zip (4364.52 KB)
Experimente mit neuronalen Netzen (Teil 1): Die Geometrie neu betrachten Experimente mit neuronalen Netzen (Teil 1): Die Geometrie neu betrachten
In diesem Artikel werde ich mit Hilfe von Experimenten und unkonventionellen Ansätzen ein profitables Handelssystem entwickeln und prüfen, ob neuronale Netze für Trader eine Hilfe sein können.
Neuronale Netze leicht gemacht (Teil 17): Reduzierung der Dimensionalität Neuronale Netze leicht gemacht (Teil 17): Reduzierung der Dimensionalität
In diesem Teil setzen wir die Diskussion über die Modelle der Künstlichen Intelligenz fort. Wir untersuchen vor allem Algorithmen für unüberwachtes Lernen. Wir haben bereits einen der Clustering-Algorithmen besprochen. In diesem Artikel stelle ich eine Variante zur Lösung von Problemen im Zusammenhang mit der Dimensionsreduktion vor.
Komplexe Indikatoren mit Objekten vereinfachen Komplexe Indikatoren mit Objekten vereinfachen
In diesem Artikel wird eine Methode zur Erstellung komplexer Indikatoren vorgestellt, bei der gleichzeitig die Probleme vermieden werden, die bei der Arbeit mit mehreren Flächen, Puffern und/oder der Kombination von Daten aus mehreren Quellen auftreten.
Lernen Sie, wie man ein Handelssystem mit dem Chaikin Oscillator entwickelt Lernen Sie, wie man ein Handelssystem mit dem Chaikin Oscillator entwickelt
Hier ist ein neuer Artikel aus unserer Serie darüber, wie man ein Handelssystem basierend auf den beliebtesten technischen Indikatoren entwirft. Lernen Sie, wie man ein Handelssystem mit Hilfe des Indikators der Standardabweichung entwickelt.