Русский 中文 Español Deutsch 日本語 Português
preview
DoEasy. Controls (Part 21): SplitContainer control. Panel separator

DoEasy. Controls (Part 21): SplitContainer control. Panel separator

MetaTrader 5Examples | 16 November 2022, 13:02
2 559 0
Artyom Trishkin
Artyom Trishkin

Contents


Concept

In the previous article, I started the development of the SplitContainer control. At the moment, the library can create such a control as a static object with two panels with default parameter values. The object has two panels with a separator. In the original object in MS Visual Studio, we can move the separator, thereby changing the size of the panels.

When hovering the mouse cursor over the separator area, a characteristic cursor appears, indicating the possibility of shifting the separator ( ), and when it is captured with the mouse, the separator is painted over with a hatched area that can be moved, thereby indicating the new location of the separator. When releasing the mouse button, the panels resize to fit the new separator position.

Since MQL5 does not provide the ability to change the appearance of the cursor, for now I will not make any "grab and drag signals". Instead, I will simply overlay the hatched area on the separator area, thereby indicating the possibility of moving it. In SplitContainer MS Visual Studio, the order of actions is as follows:

  1. When hovering the cursor over the separator area, the cursor appears indicating the ability to move it;
  2. When pressing and holding the mouse button (without moving the cursor), a dotted rectangle appears that outlines the separator area;
  3. When the mouse cursor moves, a hatched area the size of a separator appears and follows the cursor indicating the new position of the separator, which is set when the mouse button is released;
  4. When the mouse button is released, the panels are resized to fit the new separator position.

My algorithm is simpler:

  1. When hovering the mouse over the separator area, a hatched area appears;
  2. When capturing the hatched area with the mouse and moving it, the dimensions of the panels are immediately changed in accordance with the new position of the separator;
  3. When releasing the mouse button and moving the cursor away from the separator area, the hatched area is hidden, and the panels remain at their new sizes.

The separator object is constructed as an object derived from the base object of all WinForms library objects. From the CWinFormBase class, in which virtual methods for clearing and redrawing the object will be overridden, they will draw a dashed field that fills the entire area of the object. The object visibility will be managed from the event handler of the SplitContainer control. When hovering the mouse over the control area, the object will be displayed. When moving the cursor away from this area, it will be hidden. Such an area can contain both the separator itself in this object, and control buttons in other objects, such as minimizing/maximizing/closing the window in future library objects, etc.


Improving library classes

To specify the coordinates and size of the control area, add new integer properties of the graphical element object and new IDs of events and mouse states.

In \MQL5\Include\DoEasy\Defines.mqh, add the new IDs to the list of possible mouse states relative to the form:

//+------------------------------------------------------------------+
//| The list of possible mouse states relative to the form           |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Undefined state
//--- Outside the form
   MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED,         // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,             // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,               // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED,          // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_FORM_STATE_INSIDE_FORM_PRESSED,              // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_FORM_WHEEL,                // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // The cursor is inside the active area,  any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,      // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED,   // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED,       // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL,         // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED,   // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED,       // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL,         // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the window separator area
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED, // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED,     // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL,       // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+

The window resizing area is the area at the top, bottom, left and right. When hovering over it, it becomes possible to resize a graphical element, for which such a possibility is structurally provided. The IDs have been set here with an eye to the future - we will still need to do window resizing with the mouse, so why not enter these IDs now.


Add new event IDs corresponding to the new mouse states regarding the form to the list of possible mouse events:

//+------------------------------------------------------------------+
//| List of possible mouse events                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_EVENT
  {
   MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, // No event
//---
   MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_EVENT_OUTSIDE_FORM_PRESSED,                  // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_EVENT_OUTSIDE_FORM_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED,               // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_EVENT_INSIDE_FORM_PRESSED,                   // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_FORM_WHEEL,                     // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED,        // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,            // The cursor is inside the active area, any mouse button is clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,              // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,           // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED,            // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_EVENT_INSIDE_RESIZE_AREA_NOT_PRESSED,        // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_PRESSED,            // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_WHEEL,              // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the window separator area
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED,      // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED,          // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL,            // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL+1)  // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

Since the new enumeration constants appear here, we need to add the very last MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL enumeration constant instead of the previous MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL to the MOUSE_EVENT_NEXT_CODE macro substitution.


Let's add a new type of an auxiliary object I am going to implement here:

//+------------------------------------------------------------------+
//| 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
   //--- 'Container' object types are to be set below
   GRAPH_ELEMENT_TYPE_WF_CONTAINER,                   // Windows Forms container base object
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
   GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                    // Windows Forms GroupBox
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 // Windows Forms TabControl
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,             // Windows Forms SplitContainer
   //--- 'Standard control' object types are to be set below
   GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                 // Windows Forms base standard control
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
   GRAPH_ELEMENT_TYPE_WF_BUTTON,                      // Windows Forms Button
   GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                    // Windows Forms CheckBox
   GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,                 // Windows Forms RadioButton
   GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,           // Base list object of Windows Forms elements
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                    // Windows Forms ListBox
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   //--- Auxiliary elements of WinForms objects
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,               // Windows Forms ListBoxItem
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                   // Windows Forms TabField
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,       // Windows Forms SplitContainerPanel
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                // Windows Forms ArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,             // Windows Forms UpArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,           // Windows Forms DownArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,           // Windows Forms LeftArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,          // Windows Forms RightArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,        // Windows Forms UpDownArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,        // Windows Forms LeftRightArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_SPLITTER,                    // Windows Forms Splitter
  };
//+------------------------------------------------------------------+


Add a new event (separator relocation) to the list of possible WinForms control events:

//+------------------------------------------------------------------+
//| List of possible WinForms control events                         |
//+------------------------------------------------------------------+
enum ENUM_WF_CONTROL_EVENT
  {
   WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event
   WF_CONTROL_EVENT_CLICK,                            // "Click on the control" event
   WF_CONTROL_EVENT_CLICK_CANCEL,                     // "Canceling the click on the control" event
   WF_CONTROL_EVENT_TAB_SELECT,                       // "TabControl tab selection" event
   WF_CONTROL_EVENT_CLICK_SCROLL_LEFT,                // "Clicking the control left button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT,               // "Clicking the control right button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_UP,                  // "Clicking the control up button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_DOWN,                // "Clicking the control down button" event
   WF_CONTROL_EVENT_SPLITTER_MOVE,                    // "Control separator relocation" event
  };
#define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_SPLITTER_MOVE+1)  // The code of the next event after the last graphical element event code
//+------------------------------------------------------------------+

Here we also need to add the very last WF_CONTROL_EVENT_SPLITTER_MOVE enumeration constant to the WF_CONTROL_EVENTS_NEXT_CODE macro substitution since this is the very last constant of the enumeration.


Write the enumeration to be able to set how the separator should be placed in the object:

//+------------------------------------------------------------------+
//| Separator location in Split Container                            |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION
  {
   CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL,        // Vertical
   CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,      // Horizontal
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+


Add new properties to the list of integer properties of the graphical element and increase their total number up to 122:

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

   //---...
   //---...

   CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,              // Visibility scope width
   CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,             // Visibility scope height
   CANV_ELEMENT_PROP_CONTROL_AREA_X,                  // Control area X coordinate
   CANV_ELEMENT_PROP_CONTROL_AREA_Y,                  // Control area Y coordinate
   CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,              // Control area width
   CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,             // Control area height
   CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,             // Right scroll area X coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,             // Right scroll area Y coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,         // Right scroll area width
   CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,        // Right scroll area height
   CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,            // Bottom scroll area X coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,            // Bottom scroll area Y coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,        // Bottom scroll area width
   CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,       // Bottom scroll area height
   CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,          // Left edge area width
   CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,        // Bottom edge area width
   CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,         // Right edge area width
   CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,           // Upper edge area width
   CANV_ELEMENT_PROP_DISPLAYED,                       // Non-hidden control display flag
   CANV_ELEMENT_PROP_GROUP,                           // Group the graphical element belongs to

   //---...
   //---...

   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,// Distance from edge to separator
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,  // Separator width
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,// Separator location
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,// Flag for collapsed panel 1
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE, // Panel 1 minimum size
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Flag for collapsed panel 2
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Panel 2 minimum size
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (122)         // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Add new sorting criteria to the list of possible graphical element sorting criteria:

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

   //---...
   //---...

   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH,           // Sort by visibility scope width
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT,          // Sort by visibility scope height
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_X,               // Sort by control area X coordinate
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_Y,               // Sort by control area Y coordinate
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_WIDTH,           // Sort by control area width
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_HEIGHT,          // Sort by control area height
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_X_RIGHT,          // Sort by right scroll area X coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_Y_RIGHT,          // Sort by right scroll area Y coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_WIDTH_RIGHT,      // Sort by right scroll area width
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_HEIGHT_RIGHT,     // Sort by right scroll area height
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_X_BOTTOM,         // Sort by bottom scroll area X coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_Y_BOTTOM,         // Sort by bottom scroll area Y coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_WIDTH_BOTTOM,     // Sort by bottom scroll area width
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_HEIGHT_BOTTOM,    // Sort by bottom scroll area height
   SORT_BY_CANV_ELEMENT_BORDER_LEFT_AREA_WIDTH,       // Sort by left edge area width
   SORT_BY_CANV_ELEMENT_BORDER_BOTTOM_AREA_WIDTH,     // Sort by bottom edge area width
   SORT_BY_CANV_ELEMENT_BORDER_RIGHT_AREA_WIDTH,      // Sort by right edge area width
   SORT_BY_CANV_ELEMENT_BORDER_TOP_AREA_WIDTH,        // Sort by upper edge area width
   SORT_BY_CANV_ELEMENT_DISPLAYED,                    // Sort by non-hidden control display flag
   SORT_BY_CANV_ELEMENT_GROUP,                        // Sort by a group the graphical element belongs to

   //---...
   //---...

   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_DISTANCE,// Sort by distance from edge to separator
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_WIDTH,  // Sort by separator width
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_ORIENTATION,// Sort by separator location
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL1_COLLAPSED,// Sort by flag for collapsed panel 1
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL1_MIN_SIZE, // Sort by panel 1 minimum size
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Sort by flag for collapsed panel 2
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Sort by panel 2 minimum size
//--- Sort by real properties

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

Now we are able to select and sort objects by these new properties.


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

   MSG_LIB_TEXT_TOP,                                  // Top
   MSG_LIB_TEXT_BOTTOM,                               // Bottom
   MSG_LIB_TEXT_LEFT,                                 // Left
   MSG_LIB_TEXT_RIGHT,                                // Right
   MSG_LIB_TEXT_VERTICAL,                             // Vertically
   MSG_LIB_TEXT_HORISONTAL,                           // Horizontally

...

   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,   // SplitContainer control panel
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,         // SplitContainer control
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER,                // Splitter control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,            // ArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,         // UpArrowButton control

...

   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,          // Visibility scope width
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,         // Visibility scope height
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_X,              // Control area X coordinate
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_Y,              // Control area Y coordinate
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,          // Control area width
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,         // Control area height
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,         // Right scroll area X coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,         // Right scroll area Y coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,     // Right scroll area width
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,    // Right scroll area height
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,        // Bottom scroll area X coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,        // Bottom scroll area Y coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,    // Bottom scroll area width
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,   // Bottom scroll area height
   MSG_CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,      // Left edge area width
   MSG_CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,    // Bottom edge area width
   MSG_CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,     // Right edge area width
   MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,       // Upper edge area width
   MSG_CANV_ELEMENT_PROP_DISPLAYED,                   // Non-hidden control display flag
   MSG_CANV_ELEMENT_PROP_ENABLED,                     // Element availability flag

...

   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE, // Distance from edge to separator
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,    // Separator width
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION, // Separator location
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,  // Flag for collapsed panel 1
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,   // Panel 1 minimum size

and text messages corresponding to the newly added indices:

   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   {"Вертикально","Vertical"},
   {"Горизонтально","Horisontal"},

...

   {"Панель элемента управления \"SplitContainer\"","Panel of the Control element \"SplitContainer\""},
   {"Элемент управления \"SplitContainer\"","Control element \"SplitContainer\""},
   {"Элемент управления \"Splitter\"","Control element \"Splitter\""},
   {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""},
   {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""},

...

   {"Ширина области видимости","Width of object visibility area"},
   {"Высота области видимости","Height of object visibility area"},
   {"X-координата области управления","X-coordinate of the control area"},
   {"Y-координата области управления","Y-coordinate of the control area"},
   {"Ширина области управления","Control area width"},
   {"Высота области управления","Control area height"},
   {"X-координата области прокрутки справа","X-coordinate of the right scroll area"},
   {"Y-координата области прокрутки справа","Y-coordinate of the right scroll area"},
   {"Ширина области прокрутки справа","Width of the right scroll area"},
   {"Высота области прокрутки справа","Height of the right scroll area"},
   {"X-координата области прокрутки снизу","X-coordinate of the bottom scroll area"},
   {"Y-координата области прокрутки снизу","Y-coordinate of the bottom scroll area"},
   {"Ширина области прокрутки снизу","Width of the bottom scroll area"},
   {"Высота области прокрутки снизу","Height of the bottom scroll area"},
   {"Ширина области левой грани","Width of the left border area"},
   {"Ширина области нижней грани","Width of the bottom border area"},
   {"Ширина области правой грани","Width of the right border area"},
   {"Ширина области верхней грани","Width of the top border area"},
   {"Флаг отображения не скрытого элемента управления","Flag that sets the display of a non-hidden control"},
   {"Флаг доступности элемента","Element Availability Flag"},

...

   {"Расстояние от края до разделителя","Distance from edge to splitter"},
   {"Толщина разделителя","Splitter Width"},
   {"Расположение разделителя","Splitter orientation"},
   {"Флаг свёрнутости панели 1","Flag to indicate that panel 1 is collapsed"},
   {"Минимальный размер панели 1","Min size of Panel 1"},


In \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, add display of the new object type description to the method returning a graphical element type description:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)                 :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)        :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)                  :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)               :
      type==GRAPH_ELEMENT_TYPE_FORM                      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                     :
      type==GRAPH_ELEMENT_TYPE_WINDOW                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                   :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)              :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)                  :
      //--- Containers
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)             :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- Standard controls
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)           :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)           :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)                :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)     :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)         :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)      :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)       :
      //--- Auxiliary control objects
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)            :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
      type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

