Русский 中文 Español Deutsch 日本語 Português
preview
DoEasy. Controls (Part 8): Base WinForms objects by categories, GroupBox and CheckBox controls

DoEasy. Controls (Part 8): Base WinForms objects by categories, GroupBox and CheckBox controls

MetaTrader 5Examples | 3 August 2022, 14:35
4 031 0
Artyom Trishkin
Artyom Trishkin

Contents


Concept

WinForms objects we create are categorized much like MS Visual Studio:

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

Objects of all categories are inherited from the common CWinFormBase base object. Besides, objects belonging to the same category have overlapping functionality within their category. Therefore, it is worth combining all similar properties and methods of objects belonging to one category into a common class of the same category. In other words, we need to make a separate base class for each category of WinForms objects inherited from the base class of all WinForms objects. This will simplify writing the code for new objects in each category.

In this article, we will create two such objects — for container and standard control objects. Generally, in order to understand what properties and methods can be common for objects of one category, we need to create at least two objects in this category.

Let's create the GroupBox object class in the containers category — this is a container visually combining several objects inside it.
Unlike the Panel object, which is also a container, GroupBox has less functionality being just a container for visually combining objects into a common group.

In the standard controls category, create the CheckBox object. This is a checkbox with a label that can have three states: checked, unchecked and undefined. Since the object has a text label, it is inherited from the Label object. However, the functionality for drawing a checkbox (verification flag) in different states will be added to it.

All created objects are still static, i.e. they are unable to interact with the mouse. I will implement the necessary functionality after creating most of the WinForms objects planned for development.


Improving library classes

In case of created objects, we need to add the values of some properties assigned to them by default upon creation.

In \MQL5\Include\DoEasy\Defines.mqh, add new macro substitutions for these values:

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

The checkbox flag will have the default size of 12x12 pixels in the CheckBox object.

Currently, we have Base, Panel and Label types in the WinForms section of the library object type list. They are not necessary since the same types are set in another enumeration. But here we can use them as an indication of the WinForms object category. So, let's fix the names in the enumeration so that they display only object categories:

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

Now, in case of WinForms objects, we can use the library object type as a type and as a WinForms object category. In the enumeration of the graphical element types list, we will specify the type in its category:

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


CheckBox object verification flag can have one of the three states. Let's create an enumeration to specify them:

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


Add new properties at the very end of the enumeration of integer properties of a canvas-based graphical element,as well as increase the number of integer properties from 44 to 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
//+------------------------------------------------------------------+


Add the new properties to the enumeration of possible criteria of sorting graphical elements on canvas:

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

Now we can sort the lists and select objects by the new properties.


The BorderStyle property has different purposes for different WinForms objects. In case of a panel object, the property specifies the style of the object frame. In case of GroupBox, it sets the type of a frame drawn around the group of objects (the object itself has no frame). Accordingly, we can use the same method in different objects for different purposes.
To achieve this, let's make it virtual in the base class file of all WinForms objects \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);        }

Redefining the method in the inherited classes allows us to create a separate method implementation for each of them.


Base object in the Containers category

When creating the second WinForms object in the Containers category, it became obvious that many properties and methods are the same and repeated from object to object. To avoid duplications and same-type methods in different objects, we need to move all of them into a single common class objects of this category will be inherited from. Thus, each of the descendant objects will receive all these methods from its parent. The methods that should differ in their implementation for different objects of the same category should be made virtual and redefined in inherited classes.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\, create a new file Container.mqh of the CContainer class. The class should be inherited from the base class of all WinForms objects of the library whose file should be included into the created class:

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


We should move many methods from the already existing Panel container class to the new class since these methods will also be needed in other classes of the category. Since I have considered most of these methods in the sixth article when describing the Panel class, here we will simply consider the class body and implementation of its methods.

Since the class object will not have the underlay object (in contrast to the panel object), we need to somehow designate the object working area. The object area where you can place other objects bound to the container is considered a working area. Let's introduce new methods to highlight the limits of the working area:

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

We know other methods from the previous article considering creation of the panel object.

In the class constructor, initialize all the variables with default values:

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


In the class destructor, call the deinitialization method located in the form object class which is a parent one for the class of the base object for the library WinForms objects:

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


The method creating a new graphical object features the strings for creating all existing (and considered a bit later) WinForms objects (and more) not from the container objects category, except for an object of the same class:

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

