Русский 中文 Español Deutsch 日本語 Português
preview
DoEasy. Controls (Part 12): Base list object, ListBox and ButtonListBox WinForms objects

DoEasy. Controls (Part 12): Base list object, ListBox and ButtonListBox WinForms objects

MetaTrader 5Examples | 7 October 2022, 14:29
2 070 0
Artyom Trishkin
Artyom Trishkin

Contents


Concept

In this article, I will continue my work on WinForms objects in the library. In the previous article, I created the CheckedListBox object, which is essentially a list of CheckBox objects. But since further we will create more different lists of WinForms objects, it would be reasonable now to create a class of the base object list of WinForms objects and create all other classes on its basis. The class will contain the main functionality for handling the lists of WinForms objects and subsequently various data bound to the control (DataBindings in MS Visual Studio), whose data is used to display lists of elements in rows. This will allow displaying completely different data from the environment of both the library itself and the terminal and its databases and, of course, having access to them through these lists.

Based on this class, I will create the ListBox WinForms object, which is a simple list that displays a certain collection of data. For now, the list will display only the names of the list items created during its construction. These items allow interacting with the mouse (changing the background color when hovering and selecting). The object will not have any practical use yet (it will only be a visual display of the future functionality of the object), since in order to create the event functionality of WinForms objects, we still need to create a certain number of them in order to determine the data that needs to be passed to event handlers. The more different objects will be created, the more we will have an idea about the necessary data and their structure, which will be needed for the correct creation of the event functionality of WinForms objects.

Also, I will create the button list object. Since the ListBox (its rows) are created exactly based on the class of the CButton object, it would be quite logical to create an additional object that displays a set of buttons in its list (similar to the CheckedListBox object that displays CheckBox objects in its list). The list of standard controls in MS Visual Studio contains no such object, so it will be an experiment.


Improving library classes

After the last updates of the client terminal, the library's trading class CTrading stopped working — the access to its OpenPosition() and PlaceOrder() private methods from inherited classes was disabled.
Therefore, in \MQL5\Include\DoEasy\Trading.mqh, set the protected class section for these methods:

//--- Return the error handling method
   ENUM_ERROR_CODE_PROCESSING_METHOD   ResultProccessingMethod(const uint result_code);
//--- Correct errors
   ENUM_ERROR_CODE_PROCESSING_METHOD   RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj,CTradeObj *trade_obj);
   