The method returns the appropriate text message depending on the type of a graphical element passed to it.


Since we have new properties of the graphical element, we need to add them to the object structure for it to be correctly saved to the file and read from the file.

In \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, add the new properties to the structure:

private:
   int               m_shift_coord_x;                          // Offset of the X coordinate relative to the base object
   int               m_shift_coord_y;                          // Offset of the Y coordinate relative to the base object
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type

      //---...
      //---...

      bool           displayed;                                // Non-hidden control display flag
      int            split_container_fixed_panel;              // Panel that retains its size when the container is resized
      bool           split_container_splitter_fixed;           // Separator moveability flag
      int            split_container_splitter_distance;        // Distance from edge to separator
      int            split_container_splitter_width;           // Separator width
      int            split_container_splitter_orientation;     // Separator location
      bool           split_container_panel1_collapsed;         // Flag for collapsed panel 1
      int            split_container_panel1_min_size;          // Panel 1 minimum size
      bool           split_container_panel2_collapsed;         // Flag for collapsed panel 2
      int            split_container_panel2_min_size;          // Panel 2 minimum size
      int            control_area_x;                           // Control area X coordinate
      int            control_area_y;                           // Control area Y coordinate
      int            control_area_width;                       // Control area width
      int            control_area_height;                      // Control area height
      int            scroll_area_x_right;                      // Right scroll area X coordinate
      int            scroll_area_y_right;                      // Right scroll area Y coordinate
      int            scroll_area_width_right;                  // Right scroll area width
      int            scroll_area_height_right;                 // Right scroll area height
      int            scroll_area_x_bottom;                     // Bottom scroll area X coordinate
      int            scroll_area_y_bottom;                     // Bottom scroll area Y coordinate
      int            scroll_area_width_bottom;                 // Bottom scroll area width
      int            scroll_area_height_bottom;                // Bottom scroll area height
      int            border_left_area_width;                   // Left edge area width
      int            border_bottom_area_width;                 // Bottom edge area width
      int            border_right_area_width;                  // Right edge area width
      int            border_top_area_width;                    // Upper edge area width
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
      uchar          text[256];                                // Graphical element text
      uchar          descript[256];                            // Graphical element description
     };
   SData             m_struct_obj;                             // Object structure


In the public section of the class, declare the method returning the cursor location relative to the element control area:

//--- (1) Save the graphical resource to the array and (2) restore the resource from the array
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Return the cursor position relative to the (1) entire element, (2) the element active area and (3) control area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
   bool              CursorInsideControlArea(const int x,const int y);

//--- Create the element
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const bool redraw=false);

The method returns the flag indicating that the cursor is inside its control area, which may feature various controls (in this case, it is a separator).

Add the previously omitted constancy modifier to the method returning the hidden element flag:

//--- (1) Set and (2) return the flag for displaying a non-hidden control
   void              SetDisplayed(const bool flag)             { this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,flag);                   }
   bool              Displayed(void)                     const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED);           }

//--- (1) Set and (2) return the graphical element type


In the public section, add the methods returning the new object properties:

//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());                 }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());             }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());                  }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());           }

//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element control area height
   int               ControlAreaX(void)                  const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);       }
   int               ControlAreaY(void)                  const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);       }
   int               ControlAreaWidth(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);   }
   int               ControlAreaHeight(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);  }

//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element right scroll area height
   int               ScrollAreaXRight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT);        }
   int               ScrollAreaYRight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT);        }
   int               ScrollAreaWidthRight(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT);    }
   int               ScrollAreaHeightRight(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT);   }

//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element bottom scroll area height
   int               ScrollAreaXBottom(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM);       }
   int               ScrollAreaYBottom(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM);       }
   int               ScrollAreaWidthBottom(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM);   }
   int               ScrollAreaHeightBottom(void)        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM);  }

//--- Return the width of the (1) left, (2) right, (3) upper and (4) lower element edge area
   int               BorderResizeAreaLeft(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH);     }
   int               BorderResizeAreaRight(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    }
   int               BorderResizeAreaTop(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);      }
   int               BorderResizeAreaBottom(void)        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH);   }
   
//--- Return the number of colors set for the gradient filling of the (1) main background, when clicking (2), (3) when hovering the mouse over the control


Add the default values to all new object properties in the class constructors:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(colour,true);
   this.SetOpacity(opacity);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Chart subwindow index

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Element object name
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Graphical element type

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Non-hidden control display flag
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // Control area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Control area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,0);                  // Control area width
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Control area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // Right scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,0);                 // Right scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,0);             // Right scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,0);            // Right scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,0);                // Bottom scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,0);                // Bottom scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,0);            // Bottom scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,0);           // Bottom scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,0);              // Left edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,0);            // Bottom edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,0);             // Right edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,0);               // Top edge area width
      //---
      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Graphical element affiliation
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Priority of a graphical object for receiving the event of clicking on a chart
      this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL);                   // Font width type

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,50);                       // Distance from edge to separator
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,4);                           // Separator width
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,0);                     // Separator location
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Panel 1 minimum size
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Panel 2 minimum size
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+
//| Protected constructor                                            |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  descript,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetOpacity(0);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,false))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Chart subwindow index

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Element object name
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Graphical element type

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Non-hidden control display flag
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // Control area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Control area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,0);                  // Control area width
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Control area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // Right scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,0);                 // Right scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,0);             // Right scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,0);            // Right scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,0);                // Bottom scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,0);                // Bottom scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,0);            // Bottom scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,0);           // Bottom scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,0);              // Left edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,0);            // Bottom edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,0);             // Right edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,0);               // Top edge area width
      //---
      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Graphical element affiliation
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Priority of a graphical object for receiving the event of clicking on a chart
      this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL);                   // Font width type

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,FRAME_STYLE_NONE);         // Control frame style
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP,0);                     // Control frame top size

      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,50);                       // Distance from edge to separator
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,4);                           // Separator width
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,0);                     // Separator location
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Panel 1 minimum size
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Panel 2 minimum size
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+

All properties are set to zero by default. For the Separator Orientation property, zero means the vertical position of the separator.


In the method that creates the structure of the object, set the values of the new element properties to the structure fields:

//+------------------------------------------------------------------+
//| Create the object structure                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                               // Element ID
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                           // Graphical element type
   this.m_struct_obj.belong=(int)this.GetProperty(CANV_ELEMENT_PROP_BELONG);                       // Graphical element affiliation
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                          // Element ID in the list

   //---...
   //---...

   this.m_struct_obj.split_container_splitter_distance=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE);  // Distance from edge to separator
   this.m_struct_obj.split_container_splitter_width=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);        // Separator width
   this.m_struct_obj.split_container_splitter_orientation=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION);  // Separator location
   this.m_struct_obj.split_container_panel1_collapsed=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED);   // Flag for collapsed panel 1
   this.m_struct_obj.split_container_panel1_min_size=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE);      // Panel 1 minimum size
   this.m_struct_obj.split_container_panel2_collapsed=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED);   // Flag for collapsed panel 1
   this.m_struct_obj.split_container_panel2_min_size=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE);      // Panel 2 minimum size
   this.m_struct_obj.control_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);                      // Control area X coordinate
   this.m_struct_obj.control_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);                      // Control area Y coordinate
   this.m_struct_obj.control_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);              // Control area width
   this.m_struct_obj.control_area_height=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);            // Control area height
   this.m_struct_obj.scroll_area_x_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT);            // Right scroll area X coordinate
   this.m_struct_obj.scroll_area_y_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT);            // Right scroll area Y coordinate
   this.m_struct_obj.scroll_area_width_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT);    // Right scroll area width
   this.m_struct_obj.scroll_area_height_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT);  // Right scroll area height
   this.m_struct_obj.scroll_area_x_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM);          // Bottom scroll area X coordinate
   this.m_struct_obj.scroll_area_y_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM);          // Bottom scroll area Y coordinate
   this.m_struct_obj.scroll_area_width_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM);  // Bottom scroll area width
   this.m_struct_obj.scroll_area_height_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM);// Bottom scroll area height
   this.m_struct_obj.border_left_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH);      // Left edge area width
   this.m_struct_obj.border_bottom_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH);  // Bottom edge area width
   this.m_struct_obj.border_right_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    // Right edge area width
   this.m_struct_obj.border_top_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);        // Top edge area width
//--- Save real properties

//--- Save string properties
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);   // Graphical element object name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);   // Graphical resource name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text);           // Graphical element text
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description
   //--- Save the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+


In the method that creates an object from a structure, set the values of the appropriate structure fields to the object properties:

//+------------------------------------------------------------------+
//| Create the object from the structure                             |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                    // Element ID
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                                // Graphical element type
   this.SetProperty(CANV_ELEMENT_PROP_BELONG,this.m_struct_obj.belong);                            // Graphical element affiliation
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                               // Element index in the list

   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,this.m_struct_obj.split_container_splitter_distance); // Distance from edge to separator
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,this.m_struct_obj.split_container_splitter_width);       // Separator width
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,this.m_struct_obj.split_container_splitter_orientation); // Separator location
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,this.m_struct_obj.split_container_panel1_collapsed);   // Flag for collapsed panel 1
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,this.m_struct_obj.split_container_panel1_min_size);     // Panel 1 minimum size
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,this.m_struct_obj.split_container_panel2_collapsed);   // Flag for collapsed panel 1
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,this.m_struct_obj.split_container_panel2_min_size);     // Panel 2 minimum size
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_struct_obj.control_area_x);                        // Control area X coordinate
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_struct_obj.control_area_y);                        // Control area Y coordinate
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_struct_obj.control_area_width);                // Control area width
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_struct_obj.control_area_height);              // Control area height
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,this.m_struct_obj.scroll_area_x_right);              // Right scroll area X coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,this.m_struct_obj.scroll_area_y_right);              // Right scroll area Y coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,this.m_struct_obj.scroll_area_width_right);      // Right scroll area width
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,this.m_struct_obj.scroll_area_height_right);    // Right scroll area height
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,this.m_struct_obj.scroll_area_x_bottom);            // Bottom scroll area X coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,this.m_struct_obj.scroll_area_y_bottom);            // Bottom scroll area Y coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,this.m_struct_obj.scroll_area_width_bottom);    // Bottom scroll area width
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,this.m_struct_obj.scroll_area_height_bottom);  // Bottom scroll area height
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,this.m_struct_obj.border_left_area_width);        // Left edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,this.m_struct_obj.border_bottom_area_width);    // Bottom edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,this.m_struct_obj.border_right_area_width);      // Right edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,this.m_struct_obj.border_top_area_width);          // Top edge area width
//--- Save real properties

//--- Save string properties
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));   // Graphical element object name
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));   // Graphical resource name
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text));           // Graphical element text
   this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description
  }
//+------------------------------------------------------------------+


Beyond the class body, write an implementation of the method returning the cursor position relative to the element control area:

//+------------------------------------------------------------------+
//|Return the cursor position relative to the element control area   |
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideControlArea(const int x,const int y)
  {
   return(x>=this.ControlAreaX() && x<=this.ControlAreaX()+this.ControlAreaWidth() && y>=this.ControlAreaY() && y<=this.ControlAreaY()+this.ControlAreaHeight());
  }
//+------------------------------------------------------------------+

The cursor coordinates are passed to the method and the flag of finding the values of the X and Y coordinates of the cursor is within the limits of the control area set for the object.


In the \MQL5\Include\DoEasy\Objects\Graph\Form.mqh form object class file, namely in the method displaying the form, add the check for attached objects to make sure the display flag is set for the object:

//+------------------------------------------------------------------+
//| Show the form                                                    |
//+------------------------------------------------------------------+
void CForm::Show(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- If the object has a shadow, display it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Display the main form
   CGCnvElement::Show();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *element=this.m_list_elements.At(i);
      if(element==NULL || !element.Displayed())
         continue;
      //--- and display it
      element.Show();
     }
//--- Update the form
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+


In the method setting and returning the mouse status relative to the form, add the code block filling the bit of the m_mouse_state_flags variable responsible for the location of the cursor inside the control area:

//+------------------------------------------------------------------+
//| Set and get the mouse status relative to the form                |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   this.m_mouse_form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam);
//--- Get the mouse status flags from the CMouseState class object and save them in the variable
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- If the cursor is inside the form
   if(CGCnvElement::CursorInsideElement(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
     {
      //--- Set bit 8 responsible for the "cursor inside the form" flag
      this.m_mouse_state_flags |= (0x0001<<8);
      //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area"
      if(CGCnvElement::CursorInsideActiveArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- otherwise, release the bit "cursor inside the active area"
      else this.m_mouse_state_flags &=0xFDFF;
      //--- If the cursor is inside the control area, set bit 10 "cursor inside the control area",
      if(CGCnvElement::CursorInsideControlArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<10);
      //--- otherwise, remove the "cursor inside the control area" bit
      else this.m_mouse_state_flags &=0xFBFF;
      //--- If one of the mouse buttons is clicked, check the cursor location in the active area and
      //--- return the appropriate value of the pressed key (in the active area or the form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
         this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_PRESSED);
      //--- otherwise, if not a single mouse button is pressed
      else
        {
         //--- if the mouse wheel is scrolled, return the appropriate wheel scrolling value (in the active area or the form area)
         if((this.m_mouse_state_flags & 0x0080)!=0)
            this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : MOUSE_FORM_STATE_INSIDE_FORM_WHEEL);
         //--- otherwise, return the appropriate value of the unpressed key (in the active area or the form area)
         else
            this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED);
        } 
     }
//--- If the cursor is outside the form
   else
     {
      //--- return the appropriate button value in an inactive area
      this.m_mouse_form_state=
        (
         ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? 
          MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED
        );
     }
   return this.m_mouse_form_state;
  }
//+------------------------------------------------------------------+

If the CursorInsideControlArea() method returns true, this means the cursor is inside the form control area. In this case, we need to set bit 10 (set to 1) indicating that. If the cursor is outside the control area, bit 10 is removed (set to zero).


Shorten the mouse event handler by writing all 'switch' operator cases in one string:

//+------------------------------------------------------------------+
//| Mouse event handler                                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   switch(id)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED :
      case MOUSE_EVENT_OUTSIDE_FORM_PRESSED     :
      case MOUSE_EVENT_OUTSIDE_FORM_WHEEL       :
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED        :  this.MouseInsideNotPressedHandler(id,lparam,dparam,sparam);    break;
      //--- The cursor is inside the form, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_FORM_PRESSED            :  this.MouseInsidePressedHandler(id,lparam,dparam,sparam);       break;
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_FORM_WHEEL              :  this.MouseInsideWhellHandler(id,lparam,dparam,sparam);         break;
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED :  this.MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);break;
      //--- The cursor is inside the active area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED     :  this.MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL       :  this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is inside the active area, left mouse button is released
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED    :  this.MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);  break;
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED :  this.MouseScrollAreaNotPressedHandler(id,lparam,dparam,sparam);break;
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED     :  this.MouseScrollAreaPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL       :  this.MouseScrollAreaWhellHandler(id,lparam,dparam,sparam);     break;
      //--- MOUSE_EVENT_NO_EVENT
      default: break;
     }
   this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id;
  }
//+------------------------------------------------------------------+

Thus, the method becomes more readable.

Add the blanks for handling new "previous" states to the last mouse event handler:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED        :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED            :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  || 
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         || 
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
           this.SetBackgroundColor(this.BackgroundColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
          }
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      //--- The cursor is inside the form, any mouse button is clicked
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      //--- The cursor is inside the active area, any mouse button is clicked
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, left mouse button is released
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window resizing area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window separator area, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL               :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED     :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL      :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

In the future, I may need to handle these events, which were the last ones relative to the object, so I have immediately added the blanks for these event handlers here. At the moment, they are not handled in any way.


In the \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh base WinForms object class file, namely in the method redrawing the object, add the check for the object display flag. If the object should not be displayed, then there is no need to redraw it as well:

//+------------------------------------------------------------------+
//| 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 || !this.Displayed())
      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;
      if(redraw)
         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());
  }
//+------------------------------------------------------------------+


In the method returning the description of the element integer property, add the code block to return the descriptions of new graphical element properties:

//+------------------------------------------------------------------+
//| Return the description of the control integer property           |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_ID                           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TYPE                         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.TypeElementDescription()
         )  :

      //---...
      //---...

      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_X               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_X)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_Y               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_Y)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH       ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH        ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Depending on the property passed to the method, a description string is created and returned. If the property is not supported by the object, then instead of the value of the property, an entry indicating that the property is not supported is displayed. Depending on the only_prop flag, we either display a property name alone, or show it together with a value assigned to it.

Now we can start creating a new library object.


Auxiliary separator object class

Auxiliary library objects are not full-fledged controls but are used to build such objects. We need the separator object to indicate that we can move the area that separates two panels in the SplitContainer control. By moving this object with the mouse, we will call the event handler of the SplitContainer control, where this event will be handled and the sizes of the panels will be changed. At the same time, we may need such an object to interact with other controls, so it will be located in the auxiliary objects folder and used in the necessary controls.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\, create the new Splitter.mqh file of the CSplitter class.

The class should be inherited from the library WinForms object base class and its file should be included into the file of the created class:

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


In the protected section of the class, we will declare a virtual method that draws a grid (object hatching) and a protected constructor. In the public section of the class, declare a parametric constructor and methods for redrawing and clearing the background of a graphical element:

//+------------------------------------------------------------------+
//| Splitter object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CSplitter : public CWinFormBase
  {
private:

protected:
   //--- Draw the grid
   virtual void      DrawGrid(void);
//--- Protected constructor with object type, chart ID and subwindow
                     CSplitter(const ENUM_GRAPH_ELEMENT_TYPE type,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:
//--- Constructor
                     CSplitter(const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- 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);
  };
//+------------------------------------------------------------------+

The method that draws the grid of an object is declared virtual in case you need to make derived classes with a different rendering.

Let's take a closer look at the declared methods.


Protected class constructor:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CSplitter::CSplitter(const ENUM_GRAPH_ELEMENT_TYPE type,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
  }
//+------------------------------------------------------------------+

In the formal constructor parameters, we pass the type of the created object, which is passed to the parent class constructor in the initialization string. The type passed to the constructor is set in the class body and the library graphical object type is set as an auxiliary object. The Padding and Margin values, as well as frame size are set to zero.


Parametric constructor:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSplitter::CSplitter(const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_SPLITTER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
   this.SetDisplayed(false);
  }
//+------------------------------------------------------------------+

Here everything is similar to the protected constructor, but the graphical element type is set directly and not passed in the formal parameters.


The method redrawing an object:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CSplitter::Redraw(bool redraw)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the object with background color having transparency
   this.Erase(this.BackgroundColor(),this.Opacity(),true);
  }
//+------------------------------------------------------------------+

If the flag of displaying an object on a visible and accessible control is disabled, then there is no need to redraw the object. We simply exit the method. If the display flag is set, call the method for filling the object with color.


The methods that clear an element by filling it with color and opacity:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CSplitter::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- Draw the grid
   this.DrawGrid();
//--- Crop and update the element with the specified redraw flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CSplitter::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- Draw the grid
   this.DrawGrid();
//--- Crop and update the element with the specified redraw flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

The logic of the methods is fully commented in the code. The first method fills the background with a single color, the second — with a gradient.


The method drawing the grid:

//+------------------------------------------------------------------+
//| Draw the grid                                                    |
//+------------------------------------------------------------------+
void CSplitter::DrawGrid(void)
  {
   for(int y=0;y<this.Height()-1;y++)
      for(int x=0;x<this.Width();x++)
         this.SetPixel(x,y,this.ForeColor(),uchar(y%2==0 ? (x%2==0 ? 255 : 0) : (x%2==0 ? 0 : 255)));
  }
//+------------------------------------------------------------------+

We need to fill the background of the object with dots in a checkerboard pattern. To do this, arrange two loops: a loop through rows and a loop through columns.

  • If the string is even, then:
    • If the column is even, set a dot with full opacity,
    • If the column is odd, set a dot with full transparency.
  • If the string is odd, then:
    • If the column is even, set a dot with full transparency,
    • If the column is odd, set a dot with full opacity.

Thus, we fill the entire background with dots arranged in a checkerboard pattern.

This is all that is needed at this stage for the separator object class to work.

Now we need to include it into the SplitContainer control class, as well as create and manage it.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh, namely in the class private section, declare the variables for storing panel and separator coordinates and size, as well as declare the method for setting the panel parameters:

//+------------------------------------------------------------------+
//|                                               SplitContainer.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\Helpers\SplitContainerPanel.mqh"
#include "..\Helpers\Splitter.mqh"
//+------------------------------------------------------------------+
//| SplitContainer WForms control object class                       |
//+------------------------------------------------------------------+
class CSplitContainer : public CContainer
  {
private:
   int               m_panel1_x;             // panel1 X coordinate
   int               m_panel1_y;             // panel1 Y coordinate
   int               m_panel1_w;             // panel1 width
   int               m_panel1_h;             // panel1 height
   int               m_panel2_x;             // panel2 X coordinate
   int               m_panel2_y;             // panel2 Y coordinate
   int               m_panel2_w;             // panel2 width
   int               m_panel2_h;             // panel2 height
   int               m_splitter_x;           // Separator X coordinate
   int               m_splitter_y;           // Separator Y coordinate
   int               m_splitter_w;           // separator width
   int               m_splitter_h;           // separator height
//--- Create a new graphical object
   virtual CGCnvElement   *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                           const int element_num,
                                           const string descript,
                                           const int x,
                                           const int y,
                                           const int w,
                                           const int h,
                                           const color colour,
                                           const uchar opacity,
                                           const bool movable,
                                           const bool activity);