Why are we unable to create objects from the containers category? The reason is that they will be descendants of this very class, and it still does not know anything about them. However, it is possible to create the object of its own class (CContainer) here. Since this is the virtual method, we will be able to set the creation of descendant class objects of this class in the derived classes.


The method creating a new bound element is also taken from the panel object class and simply supplemented with new strings for setting the parameters of newly created objects:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  CGCnvElement *main,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h,
                                  const color colour,
                                  const uchar opacity,
                                  const bool activity,
                                  const bool redraw)
  {
//--- If the object type is less than the base WinForms object
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- report the error and return 'false'
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- If failed to create a new graphical element, return 'false'
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Set the text color of the created object as that of the base panel
   obj.SetForeColor(this.ForeColor());
//--- If the object type is a container
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CONTAINER)
     {
      //--- set the frame color equal to the background color 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- If the object type is a panel
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL)
     {
      //--- set the frame color equal to the background color 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- If the object type is GroupBox
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
     {
      //--- set the frame color equal to the background color 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- If the object type is a text label,
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_LABEL)
     {
      //--- set the object text color depending on the one passed to the method
      //--- or the panel text color or the one passed to the method and the frame color equal to the text color 
      obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour);
      obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor());
     }
//--- If the object type is CheckBox
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CHECKBOX)
     {
      //--- set the object text color depending on the one passed to the method
      //--- or the object text color or the one passed to the method and the frame color equal to the text color 
      obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour);
      obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor());
     }
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Redraw the panel and all added objects, and return 'true'
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

In this method, we can set all existing and future (but already specified in the Defines.mqh file) objects since the CWinFormBase base object type is created and known here as it is a parent class. All parameters set in code blocks, which define the setting of parameters for the created object, are used from the CWinFormBase class list causing neither collisions, nor errors.

This method will continue to be supplemented by code blocks handling newly created objects. If their initialization requires setting parameters unknown in the class, the method is virtual. The handling of unknown properties of new objects will be set in the redefined method of those new classes.

The method resetting the size of all bound objects to the initial ones:

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

Here we get the list of WinForms objects only. Next, in the loop by the obtained list, set the initial size to each object.

The method adjusting the element size to fit its content:

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

The method has also been moved from the panel object. However, since the class does not have an underlay, here we use the values of the object working area parameters instead of its property values.

Currently, the method works incorrectly. I will consider its improvement together with improving the method arranging objects in the container in the order they are indexed in subsequent articles:

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

Both methods provided above need to be reworked. They are present here only because they were present in the panel object class. I will rework them later.

The methods below were also moved from the panel object class. They have been moved without changes:

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

This completes the topic of container objects' base class.

Now, let's create the GroupBox object new class, which is also a container for storing other WinForms objects.


'GroupBox' WinForms objects

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\, create the new file GroupBox.mqh of the CGroupBox class. The class should be inherited from the newly created base class of container objects, while its file, as well as the file of the panel object class should be included into the class file:

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

In the private, protected and public sections of the class, add the already familiar methods.
Since the GroupBox object features a frame encasing a group of objects, declare the method drawing such a frame in the private section:

//+------------------------------------------------------------------+
//| GroupBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CGroupBox : public CContainer
  {
private:
//--- Draw a frame
   void              DrawFrame(void);
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
                                          
protected:
//--- Initialize the variables
   virtual void      Initialize(void);

public:
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false);
//--- Set a frame style
   virtual void      SetBorderStyle(const ENUM_FRAME_STYLE style)
                       {
                        if((this.FrameWidthTop()<2 || this.FrameWidthBottom()<2 || this.FrameWidthLeft()<2 || this.FrameWidthRight()<2) && 
                            style>FRAME_STYLE_FLAT)
                           this.SetFrameWidthAll(2);
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style);
                       }
   
//--- Constructors
                     CGroupBox(const long chart_id,
                               const int subwindow,
                               const string name,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
                     CGroupBox(const string name) : CContainer(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
                        this.Initialize();
                       }
//--- Destructor
                    ~CGroupBox(){ CForm::Deinitialize(); }
  };
//+------------------------------------------------------------------+

In the method setting the frame style, check the passed style. If the frame is not flat, its width should be at least two pixels. If this is the case, the width of 2 pixels is set to all frame sides.


