DoEasy. Controls (Part 5): Base WinForms object, Panel control, AutoSize parameter

29 June 2022, 15:15
Artyom Trishkin
0
2 371

Contents


Concept

Before implementing the AutoSize and AutoSizeMode properties of the panel object, I will create the base class of all library WinForms objects. Since many of the properties of such objects are inherited from each other, the properties inherent in the panel whose object I am working on now can be used for other WinForms objects as well. To avoid setting simimlar properties for each of the objects, I will create a base WinForms object all other objects of this type will be inherited from. The base object itself will be inherited from the form object class where interaction with the mouse is implemented.

If the object located inside the panel has the Dock property (considered in the previous article) activated, such an object "sticks" to the borders of its container. The container border is specified in the DockMode property. In this case, if each subsequent object placed inside the panel has the same binding to the side of its container (panel) as the previous object placed inside the panel, then it is attached to the nearest side of the previous object rather than to the specified side of the container. Thus, all objects placed inside the panel and bound, for example, to the left edge of the container, will be arranged in one row from left to right. If the panel has the AutoSize mode activated, the container is automatically increased in width so that all objects, located inside it and lined up in one row, do not go beyond their container. The container should behave in the same way if an object protruding beyond the edges of the panel is placed inside it. If the panel has AutoSize mode enabled, then it must adjust the size of its sides so that the object does not go beyond its limits.

At the same time, there is a difference if the object is attached to one of the sides of its container, and the container itself has the AutoSize property active or not.
All objects placed inside a container with active auto sizing are placed in the order of their priority specified by the object serial number in the list of objects attached to the container. This will allow us to predetermine the location of objects inside the container whose size automatically adjusts to the total size of all elements bound to it. I will consider this in subsequent articles. Today I will improve the library classes according to the task creating a new base WinForms object and implementing the Autosize property when creating an element attached to it directly from the panel.

In addition to the designated tasks, let's carry out a small optimization of the graphical elements construction inside the panel. Since we first need to arrange all the elements attached to the panel by their serial numbers and according to their binding values (Dock) and then resize the panel in case it has the auto size property set to fit its internal content (and if it really necessary), then we first need to "virtually" place all the elements inside the panel, then see how much we need to resize the panel and change it if necessary. Only after that should we redraw all the elements located inside the panel. If we draw the elements immediately instead, the changes in their size and binding point become visible in real time causing various rendering artifacts around the panel. Therefore, we will first arrange the elements in the right order, resize them if necessary (this depends on the method of binding the element and its number in the list of bound elements), then calculate how much the panel needs to be resized. After doing all that, we will redraw the panel and the elements located inside it again in a new order.


Improving library classes

Since I am going to implement a new library object here, I need to add its type to the list of library object types. In addition, we will need to know the object type when accessing the panel underlay object.
In \MQL5\Include\DoEasy\Defines.mqh, namely in the list of library object types, enter the new type — base WinForms object:

//+------------------------------------------------------------------+
//| List of library object types                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
   //--- WinForms
   OBJECT_DE_TYPE_GWF_BASE,                                       // WinForms Base object type (base abstract WinForms object)
   OBJECT_DE_TYPE_GWF_PANEL,                                      // WinForms Panel object type
//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                         // "Single animation frame" object type
//--- ...
//--- ...
  }


Add two new types — graphical underlay element and base WinForms object to the list of graphical element types. Also, rename the GRAPH_ELEMENT_TYPE_PANEL macro substitution into GRAPH_ELEMENT_TYPE_WF_PANEL:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Panel object underlay
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
  };
//+------------------------------------------------------------------+

These type allows us to know which object is currently selected and what to do with it if this is the necessary type.

In \MQL5\Include\DoEasy\Data.mqh, add the library new message indices:

   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Window
   MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                // Underlay of the Panel WinForms control object
   MSG_GRAPH_ELEMENT_TYPE_WF_BASE,                    // WinForms base control
   MSG_GRAPH_ELEMENT_TYPE_WF_PANEL,                   // Panel control

and text messages corresponding to the newly added indices:

   {"Окно","Window"},
   {"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""},
   {"Базовый элемент управления WinForms","Base WinForms control"},
   {"Элемент управления \"Panel\"","Control element \"Panel\""},


All library graphical objects are derived from the CGBaseObj library base graphical object class. The class contains all base methods for handling any library graphical object. In \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, add displaying the description of two new types of library graphical elements to the method returning the description of a graphical element type:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(void)
  {
   return
     (
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)           :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)  :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)         :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)               :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)             :
      //---
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)        :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)           :
      "Unknown"
     );
  }
//+------------------------------------------------------------------+


Let's make some methods virtual in the class of the graphical element object on canvas the remaining Canvas objects of the library graphics are derived from. The class is located in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
Implementation of some methods in the class may turn out to be unsuitable for some descendant objects of the class. If we make them virtual, we will be able to change these methods in the descendant classes, in which implementation of the methods should be different from the one created in the class.

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge,
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
   void              SetRightEdge(void)                        { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());           }
   void              SetBottomEdge(void)                       { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());         }

...

//+------------------------------------------------------------------+
//| The methods of filling, clearing and updating raster data        |
//+------------------------------------------------------------------+
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false);
//--- Update the element
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }

Now, in those descendant classes where a different implementation of these methods is needed, we will simply write exactly the same virtual methods, albeit with their own implementation different from the one displayed here. It is precisely those virtual methods of the descendant classes that will be called when accessing the virtual method of the parent class.

I need to add the flag indicating the need to redraw the graphical object to some methods I am currently improving. This will allow optimizing the handling of object lists to avoid constant redrawing of each object from the list but to handle all objects in the list first (for example, change the size of each object and move it to a new location). Upon completion of handling an entire list, simply redraw each object at its new coordinates or with its new size. This will be visually faster than redrawing each object in the list immediately after changing its dimensions and coordinates.

In the shadow object class file \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh, change names of variables and methods removing the word "shadow" from them. I believe, it is redundant here.

Since we will need to completely redraw the shadow with new size, albeit with the same shadow parameters, add yet another variable storing the shadow blur radius, two methods for accessing a new variable, as well as the flag of the need to redraw the entire shadow object to the shadow redraw method:

//+------------------------------------------------------------------+
//| Shadows object class                                             |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color;                         // Shadow color
   uchar             m_opacity;                       // Shadow opacity
   uchar             m_blur;                          // Blur
//--- Gaussian blur
   bool              GaussianBlur(const uint radius);
//--- Return the array of weight ratios
   bool              GetQuadratureWeights(const double mu0,const int n,double &weights[]);
//--- Draw the object shadow form
   void              DrawShadowFigureRect(const int w,const int h);