//--- Set the panel parameters
   bool              SetsPanelParams(void);
                                           
public:


In the public section of the class, declare/write new methods, change the returned type of the methods for receiving the pointers on the panel (to fix the error arising during a separate compilation of the CSplitContainerPanel class file), while the implementation of some methods is made outside the class, only the declaration of methods is left here:

public:
//--- Create the panels
   void              CreatePanels(void);

//--- Returns pointer to the specified panel
   CWinFormBase     *GetPanel(const int index)                 { return CForm::GetElement(index);  }
   
//--- Return the pointer to the (1) panel1 and (2) panel2
   CWinFormBase     *GetPanel1(void)                           { return this.GetPanel(0);          }
   CWinFormBase     *GetPanel2(void)                           { return this.GetPanel(1);          }
   
//--- Return the element from the specified panel (1) by index, (2) by type and index and (3) by name
   CGCnvElement     *GetPanelElement(const int panel,const int index);
   CGCnvElement     *GetPanelElementByType(const int panel,const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   CGCnvElement     *GetPanelElementByName(const int panel,const string name);
   
//--- Return the pointer to the separator
   CSplitter        *GetSplitter(void)                         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SPLITTER,0);                      }
   
//--- (1) set and (2) return the minimum possible size of the panel 1 and 2
   void              SetPanel1MinSize(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,value);           }
   int               Panel1MinSize(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE);     }
   void              SetPanel2MinSize(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,value);           }
   int               Panel2MinSize(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE);     }
   
//--- (1) set and (2) return the flag of collapsed panel 1
   void              SetPanel1Collapsed(const int flag);
   bool              Panel1Collapsed(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED);   }
//--- (1) set and (2) return the flag of collapsed panel 2
   void              SetPanel2Collapsed(const int flag);
   bool              Panel2Collapsed(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED);   }
   
//--- (1) set and (2) return the separator distance from the edge
   void              SetSplitterDistance(const int value);
   int               SplitterDistance(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE);   }
   
//--- (1) set and (2) return the separator non-removability flag
   void              SetSplitterFixed(const bool flag)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag);             }
   bool              SplitterFixed(void)                 const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED);     }
   
//--- (1) set and (2) return the separator width
   void              SetSplitterWidth(const int value);
   int               SplitterWidth(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);      }
   
//--- (1) set and (2) return the separator location
   void              SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);                                              }
   ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION SplitterOrientation(void) const
                       { return(ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION);      }
   
//--- (1) set and (2) return the panel that does not change its size when the container is resized
   void              SetFixedPanel(const ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,value);                                                       }
   ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL FixedPanel(void) const
                       { return(ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);        }
   
//--- Create a new attached element on the specified panel
   bool              CreateNewElement(const int panel_index,
                                      const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
   
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CSplitContainer(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


In the method that creates a new graphical object, add yet another object type that can be created:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                const int obj_num,
                                                const string descript,
                                                const int x,
                                                const int y,
                                                const int w,
                                                const int h,
                                                const color colour,
                                                const uchar opacity,
                                                const bool movable,
                                                const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL : element=new CSplitContainerPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER              : element=new CSplitter(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      default  :  break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Since now we need to create a separator inside the object in addition to the panels, the method should be able to do this. The added string creates a new separator object.


In the method creating the panels, add the code block for creating a separator object:

//+------------------------------------------------------------------+
//| Create the panels                                                |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
     }
  }
//+------------------------------------------------------------------+

Here: if failed to create a separator object, leave the method. Next, get the pointer to the created separator object, set the relocation flag for it (it will also need to be moved with the mouse), set the flag indicating that it does not need to be displayed and hide the created object.
Creation of the panels has also been changed. Now we first set the parameters to the panels in the new method SetsPanelParams(), which will be discussed below. Depending on the location of the separator and collapsed panels flags, their initial coordinates and size are set in the new variables designed for this purpose. Theur values are passed to the panel creation methods.


The method that sets the panel parameters:

//+------------------------------------------------------------------+
//| Set the panel parameters                                         |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {
      //--- The separator is positioned vertically
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL    :
        //--- If both panels are not collapsed,
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width()-this.m_panel2_x;
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=this.SplitterDistance();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- If panel2 is collapsed
        else if(this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width()-this.m_panel2_x;
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=-this.SplitterWidth();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- If panel1 is collapsed
        else if(this.Panel1Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=-this.SplitterWidth();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        break;
      //--- The separator is located horizontally
      case CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL  :
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height()-this.m_panel2_y;
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- If panel2 is collapsed
        else if(this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height()-this.m_panel2_y;
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=-this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- If panel1 is collapsed
        else if(this.Panel1Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=-this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        break;
      default:
        return false;
        break;
     }
//--- Set the coordinates and sizes of the control area equal to the properties set by the separator
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_splitter_x);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_splitter_y);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_splitter_w);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_splitter_h);
   return true;
  }
//+------------------------------------------------------------------+

Depending on the separator location (vertically or horizontally) and panel states (both panels are collapsed) or only one of them), set the panels and separator coordinates and sizes to the service variables. At the end of the method, the separator parameters set in the method are written to the properties of the coordinates and dimensions of the control area.


The method that sets the collapsed flag for panel 1:

//+------------------------------------------------------------------+
//| Set the flag of collapsed panel 1                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel1Collapsed(const int flag)
  {
//--- Set the flag, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,flag);
//--- If panel1 should be collapsed
   if(this.Panel1Collapsed())
     {
      //--- set the expanded flag for panel2
      this.SetPanel2Collapsed(false);
      //--- If the pointer to panel1 is received
      if(this.GetPanel1()!=NULL)
        {
         //--- set the flag for not displaying the panel and hide it
         this.GetPanel1().SetDisplayed(false);
         this.GetPanel1().Hide();
        }
      //--- If the pointer to panel2 is received,
      if(this.GetPanel2()!=NULL)
        {
         //--- set the panel display flag, display it and bring it to the foreground
         this.GetPanel2().SetDisplayed(true);
         this.GetPanel2().Show();
         this.GetPanel2().BringToTop();
        }
     }
  }
//+------------------------------------------------------------------+

The method is fully commented in the code. In addition to the fact that we set the flag for collapsing the panel (if false is passed to the method), we also hide it and set the non-display flag for it. The panel 2 is displayed on the foreground with the display flag.

The method that sets the collapsed flag for panel 2:

//+------------------------------------------------------------------+
//| Set the flag of collapsed panel 2                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel2Collapsed(const int flag)
  {
//--- Set the flag, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,flag);
//--- If panel2 should be collapsed,
   if(Panel2Collapsed())
     {
      //--- set the expanded flag for panel1
      this.SetPanel1Collapsed(false);
      //--- If the pointer to panel2 is received,
      if(this.GetPanel2()!=NULL)
        {
         //--- set the flag for not displaying the panel and hide it
         this.GetPanel2().SetDisplayed(false);
         this.GetPanel2().Hide();
        }
      //--- If the pointer to panel1 is received
      if(this.GetPanel1()!=NULL)
        {
         //--- set the panel display flag, display it and bring it to the foreground
         this.GetPanel1().SetDisplayed(true);
         this.GetPanel1().Show();
         this.GetPanel1().BringToTop();
        }
     }
  }