//--- (1) Open a position, (2) place a pending order
protected:
   template<typename SL,typename TP> 
   bool                 OpenPosition(const ENUM_POSITION_TYPE type,
                                    const double volume,
                                    const string symbol,
                                    const ulong magic=ULONG_MAX,
                                    const SL sl=0,
                                    const TP tp=0,
                                    const string comment=NULL,
                                    const ulong deviation=ULONG_MAX,
                                    const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 PlaceOrder( const ENUM_ORDER_TYPE order_type,
                                    const double volume,
                                    const string symbol,
                                    const PR price,
                                    const PL price_limit=0,
                                    const SL sl=0,
                                    const TP tp=0,
                                    const ulong magic=ULONG_MAX,
                                    const string comment=NULL,
                                    const datetime expiration=0,
                                    const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
                                    const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
private:                                    
//--- Return the request object index in the list by (1) ID,
//--- (2) order ticket, (3) position ticket in the request
   int                  GetIndexPendingRequestByID(const uchar id);
   int                  GetIndexPendingRequestByOrder(const ulong ticket);
   int                  GetIndexPendingRequestByPosition(const ulong ticket);

public:

Here I have created the protected section inside the private one returning the private section afterwards.


In \MQL5\Include\DoEasy\Defines.mqh, add three new types to the enumeration of graphical element types:

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


At the very end of the list of integer properties of a graphical element on canvas, add two new properties and increase the number of integer properties from 83 to 85:

//+------------------------------------------------------------------+
//| 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_CHECK_FLAG_COLOR_MOUSE_DOWN,     // Color of control checkbox when clicking on the control
   CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,     // Color of control checkbox when hovering the mouse over the control
   CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,           // Horizontal display of columns in the ListBox control
   CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,           // Width of each ListBox control column
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (85)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical elements on the canvas    |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                         // Sort by graphical element type
   //---...
   //---...
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_DOWN,  // Sort by color of control checkbox when clicking on the control
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_OVER,  // Sort by color of control checkbox when hovering the mouse over the control
   SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN,        // Sort by horizontal column display flag in the ListBox control
   SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH,        // Sort by the width of each ListBox control column
//--- Sort by real properties

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

Now we are able to select and sort objects by these two new properties. In fact, there is no particular need for this when handling graphical objects to build a GUI (I do not see the options for using search by these properties). However, we enter absolutely all the properties of GUI objects into these lists so that we can then build the GUI for our program creating and handling a graphical shell directly in the chart window. That is where you need to get and change absolutely all the properties of graphical elements.

In \MQL5\Include\DoEasy\Data.mqh, add new message indices of the library, rename the index MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ and remove the index MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ:

//--- WinForms standard
   MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,             // WinForms base standard control
   MSG_GRAPH_ELEMENT_TYPE_WF_LABEL,                   // Label control
   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                // CheckBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,             // RadioButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON,                  // Button control
   MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,       // Base list object of Windows Forms elements
   MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                // ListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,        // CheckedListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // ButtonListBox control
   
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Graphical object does not belong to a program

...

//--- CPanel
   MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ,   // Failed to create the underlay object
   MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE,           // Error. The created object should be of WinForms Base type or be derived from it

//--- ElementsListBox
   MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,     // Failed to get a graphical element 
                                                 

//--- CButtonListBox
   MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON,         // Failed to set the group for the button with the index 
   MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON,        // Failed to set the Toggle flag to the button with the index 

//--- Integer properties of graphical elements

...

   MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN, // Color of control checkbox when clicking on the control
   MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER, // Color of control checkbox when hovering the mouse over the control
   MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,       // Horizontal display of columns in the ListBox control
   MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,       // Width of each ListBox control column
//--- Real properties of graphical elements

//--- String properties of graphical elements
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Graphical element object name
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Graphical resource name
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Graphical element text

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


and the text messages corresponding to the newly added indices:

//--- WinForms standard
   {"Базовый стандартный элемент управления WinForms","Basic Standard WinForms Control"},
   {"Элемент управления \"Label\"","Control element \"Label\""},
   {"Элемент управления \"CheckBox\"","Control element \"CheckBox\""},
   {"Элемент управления \"RadioButton\"","Control element \"RadioButton\""},
   {"Элемент управления \"Button\"","Control element \"Button\""},
   {"Базовый объект-список Windows Forms элементов","Basic Windows Forms List Object"},
   {"Элемент управления \"ListBox\"","Control element \"ListBox\""},
   {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""},
   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

//--- CPanel
   {"Не удалось создать объект-подложку","Failed to create underlay object"},
   {"Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником","Error. The object being created must be of type WinForms Base or be derived from it"},

//--- ElementsListBox
   {"Не удалось получить графический элемент ","Failed to get graphic element "},
                                                                                 
//--- CButtonListBox
   {"Не удалось установить группу кнопке с индексом ","Failed to set group for button with index "},
   {"Не удалось установить флаг \"Переключатель\" кнопке с индексом ","Failed to set the \"Toggle\" flag on the button with index "},
   
//--- Integer properties of graphical elements

...

   {"Цвет флажка проверки элемента управления при нажатии мышки на элемент управления","Control Checkbox Colorl when the mouse is pressed on the control"},
   {"Цвет флажка проверки элемента управления при наведении мышки на элемент управления","Control Checkbox Colorl when hovering the mouse over the control"},
   {"Горизонтальное отображение столбцов в элементе управления ListBox","Display columns horizontally in a ListBox control"},
   {"Ширина каждого столбца элемента управления ListBox","The width of each column of the ListBox control"},
   
//--- String properties of graphical elements
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},

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


We need to return a description of the specified type of a graphical element. At the moment, the class of the base graphical object of the library has the TypeElementDescription() method, which returns a description of the type of the current graphical object, in other words, the description of its own type. But we need to return the description of the graphical element specified in the input parameters of the method. Therefore, let's do this: add a formal parameter to the existing method, in which we will pass the type of the object, and implement an overloaded method that returns the type of the current object.

Make the necessary changes in \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh.
In the public section, declare a new overloaded method, in which we pass an object type, while the previous method will pass the current object type to the new method:

//--- Return the graphical object type (ENUM_OBJECT) calculated from the object type (ENUM_OBJECT_DE_TYPE) passed to the method
   ENUM_OBJECT       GraphObjectType(const ENUM_OBJECT_DE_TYPE obj_type) const
                       { 
                        return ENUM_OBJECT(obj_type-OBJECT_DE_TYPE_GSTD_OBJ-1);
                       }
   
//--- Return the description of the type of the graphical object (1) type, (2, 3) element, (4) affiliation and (5) species
string               TypeGraphObjectDescription(void);
string               TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type);
string               TypeElementDescription(void);
string               BelongDescription(void);
string               SpeciesDescription(void);

Finalize both methods outside the class body.

In the method with a formal parameter, add returning the description of new types of library graphical elements according to the specified type:

//+------------------------------------------------------------------+
//| 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)              :
      //--- 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_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)    :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

The previous method, which returned the current object type, now returns the result of calling its overloaded method the current object type is passed to:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(void)
  {
   return this.TypeElementDescription(this.TypeGraphElement());
  }
//+------------------------------------------------------------------+


Since the operation of buttons in a group directly depends on which group the button belongs to, implement the output of the group index to the log as a text message to control the correct selection of the button group.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, namely in "The cursor is inside the active area, the left mouse button is clicked" event handler, add handling the button working in the group and write the button group indices to the log data output string:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- The mouse button released outside the element means refusal to interact with the element
   if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge())
     {
      //--- If this is a simple button, set the initial background color
      if(!this.Toggle())
         this.SetBackgroundColor(this.BackgroundColorInit(),false);
      //--- If this is the toggle button, set the initial color depending on whether the button is pressed or not
      else
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundColorToggleONInit(),false);
      //--- Set the initial frame color
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- The mouse button released within the element means a  click on the control
   else
     {
      //--- If this is a simple button, set the color for "The cursor is over the active area" status
      if(!this.Toggle())
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
      //--- If this is the toggle button,
      else
        {
         //--- if the button does not work in the group, set its state to the opposite,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- if the button is not pressed yet, set it to the pressed state
         else if(!this.State())
            this.SetState(true);
         //--- set the background color for "The cursor is over the active area" status depending on whether the button is clicked or not
         this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleONMouseOver() : this.BackgroundColorMouseOver(),false);
        }
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Set the frame color for "The cursor is over the active area" status
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
//--- Redraw the object
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

We have several operation modes for toggle buttons. A single toggle button can be either pressed or released. If there are several toggle buttons located in the same container and having the same group, then they work in such a way that pressing one of them leads to the release of the rest of the buttons in this group. If we press the already pressed button again, it is released.