public:
//--- Constructor
                     CShadowObj(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- Supported object properties (1) integer and (2) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Draw an object shadow
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
   
//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) return the shadow color
   void              SetColor(const color colour)                             { this.m_color=colour;     }
   color             Color(void)                                        const { return this.m_color;     }
//--- (1) Set and (2) return the shadow opacity
   void              SetOpacity(const uchar opacity)                          { this.m_opacity=opacity;  }
   uchar             Opacity(void)                                      const { return this.m_opacity;   }
//--- (1) Set and (2) return the shadow blur
   void              SetBlur(const uchar blur)                                { this.m_blur=blur;        }
   uchar             Blur(void)                                         const { return this.m_blur;      }
  };
//+------------------------------------------------------------------+


In the class constructor, add the default shadow opacity specified by the CLR_DEF_SHADOW_OPACITY macro substitution in the library Defines.mqh file and specify the default shadow blur using the DEF_SHADOW_BLUR macro substitution as well from the same file:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetColorBackground(clrNONE);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity=CLR_DEF_SHADOW_OPACITY;
   this.m_blur=DEF_SHADOW_BLUR;
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.m_visible=true;
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+


In the implementation of the method drawing a shadow object, we now clearly specify the shadow redraw flag. Instead of the 'radius' local variable, I will use the new variable m_blur. This will allow us to save the shadow blur value for the subsequent redrawing of the shadow object with the parameters it was originally drawn with:

//+------------------------------------------------------------------+
//| Draw the object shadow                                           |
//+------------------------------------------------------------------+
void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw)
  {
//--- Set the shadow shift values to the variables by X and Y axes
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(shift_y);
//--- Calculate the height and width of the drawn rectangle
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Draw a filled rectangle with calculated dimensions
   this.DrawShadowFigureRect(w,h);
//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant
   this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal)
   if(!this.GaussianBlur(this.m_blur))
      return;
//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw);
   CGCnvElement::Update(redraw);
  }
//+------------------------------------------------------------------+


Since I am going to implement the base object of all library WinForms objects a bit later, some variables and methods in the implemented WinForms object classes and their parent classes should be passed either to a new base class or to its parent — the CForm class. This allows all variables and methods to remain available at the levels of those classes of the inheritance hierarchy where they are needed.

For example, upon creating an object, its coordinates and size are stored in the CPanel class variables. We will need the same data in other WinForms objects. Therefore, we will move them to the parent class of WinForms objects in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
At first sight, it would be logical to move these variables and methods to the base class of WinForms objects. However, the data may also turn out to be useful for form objects whose class is the parent one for the class of the base object of all WinForms objects. So let's move these variables to it:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // List of attached elements
   CAnimations      *m_animations;                             // Pointer to the animation object
   CShadowObj       *m_shadow_obj;                             // Pointer to the shadow object
   CMouseState       m_mouse;                                  // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_form_state;                   // Mouse status relative to the form
   ushort            m_mouse_state_flags;                      // Mouse status flags
   color             m_color_frame;                            // Form frame color
   int               m_offset_x;                               // Offset of the X coordinate relative to the cursor
   int               m_offset_y;                               // Offset of the Y coordinate relative to the cursor
   int               m_init_x;                                 // Newly created form X coordinate
   int               m_init_y;                                 // Newly created form Y coordinate
   int               m_init_w;                                 // Newly created form width
   int               m_init_h;                                 // Newly created form height

//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Create a new graphical object

Let's divide the CreateNewElement() method into two to optimize the code — create yet another CreateAndAddNewElement() method where a new element will be created and added to the list. Declare the method in the protected section of the class. Move the methods for handling the variables storing the initial coordinates and object size from the CPanel class to the public section:

protected:
   CArrayObj         m_list_tmp;                               // List for storing the pointers
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom
//--- Initialize the variables
   void              Initialize(void);
   void              Deinitialize(void);
//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
//--- Update coordinates of bound objects
   virtual bool      MoveDependentObj(const int x,const int y,const bool redraw=false);
//--- Create a new bound element and add it to the list of bound objects
   CGCnvElement     *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity);
   
public:
//--- Return the initial (1) X and (2) Y coordinate, (3) form width and (4) height
   int               GetCoordXInit(void)           const { return this.m_init_x;             }
   int               GetCoordYInit(void)           const { return this.m_init_y;             }
   int               GetWidthInit(void)            const { return this.m_init_w;             }
   int               GetHeightInit(void)           const { return this.m_init_h;             }
//--- Set the initial (1) X and (2) Y coordinate, (3) form width and (4) height
   void              SetCoordXInit(const int value)      { this.m_init_x=value;              }
   void              SetCoordYInit(const int value)      { this.m_init_y=value;              }
   void              SetWidthInit(const int value)       { this.m_init_w=value;              }
   void              SetHeightInit(const int value)      { this.m_init_h=value;              }
//--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
   int               MouseCursorX(void)            const { return this.m_mouse.CoordX();     }
   int               MouseCursorY(void)            const { return this.m_mouse.CoordY();     }


Make the CreateNewElement() method virtual and add the flag indicating the necessity to render a newly created object to it:

//--- Create a new attached element
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Add a new attached element
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

The method has been made virtual so that we are able to make the same method with its own implementation in each inherited class. The object redrawing flag is needed to avoid displaying each object one by one immediately after creation. Instead, we first create all objects, next adjust the size of the panel it is created on and finally render everything afterwards. This will be much faster as we will be able to avoid visual artifacts created when displaying the changed panel size while creating each subsequent object out of all objects attached to it and created simultaneously in a loop.


In the block of methods for a simplified access to the object properties, declare two methods for handling the "Shadow blur" property:

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) get the form frame color
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;     }
   color             ColorFrame(void)                                   const { return this.m_color_frame;     }
//--- (1) Set and (2) return the form shadow color
   void              SetColorShadow(const color colour);
   color             ColorShadow(void) const;
//--- (1) Set and (2) return the form shadow opacity
   void              SetOpacityShadow(const uchar opacity);
   uchar             OpacityShadow(void) const;
//--- (1) Set and (2) return the form shadow blur
   void              SetBlurShadow(const uchar blur);
   uchar             BlurShadow(void) const;

  };
//+------------------------------------------------------------------+