//+------------------------------------------------------------------+

The logic of the method is similar to the one discussed above, but applies to panel 2.


The method setting the separator distance from the edge:

//+------------------------------------------------------------------+
//| Set the separator distance from the edge                         |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterDistance(const int value)
  {
//--- Set the value, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value);
//--- Depending on the direction of the separator (vertical or horizontal),
//--- set the values to the coordinates of the object control area
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.SplitterDistance());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.SplitterDistance());
        break;
     }
  }
//+------------------------------------------------------------------+

Since the origin of the coordinates of the object control area depends on the location of the separator, then depending on the direction of the separator, we set the coordinates of the separator into the control region — it is a physical representation of this virtual region.


The method setting the separator width:

//+------------------------------------------------------------------+
//| Set the separator width                                          |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterWidth(const int value)
  {
//--- Set the value, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value);
//--- Depending on the direction of the separator (vertical or horizontal),
//--- set the values to the object control area width and height
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.SplitterWidth());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.Height());
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.Width());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.SplitterWidth());
        break;
     }
  }
//+------------------------------------------------------------------+

The method is the same as above. Depending on the direction of the separator, we set the dimensions of the separator into the control area, so it physically displays this virtual area.

The event handler:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- If the event ID is moving the separator
   if(id==WF_CONTROL_EVENT_SPLITTER_MOVE)
     {
      //--- Get the pointer to the separator object
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL)
         return;
      //--- Declare the variables for separator coordinates
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Depending on the separator direction,
      switch(this.SplitterOrientation())
        {
         //--- vertical position
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Set the Y coordinate equal to the Y coordinate of the control element
           y=this.CoordY();
           //--- Adjust the X coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum width of the panels
           if(x<this.CoordX()+this.Panel1MinSize())
              x=this.CoordX()+this.Panel1MinSize();
           if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth())
              x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth();
           break;
         //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
         //--- horizontal position of the separator
         default:
           //--- Set the X coordinate equal to the X coordinate of the control element
           x=this.CoordX();
           //--- Adjust the Y coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum height of the panels
           if(y<this.CoordY()+this.Panel1MinSize())
              y=this.CoordY()+this.Panel1MinSize();
           if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth())
              y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth();
           break;
        }
      //--- If the separator is shifted by the calculated coordinates,
      if(splitter.Move(x,y,true))
        {
         //--- set the separator relative coordinates
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- Get the pointers to both panels
         CSplitContainerPanel *p1=this.GetPanel1();
         CSplitContainerPanel *p2=this.GetPanel2();
         if(p1==NULL || p2==NULL)
            return;
         //--- Depending on the direction of the separator, set its new coordinates
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY());
         //--- Set the panel new coordinates and sizes depending on the separator coordinates
         if(this.SetsPanelParams())
           {
            //--- If panel 1 is resized successfully
            if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
              {
               //--- If panel 2 coordinates are changed to new ones
               if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
                 {
                  //--- if panel 2 has been successfully resized,
                  if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
                    {
                     //--- set new relative coordinates of panel 2
                     p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
                     p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
                    }
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

The method logic is fully described in the code comments. In short, the handler receives the "Separator movement" event ID and calculates the panel new coordinates and sizes. Panel1 always stays at its coordinates and only resizes as the separator is moved. The panel 2, in addition to resizing, should also move after the separator since its initial coordinates are tied to it. Accordingly, the panel is resized so that it always remains inside its container when following the separator.


'The cursor is inside the active area, the mouse buttons are not clicked' event handler:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator, show and redraw it
      splitter.SetDisplayed(true);
      splitter.Show();
      splitter.Redraw(true);
     }
  }
//+------------------------------------------------------------------+

The method logic is described in the code comments. When we move the mouse cursor over the control area, an event is generated that is sent to the mouse event handler of the CForm class form object. Inside the handler, there is a redirect to handle each event in its own virtual method. In the form object, all these methods do nothing — they should be overridden in derived classes. In this class of the SplitContainer control, such a handler receives the pointer to the separator object, and if it is not displayed (its display flag is reset), then the display flag is set for it. The object itself is displayed and redrawn.


Slightly improve the SplitContainer control panel object class.

In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh class file, namely in the public section, set the methods for setting the collapsed panel flags and declare the method for displaying the panel and the "The cursor is inside the active area, the mouse buttons are not clicked" event handler:

//+------------------------------------------------------------------+
//| SplitContainerPanel object class                                 |
//| of the SplitContainer WForms control                             |
//+------------------------------------------------------------------+
class CSplitContainerPanel : public CContainer
  {
private:
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CSplitContainerPanel(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
public:
//--- (1) Set and (2) return the flag of collapsed panel
   void              SetCollapsed(const bool flag)       { this.SetDisplayed(!flag);   }
   bool              Collapsed(void)               const { return !this.Displayed();   }
//--- Display the panel
   virtual void      Show(void);
//--- Draw the panel frame
   virtual void      DrawFrame(void);
//--- 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);
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CSplitContainerPanel(const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
  };
//+------------------------------------------------------------------+


The SetCollapsed() and Collapsed() methods are the opposite of the SetDisplayed() and Displayed() methods. Therefore, they are called inside the declared methods, but the flag passed to the method or returned from the method is inverted.


In the method creating a new graphical object, add creation of a separator object:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainerPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                     const int obj_num,
                                                     const string descript,
                                                     const int x,
                                                     const int y,
                                                     const int w,
                                                     const int h,
                                                     const color colour,
                                                     const uchar opacity,
                                                     const bool movable,
                                                     const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             : element=new CSplitter(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

It is clear that all auxiliary objects do not represent any value taken separately, but all container objects should still be able to create all possible objects within themselves. Therefore, I implement the creation of all existing and new objects in container objects, regardless of which category of library objects they belong to.

The method displaying the panel:

//+------------------------------------------------------------------+
//| Display the panel                                                |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Show(void)
  {
//--- If the panel is collapsed, leave
   if(this.Collapsed())
      return;
//--- Display the panel and all objects attached to it
   CForm::Show();
  }
//+------------------------------------------------------------------+

Here, we first check the flag of the collapsed panel, and if the panel is in a collapsed state, then there is nothing to display, so we leave. Otherwise, we display the panel using the method of the form object parent class.


"The cursor is inside the active area, the mouse buttons are not clicked" event handler:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the base object
   CSplitContainer *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the pointer to the separator object from the base object
   CSplitter *splitter=base.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is displayed
   if(splitter.Displayed())
     {
      //--- Disable the display of the separator and hide it
      splitter.SetDisplayed(false);
      splitter.Hide();
     }
  }
//+------------------------------------------------------------------+

The logic of the method is commented in the code. In short, when we move the cursor away from the control area of the SplitContainer control, the cursor immediately lands on the area of the first or second panel of that control. Thus, we cannot determine in the object of the CSplitContainer class that the cursor has left the control area — the cursor immediately falls on the panel object attached to the container. This is the panel object where the event of the cursor located over the form or its active area is reiggered. Therefore, we need to get the pointer to the separator from the base object in the mouse event handler and hide the received separator, which is done here.


In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh file of the container object class, namely in its method setting the parameters for the bound object, add the code block for setting the parameters of a newly created separator object:

//+------------------------------------------------------------------+
//| Set parameters for the attached object                           |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
//--- Set the text color of the object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);
//--- If the created object is not a container, set the same group for it as the one for its base object
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)
      obj.SetGroup(this.Group());
//--- Depending on the object type
   switch(obj.TypeGraphElement())
     {
      //--- For the Container, Panel and GroupBox WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- For "Label", "CheckBox" and "RadioButton" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_LABEL                :
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             :
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          :
        obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        break;
      //--- For "Button", "TabHeader", TabField and "ListBoxItem" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           :
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            :
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        :
        obj.SetForeColor(this.ForeColor(),true);
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             :
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     :
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      //--- For "TabControl" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY);
        break;
      //--- For "SplitContainer" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_CANV_NULL : colour,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(0);
        break;
      //--- For "SplitContainerPanel" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL:
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      //--- For "Splitter" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_CANV_NULL : colour,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(0);
        obj.SetDisplayed(false);
        obj.Hide();
        break;
      //--- For the "ArrowButton" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+

For the newly created separator object, set a transparent background color (if a color is passed to the clrNONE method) and transparent frame color, set the object to full transparency, as well as the non-display flag and hide the created object — initially, the separator object should not be visible.


In the \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh file of the collection class of graphical elements, namely in its event handler, add the code block to handle moving the separator object:

      //--- In case of the mouse movement event
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         //--- If the cursor is above the form
         if(form!=NULL)
           {
            //--- If the move flag is set
            if(move)
              {
               //--- calculate the cursor movement relative to the form coordinate origin
               int x=this.m_mouse.CoordX()-form.OffsetX();
               int y=this.m_mouse.CoordY()-form.OffsetY();
               //--- get the width and height of the chart the form is located at
               int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow());
               int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow());
               //--- If the form is not within an extended standard graphical object
               if(form_index==WRONG_VALUE)
                 {
                  //--- If the form is a separator object,
                  if(form.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLITTER)
                    {
                     //--- get its base object
                     CWinFormBase *base=form.GetBase();
                     if(base==NULL)
                        return;
                     //--- and send the "Separator movement" event to the event handler of the base object
                     const long lp=x;
                     const double dp=y;
                     base.OnChartEvent(WF_CONTROL_EVENT_SPLITTER_MOVE,lp,dp,sparam);
                    }
                  //--- Adjust the calculated form coordinates if the form is out of the chart range
                  if(x<0) x=0;
                  if(x>chart_width-form.Width()) x=chart_width-form.Width();
                  if(y<0) y=0;
                  if(y>chart_height-form.Height()) y=chart_height-form.Height();
                  //--- If the one-click trading panel is not present on the chart,
                  if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK))
                    {
                     //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel button

In the method, inside the block for handling the movement of a graphical object, we check the type of the object being moved, and if it is a separator object, then we call its event handler sending the WF_CONTROL_EVENT_SPLITTER_MOVE event to it. Inside the event handler of this graphical element, this event is handled as discussed above.

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

Let's test the results.


Test

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

All we need to change in the EA is coordinates and size of text labels on SplitContainer control panels:

               //--- On each of the control panels...
               for(int j=0;j<2;j++)
                 {
                  CSplitContainerPanel *panel=split_container.GetPanel(j);
                  if(panel==NULL)
                     continue;
                  //--- ...create a text label with the panel name
                  if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,3,3,panel.Width()-6,panel.Height()-6,clrDodgerBlue,255,true,false))
                    {
                     CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                     if(label==NULL)
                        continue;
                     label.SetTextAlign(ANCHOR_CENTER);
                     label.SetText(TextByLanguage("Панель","Panel")+string(j+1));
                    }
                 }

Why? If the size of text labels match the dimensions of the panels, on which they are created, then when the cursor leaves the control area, it will not find itself on the panel area but on the text label area instead. Thus, the separator object cannot be hidden. This is a flaw that needs to be fixed. I will do that while developing the SplitContainer control further.

Compile the EA and launch it on the chart:


All works fine except for redrawing delays. Unfortunately, I was not able to find the reason for these freezes on my low-power old laptop. Possibly, the laptop was so overloaded with processes that it was not able to smoothly display changes in the coordinates and sizes of the panels, or perhaps the code needs further optimization. I noticed that such freezes are occasional. But in any case, the library code will be subject to optimization after its development is completed. Besides, displaying/hiding the separator object does not always work reliably. I will also solve this when developing the control.


What's next?

In the next article, I will continue developing the SplitContainer control and start creating the functionality for changing the parameters of an already created control.

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 13): Optimizing interaction of WinForms objects with the mouse, starting the development of the TabControl WinForms object
DoEasy. Controls (Part 14): New algorithm for naming graphical elements. Continuing work on the TabControl WinForms object
DoEasy. Controls (Part 15): TabControl WinForms object — several rows of tab headers, tab handling methods 
DoEasy. Controls (Part 16): TabControl WinForms object — several rows of tab headers, stretching headers to fit the container
DoEasy. Controls (Part 17): Cropping invisible object parts, auxiliary arrow buttons WinForms objects
DoEasy. Controls (Part 18): Functionality for scrolling tabs in TabControl
DoEasy. Controls (Part 19): Scrolling tabs in TabControl, WinForms object events
DoEasy. Controls (Part 20): SplitContainer WinForms object



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