If we add a group button flag for each group of buttons and each of them will have the same group, then such buttons will work a little differently. Similarly, pressing one button causes the rest to be released, but pressing an already pressed button will not release it. So, one of the buttons will always be pressed in this case.

Logging the group number is a debug message. After debugging, it will be deleted, but in order to check the operation validity, we sometimes need to see the group the clicked button belongs to.

Rename the GroupButton() method returning the group button flag to make it clear that the method returns exactly the flag, rather than the group number:

   bool              State(void)                         const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE);                                }
   
//--- (1) Set and (2) return the group flag
   void              SetGroupButtonFlag(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,flag);                                        }
   bool              GroupButtonFlag(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP);                                }
   
//--- (1,2) Set and (3) return the main background color for the 'enabled' status
   void              SetBackgroundColorToggleON(const color colour,const bool set_init_color)


Base class of WinForms object lists

WinForms object list objects have similar functionality, so it is advisable to create a base object that has common functionality for its descendants, and from which the rest will be inherited, as well as in which their own unique functionality inherent in the child class will be created.

In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ library folder, create a new file ElementsListBox.mqh of the CElementsListBox class.

The class should be inherited from the base container class and the class file should be included into it:

//+------------------------------------------------------------------+
//|                                              ElementsListBox.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 "..\Containers\Container.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the WForms control list              |
//+------------------------------------------------------------------+
class CElementsListBox : public CContainer
  {
  }


In the private section of the class, declare a method that returns the coordinates of the next created object in the list. In the protected section, declare a method that creates the specified number of the specified WinForms objects. In the public section of the class, declare a parametric constructor and methods for setting and returning permission to place objects in the list in several columns, as well as a method that sets the width of each column:

//+------------------------------------------------------------------+
//| Class of the base object of the WForms control list              |
//+------------------------------------------------------------------+
class CElementsListBox : public CContainer
  {
private:
//--- Return the coordinates of the next object placed in the list
   void              GetCoordsObj(CWinFormBase *obj,int &x,int &y);
protected:
//--- Create the specified number of specified WinForms objects
   void              CreateElements(ENUM_GRAPH_ELEMENT_TYPE element_type,
                                    const int count,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h,
                                    uint new_column_width=0,
                                    const bool autosize=true);
public:
//--- Constructor
                     CElementsListBox(const long chart_id,
                                      const int subwindow,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
//--- (1) Set and (2) return the flag of horizontal display of columns
   void              SetMultiColumn(const bool flag)        { this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,flag);          }
   bool              MultiColumn(void)                const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN);  }
//--- (1) Set and (2) return the width of each column
   void              SetColumnWidth(const uint value)       { this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,value);         }
   uint              ColumnWidth(void)                const { return (uint)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH);  }
   
  };
//+------------------------------------------------------------------+


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

In the class constructor, specify the type of the graphical element and the type of the graphical object of the library, set the size of a simple frame of one pixel, set the default colors for the frame and texts inside the object, disable placing the list by columns, as well as specify the column width to zero:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CElementsListBox::CElementsListBox(const long chart_id,
                                   const int subwindow,
                                   const string name,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetMultiColumn(false);
   this.SetColumnWidth(0);
  }
//+------------------------------------------------------------------+


The method that creates the specified number of certain WinForms objects:

//+------------------------------------------------------------------+
//| Create the specified number of certain WinForms objects          |
//+------------------------------------------------------------------+
void CElementsListBox::CreateElements(ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int count,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      uint new_column_width=0,
                                      const bool autosize=true)
  {
//--- Set the width of columns if the value greater than zero has been passed
   if(new_column_width>0)
     {
      if(this.ColumnWidth()!=new_column_width)
         this.SetColumnWidth(new_column_width);
     }
//--- Create a pointer to the created WinFormBase object
   CWinFormBase *obj=NULL;
//--- In the loop through the specified number of objects
   for(int i=0;i<count;i++)
     {
      //--- Get the coordinates of the created object
      int coord_x=x, coord_y=y;
      this.GetCoordsObj(obj,coord_x,coord_y);
      //--- If the object could not be created, send the appropriate message to the log and move on to the next one
      if(!this.CreateNewElement(element_type,coord_x,coord_y,w,h,clrNONE,255,true,false))
        {
         ::Print(DFUN,MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ,this.TypeElementDescription(element_type));
         continue;
        }
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(element_type));
         continue;
        }
      //--- Set the frame size of the created object to zero
      obj.SetBorderSizeAll(0);
      //--- Set the opacity of the base object and the default background color
      obj.SetOpacity(this.Opacity());
      obj.SetBackgroundColor(this.BackgroundColor(),true);
      obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER);
      obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN);
     }
//--- If the flag of auto resizing the base object is passed to the method,
//--- set the auto resize mode to "increase and decrease"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

The method logic is described in the code comments. I believe, all is clear there. Auto resizing of the panel the created objects are built on is necessary to align the dimensions of the panel with the created elements. Some classes do not require this, which is why a flag has been introduced to indicate the need to align the dimensions of the panel to its content.


The method that returns the coordinates of the next object placed in the list:

//+------------------------------------------------------------------+
//| Return the coordinates of the next object placed in the list     |
//+------------------------------------------------------------------+
void CElementsListBox::GetCoordsObj(CWinFormBase *obj,int &x,int &y)
  {
//--- Save the coordinates passed to the method in the variables
   int coord_x=x;
   int coord_y=y;
//--- If the flag of using multiple columns is not set,
   if(!this.MultiColumn())
     {
      //--- set the X coordinate the same as the one passed to the method,
      //--- set the Y coordinate for the first object in the list to be equal to the one passed to the method,
      //--- set the rest 4 pixels lower than the bottom edge of the previous object located above.
      //--- After setting the coordinates to the variables, leave the method
      x=coord_x;
      y=(obj==NULL ? coord_y : obj.BottomEdgeRelative()+4);
      return;
     }
//--- If multiple columns can be used
//--- If this is the first object in the list, 
   if(obj==NULL)
     {
      //--- set the coordinates the same as those passed to the method and leave
      x=coord_x;
      y=coord_y;
      return;
     }
//--- If this is not the first object in the list
//--- If (the bottom border of the previous object + 4 pixels) is below the bottom border of the ListBox panel (the next object will go beyond the borders),
   if(obj.BottomEdge()+4>this.BottomEdge())
     {
      //--- If the columns width is zero, then the X coordinate of the created object will be the right border of the previous object + 6 pixels
      //--- Otherwise, if the width of the columns is greater than zero, then the X coordinate of the created object will be the X coordinate of the previous one + the column width
      //--- The Y coordinate will be the value passed to the method (start placing objects in a new column)
      x=(this.ColumnWidth()==0 ? obj.RightEdgeRelative()+6 : int(obj.CoordXRelative()+this.ColumnWidth()));
      y=coord_y;
     }
//--- If the created object is placed within the ListBox panel,
   else
     {
      //--- the X coordinate of the created object will be the offset of the previous one from the panel edge minus the width of its frame,
      //--- the Y coordinate will be the lower border of the previous object located above plus 4 pixels
      x=obj.CoordXRelative()-this.BorderSizeLeft();
      y=obj.BottomEdgeRelative()+4;
     }
  }
//+------------------------------------------------------------------+

Here the logic of the method is described in detail in the comments to the code. The method calculates the coordinate of the next object based on the initial coordinates where the very first object of the list should be located, as well as based on the coordinates of the previous objects already located in the list. In addition, if a flag that allows objects to be arranged in several columns is set, then the method calculates whether the next created object will fit into the area of its panel (only its location coordinate is considered, not the entire object). If an object (its Y coordinate) fits within the panel, then it is built under the previous one. If the coordinate goes beyond the panel, then the object is built to the right relative to the right border of the previous object in the initial Y coordinate. This means the beginning of the construction of a new column. After the objects are positioned inside the panel, its dimensions are adjusted to the internal contents in the method the current one is called from. Thus, all inaccuracies in the location of the lowest objects are corrected. Inaccuracies (the Y coordinate of the bottom object is inside the panel, while its remaining part is outside the limits) can be formed due to the fact that the height of one object in the list may differ from the other, since later we will be able to place different objects in the lists. Therefore, instead of calculating the dimensions of the future object and taking into account whether it will fall entirely into the panel area and whether the distance from its bottom edge to the bottom edge of the panel will be correct, it is much easier for us to simply adjust the dimensions of the panel to the content already created inside it.


Since we now have a base object for WinForms list objects, we need to modify the already created CheckedListBox list object.

Let's make improvements in the object class in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh.

Instead of including the panel object class to the class file,

#include "..\Containers\Panel.mqh"

include the file of the newly created class instead. Accordingly, we will now inherit from it:

//+------------------------------------------------------------------+
//|                                               CheckedListBox.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 "ElementsListBox.mqh"
//+------------------------------------------------------------------+
//| CheckedListBox object class of the WForms controls               |
//+------------------------------------------------------------------+
class CCheckedListBox : public CElementsListBox
  {


In the declaration of the method creating a specified number of CheckBox object, add the formal parameters for specifying the created object width and the new column width value:

public:
//--- Create the specified number of CheckBox objects
   void              CreateCheckBox(const int count,const int width,const int new_column_width=0);
//--- Constructor


In the class constructor, namely in its initialization list, pass the parameters to the new parent class:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CCheckedListBox::CCheckedListBox(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CElementsListBox(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
  }
//+------------------------------------------------------------------+


The method creating the specified number of CheckBox objects has now been redesigned since the new parent class features the method for creating a specified number of objects with the specified type:

//+------------------------------------------------------------------+
//| Create the specified number of CheckBox objects                  |
//+------------------------------------------------------------------+
void CCheckedListBox::CreateCheckBox(const int count,const int width,const int new_column_width=0)
  {
//--- Create a pointer to the CheckBox object
   CCheckBox *obj=NULL;
//--- Create the specified number of CheckBox objects
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,count,2,2,width,DEF_CHECK_SIZE+1,new_column_width);
//--- In the loop by the created number of objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CHECKBOX));
         continue;
        }
      //--- Set the left center alignment of the checkbox and the text
      obj.SetCheckAlign(ANCHOR_LEFT);
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Set the object text
      obj.SetText("CheckBox"+string(i+1));
     }
  }
//+------------------------------------------------------------------+

So create the object list first and then specify the necessary parameters for each object in the loop by the number of created objects.

The method has become shorter and more readable.


ListBox and ButtonListBox WinForms object classes

Let's start the development of the new ListBox WinForms object class.

This object is a simple text list, in which you can select any item. Since we need the rows of the list to be able to interact with the mouse, while the text label object (class of the CLabel library) does not have such functionality, it would be logical to use the class of button objects to display the list. They can react to hovering the mouse over them and be selected (button pressed).


To make the buttons look like text list items, we need to make their border color match the background color. In this case, the button will blend into the background and only the text on it will be visible. When hovering the cursor over the button area (visually on the text), the background color of the text changes. When clicking on the text (button), it becomes selected (the button is pressed).