After calling the property initialization method, add calling the methods of initialization of properties storing the object's initial size and coordinates to all parametric constructors:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Current chart constructor specifying the subwindow               |
//+------------------------------------------------------------------+
CForm::CForm(const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Constructor on the current chart in the main chart window        |
//+------------------------------------------------------------------+
CForm::CForm(const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


In the initialization method itself, set zeros in these values:

//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CForm::Initialize(void)
  {
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
   this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   this.m_init_x=0;
   this.m_init_y=0;
   this.m_init_w=0;
   this.m_init_h=0;
   CGCnvElement::SetInteraction(false);
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(m_animations);
  }
//+------------------------------------------------------------------+


The method creating a new attached element and adding it to the list of attached objects:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//| and add it to the list of bound objects                          |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity)
  {
//--- If the type of a created graphical element is less than the "element", inform of that and return 'false'
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return NULL;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total();
//--- Create a graphical element name
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;
//--- Get the screen coordinates of the object relative to the coordinate system of the base object
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return NULL;
//--- and add it to the list of bound graphical elements
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return NULL;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetColorBackground(colour);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(main);
   obj.SetBase(this.GetObject());
   obj.SetID(this.ID());
   obj.SetNumber(num);
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(x);
   obj.SetCoordYRelativeInit(y);
   return obj;
  }
//+------------------------------------------------------------------+

The method logic essentially repeats the CreateNewElement() method logic, although there is no unconditional rendering of the created element. The method simply returns the pointer to a successfully created graphical element or NULL in case of an error occurred when creating or adding the element to the list. Besides, I have fixed a minor error here. The list of bound objects now features an object index for each object.

The method creating a new attached element now looks different — the entire code of adding a new object and adding it to the list has been moved to the method considered above and called here. If the object is not created or not added to the list, the method returns false, otherwise the object is rendered with the flag indicating the need for its physical rendering and the method returns true.

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             CGCnvElement *main,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
//--- If the object has been created, draw the added object and return 'true'
   if(obj==NULL)
      return false;
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+


In the method drawing the shadow, we now pass the flag indicating the necessity to draw the shadow in the Draw() method of the shadow object class:

//+------------------------------------------------------------------+
//| Draw the shadow                                                  |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR)
  {
//--- If the shadow flag is disabled, exit
   if(!this.m_shadow)
      return;
//--- If there is no shadow object, create it
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- If the shadow object exists, draw the shadow on it,
//--- set the shadow object visibility flag and
//--- move the form object to the foreground
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.Draw(shift_x,shift_y,blur,true);
      this.m_shadow_obj.SetVisible(true,false);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+

Here we will always pass true to draw the shadow. In case the shadow is not needed, this will be checked in the calling methods by a conditional operator. This is easier than controlling the rendering of an unblurred shadow rectangle, keeping track of whether it is drawn or not before using the blur of this rectangle, as well as catching other related pitfalls. Instead, it is much easier to simply check if the shadow needs to be rendered and call the method.


The method setting the form shadow blur:

//+------------------------------------------------------------------+
//| Set the form shadow blur                                         |
//+------------------------------------------------------------------+
void CForm::SetBlurShadow(const uchar blur)
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return;
     }
   this.m_shadow_obj.SetBlur(blur);
  }
//+------------------------------------------------------------------+

If no shadow object exists, display the message indicating that the shadow object should be created first. Otherwise, the method for setting the blur amount into the shadow object is called.

The method returning the form shadow blur:

//+------------------------------------------------------------------+
//| Return the form shadow blur                                      |
//+------------------------------------------------------------------+
uchar CForm::BlurShadow(void) const
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return 0;
     }
   return this.m_shadow_obj.Blur();
  }
//+------------------------------------------------------------------+

If no shadow object exists, display the message indicating that the shadow object should be created first. Otherwise, the method for setting the blur amount of the shadow object is returned.

Apart from the changes mentioned above, the class has received some minor improvements that do not affect its logic. There is no point in describing them here. You can view all the changes in the files attached below.


Base WinForms object class

In \MQL5\Include\DoEasy\Objects\Graph\WForms\, create a new file WinFormBase.mqh of the CWinFormBase class. The class should be inherited from the CForm form object class. In order for the base class to be visible in the file, include the form object class file to it:

//+------------------------------------------------------------------+
//|                                                  WinFormBase.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Form.mqh"
//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
  }

Move the variables and methods for handling them from the Panel WinForms object class file \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh to this class. It is to store all variables and methods we implemented for the panel object. Of course, the new ones will be added as well.

Place the following variables in the protected section of the class:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color;                                   // Default text color for all control objects
   ENUM_FW_TYPE      m_bold_type;                                    // Font width type
   ENUM_FRAME_STYLE  m_border_style;                                 // Control frame style
   bool              m_autosize;                                     // Flag of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Mode of binding control borders to the container
   int               m_margin[4];                                    // Array of gaps of all sides between the fields of the current and adjacent controls
   int               m_padding[4];                                   // Array of gaps of all sides inside controls

private:

In the private section, place the method returning the font flags:

private:
//--- Return the font flags
   uint              GetFontFlags(void);

public:

while in the public section, declare the virtual methods for clearing the element, redrawing it and changing the size, as well as class constructors, and move such methods from the CPanel class file:

public:
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Set the new size for the (1) current object and (2) the object specified by index
   virtual bool      Resize(const int w,const int h,const bool redraw);
   virtual bool      Resize(const int index,const int w,const int h,const bool redraw);

//--- Constructors
                     CWinFormBase(const long chart_id,
                                  const int subwindow,
                                  const string name,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
                     CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
                       }
                       
//--- (1) Set and (2) return the default text color of all panel objects
   void              SetForeColor(const color clr)                   { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }

//--- (1) Set and (2) return the Bold font flag
   void              SetBold(const bool flag);
   bool              Bold(void);
//--- (1) Set and (2) return the Italic font flag
   void              SetItalic(const bool flag);
   bool              Italic(void);
//--- (1) Set and (2) return the Strikeout font flag
   void              SetStrikeout(const bool flag);
   bool              Strikeout(void);
//--- (1) Set and (2) return the Underline font flag
   void              SetUnderline(const bool flag);
   bool              Underline(void);
//--- (1) Set and (2) return the font style
   void              SetFontDrawStyle(ENUM_FONT_STYLE style);
   ENUM_FONT_STYLE   FontDrawStyle(void);
//--- (1) Set and (2) return the font width type
   void              SetFontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return this.m_bold_type;             }
//--- (1) Set and (2) return the frame style
   void              SetBorderStyle(const ENUM_FRAME_STYLE style)    { this.m_border_style=style;           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return this.m_border_style;          }

//--- (1) Set and (2) return the flag of the element auto resizing depending on the content
   virtual void      SetAutoSize(const bool flag,const bool redraw)  { this.m_autosize=flag;                }
   bool              AutoSize(void)                                  { return this.m_autosize;              }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        this.m_dock_mode=mode;
                       }
   ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void)                  const { return this.m_dock_mode;             }
   
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control
   void              SetMarginLeft(const int value)                  { this.m_margin[0]=value;              }
   void              SetMarginTop(const int value)                   { this.m_margin[1]=value;              }
   void              SetMarginRight(const int value)                 { this.m_margin[2]=value;              }
   void              SetMarginBottom(const int value)                { this.m_margin[3]=value;              }
   void              SetMarginAll(const int value)
                       {
                        this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value);
                       }
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control
   int               MarginLeft(void)                          const { return this.m_margin[0];             }
   int               MarginTop(void)                           const { return this.m_margin[1];             }
   int               MarginRight(void)                         const { return this.m_margin[2];             }
   int               MarginBottom(void)                        const { return this.m_margin[3];             }

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   virtual void      SetPaddingLeft(const uint value)
                       {
                        this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Set the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom
   virtual void      SetFrameWidthLeft(const uint value)             { this.m_frame_width_left=(int)value;  }
   virtual void      SetFrameWidthTop(const uint value)              { this.m_frame_width_top=(int)value;   }
   virtual void      SetFrameWidthRight(const uint value)            { this.m_frame_width_right=(int)value; }
   virtual void      SetFrameWidthBottom(const uint value)           { this.m_frame_width_bottom=(int)value;}
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }
   
