English Русский 中文 Deutsch 日本語 Português
preview
DoEasy. Elementos de control (Parte 8): Objetos básicos WinForms por categorías, controles "GroupBox" y "CheckBox

DoEasy. Elementos de control (Parte 8): Objetos básicos WinForms por categorías, controles "GroupBox" y "CheckBox

MetaTrader 5Ejemplos | 23 agosto 2022, 15:13
311 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Los objetos WinForms que hemos creado se clasifican de forma similar a los de MS Visual Studio:

  • Standard controls
  • Containers
  • Menus and toolbars
  • Data
  • Components
  • Print
  • Dialog windows

  • Además de heredar los objetos de todas las categorías del objeto básico común CWinFormBase, los objetos que pertenecen a la misma categoría tienen una funcionalidad superpuesta dentro de su propia categoría. Por lo tanto, merece la pena combinar todas las propiedades y métodos de los objetos de la misma categoría en una clase común de la misma categoría. En otras palabras: tenemos que hacer que cada una de las categorías de los objetos WinForms sea una clase básica distinta, heredada de la clase básica de todos los objetos WinForms. Esto hará más fácil y simple la escritura del código para los nuevos objetos de cada categoría.

    Hoy vamos a crear dos de estos objetos, uno para la categoría de objetos contenedores y otro para la categoría de objetos de control estándar. En general, para entender qué propiedades y métodos pueden compartirse entre los objetos de la misma categoría, deberemos crear al menos dos objetos de esa categoría.

    En la categoría de contenedores, crearemos la clase de objeto GroupBox, que es un contenedor que combina visualmente varios objetos en su interior.
    A diferencia del objeto «Panel», que también es un contenedor, el objeto GroupBox posee una funcionalidad menor: es simplemente un contenedor en el que se combinan visualmente objetos en un grupo común.

    En la categoría de controles estándar, vamos a crear un objeto CheckBox, es decir, una «casilla de verificación» con una firma que puede tener tres estados: marcado, no marcado e indefinido. Como este objeto tiene una firma, heredará del objeto "Label", pero se le añadirá la funcionalidad necesaria para dibujar una marca de verificación (bandera de verificación) en diferentes estados.

    Todos los objetos que hemos creado hasta ahora son estáticos, es decir, no tienen ninguna funcionalidad para interactuar con el ratón. Lo haremos después de crear la mayoría de los objetos WinForms previstos.


    Mejorando las clases de la biblioteca

    Para los objetos creados, necesitaremos añadir valores a algunas de las propiedades que se les asignan por defecto al crearse.

    En el archivo \MQL5\Include\DoEasy\Defines.mqh, escribimos las nuevas macrosustituciones para estos valores:

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

    Por defecto, la casilla de verificación del objeto CheckBox tendrá un tamaño de 12x12 píxeles.

    Actualmente, tenemos los tipos Base, Panel y Label en la lista de tipos de objetos de la biblioteca en la sección WinForms, pero esto resulta innecesario: tenemos estos mismos tipos en otra enumeración. No obstante, aquí podemos utilizarlos como indicación de la categoría de los objetos WinForms. Así que vamos a corregir los nombres en esta enumeración para que solo muestren las categorías de los objetos:

    //+------------------------------------------------------------------+
    //| 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
    
    

    En el caso de los objetos WinForms, ahora podremos usar el tipo de objeto de la biblioteca no solo como tipo, sino también como categoría de objeto WinForms, y en la enumeración de los tipos de elementos gráficos ya especificaremos exactamente el tipo de nuestra categoría:

    //+------------------------------------------------------------------+
    //| 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
      };
    //+------------------------------------------------------------------+
    
    


    La casilla de verificación del objeto CheckBox puede mostrar tres estados. Vamos a crear una enumeración para indicarlos:

    //+------------------------------------------------------------------+
    //| 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        |
    //+------------------------------------------------------------------+
    
    


    En la lista de propiedades enteras del elemento gráfico en el lienzo, añadimos las nuevas propiedades al final
    y aumentamos el número de propiedades enteras de 44 a 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
    //+------------------------------------------------------------------+
    
    


    Vamos a añadir a la enumeración de posibles criterios de clasificación de los objetos gráficos en el lienzo estas nuevas propiedades:

    //+------------------------------------------------------------------+
    //| 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
      };
    //+------------------------------------------------------------------+
    
    

    Ahora podremos clasificar y filtrar las listas, y también seleccionar los objetos según estas nuevas propiedades.


    La propiedad BorderStyle tiene diferentes propósitos para diferentes objetos WinForms. Para un objeto de panel, esta propiedad indicará el estilo del marco que rodea al propio objeto, mientras que para el objeto GroupBox creado hoy, será el tipo de marco dibujado alrededor del grupo de objetos (el propio objeto no tiene marco). Por lo tanto, podremos usar el mismo método en diferentes objetos para diferentes propósitos.
    Para ello, haremos virtual dicho método en el archivo de la clase básica de todos los objetos WinForms \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:

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

    Sobrescribiendo este método en las clases heredadas, podremos crear nuestra propia implementación para cada clase.


    El objeto básico en la categoría "Contenedores"

    Al crear el segundo objeto WinForms en la categoría "Contenedores", se hizo patente que muchas de las propiedades y métodos son los mismos, y que se repiten de objeto a objeto. Por ello, para evitar la repetición de los mismos métodos en diferentes objetos, tendremos que trasladarlos todos a una clase común, de la que heredarán los objetos de esta categoría. De esta forma, cada uno de los objetos heredados recibirá todos estos métodos de su padre. Aquellos métodos que deban distinguirse en su implementación en diferentes objetos de la misma categoría, deberán hacerse virtuales y anularse en las clases heredadas.

    En la carpeta \MQL5\Include\DoEasy\Objects\Graph\WFormsContainers\, creamos el nuevo archivo Container.mqh de la clase CContainer. La clase deberá heredarse de la clase básica de todos los objetos WinForms de la biblioteca, cuyo archivo deberemos adjuntar a la clase que estamos creando:

    //+------------------------------------------------------------------+
    //|                                                    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
      {
      }
    


    En esta nueva clase, necesitaremos transferir muchos métodos de la clase contenedora "Panel" existente, ya que estos métodos también serán necesarios en otras clases de esta categoría. En su día, ya analizamos en el sexto artículo la mayoría de los métodos que se encuentran en la clase, cuando describimos la creación de la clase "Panel": aquí nos limitaremos a estudiar el cuerpo de la clase, y luego ya implementaremos sus métodos.

    Como este objeto de clase no tendrá un objeto de sustrato (como un objeto de panel), necesitaremos definir de alguna manera el área de trabajo del objeto. El área de trabajo será la zona del objeto sobre la que se pueden colocar otros objetos fijados al contenedor. Vamos a introducir los nuevos métodos para indicar los límites de esta área de trabajo:

    //+------------------------------------------------------------------+
    //|                                                    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();
      };
    //+------------------------------------------------------------------+
    
    

    El resto de los métodos ya nos son familiares del último artículo, donde vimos la creación de un objeto de panel.

    En el constructor de la clase, inicializamos todas las variables con los valores por defecto:

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


    En el destructor de la clase, llamamos al método de desinicialización, ubicado en la clase de objetos de formulario, que es la clase padre para la clase de objeto básico de los objetos WinForms de la biblioteca:

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


    En el método que crea un nuevo objeto gráfico (adelantándonos un poco), se ubican las líneas para crear todos los objetos WInForms (que analizaremos aquí un poco más tarde) disponibles (y no solo estos), no desde la categoría de objetos de contenedor, excepto un objeto de la misma clase:

    //+------------------------------------------------------------------+
    //| 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;
      }
    //+------------------------------------------------------------------+
    
    

    ¿Por qué no se pueden crear objetos aquí desde la categoría de contenedor? Simplemente porque serán los herederos de esa misma clase, y el objeto no sabe nada de ellos todavía. Sin embargo, aquí podemos crear un objeto de nuestra propia clase (CContainer). Como este método es virtual, ya podremos escribir la creación de objetos de las clases descendientes de esta clase en las clases heredadas.


    El método que crea un nuevo elemento anexado también se toma de la clase de objeto de panel, y simplemente se complementa con nuevas líneas para establecer los parámetros de los objetos recién creados:

    //+------------------------------------------------------------------+
    //| 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;
      }
    //+------------------------------------------------------------------+
    
    

    En este método ya podemos escribir todos los objetos existentes y futuros (pero ya escritos en Defines.mqh), porque aquí se crea el tipo de objeto básico CWinFormBase; este se conoce aquí porque es la clase padre. Y todos los parámetros a establecer en los bloques de código que definen cómo asignar los parámetros al objeto creado se usarán de la lista de clases CWinFormBase, lo cual no causará ninguna colisión o error.

    Este método continuará siendo complementado con bloques de código que gestionarán los objetos recién creados. Pero si su inicialización requiere el establecimiento de parámetros desconocidos en esta clase, entonces este método será virtual, y la gestión de las propiedades desconocidas de los nuevos objetos se escribirá en el método redefinido de esas nuevas clases.

    Método que restablece las dimensiones originales de todos los objetos vinculados:

    //+------------------------------------------------------------------+
    //| 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;
      }
    //+------------------------------------------------------------------+
    
    

    Aquí, obtenemos una lista que contiene solo los objetos WinForms, y luego restablecemos para cada objeto su tamaño original en un ciclo.

    Método que ajusta el tamaño de un elemento al tamaño de su contenido interno:

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

    El método también se traslada desde la clase de objeto de panel, pero como esta clase no tiene sustrato, aquí usaremos los valores de los parámetros del área de trabajo del objeto, en lugar de los valores de sus propiedades.

    Por el momento, el método funciona incorrectamente. En futuros artículos, consideraremos su mejora junto con el método encargado de colocar los objetos en el contenedor en su orden de indexación:

    //+------------------------------------------------------------------+
    //| 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;
      }
    //+------------------------------------------------------------------+
    
    

    Los dos métodos anteriormente presentados requieren un reajuste, y por lo tanto, solo están aquí porque se encontraban en la clase de objeto de panel, pero la reharemos después.

    Los métodos que indicamos a continuación también se trasladan de la clase de objeto de panel. Hemos trasladado sin cambios:

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

    Hemos explorado completamente la clase básica de objetos contenedores.

    Ahora crearemos una nueva clase de objeto GroupBox, que también será un contenedor para almacenar otros objetos WinForms.


    El objeto WinForms GroupBox

    Vamos a crear en la carpeta \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ el nuevo archivo GroupBox.mqh. La clase deberá ser heredada de la clase básica del objeto de contenedor recién creado, y su archivo y el de la clase del objeto de panel deberán estar vinculados al archivo de esa clase:

    //+------------------------------------------------------------------+
    //|                                                     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
      {
      }
    

    En las secciones privada, protegida y pública de la clase, escribiremos los métodos que ya conocemos de la clase de objeto de contenedor básico.
    Como el objeto GroupBox tiene un marco que rodea a un grupo de objetos, declararemos un método en la sección privada que dibujará ese marco:

    //+------------------------------------------------------------------+
    //| 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(); }
      };
    //+------------------------------------------------------------------+
    
    

    En el método que establece el estilo del marco, comprobaremos el estilo que se le ha transmitido, y si el marco no es plano, deberá tener al menos dos píxeles de anchura. Y, si es así, todos los lados del marco se ajustarán a una anchura de 2 píxeles.


    En el constructor paramétrico, establecemos las dimensiones y coordenadas iniciales del objeto y llamamos al método de inicialización:

    //+------------------------------------------------------------------+
    //| 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();
      }
    //+------------------------------------------------------------------+
    
    

    Como podemos ver, aquí el tipo de objeto WinForms está escrito como GroupBox y el tipo de objeto de biblioteca, como contenedor.


    En el método que crea un nuevo objeto gráfico, podemos crear tanto objetos de panel como objetos de esta clase, así como objetos de la clase CheckBox, lo cual haremos más adelante en este artículo; no obstante, aquí lo escribiremos de inmediato, para no volver al archivo de esta clase más tarde:

    //+------------------------------------------------------------------+
    //| 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;
      }
    //+------------------------------------------------------------------+
    
    


    Método de inicialización de variables:

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

    El método inicializa todas las variables de la clase usando los valores por defecto. Los valores de inicialización de algunas variables se distinguen de los establecidos en la clase padre.

    Métodos para limpiar el objeto:

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

    Los métodos son virtuales, y además de rellenar todo el objeto con el color de fondo (transparente, por defecto), también dibujan un marco y muestran el texto del encabezado del grupo de objetos sobre él.

    Método que dibuja un marco:

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

    La lógica del método se explica en los comentarios del código. En pocas palabras: necesitamos dibujar un marco alrededor de un grupo de objetos que están colocados en un contenedor. El marco debe dibujarse alrededor de los bordes de todo el objeto, y tener el estilo y color escritos en sus propiedades. El borde superior del marco no deberá pasar a lo largo del borde superior del objeto, sino por el centro del encabezado. Para calcular la coordenada Y inicial del marco, tomaremos la altura de la fuente y la dividiremos por 2, esta será la coordenada Y del marco. El texto del encabezado no deberá dibujarse directamente sobre el marco, es decir, no deberá haber ninguna línea del marco en la ubicación del texto.
    Para ello, simplemente borraremos la línea en el lugar deseado, dibujando un rectángulo transparente sobre la línea, un píxel más grande por cada lado de la inscripción.

    El objeto GroupBox de WinForms está listo.

    Vamos a abrir el archivo de la clase de objeto de panel \NMQL5\Include\NDoEasy\NObjects\Graph\WForms\Containers\Panel.mqh y a eliminar todos los métodos transferidos a la clase básica. Además, conectaremos el archivo de la clase básica del objeto de contenedor y el archivo de la clase GroupBox al archivo de clase. La clase será ahora heredada del objeto de contenedor básico:

    //+------------------------------------------------------------------+
    //|                                                        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
    
    

    De la sección privada de la clase, eliminamos las variables de puntero a los objetos a los que se vincula el objeto Dock, y el método que establece el sustrato a ese objeto, ya que ahora usaremos los métodos de la clase básica CContainer para hacerlo:

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


    Asimismo, eliminamos las líneas adicionales de los constructores de la clase, ya que ahora se escriben en la clase padre:

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

    Además, escribimos un nuevo tipo de objeto en los constructores e inicializamos la clase CContainer en lugar de la anterior CWinFormBase en la lista de inicialización:

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


    En el método que crea un nuevo objeto gráfico, escribimos la creación de nuevos objetos:

    //+------------------------------------------------------------------+
    //| 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;
      }
    //+------------------------------------------------------------------+
    
    

    Veremos aquí la creación del objeto CheckBox un poco más tarde.

    Los otros cambios en la clase son menores, por lo que no tiene mucho sentido analizarlos aquí: el lector podrá encontrarlos en los archivos adjuntos al artículo. Lo principal es la eliminación del archivo de los métodos trasladados desde aquí a la clase básica.

    Ahora tenemos que crear un objeto básico para los controles WinForms estándar de la misma manera.


    El objeto básico de la categoría "Controles estándar".

    En este objeto, transferimos los métodos del objeto de etiqueta de texto que discutimos en el último artículo. La mayoría de los métodos de este objeto se duplicarán en los otros objetos de esta categoría, por lo que necesitaremos un objeto básico aquí (y también en las otras categorías).

    En el directorio de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, crearemos el nuevo archivo CommonBase.mqh de la clase CCommonBase.
    La clase deberá heredarse de la clase básica de los objetos WinForms de la biblioteca
    , y le deberemos adjuntar un archivo de esta clase:

    //+------------------------------------------------------------------+
    //|                                                   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
      {
      }
    
    


    La clase es bastante pequeña. Echemos un vistazo al cuerpo completo de la misma:

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

    Aquí, declaramos un método virtual vacío para cambiar automáticamente el tamaño del objeto. El método deberá implementarse en las clases heredadas, ya que cada clase puede tener su propio criterio de tamaño para dimensionar el objeto.

    El constructor de la clase especifica los tipos de objeto y establece todos los valores de las propiedades por defecto.
    Una vez establecidos todos los valores, el objeto se redibuja:

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


    Método de inicialización:

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


    Métodos para limpiar y pintar el fondo de un objeto:

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

    Todos estos métodos nos resultan familiares de otros objetos de la biblioteca. Ya los hemos comentado todos en el código, así que no será necesario explicarlos. La inicialización y el limpiado de cada objeto es similar, pero hay ligeras diferencias en los valores por defecto de las variables, así como en los principios y el orden usados para pintar el fondo, y en el dibujado de algunos elementos sobre él. Por este motivo, cada clase puede tener su propia implementación de estos métodos.

    Como hemos transferido algunos métodos de la clase de objeto Text Label a la clase de objeto básico de controles estándar, tenemos que modificar la clase CLabel en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.

    En lugar del archivo "WinFormBase.mqh", añadiremos a la clase el archivo del objeto básico y heredaremos la clase de él:

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


    En el constructor de la clase, en la lista de inicialización, escribimos la inicialización de la nueva clase padre y establecemos un nuevo tipo de objeto de biblioteca:

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

    Los otros cambios en la clase son menores y están relacionados con la eliminación de los métodos transferidos de esta clase a la clase padre.
    Podrá encontrarlos en los archivos adjuntos al artículo.


    El objeto WinForms CheckBox

    El objeto CheckBox es una casilla de selección con una etiqueta. La etiqueta y la casilla de selección pueden estar cada una en nueve posiciones en relación con los límites del objeto:

    • Arriba a la izquierda
    • A la izquierda al centro
    • Abajo a la izquierda
    • Abajo al centro
    • Abajo a la derecha
    • A la derecha al centro
    • Arriba a la derecha
    • Arriba al centro
    • En el centro

    Si hay varias combinaciones de posiciones de la casilla y la etiqueta, la posición del texto deberá ajustarse para que no se superponga a la casilla en la medida de lo posible. Un objeto puede tener su marco representado.

    Como el objeto tiene texto, y el texto (y la casilla) cambia automáticamente de tamaño, será razonable heredar del objeto «Etiqueta de texto» y añadir la funcionalidad necesaria para mostrar la casilla en tres combinaciones:

    • Sin marcar
    • Marcada
    • Indefinida

    Un estado indefinido podría ser cuando CheckBox muestra el estado de un grupo de objetos iguales, y algunos de ellos están seleccionados y otros no.

    En el directorio de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, creamos el nuevo archivo Label.mqh de la clase CLabel. Debemos adjuntar al archivo un archivo de clase CLabel y la clase debe heredarse de él:

    //+------------------------------------------------------------------+
    //|                                                     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
      {
      }
    
    


    En la sección privada de la clase, declaramos las variables para guardar las coordenadas y los tamaños de la etiqueta de texto y la casilla de selección, así como los métodos para cambiar el tamaño del objeto y para gestionar las coordenadas del texto y la casilla de selección:

    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:
    
    

    En las secciones protegida y pública, declaramos los métodos para trabajar con la clase:

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

    Todos estos métodos han recibido el nombre correspondiente en las descripciones de los métodos y, a nuestro juicio, no requieren explicación. Vamos a analizar su aplicación.

    En el constructor de la clase, establecemos las propiedades en sus valores por defecto:

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


    Método virtual que redibuja un objeto:

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

    Aquí es sencillo: primero borramos completamente el objeto rellenándolo con el color de fondo (transparente por defecto), luego calculamos las coordenadas correctas del texto en relación con la casilla de selección, dibujamos el texto y la casilla de selección, y finalmente actualizamos el objeto.


    Método que establece las coordenadas X e Y de la casilla según el tipo de alineación:

    //+------------------------------------------------------------------+
    //| 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;
         }
      }
    //+------------------------------------------------------------------+
    
    

    Luego se transmiten por referencia al método las variables en las que se escribirán las coordenadas calculadas de la casilla de selección. Según el método de alineación (la ubicación de la casilla dentro de los límites del objeto), calculamos sus coordenadas y las escribimos en las variables transmitidas al método.


    Método que establece las coordenadas X e Y del texto según el tipo de alineación:

    //+------------------------------------------------------------------+
    //| 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;
         }
      }
    //+------------------------------------------------------------------+
    
    

    Esto es similar al método para calcular las coordenadas de las casillas que hemos discutido anteriormente, pero el punto de anclaje del texto también se cambiará para facilitar el cálculo de las nuevas coordenadas de la etiqueta de texto.


    Método que establece las coordenadas correctas del texto según la alineación del texto y la casilla de verificación:

    //+------------------------------------------------------------------+
    //| 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;
         }
      }
    //+------------------------------------------------------------------+
    
    

    Aquí, según la posición relativa de la casilla y de la etiqueta de texto, se establecen las nuevas coordenadas del texto para que éste no se superponga al icono de la casilla. No podemos implementarlo para todas las situaciones, pero aquí se tienen en cuenta todas las relaciones básicas. Las colisiones restantes entre los dos componentes del objeto podrán resolverse cambiando el tamaño del propio objeto o estableciendo la relación correcta entre el texto y la bandera.

    Método que muestra una casilla de verificación para el estado especificado:

    //+------------------------------------------------------------------+
    //| 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;
         }
      }
    //+------------------------------------------------------------------+
    
    

    Dependiendo del estado de la casilla transmitido al método, dibujaremos una marca de verificación o un rectángulo sombreado dentro de los límites de la casilla de verificación. Las coordenadas para dibujar la línea discontinua se calculan de forma que las proporciones de la marca dibujada siempre sean las mismas, independientemente del tamaño de los bordes de la casilla. Para un estado indefinido, se dibuja un rectángulo. Hasta ahora, solo hay un tipo de estos iconos: una marca de verificación y un rectángulo de un solo color. Más adelante, introduciremos algunos tipos más de visualización de los estados de la casilla.

    Método que establece automáticamente la anchura y la altura de un elemento:

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

    El método virtual, a diferencia del método de la clase padre, considera las dimensiones de la casilla al cambiar las dimensiones del objeto.

    Para que podamos crear objetos de esta clase desde una clase de contenedor y adjuntarlos a esta, incluiremos este archivo de clase en el archivo de clase CContainer (abrimos el archivo de clase de contenedor y adjuntamos esta clase a él):

    //+------------------------------------------------------------------+
    //|                                                    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
    
    


    Para que todas las nuevas clases sean visibles en la clase de colección de elementos gráficos, añadiremos el archivo de la clase CGroupBox a la clase de colección en el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh:

    //+------------------------------------------------------------------+
    //|                                      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"
    
    

    Todas las demás clases estarán conectadas a GroupBox.mqh y serán visibles aquí.

    Vamos a escribir un método que cree un objeto gráfico WinForms GroupBox en el lienzo sobre el gráfico y la subventana especificados:

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

    Los parámetros necesarios para crear el objeto son transmitidos al método. Una vez que el objeto ha sido creado con éxito, estos parámetros son establecidos en las propiedades del objeto. El método retornará el identificador del objeto obtenido al crearlo.

    Para tener acceso a la creación de nuevos objetos desde nuestros programas, en el archivo \MQL5\Include\DoEasy\Engine.mqh de la clase del objeto principal de la biblioteca, escribiremos los métodos para crear y obtener nuevos objetos.

    Métodos que retornan el objeto WForm GroupBox:

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

    Cada método recupera una lista de objetos de tipo GroupBox y la filtra según los parámetros transmitidos al método. Si el objeto buscado está en la lista, será el único en la misma: el puntero a este será devuelto por el método. Si el objeto no se encuentra en la lista, el método retornará NULL.

    Vamos a añadir tres métodos para crear un objeto WinForm GroupBox:

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

    El primero de los métodos crea el objeto GroupBox en el gráfico especificado según el identificador, en la subventana especificada con los parámetros transmitidos al método. Luego se llamará al método necesario para crear dicho objeto a partir de la clase de colección anteriormente analizada, y se retornará el puntero al objeto creado, derivado del identificador del objeto recién creado. Todos los demás métodos llamarán a este (primer) método, especificando explícitamente el identificador del gráfico actual y la ventana del gráfico principal en la línea de parámetros.

    Con esto, podemos dar por concluida la creación de los nuevos objetos de la biblioteca.


    Simulación

    Para la prueba, vamos a tomar el asesor del artículo anterior y guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part108\ con el nuevo nombre TstDE108.mq5.

    ¿Cómo realizaremos la prueba? Vamos a crear un objeto de panel y colocar objetos adjuntos en él: dos objetos de panel con etiquetas de texto adjuntas a su vez. Debajo de estos paneles, crearemos y adjuntaremos un objeto GroupBox. Y, ya que hoy no hemos implementado la posibilidad de crear y adjuntar un objeto CheckBox a partir de los objetos de contenedor, crearemos un objeto de este tipo aparte del panel principal, solo en el gráfico, para ver lo que hemos logrado. Los ajustes de este objeto se mostrarán en los ajustes del asesor, para que podamos cambiarlos de forma rápida y clara y ver el resultado.

    En el área global, crearemos dos nuevas enumeraciones para las versiones inglesa y rusa de la compilación y los nuevos parámetros:

    //--- 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[];
    //+------------------------------------------------------------------+
    
    


    En el manejador OnInit(), escribiremos este bloque de código para crear los objetos GroupBox y CheckBox, y haremos pequeños cambios en el número de objetos creados y en sus coordenadas de ubicación:

    //+------------------------------------------------------------------+
    //| 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)
               {
                //--- get the pointer to the GroupBox object by its index in the list of bound objects
                //--- 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);
      }
    //+------------------------------------------------------------------+
    
    


    En el manejador OnChartEvent(), cambiaremos el tipo de objeto para evitar el error de conversión de tipo del objeto:

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

    Antes, aquí obteníamos el tipo de objeto CPanel especificado, no la clase básica de los objetos WinForms de la biblioteca. No ha habido ningún error porque solo los objetos de este tipo (panel) han participado en la prueba.

    Vamos a compilar el asesor y ejecutarlo en el gráfico:


    Como podemos ver, el posicionamiento de los componentes del objeto CheckBox funciona correctamente, el objeto GroupBox se ha creado en el panel y se adjunta al mismo.

    Obviamente, hasta ahora, todos estos objetos son estáticos: no tienen funcionalidad para interactuar con el ratón, pero implementaremos esto más adelante, y para muchos objetos WinForms a la vez.

    ¿Qué es lo próximo?

    En el próximo artículo, continuaremos desarrollando los objetos WinForms.

    Más abajo, adjuntamos todos los archivos de la versión actual de la biblioteca, así como los archivos del asesor de prueba y el indicador de control de eventos de los gráficos para MQL5. Podrá descargarlo todo y ponerlo a prueba por sí mismo. Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

    Volver al contenido

    *Artículos de esta serie:

    DoEasy. Controles (Parte 1): Primeros pasos
    DoEasy. Elementos de control (Parte 2): Continuamos trabajando con la clase CPanel
    DoEasy. Elementos de control (Parte 3): Creando controles vinculados
    DoEasy. Elementos de control (Parte 4): Elemento de control "Panel", parámetros Padding y Dock
    DoEasy. Elementos de control (Parte 5): Objeto básico WinForms, control «Panel», parámetro AutoSize
    DoEasy. Elementos de control (Parte 6): Control «Panel», cambio automático del tamaño del contenedor según el contenido interno
    DoEasy. Elementos de control (Parte 7): Elemento de control «etiqueta de texto».



    Traducción del ruso hecha por MetaQuotes Ltd.
    Artículo original: https://www.mql5.com/ru/articles/11075

    Archivos adjuntos |
    MQL5.zip (4364.52 KB)
    Experimentos con redes neuronales (Parte 1): Recordando la geometría Experimentos con redes neuronales (Parte 1): Recordando la geometría
    Las redes neuronales lo son todo. En este artículo, usaremos la experimentación y enfoques no estándar para desarrollar un sistema comercial rentable y comprobaremos si las redes neuronales pueden ser de alguna ayuda para los comerciantes.
    Trading de cuadrícula automatizado utilizando órdenes límite en la Bolsa de Moscú MOEX Trading de cuadrícula automatizado utilizando órdenes límite en la Bolsa de Moscú MOEX
    Hoy vamos a desarrollar un asesor comercial en el lenguaje de estrategias comerciales MQL5 para MetaTrader 5 de la Bolsa de Moscú MOEX. El asesor comerciará con una estrategia de cuadrícula en el terminal MetaTrader 5 en los mercados de la Bolsa de Moscú MOEX; también incluirá el cierre de posiciones usando stop loss o take profit, y eliminará las órdenes pendientes al suceder ciertas condiciones del mercado.
    Aprendiendo a diseñar un sistema de trading con Volumes Aprendiendo a diseñar un sistema de trading con Volumes
    En este nuevo artículo de la serie sobre la creación de sistemas comerciales basados en indicadores técnicos populares, hablaremos del indicador Volumes. El volumen como concepto es un factor importante en el comercio en los mercados financieros y, por tanto, debe tenerse siempre en cuenta. En este artículo, aprenderemos a desarrollar un sistema comercial basado en el indicador Volumes.
    Redes neuronales: así de sencillo (Parte 17): Reducción de la dimensionalidad Redes neuronales: así de sencillo (Parte 17): Reducción de la dimensionalidad
    Seguimos analizando modelos de inteligencia artificial, y en particular, los algoritmos de aprendizaje no supervisado. Ya nos hemos encontrado con uno de los algoritmos de clusterización. Y en este artículo queremos compartir con ustedes una posible solución a los problemas de la reducción de la dimensionalidad.