In order for the list created from buttons to behave like the ListBox in MS Visual Studio, we need to include all the buttons of the list into a group (set the group flag for them) and make them toggles (with the ability to have two states - on/off). Each button (list row) will have a group index corresponding to the group number of the panel, while the group flag set for each of the list buttons will not allow deselecting an already selected list item.

In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ library folder, create a new file ListBox.mqh of the CListBox class.

The class should be derived from the base class of list objects and its file should be included into the created class file:

//+------------------------------------------------------------------+
//|                                                      ListBox.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 "ElementsListBox.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the WForms control list              |
//+------------------------------------------------------------------+
class CListBox : public CElementsListBox
  {
  }


In the private section of the class, we will declare a virtual method for creating a new graphical object, while in the public section, declare the method for creating the list and the parametric constructor:

//+------------------------------------------------------------------+
//| Class of the base object of the WForms control list              |
//+------------------------------------------------------------------+
class CListBox : public CElementsListBox
  {
private:
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
public:
//--- Create a list from the specified number of rows (Label objects)
   void              CreateList(const int line_count);
//--- Constructor
                     CListBox(const long chart_id,
                              const int subwindow,
                              const string name,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
  };
//+------------------------------------------------------------------+


In the parametric constructor, specify the graphical element type and the library object type, and set default values for the object frame, frame color and text, as well as disable creating several columns. Set the column width to zero:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CListBox::CListBox(const long chart_id,
                   const int subwindow,
                   const string name,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CElementsListBox(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_LIST_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetMultiColumn(false);
   this.SetColumnWidth(0);
  }
//+------------------------------------------------------------------+


The method that creates a list out of the specified number of rows:

//+--------------------------------------------------------------------+
//| Create the list from the specified number of rows (Button objects) |
//+--------------------------------------------------------------------+
void CListBox::CreateList(const int count)
  {
//--- Create the pointer to the Button object
   CButton *obj=NULL;
//--- Create the specified number of Button objects
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,this.Width()-4,12,0,false);
//--- In the loop by the created number of objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Set left center text alignment
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Set the object text
      obj.SetFontSize(8);
      obj.SetText("ListBoxItem"+string(i+1));
      //--- Set the frame colors equal to the background colors and the flag of the toggle button
      obj.SetBorderColor(obj.BackgroundColor(),true);
      obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown());
      obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver());
      obj.SetToggleFlag(true);
      obj.SetGroupButtonFlag(true);
     }
  }
//+------------------------------------------------------------------+

The method is identical to the revised CheckedListBox method above. It creates button objects as rows and sets the border color settings for them so that it blends with the background color. The toggle button flag and the flag of a button working in a group is set for each created button. The group index for each button is inherited from the panel it is placed on.


The virtual method creating a new graphical object:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                         const int obj_num,
                                         const string obj_name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const color colour,
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
//--- create the Button object
   CGCnvElement *element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
//--- set the object relocation flag and relative coordinates
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

The button object is created in the method and the minimum parameters are set for it — the relocation flag and the relative coordinates of the object.

At this stage, this is all that is needed for this class to work. A bit later, I will extend the classes of our list objects with the functionality necessary to get the selected objects and send messages.


Now let's create a list object class of button objects. Such an object will combine the buttons created on the panel. The buttons can be assigned to different groups, group button flags can be set for them together with other parameters of their properties. Thus, it will be possible to create different groups of buttons in one object (on one panel).

In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ library folder, create a new file ButtonListBox.mqh of the CButtonListBox class.

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

//+------------------------------------------------------------------+
//|                                                ButtonListBox.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 "ElementsListBox.mqh"
//+------------------------------------------------------------------+
//| ButtonListBox object class of WForms controls                    |
//+------------------------------------------------------------------+
class CButtonListBox : public CElementsListBox
  {
  }

In the private section of the class, declare the method for creating a new graphical object. In the public section, declare the method for creating a specified number of buttons, the parametric constructor and the methods for handling buttons created on the panel:

//+------------------------------------------------------------------+
//| ButtonListBox object class of WForms controls                    |
//+------------------------------------------------------------------+
class CButtonListBox : public CElementsListBox
  {
private:
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
public:
//--- Create the specified number of CheckBox objects
   void              CreateButton(const int count,const int width,const int height,const int new_column_width=0);
//--- Constructor
                     CButtonListBox(const long chart_id,
                                    const int subwindow,
                                    const string name,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h);

//--- (1) Set and (2) return the group of the button specified by index
   void              SetButtonGroup(const int index,const int group);
   int               ButtonGroup(const int index);
//--- (1) Set and (2) return the flag of the group button specified by the button index
   void              SetButtonGroupFlag(const int index,const bool flag);
   bool              ButtonGroupFlag(const int index);
//--- Sets the specified button "multiselect" mode
   void              SetMultiSelect(const bool flag);
//--- (1) Set and (2) return the "Toggle button" flag of the button specified by index
   void              SetButtonToggle(const int index,const bool flag);
   bool              ButtonToggle(const int index);
//--- Set the "Toggle button" flag for all buttons of the object
   void              SetToggle(const bool flag);
   
  };
//+------------------------------------------------------------------+

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

In the parametric constructor, specify the graphical element type, library object type and set the default values for the panel frame and for its color and style, as well as the color for object texts on the panel:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CButtonListBox::CButtonListBox(const long chart_id,
                               const int subwindow,
                               const string name,
                               const int x,
                               const int y,
                               const int w,
                               const int h) : CElementsListBox(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
  }
//+------------------------------------------------------------------+


The method that creates the specified number of Button objects:

//+------------------------------------------------------------------+
//| Create the specified number of Button objects                    |
//+------------------------------------------------------------------+
void CButtonListBox::CreateButton(const int count,const int width,const int height,const int new_column_width=0)
  {
//--- Create the pointer to the Button object
   CButton *obj=NULL;
//--- Create the specified number of Button objects
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,width,height,new_column_width);
//--- In the loop by the created number of objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Set left center text alignment
      obj.SetTextAlign(ANCHOR_CENTER);
      //--- Set the object text
      obj.SetText("Button"+string(i+1));
     }
  }
//+------------------------------------------------------------------+

The method is identical to similar methods of the above list objects. First, the specified number of button objects are created. Then they are set to their default values in a loop by the created number of objects.


The virtual method creating a new graphical object:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CButtonListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                               const int obj_num,
                                               const string obj_name,
                                               const int x,
                                               const int y,
                                               const int w,
                                               const int h,
                                               const color colour,
                                               const uchar opacity,
                                               const bool movable,
                                               const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
//--- create the CButton object
   CGCnvElement *element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
//--- set the object relocation flag and relative coordinates
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

Here everything is exactly the same as in the similar methods of list object classes.


The method that sets the group of the button specified by index:

//+------------------------------------------------------------------+
//| Set the group of the button specified by index                   |
//+------------------------------------------------------------------+
void CButtonListBox::SetButtonGroup(const int index,const int group)
  {
   CButton *butt=this.GetElement(index);
   if(butt==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON),(string)index);
      return;
     }
   butt.SetGroup(group);
  }
//+------------------------------------------------------------------+

Get the object from the list by the specified index. If failed to get the object, inform of that in the log and exit the method.
Set the group index passed to the method for the obtained object.


The method that returns the group of the button specified by index:

//+------------------------------------------------------------------+
//| Return the group of the button specified by index                |
//+------------------------------------------------------------------+
int CButtonListBox::ButtonGroup(const int index)
  {
   CButton *butt=this.GetElement(index);
   return(butt!=NULL ? butt.Group() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

Get the object by index from the list. If the object has been received, get its group, otherwise, return -1.


The method that sets the flag of the group button specified by index:

//+------------------------------------------------------------------+
//| Set the flag of the group button specified by the button index   |
//+------------------------------------------------------------------+
void CButtonListBox::SetButtonGroupFlag(const int index,const bool flag)
  {
   CButton *butt=this.GetElement(index);
   if(butt==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON),(string)index);
      return;
     }
   butt.SetGroupButtonFlag(flag);
  }
//+------------------------------------------------------------------+

Get the object from the list by the specified index. If failed to get the object, inform of that in the log and leave the method.
Set the flag, passed to the method, for the obtained object.


The method that returns the flag of the group button specified by the button index:

//+------------------------------------------------------------------+
//| Return the flag of the group button specified by the button index|
//+------------------------------------------------------------------+
bool CButtonListBox::ButtonGroupFlag(const int index)
  {
   CButton *butt=this.GetElement(index);
   return(butt!=NULL ? butt.GroupButtonFlag() : false);
  }
//+------------------------------------------------------------------+

Get the object from the list by the specified index. If the object has been received, return the group button flag, otherwise, return false.


The method that sets the "multi-selection" mode of the buttons:

//+------------------------------------------------------------------+
//| Set the "multiselect" mode of the buttons                        |
//+------------------------------------------------------------------+
void CButtonListBox::SetMultiSelect(const bool flag)
  {
   int group=this.Group()+(flag ? 1 : 0);
   for(int i=0;i<this.ElementsTotal();i++)
      this.SetButtonGroup(i,group+(flag ? i : 0));
  }
//+------------------------------------------------------------------+

The method allows making the buttons placed on the panel work independently of each other, so that each one can be pressed/released independently of the other buttons on the panel. To do this, each button should have its own group.

First, set the initial value for the group of the first button as the group of the panel plus 1 if we want to allow multi-selection of buttons, or 0 if the buttons should be dependent on each other. Next, in the loop by all the created buttons, either set a new group for each subsequent button calculated as the panel group number plus the cycle index, which means that each button will have a group equal to the button position in the list + 1, or add zero to the panel group number, which means that all buttons will have a group equal to the panel group.


The method that sets the "Toggle button" flag for the button specified by index:

//+------------------------------------------------------------------+
//| Set the "Toggle button" flag                                     |
//| button specified by index                                        |
//+------------------------------------------------------------------+
void CButtonListBox::SetButtonToggle(const int index,const bool flag)
  {
   CButton *butt=this.GetElement(index);
   if(butt==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON),(string)index);
      return;
     }
   butt.SetToggleFlag(flag);
  }
//+------------------------------------------------------------------+

Get the button by the specified index from the list. If failed to get the button, inform of that in the log and exit the method.
The flag passed in the method is set for the obtained button.


The method that returns the "Toggle button" flag of the button specified by index:

//+------------------------------------------------------------------+
//| Return the "Toggle button" flag                                  |
//| button specified by index                                        |
//+------------------------------------------------------------------+
bool CButtonListBox::ButtonToggle(const int index)
  {
   CButton *butt=this.GetElement(index);
   return(butt!=NULL ? butt.Toggle() : false);
  }
//+------------------------------------------------------------------+

Get the button by the specified index from the list. If the button has been received, return its toggle button flag, otherwise, return false.


The method that sets the "Toggle button" flag for all buttons of the object:

//+------------------------------------------------------------------+
//| Set the "Toggle button" flag to all buttons of the object        |
//+------------------------------------------------------------------+
void CButtonListBox::SetToggle(const bool flag)
  {
   for(int i=0;i<this.ElementsTotal();i++)
      this.SetButtonToggle(i,flag);
  }