//--- Return the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom
   int               FrameWidthLeft(void)                      const { return this.m_frame_width_left;      }
   int               FrameWidthTop(void)                       const { return this.m_frame_width_top;       }
   int               FrameWidthRight(void)                     const { return this.m_frame_width_right;     }
   int               FrameWidthBottom(void)                    const { return this.m_frame_width_bottom;    }
   
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control
   int               PaddingLeft(void)                         const { return this.m_padding[0];            }
   int               PaddingTop(void)                          const { return this.m_padding[1];            }
   int               PaddingRight(void)                        const { return this.m_padding[2];            }
   int               PaddingBottom(void)                       const { return this.m_padding[3];            }
   
  };
//+------------------------------------------------------------------+

Most transferred methods are made virtual so that it is possible to redefine them in inherited classes if necessary. All methods for setting the properties now how the "Set" prefix in their names. This unambiguously indicates the method affiliation.

In the parametric constructor, set the graphical element and library object types, as well as default property values or the values passed in the parameters:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
//--- Set the graphical element and library object types as a base WinForms object
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.m_fore_color=CLR_DEF_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_frame_width_right=0;
   this.m_frame_width_left=0;
   this.m_frame_width_top=0;
   this.m_frame_width_bottom=0;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

The methods moved from the CPanel class:

//+------------------------------------------------------------------+
//| Return the font flags                                            |
//+------------------------------------------------------------------+
uint CWinFormBase::GetFontFlags(void)
  {
   string name;
   int size;
   uint flags;
   uint angle;
   CGCnvElement::GetFont(name,size,flags,angle);
   return flags;
  }
//+------------------------------------------------------------------+
//| Set the Bold font flag                                           |
//+------------------------------------------------------------------+
void CWinFormBase::SetBold(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
     {
      this.m_bold_type=FW_TYPE_BOLD;
      CGCnvElement::SetFontFlags(flags | FW_BOLD);
     }
   else
      this.m_bold_type=FW_TYPE_NORMAL;
  }
//+------------------------------------------------------------------+
//| Return the Bold font flag                                        |
//+------------------------------------------------------------------+
bool CWinFormBase::Bold(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FW_BOLD)==FW_BOLD;
  }
//+------------------------------------------------------------------+
//| Set the Italic font flag                                         |
//+------------------------------------------------------------------+
void CWinFormBase::SetItalic(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_ITALIC);
  }
//+------------------------------------------------------------------+
//| Return the Italic font flag                                      |
//+------------------------------------------------------------------+
bool CWinFormBase::Italic(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_ITALIC)==FONT_ITALIC;
  }
//+------------------------------------------------------------------+
//| Set the Strikeout font flag                                      |
//+------------------------------------------------------------------+
void CWinFormBase::SetStrikeout(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT);
  }
//+------------------------------------------------------------------+
//| Return the Strikeout font flag                                   |
//+------------------------------------------------------------------+
bool CWinFormBase::Strikeout(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT;
  }
//+------------------------------------------------------------------+
//| Set the Underline font flag                                      |
//+------------------------------------------------------------------+
void CWinFormBase::SetUnderline(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE);
  }
//+------------------------------------------------------------------+
//| Return the Underline font flag                                   |
//+------------------------------------------------------------------+
bool CWinFormBase::Underline(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_UNDERLINE)==FONT_UNDERLINE;
  }
//+------------------------------------------------------------------+
//| Set the font style                                               |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style)
  {
   switch(style)
     {
      case FONT_STYLE_ITALIC     :  this.SetItalic(true);      break;
      case FONT_STYLE_UNDERLINE  :  this.SetUnderline(true);   break;
      case FONT_STYLE_STRIKEOUT  :  this.SetStrikeout(true);   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Return the font style                                            |
//+------------------------------------------------------------------+
ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void)
  {
   return
     (
      this.Italic()     ?  FONT_STYLE_ITALIC    :
      this.Underline()  ?  FONT_STYLE_UNDERLINE :
      this.Strikeout()  ?  FONT_STYLE_UNDERLINE :
      FONT_STYLE_NORMAL
     );
  }
//+------------------------------------------------------------------+
//| Set the font width type                                          |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type)
  {
   this.m_bold_type=type;
   uint flags=this.GetFontFlags();
   switch(type)
     {
      case FW_TYPE_DONTCARE   : CGCnvElement::SetFontFlags(flags | FW_DONTCARE);    break;
      case FW_TYPE_THIN       : CGCnvElement::SetFontFlags(flags | FW_THIN);        break;
      case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT);  break;
      case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT);  break;
      case FW_TYPE_LIGHT      : CGCnvElement::SetFontFlags(flags | FW_LIGHT);       break;
      case FW_TYPE_REGULAR    : CGCnvElement::SetFontFlags(flags | FW_REGULAR);     break;
      case FW_TYPE_MEDIUM     : CGCnvElement::SetFontFlags(flags | FW_MEDIUM);      break;
      case FW_TYPE_SEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD);    break;
      case FW_TYPE_DEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD);    break;
      case FW_TYPE_BOLD       : CGCnvElement::SetFontFlags(flags | FW_BOLD);        break;
      case FW_TYPE_EXTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD);   break;
      case FW_TYPE_ULTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD);   break;
      case FW_TYPE_HEAVY      : CGCnvElement::SetFontFlags(flags | FW_HEAVY);       break;
      case FW_TYPE_BLACK      : CGCnvElement::SetFontFlags(flags | FW_BLACK);       break;
      default                 : CGCnvElement::SetFontFlags(flags | FW_NORMAL);      break;
     }
  }
//+------------------------------------------------------------------+

I have considered all these methods in the previous articles on creating the panel object.