Attached files |
MQL5.zip (4478.46 KB)
Neural networks made easy (Part 28): Policy gradient algorithm Neural networks made easy (Part 28): Policy gradient algorithm
We continue to study reinforcement learning methods. In the previous article, we got acquainted with the Deep Q-Learning method. In this method, the model is trained to predict the upcoming reward depending on the action taken in a particular situation. Then, an action is performed in accordance with the policy and the expected reward. But it is not always possible to approximate the Q-function. Sometimes its approximation does not generate the desired result. In such cases, approximation methods are applied not to utility functions, but to a direct policy (strategy) of actions. One of such methods is Policy Gradient.
Data Science and Machine Learning (Part 09): The K-Nearest Neighbors Algorithm (KNN) Data Science and Machine Learning (Part 09): The K-Nearest Neighbors Algorithm (KNN)
This is a lazy algorithm that doesn't learn from the training dataset, it stores the dataset instead and acts immediately when it's given a new sample. As simple as it is, it is used in a variety of real-world applications.
Population optimization algorithms: Particle swarm (PSO) Population optimization algorithms: Particle swarm (PSO)
In this article, I will consider the popular Particle Swarm Optimization (PSO) algorithm. Previously, we discussed such important characteristics of optimization algorithms as convergence, convergence rate, stability, scalability, as well as developed a test stand and considered the simplest RNG algorithm.
DoEasy. Controls (Part 20): SplitContainer WinForms object DoEasy. Controls (Part 20): SplitContainer WinForms object
In the current article, I will start developing the SplitContainer control from the MS Visual Studio toolkit. This control consists of two panels separated by a vertical or horizontal movable separator.