//+------------------------------------------------------------------+

In the loop by all list objects, set the specified flag for each next button using the SetButtonToggle() method considered above.

I have created all the objects planned for the current article.

Now we need to make sure that the library "knows" about them and we can create them from our programs.

Let's make small improvements in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh of the base container object. Later, we will need to add previously created objects to the container rather than create new objects inside it. To simplify this action, we need to split the method that creates the new attached object into two — one method will create a new object, and the other will set some default properties to the new object. When adding an object, we will not create a new one, but add the specified one to the list, and, if necessary, change its parameters.

In the protected section of the class, declare the new method to set parameters for the created object:

protected:
//--- Adjust the element size to fit its content
   bool              AutoSizeProcess(const bool redraw);
//--- Set parameters for the attached object
   void              SetObjParams(CWinFormBase *obj,const color colour);
   
public:

Let's write its implementation outside the class body:

//+------------------------------------------------------------------+
//| Set parameters for the attached object                           |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
//--- 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_GROUPBOX)
      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          :
        //--- set the frame color equal to the background color 
        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       :
        //--- set the object text color depending on the one passed to the method:
        //--- either the container text color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        //--- Set the background color to transparent
        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 the Button WinForms object
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        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   :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        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;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Here we simply moved the block of code that sets the object parameters from the CreateNewElement() method. Pass the pointer to the created object and the color, passed to the CreateNewElement() method, to the method. Here I also added handling new objects, which is similar to handling the previously created CheckedListBox object. Therefore, I did not have to write anything extra — I just indicated that these objects are handled similarly to the CheckedListBox object in one 'switch' case.

The modified method that creates a new bound element:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CContainer::CreateNewElement(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)
  {
//--- If the object type is less than the base WinForms object
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- report the error and return 'false'
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- If failed to create a new graphical element, return 'false'
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Set parameters for the created object
   this.SetObjParams(obj,colour);
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Redraw the panel and all added objects, and return 'true'
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Now we have calling the new method instead of the code block moved to the new method. Accordingly, the code has become shorter, simpler and clearer, and now we can use this division of one method into two to attach already created objects to the list.


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

Add new object files created today to the list of include files:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+


Add code blocks for creating new objects to the method creating a new graphical object:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string obj_name,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+


The same changes concerning the creation of new objects have been added to the same CreateNewGObject() method of the GroupBox container object class in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh. I will not dwell on them here. You can view the changes in the files attached below.

Include the ButtonListBox object class file to \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh of the graphical element collection class:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\WForms\Containers\GroupBox.mqh"
#include "..\Objects\Graph\WForms\Containers\Panel.mqh"
#include "..\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh"
#include "..\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh"
#include "..\Objects\Graph\Standard\GStdVLineObj.mqh"

After that, all other objects created in the current article will become visible in programs created on the basis of the library.

These are all objects and improvements planned for today.


Test

To perform the test, let's use the EA from the previous article and save it in \MQL5\Experts\TestDoEasy\Part112\ as TstDE112.mq5.

In the second group of objects GroupBox2, create the new ButtonListBox and ListBox list objects. The last object location coordinates in the container will depend on the appearance of the CheckedListBox and ButtonListBox objects. If the flag indicating the possibility of building lists in several columns is enabled for them, then the ListBox object will be located below, otherwise, to the right of the first two.

Also check the operation of group buttons — the possibility of their work in different groups and the ability of the button to be released when pressed again.

In the EA inputs, add two new parameters:

//--- input parameters
sinput   bool                          InpMovable           =  true;                   // Panel Movable flag
sinput   ENUM_INPUT_YES_NO             InpAutoSize          =  INPUT_YES;              // Panel Autosize
sinput   ENUM_AUTO_SIZE_MODE           InpAutoSizeMode      =  AUTO_SIZE_MODE_GROW;    // Panel Autosize mode
sinput   ENUM_BORDER_STYLE             InpFrameStyle        =  BORDER_STYLE_SIMPLE;    // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign         =  ANCHOR_CENTER;          // Label text align
sinput   ENUM_INPUT_YES_NO             InpTextAutoSize      =  INPUT_NO;               // Label autosize
sinput   ENUM_ANCHOR_POINT             InpCheckAlign        =  ANCHOR_LEFT;            // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign    =  ANCHOR_LEFT;            // Check label text align
sinput   ENUM_CHEK_STATE               InpCheckState        =  CHEK_STATE_UNCHECKED;   // Check flag state
sinput   ENUM_INPUT_YES_NO             InpCheckAutoSize     =  INPUT_YES;              // CheckBox autosize
sinput   ENUM_BORDER_STYLE             InpCheckFrameStyle   =  BORDER_STYLE_NONE;      // CheckBox border style
sinput   ENUM_ANCHOR_POINT             InpButtonTextAlign   =  ANCHOR_CENTER;          // Button text align
sinput   ENUM_INPUT_YES_NO             InpButtonAutoSize    =  INPUT_YES;              // Button autosize
sinput   ENUM_AUTO_SIZE_MODE           InpButtonAutoSizeMode=  AUTO_SIZE_MODE_GROW;    // Button Autosize mode
sinput   ENUM_BORDER_STYLE             InpButtonFrameStyle  =  BORDER_STYLE_NONE;      // Button border style
sinput   bool                          InpButtonToggle      =  false;                  // Button toggle flag
sinput   bool                          InpListBoxMColumn    =  false;                  // ListBox MultiColumn flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

The first parameter will indicate the possibility of creating a list consisting of several columns (if all objects do not fit in the height of the panel), the second parameter will set the possibility of multiple selection of buttons in the group.

Add the code block for creating new objects to the EA's OnInit() handler (only a part of the code is shown):

      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects
         gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1);
         if(gbox2!=NULL)
           {
            //--- set the "indented frame" type, the frame color matches the main panel background color,
            //--- while the text color is the background color of the last attached panel darkened by 1
            gbox2.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox2.SetBorderColor(pnl.BackgroundColor(),true);
            gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox2.SetText("GroupBox2");
            //--- Create the CheckedListBox object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false);
            //--- get the pointer to the CheckedListBox object by its index in the list of bound objects of the CheckBox type
            CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0);
            //--- If CheckedListBox is created and the pointer to it is received
            if(clbox!=NULL)
              {
               clbox.SetMultiColumn(InpListBoxMColumn);
               clbox.SetColumnWidth(0);
               clbox.CreateCheckBox(4,66);
              }
            //--- Create the ButtonListBox object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,clbox.BottomEdgeRelative()+6,160,30,clrNONE,255,true,false);
            //--- get the pointer to the ButtonListBox object by its index in the list of attached objects of the Button type
            CButtonListBox *blbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0);
            //--- If ButtonListBox is created and the pointer to it is received
            if(blbox!=NULL)
              {
               blbox.SetMultiColumn(InpListBoxMColumn);
               blbox.SetColumnWidth(0);
               blbox.CreateButton(4,66,16);
               blbox.SetMultiSelect(InpButtListMSelect);
               blbox.SetToggle(InpButtonToggle);
               for(int i=0;i<blbox.ElementsTotal();i++)
                 {
                  blbox.SetButtonGroup(i,(i % 2==0 ? blbox.Group()+1 : blbox.Group()+2));
                  blbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false));
                 }
              }
            //--- Create the ListBox object
            int lbx=4;
            int lby=blbox.BottomEdgeRelative()+6;
            int lbw=146;
            if(!InpListBoxMColumn)
              {
               lbx=blbox.RightEdgeRelative()+6;
               lby=14;
               lbw=100;
              }
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,lbx,lby,lbw,70,clrNONE,255,true,false);
            //--- get the pointer to the ListBox object by its index in the list of attached objects of Button type
            CListBox *lbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0);
            //--- If ListBox has been created and the pointer to it has been received
            if(lbox!=NULL)
              {
               lbox.CreateList(4);
              }
           }
        }
      //--- Redraw all objects according to their hierarchy
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Here we create two new list objects in the GroupBox2 object. If they are successfully created, then we create four objects in each of them.