The virtual methods of clearing the element (filling with color):

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::Erase(colour,opacity,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

These virtual methods override the same parent class methods. The parent class is called here first for filling the element with color, the object frame flag is then checked. If the frame should be present and the object redrawing flag is set, the frame is drawn. Next, the object is updated.


The method setting the new size for the current object:

//+------------------------------------------------------------------+
//| Set the new size for the current object                          |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int w,const int h,const bool redraw)
  {
//--- If the object width and height are equal to the passed ones, return 'true'
   if(this.Width()==w && this.Height()==h)
      return true;
//--- Declare the variable with the property change result
   bool res=true;
//--- Save the panel initial size
   int prev_w=this.Width();
   int prev_h=this.Height();
//--- Set the property change result to the 'res' variable
//--- (if the property value is not equal to the passed value)
   if(this.Width()!=w)
      res &=this.SetWidth(w);
   if(this.Height()!=h)
      res &=this.SetHeight(h);
   if(!res)
      return false;
//--- Calculate the value, by which the size should be changed
   int excess_w=this.Width()-prev_w;
   int excess_h=this.Height()-prev_h;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object has been received,
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- save shadow shifts by X and Y,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- set the shadow new width and height
      res &=shadow.SetWidth(shadow.Width()+excess_w);
      res &=shadow.SetHeight(shadow.Height()+excess_h);
      if(!res)
         return false;
      //--- If there is no need to redraw, remove the shadow
      if(!redraw)
         shadow.Erase();
      //--- Save the previously set shadow shift values relative to the panel
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- Redraw the element with new size
   if(redraw)
      this.Redraw(true);
//--- All is successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+

The method logic has been described in the code comments in detail. In brief, if existing object size is passed to the method, there is no need to change anything — return true immediately. If the size passed to the method does not match the one the object currently has, change it. If there is a shadow, change its size and redraw the entire object if the redraw flag is set. In other words, if the redraw flag is not set, the object size changes, while the object itself is not redrawn. This is needed to accelerate redrawing of multiple objects bound to another object. After changing the size of all bound objects, they should be redrawn.

The method setting the new size for the object specified by the index:

//+------------------------------------------------------------------+
//| Set the new size for the object specified by object index        |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw)
  {
   CWinFormBase *obj=this.GetElement(index);
   return(obj!=NULL ? obj.Resize(w,h,redraw) : false);
  }
//+------------------------------------------------------------------+

The method receives the object index in the bound objects list that needs to be resized (the size is also passed to the method).
Get the object by index in the list and return the result of calling its Resize() method considered above.


The method redrawing an object:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- If the object type is less than the "Base WinForms object", exit
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE)
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object exists, redraw it
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- remove the previously drawn shadow,
      shadow.Erase();
      //--- save the relative shadow coordinates,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- redraw the shadow,
      if(redraw)
         shadow.Draw(0,0,shadow.Blur(),redraw);
      //--- restore relative shadow coordinates
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- If the redraw flag is set,
   if(redraw)
     {
      //--- completely redraw the object and save its new initial look
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw);
      this.Done();
     }
//--- otherwise, remove the object
   else
      this.Erase();
//--- Redraw all bound objects with the redraw flag
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      element.Redraw(redraw);
     }
//--- If the redraw flag is set and if this is the main object the rest are bound to,
//--- redraw the chart to display changes immediately
   if(redraw && this.GetMain()==NULL)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

The method logic is similar to the logic of the method changing the object size. First, we redraw the shadow object since it should be lower than other objects.Next, if the redraw flag is set, completely redraw the object and set its current appearance as a "reference" one. If the redraw flag is not set, remove the drawn shadow object and the object itself. Next, loop through the list of attached objects and redraw each of them in accordance with the redraw flag — either remove or draw everything.
Finally, update the chart for immediate display of implemented changes in case the redraw flag is set and the flag is the main one in the entire hierarchy of bound objects (it has no main object and its GetMain() method returns NULL).

The base class of all library WinForms objects is ready.

Now improve the panel object class in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.

If the Dock property is set for the objects bound to the panel, these objects should automatically be placed in the order of their indices in the list of bound objects and, according to the rules, set for the binding type. In other words, if Dock is located to the left, the object is stretched in height to the entire height of the panel it is attached to, while its left coordinate is the left edge of either the panel underlay (if the object is the first one in the list) or the right edge of the previous object from the list of bound objects. This rule is applied to each object from the list. Besides, we should also consider the panel auto resize flag and the resize change mode — only increase or increase and decrease.

In the current article, I will implement handling the Dock property for bound objects only in the disabled panel auto resize mode. In order to define the object edge the current object from the list of bound objects is attached to, create four objects by the number of edges — top, bottom, left and right. Each object the Dock property is set for fits itself into one of these objects, so that the rest of the objects from the list are able to "know" the coordinates of which object they should be bound to (since they are attached to the edges of the previous object in the list if the Dock property is set for it as well).

Since now all WinForms objects are inherited from the base WinForms object, then instead of the included form object file:

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

we will include the file of the base WinForms object:

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


In the private section of the class, declare the pointers to four objects to store the pointers to the objects whose coordinates we need to bind Dock objects:

class CPanel : public CWinFormBase
  {
private:
   CGCnvElement     *m_obj_top;                                      // Pointer to the object whose coordinates the current upper object is bound to
   CGCnvElement     *m_obj_bottom;                                   // Pointer to the object whose coordinates the current bottom object is bound to
   CGCnvElement     *m_obj_left;                                     // Pointer to the object whose coordinates the current left object is bound to
   CGCnvElement     *m_obj_right;                                    // Pointer to the object whose coordinates the current right object is bound to
   CGCnvElement     *m_underlay;                                     // Underlay for placing elements
   bool              m_autoscroll;                                   // Auto scrollbar flag
   int               m_autoscroll_margin[2];                         // Array of fields around the control during an auto scroll
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Mode of the element auto resizing depending on the content
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Return the initial coordinates of a bound object
   virtual void      GetCoords(int &x,int &y);
//--- Create the underlay object
   bool              CreateUnderlayObj(void);
//--- Set the underlay as a coordinate system zero
   void              SetUnderlayAsBase(void);

protected:

Instead of the SetDockingToContainer() method, declare the SetUnderlayAsBase() method which will set the underlay object into all four pointers declared above. Immediately after creating an object, its underlay is used as a binding coordinates origin of the first bound object. As Dock objects are placed, the pointers to the appropriate objects already bound to the necessary edges are set in the variables. However, the underlay serves as a binding object at the very start.

In the protected section of the class, declare two methods returning the maximum value, by which all Dock objects go beyond the object they are bound to, declare the method featuring the auto resizing of container size to fit the objects it stores and write four methods returning the pointers to objects, whose edges are used by a Dock object from the list of objects bound to the container:

protected:
//--- Return the maximum value of Dock object borders going beyond the container by width and (2) height
   int               GetExcessMaxX(void);
   int               GetExcessMaxY(void);
//--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters
   bool              SetCoordXUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false);   }
   bool              SetCoordYUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false);   }
   bool              SetWidthUnderlay(const int value)               { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value)  : false);   }
   bool              SetHeightUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false);   }
   bool              SetUnderlayParams(void);