In the parametric constructor, set initial object size and coordinates and call the initialization method:

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

As you can see, WinForms object type is set as GroupBox, while the library object type is set as a container.


In the method creating a new graphical object, we can create panel objects, class objects and CheckBox class objects to be implemented later in the article.

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


The variable initialization method:

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

All class variables are initialized in the method using default values. Initializing values of some variables differ from the values set in the parent class.

The methods for clearing the object:

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

The methods are virtual. Apart from filling the entire object with a background color (the default is transparent), the frame and the object group header text above it are displayed as well.

The method drawing the frame:

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

The method logic is clarified in the code comments. In short, we need to draw a frame encasing the group of objects located in the container. The frame should be drawn along the edges of the entire object and have the style and color specified in its properties. The upper edge of the frame should not run along the top edge of the object, but along the center of the header. To calculate the initial Y coordinate of the frame, take the font height and divide it by 2. This will be the Y coordinate of the frame. The header text should not be drawn directly on the frame, i.e. there should be no frame line at the location of the text.
To achieve this, we simply erase the line in the right place by drawing a rectangle with a transparent color on top of the line. The rectangle size should exceed the label size by one pixel from each side.

The 'GroupBox' WinForms object is ready.

Open the \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh file of the panel object class and remove all methods moved to the base class from it. Besides, include the file of the container objects base class and the file of the GroupBox class to the class file. The class is now inherited from the base container object:

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

From the private section of the class, remove the variables of pointers to the objects, to whose coordinates the Dock object is bound, and the method setting the underlay using such an object, since now we will use the methods of the CContainer base class for that:

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


Remove unnecessary strings from the class constructors, since they are now set in the parent class:

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

Besides, in the constructors, set a new object type and initialization of the CContainer class instead of the previous CWinFormBase in the initialization list:

//--- Constructors
                     CPanel(const long chart_id,
                            const int subwindow,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h);
                     CPanel(const string name) : CContainer(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; 
                        this.CreateUnderlayObj();
                       }
//--- Destructor
                    ~CPanel(){ CForm::Deinitialize(); }
  };
//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.CreateUnderlayObj();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


In the method creating a new graphical object, set creation of new objects:

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

I will consider creating the CheckBox object below.

The remaining changes in the class are insignificant. There is no point in considering them here. You can find them in the files attached to the article. The main thing is the removal of the methods that have been moved to the base class.

Now let's create the base object for the WinForms standard control objects in the same way.


Base object in the Standard Controls category

This object is to store the methods from the text label object I considered in the previous article. Most of the methods of this object will be duplicated in other objects of this category, so we also need a base object here (and in other categories).

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ library file, create a new file CommonBase.mqh of the CCommonBase class.
The class should be inherited from the base class of the library WinForms objects
, and the class file should be included into the file:

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


The class is quite small. Let's have a look at it in its entirety:

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

Here we have declared an empty virtual method for object auto sizing. The method should be implemented in inherited classes since each class can have its own size criteria used to set the object size.

The class constructor specifies object types and all default property values.
After setting all values, the object is redrawn:

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


Initialization method:

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


The methods of clearing and filling the object background:

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

We have already encountered all these methods in other library objects. They have all been commented in the code and require no explanations. Initialization and cleanup for each object are similar, but there are slight differences in the default values of the variables and in the principles and sequence of painting the background and drawing some elements on it. For this reason, each class can have its own implementation of these methods.

Since I have moved some methods from the "Text label" object class to the class of the base objects of standard controls, we need to correct the CLabel class in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.

Instead of WinFormBase.mqh, include the base object file class to the class and inherit the class from it:

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


In the class constructor, namely in the initialization list, set the initialization of a new parent class and set a new library object type:

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

The remaining changes in the class are insignificant and have to do with the removal of the methods moved from the current class to the parent one.
You can find them in the files attached below.


'CheckBox' WinForms object

The CheckBox object is a checkbox with a label. The label and the flag can each be in nine positions relative to the object boundaries:

  • Top left
  • Center left
  • Bottom left
  • Bottom center
  • Bottom right
  • Center right
  • Top right
  • Top center
  • Center

With various combinations of the checkbox and text location, it is necessary to adjust the text location so that it does not overlap the checkbox if possible. The object can have its frame displayed.