The full code of the EA's OnInit() handler can be found in the files attached below.


Compile the EA and launch it on the chart:


Here we can see that the top two ButtonListBox buttons work a little differently than the bottom two. This depends on the flags set. In the first case, the buttons cannot be disabled when pressed again. We can disable one button only by pressing the second one. In the second case, the button can be disabled both by clicking on the second one, and by pressing again on the already enabled one. This is affected by the group button flag. If it is set, then the buttons are completely dependent on each other because they work in the group.

The list object works correctly. But the appearance leaves much to be desired. In MS Visual Studio, the list is more compressed, the objects are closer to each other. But here we are still prevented from doing so by the fact that if you place objects closer to each other, then changing the background color of the object when interacting with the mouse does not always work correctly. As soon as we find and fix this, we will be able to adjust the appearance of the created objects.


What's next?

In the next article, I will continue my work on graphical elements of GUI programs based on the library.

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

Back to contents

*Previous articles within the series:

DoEasy. Controls (Part 1): First steps
DoEasy. Controls (Part 2): Working on the CPanel class
DoEasy. Controls (Part 3): Creating bound controls
DoEasy. Controls (Part 4): Panel control, Padding and Dock parameters
DoEasy. Controls (Part 5): Base WinForms object, Panel control, AutoSize parameter
DoEasy. Controls (Part 6): Panel control, auto resizing the container to fit inner content
DoEasy. Controls (Part 7): Text label control
DoEasy. Controls (Part 8): Base WinForms objects by categories, GroupBox and CheckBox controls
DoEasy. Controls (Part 9): Re-arranging WinForms object methods, RadioButton and Button controls
DoEasy. Controls (Part 10): WinForms objects — Animating the interface
DoEasy. Controls (Part 11): WinForms objects — groups, CheckedListBox WinForms object



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

Attached files |
MQL5.zip (4405.07 KB)
Neural networks made easy (Part 21): Variational autoencoders (VAE) Neural networks made easy (Part 21): Variational autoencoders (VAE)
In the last article, we got acquainted with the Autoencoder algorithm. Like any other algorithm, it has its advantages and disadvantages. In its original implementation, the autoenctoder is used to separate the objects from the training sample as much as possible. This time we will talk about how to deal with some of its disadvantages.
Data Science and Machine Learning (Part 07): Polynomial Regression Data Science and Machine Learning (Part 07): Polynomial Regression
Unlike linear regression, polynomial regression is a flexible model aimed to perform better at tasks the linear regression model could not handle, Let's find out how to make polynomial models in MQL5 and make something positive out of it.
CCI indicator. Three transformation steps CCI indicator. Three transformation steps
In this article, I will make additional changes to the CCI affecting the very logic of this indicator. Moreover, we will be able to see it in the main chart window.
Experiments with neural networks (Part 2): Smart neural network optimization Experiments with neural networks (Part 2): Smart neural network optimization
In this article, I will use experimentation and non-standard approaches to develop a profitable trading system and check whether neural networks can be of any help for traders. MetaTrader 5 as a self-sufficient tool for using neural networks in trading.