//--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height
   int               GetCoordXUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordX() : 0);              }
   int               GetCoordYUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordY() : 0);              }
   int               GetWidthUnderlay(void)                    const { return(this.m_underlay!=NULL ?  this.m_underlay.Width()  : 0);              }
   int               GetHeightUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.Height() : 0);              }
//--- Return the underlay (1) X and (2) Y coordinate relative to the panel
   int               GetCoordXUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordXRelative() : 0);      }
   int               GetCoordYUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordYRelative() : 0);      }
   
//--- Return the object whose coordinates the current (1) top, (2) bottom, (3) left or (4) right object is bound to
   CGCnvElement     *GetTopObj(void)                                 { return this.m_obj_top;               }
   CGCnvElement     *GetBottomObj(void)                              { return this.m_obj_bottom;            }
   CGCnvElement     *GetLeftObj(void)                                { return this.m_obj_left;              }
   CGCnvElement     *GetRightObj(void)                               { return this.m_obj_right;             }

//--- Adjust the element size to fit its content
   bool              AutoSizeProcess(const bool redraw);
   
public:

The remaining variables and methods have already been moved to the base class of the WinForms object. Find out more about such changes in the files attached to the article.

Considering all moved methods, adjustments and new methods, the public section of the class now looks as follows:

public:
//--- Return the (1) underlay and (2) the object the current object is bound to
   CGCnvElement     *GetUnderlay(void)                               { return this.m_underlay;              }
//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
//--- Create a new attached element
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Update the element
   void              Update(const bool redraw=false)                 { this.m_canvas.Update(redraw);        }
   
//--- Reset the size of all bound objects to the initial ones
   bool              ResetSizeAllToInit(void);
//--- Place bound objects in the order of their Dock binding
   bool              ArrangeObjects(const bool redraw);

//--- (1) Set and (2) return the auto scrollbar flag
   void              SetAutoScroll(const bool flag)                  { this.m_autoscroll=flag;              }
   bool              AutoScroll(void)                                { return this.m_autoscroll;            }
//--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling
   void              SetAutoScrollMarginWidth(const int value)       { this.m_autoscroll_margin[0]=value;   }
   void              SetAutoScrollMarginHeight(const int value)      { this.m_autoscroll_margin[1]=value;   }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
//--- Return the (1) field width and (2) height around the control during auto scrolling
   int               AutoScrollMarginWidth(void)               const { return this.m_autoscroll_margin[0];  }
   int               AutoScrollMarginHeight(void)              const { return this.m_autoscroll_margin[1];  }
  
//--- (1) Set the flag of the element auto resizing depending on the content
   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