Since the object has a text, while the text (and flag) size are used to automatically resize the object, it makes sense to inherit from the "Text label" object and add functionality to display the checkbox in three combinations:

  • Unchecked
  • Checked
  • Undefined

Undefined state occurs if CheckBox displays the status of groups of the same objects, some of which are selected, while some are not.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, create a new file CheckBox.mqh of the CCheckBox class. The CLabel class file should be included into the file and the class should be inherited from it:

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


In the private section of the class, add the variables for storing the text label and checkbox coordinates and size, as well as the methods for changing the object size and handling text and checkbox coordinates:

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:

In the protected and public sections, declare the methods for handling the class:

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

All these methods are named in the method descriptions. Let's consider their implementation.

In the class constructor, set the default values for properties:

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


The method redrawing an object:

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

Here all is simple. First, completely erase the entire object by filling it with the background color (which is transparent by default). Next, calculate valid text coordinates relative to the checkbox, draw the text and checkbox, and update the object.


The method setting X and Y flag coordinates depending on the alignment type:

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

The method receives the variables the calculated checkbox coordinates should be set into. Depending on the alignment method (checkbox location within the object boundaries), calculate its coordinates and set them in the variables passed to the method.


The method setting the X and Y text coordinates depending on the alignment type:

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

Here all is similar to the method of the checkbox coordinates calculation considered above. The text anchor point is also additionally changed here simplifying the calculation of the new text label coordinates.


The method setting valid text coordinates depending on the text alignment and checkbox:

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

Here, depending on the relative position of the checkbox and the text label, new text coordinates are set so that the text does not overlap the checkbox icon. This is not a universal solution, but all the main correlations are taken into account here. Other collisions of the two object components can be solved by changing the size of the object itself or by setting the correct ratio between the text and checkbox positions.

The method displaying the checkbox for the specified state:

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

Depending on the state of the checkbox passed to the method, we draw either a checkmark or a filled rectangle inside the checkbox boundaries. Polyline coordinates are calculated so that the proportions of the drawn checkmark always remain the same regardless of the size of the checkbox borders. A rectangle is drawn for an undefined state. So far, there is only one kind of these icons - a checkmark and a rectangle with one color. In the future, we will introduce several more types of displaying the flag states.

The method automatically setting the element width and height:

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

Unlike the parent class method, the virtual method considers the checkbox size when changing the object size.

Include the file of the class to the CContainer class file (open the class container file and include the class to it) so that we can create the class objects from the container class and bind them to it:

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


To let all new classes remain visible in the collection class of graphical elements, include the file of the CGroupBox class to the collection class in file \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"

The remaining classes are included into GroupBox.mqh and are visible there.

Write the method creating the 'GroupBox' WinForms graphical object on canvas on the specified chart and subwindow:

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

The method receives the parameters that are necessary for creating an object. After the object has been created successfully, these parameters are set to the object properties. The method returns the object ID obtained during its creation.

In order to have access to creating new objects from custom programs, in \MQL5\Include\DoEasy\Engine.mqh of the library main object class, write the methods for creating and receiving new objects.

The methods returning the 'GroupBox' WForm object:

//--- Return the WForm Panel object by object ID
   CPanel              *GetWFPanel(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
                          
//--- Return the GroupBox WForm object by object name on the current chart
   CGroupBox           *GetWFGroupBox(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the GroupBox WForm object by chart ID and object name
   CGroupBox           *GetWFGroupBox(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm GroupBox object by object ID
   CGroupBox           *GetWFGroupBox(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

//--- Create the WinForm Element object

In each of the methods, get the list of objects of GroupBox type and sort the obtained list by the parameters passed to the method. If the desired object is in the list, then it is the only one in it — the pointer to it is returned from the method. If the object is not found in the list, the method returns NULL.

Add three methods for creating the 'GroupBox' WinForm object:

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

The very first of the methods creates the GroupBox object on the chart specified by ID in the specified subwindow with parameters passed to the method. The method for creating such an object from the collection class considered above is called, and the pointer to the created object , obtained by the ID of a newly created object, is returned. All other methods call this (first) method explicitly specifying the current chart ID and the main chart window in the parameters' line.

This concludes the creation of new library objects.


Test

To perform the test, I will use the EA from the previous article and save it in \MQL5\Experts\TestDoEasy\Part108\ as TstDE108.mq5.

Let's create a panel object and place bound objects on it: two panel objects with text label objects bound to them. Create and attach the GroupBox object below these panels. Since I have not provided the ability to create and bind the CheckBox object from container objects, let's create such an object separately from the main panel — simply on the chart to see what we got from it. Install the object parameters in the EA settings to change them quickly and clearly, and see the result.

In the global area, create two new enumerations for English and Russian compilation versions, as well as new inputs:

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // None
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (bevel)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (stamp)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Indeterminate
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Increase only
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Increase and decrease
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // No frame
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple frame
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat frame
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (convex)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (concave)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Undefined
  };
#endif 
//--- input parameters
sinput   bool                          InpMovable        =  true;                   // Movable forms flag
sinput   ENUM_INPUT_YES_NO             InpAutoSize       =  INPUT_YES;              // Autosize
sinput   ENUM_AUTO_SIZE_MODE           InpAutoSizeMode   =  AUTO_SIZE_MODE_GROW;    // Autosize mode
sinput   ENUM_BORDER_STYLE             InpFrameStyle     =  BORDER_STYLE_NONE;      // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign      =  ANCHOR_LEFT_UPPER;      // Label text align
sinput   ENUM_ANCHOR_POINT             InpCheckAlign     =  ANCHOR_LEFT_UPPER;      // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign =  ANCHOR_LEFT_UPPER;      // Check label text align
sinput   ENUM_CHEK_STATE               InpCheckState     =  CHEK_STATE_UNCHECKED;   // Check flag state
sinput   ENUM_INPUT_YES_NO             InpCheckAutoSize  =  INPUT_YES;              // CheckBox autosize
sinput   ENUM_BORDER_STYLE             InpCheckFrameStyle=  BORDER_STYLE_NONE;      // CheckBox border style
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+


In the OnInit() handler, set the code block for creating the GroupBox and CheckBox objects, and make small corrections for changing the number of created objects and their placement coordinates:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions

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


In the OnChartEvent() handler, change the object type to avoid object type cast error:

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

Previously, we got the specific type of the CPanel object here, rather than the base class of the library WinForms objects. There was no error because only objects of this type (panel) participated in the test.

Compile the EA and launch it on the chart:


As we can see, the positioning of the CheckBox object components works correctly, the GroupBox object is created on the panel and bound to it.

For now, all such objects are static — they have no functionality of interacting with the mouse. I will implement it later — for multiple WinForms objects at once.

What's next?

In the next article, I will continue the development of WinForms objects.

All files of the current library version, test EA and chart event control indicator for MQL5 are attached below for you to test and download. Leave your questions, comments and suggestions in the comments.

Back to contents

*Previous articles within the series:

DoEasy. Controls (Part 1): First steps
DoEasy. Controls (Part 2): Working on the CPanel class
DoEasy. Controls (Part 3): Creating bound controls
DoEasy. Controls (Part 4): Panel control, Padding and Dock parameters
DoEasy. Controls (Part 5): Base WinForms object, Panel control, AutoSize parameter
DoEasy. Controls (Part 6): Panel control, auto resizing the container to fit inner content
DoEasy. Controls (Part 7): Text label control



Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/11075

Attached files |
MQL5.zip (4364.52 KB)
Experiments with neural networks (Part 1): Revisiting geometry Experiments with neural networks (Part 1): Revisiting geometry
In this article, I will use experimentation and non-standard approaches to develop a profitable trading system and check whether neural networks can be of any help for traders.
Learn how to design a trading system by Force Index Learn how to design a trading system by Force Index
Welcome to a new article in our series about how to design a trading system by the most popular technical indicators. In this article, we will learn about a new technical indicator and how to create a trading system using the Force Index indicator.
Data Science and Machine Learning — Neural Network (Part 01): Feed Forward Neural Network demystified Data Science and Machine Learning — Neural Network (Part 01): Feed Forward Neural Network demystified
Many people love them but a few understand the whole operations behind Neural Networks. In this article I will try to explain everything that goes behind closed doors of a feed-forward multi-layer perception in plain English.
Complex indicators made easy using objects Complex indicators made easy using objects
This article provides a method to create complex indicators while also avoiding the problems that arise when dealing with multiple plots, buffers and/or combining data from multiple sources.