//--- (1) Set and (2) return the mode of the element auto resizing depending on the content
   void              SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw)
                       {
                        ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode();
                        if(prev==mode)
                           return;
                        this.m_autosize_mode=mode;
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)         const { return this.m_autosize_mode;         }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        if(this.DockMode()==mode)
                           return;
                        CWinFormBase::SetDockMode(mode,redraw);
                        CPanel *base=this.GetBase();
                        if(base!=NULL)
                           base.ArrangeObjects(redraw);
                       }

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   virtual void      SetPaddingLeft(const uint value)
                       {
                        CWinFormBase::SetPaddingLeft(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the X axis
                           this.m_underlay.SetCoordXRelative(this.PaddingLeft());
                           //--- Set the X coordinate and the underlay width
                           this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        CWinFormBase::SetPaddingTop(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the Y axis
                           this.m_underlay.SetCoordYRelative(this.PaddingTop());
                           //--- Set the Y coordinate and underlay height
                           this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        CWinFormBase::SetPaddingRight(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay width
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        CWinFormBase::SetPaddingBottom(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay height
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control
   virtual void      SetFrameWidthLeft(const uint value)
                       {
                        this.m_frame_width_left=(int)value;
                        if(this.PaddingLeft()<this.FrameWidthLeft())
                           this.SetPaddingLeft(this.FrameWidthLeft());
                       }
   virtual void      SetFrameWidthTop(const uint value)
                       {
                        this.m_frame_width_top=(int)value;
                        if(this.PaddingTop()<this.FrameWidthTop())
                           this.SetPaddingTop(this.FrameWidthTop());
                       }
   virtual void      SetFrameWidthRight(const uint value)
                       {
                        this.m_frame_width_right=(int)value;
                        if(this.PaddingRight()<this.FrameWidthRight())
                           this.SetPaddingRight(this.FrameWidthRight());
                       }
   virtual void      SetFrameWidthBottom(const uint value)
                       {
                        this.m_frame_width_bottom=(int)value;
                        if(this.PaddingBottom()<this.FrameWidthBottom())
                           this.SetPaddingBottom(this.FrameWidthBottom());
                       }
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }

//--- Constructors
                     CPanel(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_BEVEL);
                        this.SetAutoScroll(false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                        if(this.CreateUnderlayObj())
                           this.SetUnderlayAsBase();
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+

All Set methods now have the "Set" prefix in their names.

The methods for setting the auto resizing, auto resizing mode and the methods for binding Dock objects to the container first call the method of the base class for setting the property. Then they call the method of resizing the container:

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

The container should be resized only if at least one object is bound to the container.

Other similar methods are made with similar logic. You can explore them on your own.
If you have any questions, feel free to ask them in the comments below.

In the method setting all underlay parameters, specify the "underlay" object type:

//+------------------------------------------------------------------+
//| Set all underlay parameters                                      |
//+------------------------------------------------------------------+
bool CPanel::SetUnderlayParams(void)
  {
//--- Set the object type
   this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY);
//--- Set the underlay shift values to the variables by X and Y axes
   this.m_underlay.SetCoordXRelative(this.PaddingLeft());
   this.m_underlay.SetCoordYRelative(this.PaddingTop());
//--- Set the underlay coordinates and size
   bool res=true;
   res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
   res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
   res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
   res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
   return res;
  }
//+------------------------------------------------------------------+

The knowledge of a selected object having the "underlay" type is necessary when placing Dock objects inside the container. If an object whose edges should be used to attach the current object from the list of bound objects is an underlay, then the binding edge, for example, to the left is an underlay X coordinate. If it is not an underlay but another Dock object, the right edge of that Dock object is considered to be a binding edge.

In the methods setting the panel coordinates and its size, we first call the method for setting the base object properties. Then we change underlay object coordinates and size:

//+------------------------------------------------------------------+
//| Set the panel X coordinate                                       |
//+------------------------------------------------------------------+
bool CPanel::SetCoordX(const int coord_x)
  {
   if(!CGCnvElement::SetCoordX(coord_x))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel Y coordinate                                       |
//+------------------------------------------------------------------+
bool CPanel::SetCoordY(const int coord_y)
  {
   if(!CGCnvElement::SetCoordY(coord_y))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel width                                              |
//+------------------------------------------------------------------+
bool CPanel::SetWidth(const int width)
  {
   if(!CGCnvElement::SetWidth(width))
      return false;
   return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel height                                             |
//+------------------------------------------------------------------+
bool CPanel::SetHeight(const int height)
  {
   if(!CGCnvElement::SetHeight(height))
      return false;
   return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true);
  }
//+------------------------------------------------------------------+


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

In the loop by all objects bound to the container, get the next object and set the result of resizing the object to its initial size in the res variable. Upon the loop completion, the total resizing result will be set in the res variable. If resizing of at least one object ends in an error, the res variable is set to false, otherwise — true.


The method that creates a new attached element:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CPanel::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                              CGCnvElement *main,
                              const int x,
                              const int y,
                              const int w,
                              const int h,
                              const color colour,
                              const uchar opacity,
                              const bool activity,
                              const bool redraw)
  {
//--- If failed to create a new graphical element, return 'false'
   CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- If the object type is a base WinForms object and higher,
   if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- declare the pointer with the base WinForms object type and assign the pointer to the newly created object to it,
      //--- set the frame color equal to the background color 
      CWinFormBase *wf=obj;
      wf.SetColorFrame(wf.ColorBackground());
     }
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Redraw the panel and all added objects, and return 'true'
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

The method logic is described in the code comments and is quite simple.

The method returning the maximum value of Dock object borders going beyond the container by width:

//+------------------------------------------------------------------+
//| Return the maximum value of Dock object borders                  |
//| going beyond the container by width                              |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxX(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.RightEdge()>value)
         value=element.RightEdge();
     }
   return value-this.m_underlay.RightEdge();
  }
//+------------------------------------------------------------------+

The method returning the maximum value of Dock object borders going beyond the container by height:

//+------------------------------------------------------------------+
//| Return the maximum value of Dock object borders                  |
//| going beyond the container by height                             |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxY(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.BottomEdge()>value)
         value=element.BottomEdge();
     }
   return value-this.m_underlay.BottomEdge();
  }
//+------------------------------------------------------------------+

The logic behind both methods is identical: In the loop by all objects bound to the container, get the next object and, if its right (bottom) edge exceeds the value set in the value variable, the edge value is assigned to that variable. Upon the loop completion, the value variable features the maximum value of the right (bottom) edge of all objects and the border between the found value and the right (botom) underlay object edge is returned. Thus, we will get a positive or negative value in pixels, by which we need to resize the container.


The method placing bound objects in the order of their Dock binding:

//+------------------------------------------------------------------+
//| Place bound objects in the order of their Dock binding           |
//+------------------------------------------------------------------+
bool CPanel::ArrangeObjects(const bool redraw)
  {
   CWinFormBase *prev=NULL, *obj=NULL;
   //--- If auto resizing mode is enabled
   if(this.AutoSize())
     {
      //--- In the loop by all bound objects,
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Get the previous element from the list
         prev=this.GetElement(i-1);
         //--- If there is no previous element, set the underlay as a previous element
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- Get the current element
         obj=GetElement(i);
         //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         //--- Depending on the current object binding mode...
         //--- Top
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            
           }
         //--- Bottom
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            
           }
         //--- Left
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            
           }
         //--- Right
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            
           }
         //--- Binding with filling
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            
           }
         //--- No binding
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            
           }
        }
      this.Resize(this.GetWidthInit(),this.GetHeightInit(),false);
     }
   //--- If auto resizing mode disabled 
   else
     {
      //--- In the loop by all bound objects,
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Get the current and previous elements from the list
         obj=this.GetElement(i);
         prev=this.GetElement(i-1);
         //--- If there is no previous element, set the underlay as a previous element
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         int x=0, y=0; // Object binding coordinates
         //--- Depending on the current object binding mode...
         //--- Top
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Get the pointer to the object at the top whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetTopObj();
            //--- Get the object binding coordinates
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1);
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the top one whose edges will be used to bind the next one
            this.m_obj_top=obj;
           }
         //--- Bottom
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Get the pointer to the object at the bottom whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetBottomObj();
            //--- Get the object binding coordinates
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1);
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the bottom one whose edges will be used to bind the next one
            this.m_obj_bottom=obj;
           }
         //--- Left
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Get the pointer to the object at the left whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetLeftObj();
            //--- Get the object binding coordinates
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1);
            y=this.GetCoordYUnderlay();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the left one whose edges will be used to bind the next one
            this.m_obj_left=obj;
           }
         //--- Right
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Get the pointer to the object at the right whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetRightObj();
            //--- Get the object binding coordinates
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1);
            y=this.GetCoordYUnderlay();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the right one whose edges will be used to bind the next one
            this.m_obj_right=obj;
           }
         //--- Binding with filling
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            //--- If failed to change the object size (for the entire underlay width and height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false))
               continue;
            //--- Set the underlay as a binding object
            this.SetUnderlayAsBase();
            //--- Get the object binding coordinates
            x=this.GetLeftObj().CoordX();
            y=this.GetTopObj().CoordY();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- No binding
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            //--- Reset the object size
            obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false);
            //--- Get the initial object location coordinates
            x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit();
            y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- Calculate and set the relative object coordinates
         obj.SetCoordXRelative(x-this.m_underlay.CoordX());
         obj.SetCoordYRelative(y-this.m_underlay.CoordY());
        }
     }
//--- Redraw the object with the redraw flag and return 'true'
   this.Redraw(redraw); 
   return true;
  }
//+------------------------------------------------------------------+

The method logic has been described in the code comments in detail. In case of the panel having the auto resize flag, I will not arrange the objects inside the container yet (there are only stubs to be replaced with handlers), since it will have a different logic than the one implemented for the panel without auto resize.

The method setting the underlay as a coordinate system zero for bound Dock objects:

//+------------------------------------------------------------------+
//| Set the underlay as a coordinate system zero                     |
//+------------------------------------------------------------------+
void CPanel::SetUnderlayAsBase(void)
  {
   this.m_obj_left=this.m_underlay;
   this.m_obj_right=this.m_underlay;
   this.m_obj_top=this.m_underlay;
   this.m_obj_bottom=this.m_underlay;
  }
//+------------------------------------------------------------------+

Assign the pointer to the underlay for all four binding objects.

The method adjusting the element size to fit its content:

//+------------------------------------------------------------------+
//| Adjust the element size to fit its content                       |
//+------------------------------------------------------------------+
bool CPanel::AutoSizeProcess(const bool redraw)
  {
//--- Get values along X and Y axes, by which the panel size is to be changed
   int excess_w=this.GetExcessMaxX();
   int excess_h=this.GetExcessMaxY();
//--- If failed to change the size, return 'true'
   if(excess_w==0 && excess_h==0)
      return true;
   //--- If failed to change the panel size, return the result of its adjustment
   return
     (
      //--- if only a size increase
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_w>0  ? excess_w : 0),this.Height()+(excess_h>0  ? excess_h : 0),redraw) :
      //--- if both increase and decrease
      this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw)
     );
  }
//+------------------------------------------------------------------+

The method logic is described in the code comments. In brief, we get the maximum values along X and Y axes, by which the bound objects go beyond the underlay borders. A positive value indicates that the objects extend beyond the underlay, while a negative value indicates that the underlay is too large and can be reduced. Also, return the result of changing the panel size considering the auto resize mode (either increase only, or increase and decrease)


In \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh file of the graphical element collection class, we need to replace the names of the methods setting the BorderStyle() and FrameWidthAll() properties in the panel creation method with the new ones: SetBorderStyle() and SetFrameWidthAll(). The methods have already been renamed in the attached files.

In \MQL5\Include\DoEasy\Engine.mqh, namely in the methods returning the WForm Panel object, replace the GRAPH_ELEMENT_TYPE_PANEL macro substitution name with GRAPH_ELEMENT_TYPE_WF_PANEL:

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

//--- Create the WinForm Element object

These are all the changes and improvements I have planned for the current article.


Test

To perform the test, let's use the EA from the previous article and save it to \MQL5\Experts\TestDoEasy\Part105\ as TestDoEasyPart105.mq5.

Let's add the panel auto resize flag for adjusting to the panel content, as well as the auto resize mode.

Also, add a new key for placing objects inside the container. When pressing Q, all objects located inside the panel are placed according to their binding mode. We will have 6 such objects according to the number of binding modes:

//+------------------------------------------------------------------+
//| Control borders bound to the container                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_DOCK_MODE
  {
   CANV_ELEMENT_DOCK_MODE_NONE,                       // Attached to the specified coordinates, size does not change
   CANV_ELEMENT_DOCK_MODE_TOP,                        // Attaching to the top and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_BOTTOM,                     // Attaching to the bottom and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_LEFT,                       // Attaching to the left and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_RIGHT,                      // Attaching to the right and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_FILL,                       // Stretching along the entire container width and height
  };
//+------------------------------------------------------------------+

Accordingly, each of these objects will receive a binding mode corresponding to its number in the list of panel objects. The first object in the list will have no binding (CANV_ELEMENT_DOCK_MODE_NONE), the second will have the CANV_ELEMENT_DOCK_MODE_TOP mode, the third one — MODE_BOTTOM, etc.

In the global area, add the macro substitution for the Q key and enumerations for inputs in English and user's country language, as well as add new inputs:

//+------------------------------------------------------------------+
//|                                            TestDoEasyPart105.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (3)   // Number of created forms
#define  START_X     (4)   // Initial X coordinate of the shape
#define  START_Y     (4)   // Initial Y coordinate of the shape
#define  KEY_LEFT    (65)  // (A) Left
#define  KEY_RIGHT   (68)  // (D) Right
#define  KEY_UP      (87)  // (W) Up
#define  KEY_DOWN    (88)  // (X) Down
#define  KEY_FILL    (83)  // (S) Filling
#define  KEY_ORIGIN  (90)  // (Z) Default
#define  KEY_INDEX   (81)  // (Q) By index

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Increase only
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Increase and decrease
  };
#endif 
//--- input parameters
sinput   bool                 InpMovable        =  true;                // Movable forms flag
sinput   ENUM_INPUT_YES_NO    InpAutoSize       =  INPUT_YES;           // Autosize
sinput   ENUM_AUTO_SIZE_MODE  InpAutoSizeMode   =  AUTO_SIZE_MODE_GROW; // Autosize Mode
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

Remove the code for creating forms and elements from the OnInit() handler. Leave only the creation of one panel. In the loop in the panel itself, create six bound panel objects with the false redraw flag. In this case, the height of the panel will be slightly less than the total height of all objects built inside it, and the width, on the contrary, will be larger. Thus, we can see how the panel adjusts to the size of the objects built inside it:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true);
   if(pnl!=NULL)
     {
      //--- Set Padding to 4
      pnl.SetPaddingAll(4);
      //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
      //--- In the loop, create 6 bound panel objects
      for(int i=0;i<6;i++)
        {
         //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 50
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20);
         int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16));
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false);
        }
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

After creating all elements inside the panel, completely redraw the entire panel along with the elements created in it (true redraw flag)

In the OnChartEvent() event handler, namely in the keystroke handling block, add the Q key press handler:

   //--- If a key is pressed
   if(id==CHARTEVENT_KEYDOWN)
     {
      CPanel *panel=engine.GetWFPanel(0);
      if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX))
        {
         for(int i=0;i<panel.ElementsTotal();i++)
           {
            CPanel *obj=panel.GetElement(i);
            if(obj!=NULL)
              {
               if(lparam==KEY_UP)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false);
               else if(lparam==KEY_DOWN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false);
               else if(lparam==KEY_LEFT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false);
               else if(lparam==KEY_RIGHT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false);
               else if(lparam==KEY_FILL)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false);
               else if(lparam==KEY_ORIGIN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
               else if(lparam==KEY_INDEX)
                 {
                  obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true);
                  Sleep(i>0 ? 500 : 0);
                 }
              }
           }
         panel.Redraw(true);
        }
     }

In order to clearly see how objects move to their places in accordance with the binding mode, make a delay of half a second after each next movement of the object to new coordinates.

Compile the EA and launch it on the chart:


As we can see, the objects are bound to each of the panel sides correctly. When pressing Q, each object is attached to the appropriate panel side. When changing the panel auto resize modes, it adapts to its internal content in accordance with the auto sizing mode.

What's next?

In the next article, I will continue my work on 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


Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/10794

Attached files |
MQL5.zip (4343.05 KB)
Developing a trading Expert Advisor from scratch (Part 11): Cross order system Developing a trading Expert Advisor from scratch (Part 11): Cross order system
In this article we will create a system of cross orders. There is one type of assets that makes traders' life very difficult for traders — futures contracts. But why do they make life difficult?
Developing a trading Expert Advisor from scratch (Part 10): Accessing custom indicators Developing a trading Expert Advisor from scratch (Part 10): Accessing custom indicators
How to access custom indicators directly in an Expert Advisor? A trading EA can be truly useful only if it can use custom indicators; otherwise, it is just a set of codes and instructions.
Developing a trading Expert Advisor from scratch (Part 12): Times and Trade (I) Developing a trading Expert Advisor from scratch (Part 12): Times and Trade (I)
Today we will create Times & Trade with fast interpretation to read the order flow. It is the first part in which we will build the system. In the next article, we will complete the system with the missing information. To implement this new functionality, we will need to add several new things to the code of our Expert Advisor.
Data Science and Machine Learning (Part 05): Decision Trees Data Science and Machine Learning (Part 05): Decision Trees
Decision trees imitate the way humans think to classify data. Let's see how to build trees and use them to classify and predict some data. The main goal of the decision trees algorithm is to separate the data with impurity and into pure or close to nodes.