DoEasy. Элементы управления (Часть 12): Базовый объект-список, WinForms-объекты ListBox и ButtonListBox

22 июля 2022, 13:18
Artyom Trishkin
0
281

Содержание


Концепция

Продолжаем работу над созданием WinForms-объектов в библиотеке. В прошлой статье мы создали объект CheckedListBox, который по сути является списком объектов CheckBox. Но так как далее мы будем создавать ещё различные списки WinForms-объектов, то логично теперь создать класс базового объекта-списка WinForms-объектов, и на его основе создавать уже все различные остальные. Класс будет содержать основной функционал для работы со списками WinForms-объектов, а впоследствии — и различных данных, привязанных к элементу управления (DataBindings в MS Visual Studio), данные из которых используются для отображения в строках списков элементов, что позволит отображать  в списках совершенно разные данные из окружения как самой библиотеки, так и терминала и его баз данных и, конечно же, иметь к ним доступ посредством этих списков.

На основе этого базового класса сегодня создадим ещё один WinForms-объект ListBox — простой список, отображающий некую коллекцию данных. Список пока будет отображать лишь создаваемые при его построении наименования пунктов списка, позволяющие взаимодействовать с мышкой (менять цвет фона при наведении и выборе). Но практической пользы объект пока никакой иметь не будет (это будет лишь визуальное отображение будущего функционала объекта), так как для создания событийного функционала WinForms-объектов нам необходимо ещё создать некое их количество — чтобы определиться с теми данными, которые необходимо передавать в обработчики событий. И, чем больше различных объектов будет создано, тем больше мы будем иметь представление о необходимых данных и их структуре, которые нужны будут для правильного создания событийного функционала WinForms-объектов.

Также сегодня создадим объект-список кнопок. Так как ListBox — его строки, как раз создаются на основе класса объекта CButton, то вполне логично будет создать дополнительный объект, отображающий в своём списке набор кнопок (аналогично объекту CheckedListBox, отображающему в своём списке объекты CheckBox). Да, такого объекта нет в списке стандартных элементов управления в MS Visual Studio, но что нам мешает поэкспериментировать?


Доработка классов библиотеки

После последних обновлений клиентского терминала перестал работать торговый класс CTrading библиотеки — доступ к его приватным методам OpenPosition() и PlaceOrder() из наследуемых классов был прекращён.
Поэтому в файле \MQL5\Include\DoEasy\Trading.mqh установим для этих методов защищённую секцию класса:

//--- Возвращает метод обработки ошибки
   ENUM_ERROR_CODE_PROCESSING_METHOD   ResultProccessingMethod(const uint result_code);
//--- Корректировка ошибок
   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) Открывает позицию, (2) устанавливает отложенный ордер
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:                                    
//--- Возвращает индекс объекта-запроса в списке по (1) идентификатору,
//--- (2) тикету ордера, (3) тикету позиции в запросе
   int                  GetIndexPendingRequestByID(const uchar id);
   int                  GetIndexPendingRequestByOrder(const ulong ticket);
   int                  GetIndexPendingRequestByPosition(const ulong ticket);

public:

Здесь мы внутри приватной секции класса создали защищённую, а затем опять вернули приватную.


В файле \MQL5\Include\DoEasy\Defines.mqh в перечисление типов графических элементов добавим три новых типа:

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Стандартный графический объект
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Расширенный стандартный графический объект
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Объект тени
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Элемент
   GRAPH_ELEMENT_TYPE_FORM,                           // Форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Подложка объекта-панели
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   //--- Ниже нужно вписывать типы объектов "контейнер"
   GRAPH_ELEMENT_TYPE_WF_CONTAINER,                   // Windows Forms базовый объект-контейнер
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
   GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                    // Windows Forms GroupBox
   //--- Ниже нужно вписывать типы объектов "стандартный элемент управления"
   GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                 // Windows Forms базовый стандартный элемент управления
   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,           // Базовый объект-список Windows Forms элементов
   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
  };
//+------------------------------------------------------------------+


В самом конце списка целочисленных свойств графического элемента на канвасе добавим два новых свойства и увеличим количество целочисленных свойств с 83 до 85:

//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Идентификатор элемента
   CANV_ELEMENT_PROP_TYPE,                            // Тип графического элемента
   //---...
   //---...
   CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,     // Цвет флажка проверки элемента управления при нажатии мышки на элемент управления
   CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,     // Цвет флажка проверки элемента управления при наведении мышки на элемент управления
   CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,           // Горизонтальное отображение столбцов в элементе управления ListBox
   CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,           // Ширина каждого столбца элемента управления ListBox
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (85)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+


Добавим два этих новых свойства в перечисление возможных критериев сортировки графических элементов на канвасе:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#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_CANV_ELEMENT_ID = 0,                       // Сортировать по идентификатору элемента
   SORT_BY_CANV_ELEMENT_TYPE,                         // Сортировать по типу графического элемента
   //---...
   //---...
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_DOWN,  // Сортировать по цвету флажка проверки элемента управления при нажатии мышки на элемент управления
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_OVER,  // Сортировать по цвету флажка проверки элемента управления при наведении мышки на элемент управления
   SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN,        // Сортировать по флагу горизонтального отображения столбцов в элементе управления ListBox
   SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH,        // Сортировать по ширине каждого столбца элемента управления ListBox
//--- Сортировка по вещественным свойствам

//--- Сортировка по строковым свойствам
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Сортировать по имени объекта-элемента
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Сортировать по имени графического ресурса
   SORT_BY_CANV_ELEMENT_TEXT,                         // Сортировать по тексту графического элемента
  };
//+------------------------------------------------------------------+

Теперь мы сможем выбирать, сортировать и фильтровать объекты по этим двум новым свойствам. В принципе, в этом нет особой необходимости для работы с графическими объектами для построения GUI (не вижу вариантов использования поиска по этим свойствам), но вписываем в эти списки мы абсолютно все свойства GUI-объектов для того, чтобы можно было затем построить в своей программе графический интерфейс для программы визуального создания графической оболочки непосредственно в окне чарта и работать с ним — вот там потребуется получать и изменять абсолютно все свойства графических элементов.

В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений библиотеки, переименуем индекс MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ на новый и удалим индекс MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ:

//--- WinForms-стандартные
   MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,             // Базовый стандартный элемент управления WinForms
   MSG_GRAPH_ELEMENT_TYPE_WF_LABEL,                   // Элемент управления Label
   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                // Элемент управления CheckBox
   MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,             // Элемент управления RadioButton
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON,                  // Элемент управления Button
   MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,       // Базовый объект-список Windows Forms элементов
   MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                // Элемент управления ListBox
   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,        // Элемент управления CheckedListBox
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // Элемент управления ButtonListBox
   
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе

...

//--- CPanel
   MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ,   // Не удалось создать объект-подложку
   MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE,           // Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником

//--- ElementsListBox
   MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,     // Не удалось получить графический элемент 
                                                 

//--- CButtonListBox
   MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON,         // Не удалось установить группу кнопке с индексом 
   MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON,        // Не удалось установить флаг "Переключатель" кнопке с индексом 

//--- Целочисленные свойства графических элементов

...

   MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN, // Цвет флажка проверки элемента управления при нажатии мышки на элемент управления
   MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER, // Цвет флажка проверки элемента управления при наведении мышки на элемент управления
   MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,       // Горизонтальное отображение столбцов в элементе управления ListBox
   MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,       // Ширина каждого столбца элемента управления ListBox
//--- Вещественные свойства графических элементов

//--- Строковые свойства графических элементов
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Имя объекта-графического элемента
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Имя графического ресурса
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Текст графического элемента

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


и текстовые сообщения, соответствующие вновь добавленным индексам:

//--- WinForms-стандартные
   {"Базовый стандартный элемент управления 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 "},
   
//--- Целочисленные свойства графических элементов

...

   {"Цвет флажка проверки элемента управления при нажатии мышки на элемент управления","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"},
   
//--- Строковые свойства графических элементов
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},

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


Нам потребуется возвращать описание указанного типа графического элемента. На данный момент в классе базового графического объекта библиотеки есть метод TypeElementDescription(), возвращающий описание типа текущего графического объекта — т.е. описание типа самого себя. Но нам нужно будет возвращать описание того графического элемента, который указан во входных параметрах метода. Поэтому сделаем так: в существующий метод добавим формальный параметр, в котором будем передавать тип объекта, и напишем перегруженный метод, возвращающий тип текущего объекта.

В файле \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh сделаем необходимые изменения.
В публичной секции объявим новый перегруженный метод, в котором будем передавать в него тип объекта, тогда как прошлый метод будет передавать в новый метод тип текущего объекта:

//--- Возвращает тип графического объекта (ENUM_OBJECT), рассчитанного из значения типа объекта (ENUM_OBJECT_DE_TYPE), переданного в метод
   ENUM_OBJECT       GraphObjectType(const ENUM_OBJECT_DE_TYPE obj_type) const
                       { 
                        return ENUM_OBJECT(obj_type-OBJECT_DE_TYPE_GSTD_OBJ-1);
                       }
   
//--- Возвращает описание типа графического (1) объекта, (2,3) элемента, (4) принадлежности, (5) вида графического объекта
string               TypeGraphObjectDescription(void);
string               TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type);
string               TypeElementDescription(void);
string               BelongDescription(void);
string               SpeciesDescription(void);

и за пределами тела класса доработаем оба метода.

В методе с формальным параметром допишем возврат описания новых типов графических элементов библиотеки в соответствии с указанным типом:

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
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)               :
      //--- Контейнеры
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)          :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)              :
      //--- Стандартные элементы управления
      type==GRAPH_ELEMENT_TYPE_WF_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"
     );
  }  
//+------------------------------------------------------------------+

А прошлый метод, который возвращал тип текущего объекта, теперь будет возвращать результат вызова своего перегруженного метода, в который передаётся тип текущего объекта:

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(void)
  {
   return this.TypeElementDescription(this.TypeGraphElement());
  }
//+------------------------------------------------------------------+


Так как работа кнопок, состоящих в группе, напрямую зависит от того, в какую группу входит кнопка, то для контроля за правильностью выбора группы кнопки в пишем вывод номера группы в тестовое сообщение в журнал.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh в обработчике события "Курсор в пределах активной области, отжата кнопка мышки (левая)" добавим обработку кнопки, работающей в группе, и впишем в строку вывода данных в журнал номера группы кнопки:

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| отжата кнопка мышки (левая)                                      |
//+------------------------------------------------------------------+
void CButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Если кнопка мышки отпущена за пределами элемента - это отказ от взаимодействия с элементом
   if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge())
     {
      //--- Если это простая кнопка - устанавливаем изначальный цвет фона
      if(!this.Toggle())
         this.SetBackgroundColor(this.BackgroundColorInit(),false);
      //--- Если это кнопка-переключатель - устанавливаем изначальный цвет в зависимости от того нажата кнопка или нет
      else
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundColorToggleONInit(),false);
      //--- Устанавливаем изначальный цвет рамки
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Выводим тестовое сообщение в журнал
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- Если кнопка мышки отпущена в пределах элемента - это щелчок по элементу управления
   else
     {
      //--- Если это простая кнопка - устанавливаем цвет фона для состояния "Курсор мышки над активной зоной"
      if(!this.Toggle())
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
      //--- Если это кнопка-переключатель -
      else
        {
         //--- если кнопка не работает в группе - устанавливаем её состояние на противоположное,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- иначе - если кнопка ещё не нажата - устанавливаем её в нажатое состояние
         else if(!this.State())
            this.SetState(true);
         //--- устанавливаем цвет фона для состояния "Курсор мышки над активной зоной" в зависимости от того нажата кнопка или нет
         this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleONMouseOver() : this.BackgroundColorMouseOver(),false);
        }
      //--- Выводим тестовое сообщение в журнал
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Устанавливаем цвет рамки для состояния "Курсор мышки над активной зоной"
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
//--- Перерисовываем объект
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

Для кнопок-переключателей у нас есть несколько режимов работы. Одиночная кнопка-переключатель может быть как в нажатом, так и отжатом состоянии. Если есть несколько кнопок-переключателей, расположенных в одном контейнере, и имеющих одинаковую группу, то такие кнопки работают так, что нажатие одной приводит к отжатию остальных кнопок этой группы. При этом, если повторно нажать на уже нажатую кнопку, то она отожмётся.


Если же для группы кнопок, каждой добавить флаг групповой кнопоки, и у каждой из них будет одна и та же группа, то такие кнопки будут работать уже несколько иначе. Точно так же, нажатие одной приводит к отжатию остальных, но нажатие на уже нажатую кнопку её не отожмёт. Т.е., в этом случае всегда будет нажата какая-то одна из кнопок.

Вывод в журнал номера группы — это отладочное сообщение — после отладки оно будет удалено, но для понимания правильности работы нам иногда необходимо видеть какой группе принадлежит кнопка, по которой был щелчок мышки.

Метод GroupButton(), возвращающий флаг групповой кнопки, переименуем — чтобы было понятно, что метод возвращает именно флаг, а не номер группы:

   bool              State(void)                         const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE);                                }
   
//--- (1) Устанавливает, (2) возвращает флаг группы
   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) Устанавливает, (3) возвращает основной цвет фона для состояния "включено"
   void              SetBackgroundColorToggleON(const color colour,const bool set_init_color)


Базовый класс списков WinForms-объектов


Объекты-списки WinForms-объектов имеют схожий функционал, поэтому целесообразно для них создать базовый объект, имеющий общий функционал для своих наследников, и от которого будут наследоваться остальные, и в которых уже будет создаваться свой уникальный функционал, присущий дочернему классу.

В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ создадим новый файл ElementsListBox.mqh класса CElementsListBox.

Класс должен быть унаследован от базового класса-контейнера, и к нему должен быть подключен файл этого класса:

//+------------------------------------------------------------------+
//|                                              ElementsListBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4 
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Containers\Container.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта списка элементов управления WForms        |
//+------------------------------------------------------------------+
class CElementsListBox : public CContainer
  {
  }


В приватной секции класса объявим метод, возвращающий координаты очередного создаваемого объекта в списке. В защищённой секции объявим метод, создающий указанное количество указанных WinForms-объектов. В публичной секции класса объявим параметрический конструктор и методы для установки и возврата разрешения размещать объекты в списке в нескольких столбцах и метод, устанавливающий ширину каждого столбца:

//+------------------------------------------------------------------+
//| Класс базового объекта списка элементов управления WForms        |
//+------------------------------------------------------------------+
class CElementsListBox : public CContainer
  {
private:
//--- Возвращает координаты очередного размещаемого в списке объекта
   void              GetCoordsObj(CWinFormBase *obj,int &x,int &y);
protected:
//--- Создаёт указанное количество указанных WinForms-объектов
   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:
//--- Конструктор
                     CElementsListBox(const long chart_id,
                                      const int subwindow,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
//--- (1) Устанавливает, (2) возвращает флаг горизонтального отображения столбцов
   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) Устанавливает, (2) возвращает ширину каждого столбца
   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);  }
   
  };
//+------------------------------------------------------------------+


Рассмотрим объявленные методы подробнее.

В конструкторе класса указываем тип графического элемента и тип графического объекта библиотеки, указываем размер простой рамки величиной в один пиксель, устанавливаем цвета по умолчанию для рамки и текстов внутри объекта, и задаём запрет на расположение списка по столбцам, а также устанавливаем ширину стольбцов в ноль:

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+


Метод, создающий указанное количество указанных WinForms-объектов:

//+------------------------------------------------------------------+
//| Создаёт указанное количество указанных WinForms-объектов         |
//+------------------------------------------------------------------+
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)
  {
//--- Устанавливаем ширину столбцов, если перелано значение больше нуля
   if(new_column_width>0)
     {
      if(this.ColumnWidth()!=new_column_width)
         this.SetColumnWidth(new_column_width);
     }
//--- Создаём указатель на создаваемый объект WinFormBase
   CWinFormBase *obj=NULL;
//--- В цикле по указанному количеству объектов
   for(int i=0;i<count;i++)
     {
      //--- Получаем координаты создаваемого объекта
      int coord_x=x, coord_y=y;
      this.GetCoordsObj(obj,coord_x,coord_y);
      //--- Если объект создать не удалось - выводим об этом сообщение в журнал и идём к следующему
      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;
        }
      //--- Получаем созданный объект из списка по индексу цикла
      obj=this.GetElement(i);
      //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(element_type));
         continue;
        }
      //--- Устанавливаем размер рамки созданного объекта в ноль
      obj.SetBorderSizeAll(0);
      //--- Устанавливаем непрозрачность как у базового объекта и цвета фона по умолчанию
      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(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

Логика метода описана в комментариях к коду и, надеюсь, там всё понятно. Автоизменение размера панели, на которой выстраиваются создаваемые объекты, необходимо для выравнивания размеров панели под созданные элементы. В некоторых классах этого не нужно будет делать, поэтому и введён флаг, указывающий на необходимость выровнять размеры панели под её содержимое.


Метод, возвращающий координаты очередного размещаемого в списке объекта:

//+------------------------------------------------------------------+
//| Возвращает координаты очередного размещаемого в списке объекта   |
//+------------------------------------------------------------------+
void CElementsListBox::GetCoordsObj(CWinFormBase *obj,int &x,int &y)
  {
//--- Сохраним в переменных переданные в метод координаты
   int coord_x=x;
   int coord_y=y;
//--- Если не стоит флаг использования нескольких столбцов -
   if(!this.MultiColumn())
     {
      //--- устанавливаем координату X такую же, как переданная в метод,
      //--- координату Y устанавливаем для первого объекта в списке равную переданной в метод,
      //--- для остальных - ниже на 4 пикселя нижнего края предыдущего, вышестоящего объекта.
      //--- После установки координат в переменные, уходим из метода
      x=coord_x;
      y=(obj==NULL ? coord_y : obj.BottomEdgeRelative()+4);
      return;
     }
//--- Если возможно использование нескольких столбцов
//--- Если это первый объект в списке - 
   if(obj==NULL)
     {
      //--- устанавливаем координаты такими же, как переданные в метод и уходим
      x=coord_x;
      y=coord_y;
      return;
     }
//--- Если это не первый объект в списке
//--- Если (нижняя граница прошлого объекта + 4 пикселя) ниже нижней границы панели ListBox (следующий объект выйдет за границы),
   if(obj.BottomEdge()+4>this.BottomEdge())
     {
      //--- Если ширина колонок равно нулю, то координатой X создаваемого объекта будет правая граница прошлого объекта + 6 пикселей
      //--- Иначе, если ширина колонок больше нуля, то координатой X создаваемого объекта будет координата X прошлого + ширина колонки
      //--- Координатой Y будет переданное в метод значение (начинаем размещать объекты в новой колонке)
      x=(this.ColumnWidth()==0 ? obj.RightEdgeRelative()+6 : int(obj.CoordXRelative()+this.ColumnWidth()));
      y=coord_y;
     }
//--- Если создаваемый объект помещается в пределы панели ListBox -
   else
     {
      //--- координатой X создаваемого объекта будет смещение прошлого от края панели минус ширина её рамки,
      //--- координатой Y будет нижняя граница прошлого, вышестоящего объекта плюс 4 пикселя
      x=obj.CoordXRelative()-this.BorderSizeLeft();
      y=obj.BottomEdgeRelative()+4;
     }
  }
//+------------------------------------------------------------------+

Здесь тоже логика метода подробно расписана в комментариях к коду. Метод рассчитывает координату следующего объекта, исходя из начальных координат, в которых должен располагаться самый первый объект списка, и из координат уже расположенных в списке объектов (предыдущих). Кроме того, если установлен флаг, разрешающий располагать объекты в несколько столбцов, то в методе рассчитывается вместится ли следующий создаваемый объект в область своей панели (считается только его координата расположения, а не весь объект). Если объект (его координата Y) помещается в пределах панели, то он строится под предыдущим. Если же координата выходит за пределы панели, то объект строится справа относительно правой границы предыдущего объекта в начальной координате Y — это означает начало построения нового столбца. После расположения объектов внутри панели, её размеры подстраиваются под внутреннее содержимое в методе, из которого вызывается этот. Таким образом, все неточности расположения самых нижних объектов будут скорректированы. Неточности (координата Y нижнего объекта находится внутри панели, а её остальная часть выходит за пределы) могут образоваться из-за того, что высота одного объекта в списке может отличаться от другого, так как впоследствии мы сможем размещать в списках разные объекты. Поэтому, вместо того, чтобы рассчитывать размеры будущего объекта, и учитывать попадёт ли он целиком в область панели, и будет ли корректна дистанция от его нижнего края до нижнего края панели, нам гораздо проще просто подстроить размеры панели под уже созданное внутри неё содержимое.


В связи с тем, что теперь у нас есть базовый объект для WinForms-объектов-списков, нам нужно изменить уже созданный объект-список CheckedListBox.

Внесём исправления в класс этого объекта в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh.

Вместо подключения к файлу этого класса файла класса объекта-панели

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

подключим файл только что созданного класса. Соответственно, и наследоваться теперь будем от него:

//+------------------------------------------------------------------+
//|                                               CheckedListBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ElementsListBox.mqh"
//+------------------------------------------------------------------+
//| Класс объекта CheckedListBox элементов управления WForms         |
//+------------------------------------------------------------------+
class CCheckedListBox : public CElementsListBox
  {


В объявлении метода, создающего указанное количество объектов CheckBox, добавим формальные параметры для указания ширины создаваемого объекта и нового значения ширины колонок:

public:
//--- Создаёт указанное количество объектов CheckBox
   void              CreateCheckBox(const int count,const int width,const int new_column_width=0);
//--- Конструктор


В конструкторе класса, в его списке инициализации, теперь передаём параметры в новый родительский класс:

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+


Метод, создающий указанное количество объектов CheckBox, теперь переработан, так как в новом родительском классе есть метод для создания указанного количество объектов с указанным типом:

//+------------------------------------------------------------------+
//| Создаёт указанное количество объектов CheckBox                   |
//+------------------------------------------------------------------+
void CCheckedListBox::CreateCheckBox(const int count,const int width,const int new_column_width=0)
  {
//--- Создаём указатель на объект CheckBox
   CCheckBox *obj=NULL;
//--- Создаём указанное количество объектов CheckBox
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,count,2,2,width,DEF_CHECK_SIZE+1,new_column_width);
//--- В цикле по созданному количеству объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Получаем созданный объект из списка по индексу цикла
      obj=this.GetElement(i);
      //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CHECKBOX));
         continue;
        }
      //--- Устанавливаем выравнивание флажка проверки и текста слева по центру
      obj.SetCheckAlign(ANCHOR_LEFT);
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Устанавливаем текст объекта
      obj.SetText("CheckBox"+string(i+1));
     }
  }
//+------------------------------------------------------------------+

Поэтому теперь сначала создаём список объектов, а потом в цикле по количеству созданных объектов устанавливаем каждому необходимые параметры.

Метод стал короче и более читаемым.


Классы WinForms-объектов ListBox и ButtonListBox

Приступим к созданию нового класса Winforms-объекта ListBox.

Этот объект представляет из себя простой текстовый список, в котором можно выбрать какой-либо пункт из списка. Так как нам необходимо, чтобы строки списка могли взаимодействовать с мышкой, а объект-текстовая метка (класс библиотеки CLabel) не обладает таким функционалом, то логично для отображения списка использовать класс объектов-кнопок. Они могут реагировать на наведение мышки на них и быть выбранными (нажата кнопка).


Просто, чтобы сделать кнопки похожими на элементы текстового списка, нужно сделать цвет их рамки равным цвету фона. В этом случае кнопка будет сливаться с фоном и будет виден лишь текст, находящийся на ней. При наведении указателя на область кнопки (визуально на надпись), цвет фона надписи будет изменяться. А при щелчке по надписи (кнопке) она станет выделенной (кнопка нажатой).

Чтобы список, созданный из кнопок, вёл себя подобно списку ListBox в MS Visual Studio, нам нужно сделать все кнопки списка групповыми (установить для них флаг группы) и сделать их кнопками-переключателями (с возможностью иметь два состояния — вкл/выкл). Каждой кнопке (строке списка) будет присвоен номер группы, соответствующий номеру группы панели, а флаг группы, установленный для каждой из кнопок списка, не даст снимать выделение с уже выделенного элемента списка.

В папке бибилотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ создадим новый файл ListBox.mqh класса CListBox.

Класс должен быть унаследован от базового класса объектов-списков и его файл должен быть подключен к файлу создаваемого класса:

//+------------------------------------------------------------------+
//|                                                      ListBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ElementsListBox.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта списка элементов управления WForms        |
//+------------------------------------------------------------------+
class CListBox : public CElementsListBox
  {
  }


В приватной секции класса объявим виртуальный метод для создания нового графического объекта, а в публичной — метод для создания списка и параметрический конструктор:

//+------------------------------------------------------------------+
//| Класс базового объекта списка элементов управления WForms        |
//+------------------------------------------------------------------+
class CListBox : public CElementsListBox
  {
private:
//--- Создаёт новый графический объект
   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:
//--- Создаёт список из указанного количества строк (объектов Label)
   void              CreateList(const int line_count);
//--- Конструктор
                     CListBox(const long chart_id,
                              const int subwindow,
                              const string name,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
  };
//+------------------------------------------------------------------+


В параметрическом конструкторе укажем тип графического элемента и тип объекта библиотеки и установим значения по умолчанию для рамки объекта, цвета рамки и текста, а также укажем запрет на создание нескольких колонок и ширину колонки равной нулю:

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+


Метод, создающий список из указанного количества строк:

//+------------------------------------------------------------------+
//| Создаёт список из указанного количества строк (объектов Button)  |
//+------------------------------------------------------------------+
void CListBox::CreateList(const int count)
  {
//--- Создаём указатель на объект Button
   CButton *obj=NULL;
//--- Создаём указанное количество объектов Button
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,this.Width()-4,12,0,false);
//--- В цикле по созданному количеству объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Получаем созданный объект из списка по индексу цикла
      obj=this.GetElement(i);
      //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Устанавливаем выравнивание текста слева по центру
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Устанавливаем текст объекта
      obj.SetFontSize(8);
      obj.SetText("ListBoxItem"+string(i+1));
      //--- Устанавливаем цвета рамки равными цветам фона и флаг кнопки-переключателя
      obj.SetBorderColor(obj.BackgroundColor(),true);
      obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown());
      obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver());
      obj.SetToggleFlag(true);
      obj.SetGroupButtonFlag(true);
     }
  }
//+------------------------------------------------------------------+

Метод идентичен вышерассмотренному переработанному методу объекта CheckedListBox. В нём в качестве строк создаются объекты-кнопки, и для них устанавливаются такие параметры цвета рамки, чтобы она сливалась с цветом фона. Для каждой созданной кнопки ставится флаг кнопки-переключателя и флаг кнопки, работающей в группе. Номер группы для каждой кнопки наследуется от панели, на которой они размещаются.


Виртуальный метод, создающий новый графический объект:

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
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);
//--- создаём объект Button
   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);
//--- установим объекту флаг перемещаемости и относительные координаты
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

В методе создаётся объект-кнопка и для неё устанавливаются минимальные параметры — флаг перемещаемости и относительные координаты объекта.

На данном этапе это всё, что нужно для работы этого класса. Естественно, далее мы будем расширять классы наших объектов-списков функционалом, необходимым для получения выделенных объектов и отправки сообщений, но это мы будем делать чуть позже.


Теперь создадим класс объекта-списка объектов-кнопок. Такой объект будет объединять в себе кнопки, созданные на панели. Кнопки могут быть распределены по разным группам, для них можно будет устанавливать флаги групповых кнопок и остальные параметры их свойств. Таким образом, можно будет в одном объекте (на одной панели) создавать разные группы кнопок.

В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ создадим новый файл ButtonListBox.mqh класса CButtonListBox.

Класс должен быть унаследован от класса базового объекта-списка и его файл должен быть подключен к файлу создаваемого класса:

//+------------------------------------------------------------------+
//|                                                ButtonListBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ElementsListBox.mqh"
//+------------------------------------------------------------------+
//| Класс объекта ButtonListBox элементов управления WForms          |
//+------------------------------------------------------------------+
class CButtonListBox : public CElementsListBox
  {
  }

В приватной секции класса объявим метод для создания нового графического объекта, а в публичной секции объявим метод для создания указанного количества кнопок, параметрический конструктор, и методы для работы с созданными на панели кнопками:

//+------------------------------------------------------------------+
//| Класс объекта ButtonListBox элементов управления WForms          |
//+------------------------------------------------------------------+
class CButtonListBox : public CElementsListBox
  {
private:
//--- Создаёт новый графический объект
   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:
//--- Создаёт указанное количество объектов CheckBox
   void              CreateButton(const int count,const int width,const int height,const int new_column_width=0);
//--- Конструктор
                     CButtonListBox(const long chart_id,
                                    const int subwindow,
                                    const string name,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h);

//--- (1) Устанавливает, (2) возвращает группу указанной по индексу кнопки
   void              SetButtonGroup(const int index,const int group);
   int               ButtonGroup(const int index);
//--- (1) Устанавливает, (2) возвращает флаг групповой кнопки указанной по индексу кнопки
   void              SetButtonGroupFlag(const int index,const bool flag);
   bool              ButtonGroupFlag(const int index);
//--- Устанавливает указанный режим "мультивыбор" кнопок
   void              SetMultiSelect(const bool flag);
//--- (1) Устанавливает, (2) возвращает флаг "Кнопка-переключатель" указанной по индексу кнопки
   void              SetButtonToggle(const int index,const bool flag);
   bool              ButtonToggle(const int index);
//--- Устанавливает флаг "Кнопка-переключатель" всем кнопкам объекта
   void              SetToggle(const bool flag);
   
  };
//+------------------------------------------------------------------+

Рассмотрим объявленные методы подробнее.

В параметрическом конструкторе укажем тип графического элемента, тип объекта библиотеки, и установим значения по умолчанию для рамки панели, её цвета и стиля, а также цвет для текстов объектов на панели:

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+


Метод, создающий указанное количество объектов Button:

//+------------------------------------------------------------------+
//| Создаёт указанное количество объектов Button                     |
//+------------------------------------------------------------------+
void CButtonListBox::CreateButton(const int count,const int width,const int height,const int new_column_width=0)
  {
//--- Создаём указатель на объект Button
   CButton *obj=NULL;
//--- Создаём указанное количество объектов Button
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,width,height,new_column_width);
//--- В цикле по созданному количеству объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Получаем созданный объект из списка по индексу цикла
      obj=this.GetElement(i);
      //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Устанавливаем выравнивание текста слева по центру
      obj.SetTextAlign(ANCHOR_CENTER);
      //--- Устанавливаем текст объекта
      obj.SetText("Button"+string(i+1));
     }
  }
//+------------------------------------------------------------------+

Метод идентичен аналогичным методам вышерассмотренных объектов-списков. Сначала создаётся указанное количество объектов-кнопок, а затем, в цикле по созданному количеству объектов, им устанавливаются значения по умолчанию.


Виртуальный метод, создающий новый графический объект:

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
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);
//--- создаём объект CButton
   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);
//--- установим объекту флаг перемещаемости и относительные координаты
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

И здесь всё точно так же, как в аналогичных методах классов объектов-списков.


Метод, устанавливающий группу указанной по индексу кнопке:

//+------------------------------------------------------------------+
//| Устанавливает группу указанной по индексу кнопке                 |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Получаем объект из списка по указанному индексу. Если объект получить не удалось — сообщаем об этом в журнал и уходим из метода.
Полученному объекту устанавливаем номер группы, переданный в метод.


Метод, возвращающий группу указанной по индексу кнопки:

//+------------------------------------------------------------------+
//| Возвращает группу указанной по индексу кнопки                    |
//+------------------------------------------------------------------+
int CButtonListBox::ButtonGroup(const int index)
  {
   CButton *butt=this.GetElement(index);
   return(butt!=NULL ? butt.Group() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

Получаем объект по индексу из списка. Если объект получен — возвращаем его группу, иначе — возвращаем -1.


Метод, устанавливающий флаг групповой кнопки, указанной по индексу кнопке:

//+------------------------------------------------------------------+
//| Устанавливает флаг групповой кнопки указанной по индексу кнопки  |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Получаем объект из списка по указанному индексу. Если объект получить не удалось — сообщаем об этом в журнал и уходим из метода.
Полученному объекту устанавливаем переданный в метод флаг.


Метод, возвращающий флаг групповой кнопки указанной по индексу кнопки:

//+------------------------------------------------------------------+
//| Возвращает флаг групповой кнопки указанной по индексу кнопки     |
//+------------------------------------------------------------------+
bool CButtonListBox::ButtonGroupFlag(const int index)
  {
   CButton *butt=this.GetElement(index);
   return(butt!=NULL ? butt.GroupButtonFlag() : false);
  }
//+------------------------------------------------------------------+

Получаем объект из списка по указанному индексу. Если объект получен — возвращаем флаг групповой кнопки, иначе — возвращаем false.


Метод, устанавливающий режим "мультивыбор" кнопок:

//+------------------------------------------------------------------+
//| Устанавливает режим "мультивыбор" кнопок                         |
//+------------------------------------------------------------------+
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));
  }
//+------------------------------------------------------------------+

Метод позволяет сделать так, чтобы кнопки, размещённые на панели, могли работать независимо друг от друга — чтобы каждая могла быть нажата/отжата независимо от остальных кнопок панели. Для этого необходимо каждой кнопке назначить собственную группу.

Сначала задаём начальное значение для группы первой кнопки как группа панели плюс 1 — если нужно разрешить мультивыбор кнопок, либо 0 — если кнопки должны быть зависимы друг от друга. Далее в цикле по всем созданным кнопкам устанавливаем либо новую группу каждой последующей кнопке, рассчитываемую как номер группы панели плюс индекс цикла, что означает, что каждая кнопка будет иметь группу, равную положению кнопки в списке+1, либо к номеру группы панели прибавляем ноль, что означает, что все кнопки будут иметь группу, равную группе панели.


Метод, устанавливающий флаг "Кнопка-переключатель" указанной по индексу кнопке:

//+------------------------------------------------------------------+
//| Устанавливает флаг "Кнопка-переключатель"                        |
//| указанной по индексу кнопке                                      |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Получаем кнопку по указанному индексу из списка. Если кнопку получить не удалось — сообщаем об этом в журнал и уходим из метода.
Полученной кнопке устанавливаем переданный в метод флаг.


Метод, возвращающий флаг "Кнопка-переключатель" указанной по индексу кнопке:

//| Возвращает флаг "Кнопка-переключатель"                           |
//| указанной по индексу кнопке                                      |
//+------------------------------------------------------------------+
bool CButtonListBox::ButtonToggle(const int index)
  {
   CButton *butt=this.GetElement(index);
   return(butt!=NULL ? butt.Toggle() : false);
  }
//+------------------------------------------------------------------+

Получаем кнопку по указанному индексу из списка. Если кнопка получена — возвращаем её флаг кнопки-переключателя, иначе — возвращаем false.


Метод, устанавливающий флаг "Кнопка-переключатель" всем кнопкам объекта:

//+------------------------------------------------------------------+
//| Устанавливает флаг "Кнопка-переключатель" всем кнопкам объекта   |
//+------------------------------------------------------------------+
void CButtonListBox::SetToggle(const bool flag)
  {
   for(int i=0;i<this.ElementsTotal();i++)
      this.SetButtonToggle(i,flag);
  }
//+------------------------------------------------------------------+

В цикле по всем объектам в списке устанавливаем каждой очередной кнопке указанный флаг при помощи метода SetButtonToggle(), рассмотренного выше.

Все запланированные на сегодня объекты мы создали.

Теперь нужно сделать так, чтобы библиотека о них "знала" и мы могли их создавать из своих программ.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh базового объекта-контейнера напишем небольшие доработки: нам нужно будет впоследствии не создавать новые объекты прямо внутри контейнера, а добавлять в него уже ранее созданные. Для упрощения такого действия нам нужно разделить метод, создающий новый присоединённый объект, на два — один метод будет создавать новый объект, а другой — устанавливать новому объекту некоторые свойства по умолчанию. При добавлении объекта мы будем не создавать новый, а добавлять указанный в список, и при необходимости изменять его параметры.

В защищённой секции класса объявим новый метод для установки параметров созданному объекту:

protected:
//--- Подстраивает размеры элемента под его внутреннее содержимое
   bool              AutoSizeProcess(const bool redraw);
//--- Устанавливает параметры присоединённому объекту
   void              SetObjParams(CWinFormBase *obj,const color colour);
   
public:

За пределами тела класса напишем его реализацию:

//+------------------------------------------------------------------+
//| Устанавливает параметры присоединённому объекту                  |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
//--- Устанавливаем объекту цвет текста как у базового контейнера
   obj.SetForeColor(this.ForeColor(),true);
//--- Если созданный объект не является контейнером - устанавливаем для него такую же группу, как у его базового объекта
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
      obj.SetGroup(this.Group());
//--- В зависимости от типа объекта
   switch(obj.TypeGraphElement())
     {
      //--- Для WinForms-объектов "Контейнер", "Панель", "GroupBox"
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
        //--- устанавливаем цвет рамки равным цвету фона 
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- Для WinForms-объектов "Label", "CheckBox", "RadioButton"
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
        //--- устанавливаем цвет текста объекта в зависимости от переданного в метод:
        //--- либо цвет текста контейнера, либо переданный в метод.
        //--- Цвет рамки устанавливаем равным цвету текста
        //--- Цвет фона устанавливаем прозрачным
        obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        break;
      //--- Для WinForms-объекта "Button"
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
        //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод:
        //--- цвет фона устанавливаем  в зависимости от переданного в метод:
        //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод.
        //--- Цвет рамки устанавливаем равным цвету текста
        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;
      //--- Для WinForms-объекта "ListBox", "CheckedListBox", "ButtonListBox"
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
        //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод:
        //--- цвет фона устанавливаем  в зависимости от переданного в метод:
        //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод.
        //--- Цвет рамки устанавливаем равным цвету текста
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Сюда мы просто перенесли блок кода, устанавливающий параметры объекту, из метода CreateNewElement(). В метод передаём указатель на созданный объект и цвет, передаваемый в метод CreateNewElement(). Также мы тут добавили обработку новых объектов, и она совпадает с обработкой ранее созданного объекта CheckedListBox. Поэтому нам не пришлось писать что-то дополнительно — мы лишь указали, что эти объекты обрабатываются аналогично объекту CheckedListBox в одном кейсе переключателя switch.

Изменённый метод, создающий новый присоединённый элемент:

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
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)
  {
//--- Если тип объекта - меньше, чем базовый WinForms-объект
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- сообщаем об ошибке и возвращаем false
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- Если не удалось создать новый графический элемент - возвращаем false
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Устанавливаем параметры созданному объекту
   this.SetObjParams(obj,colour);
//--- Если у панели включено автоизменение размеров и есть привязанные объекты - вызываем метод изменения размеров
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Перерисовываем панель и все добавленные объекты и возвращаем true
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Теперь вместо перенесённого в новый метод блока кода у нас стоит вызов этого нового метода. Соответственно, код стал короче, проще и понятнее, и теперь мы сможем использовать такое разделение одного метода на два для присоединения уже созданных объектов к списку.


Доработаем класс объекта-панели в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.

В список включаемых файлов добавим файлы новых объектов, созданных сегодня:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+


В методе, создающем новый графический объект, добавим блоки кода для создания новых объектов:

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+


Идентичные изменения по созданию новых объектов добавлены в такой же метод CreateNewGObject() класса объекта-контейнера GroupBox
в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh. Здесь мы их рассматривать не будем — с этими изменениями можно ознакомиться в прилагаемых к статье файлах.

Подключим файл класса объекта ButtonListBox к файлу \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh класса-коллекции графических элементов:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#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"

После этого все остальные созданные сегодня объекты станут видны в прграммах, создаваемых на основе библиотеки.

Всё, на сегодня мы сделали все запланированные объекты и доработки.


Тестирование

Для теста возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part112\ под новым именем TstDE112.mq5.

Как будем тестировать? Во второй группе объектов GroupBox2 создадим новые объекты-списки ButtonListBox и ListBox. Последний объект, его координаты расположения в контейнере, будут зависеть от внешнего вида объектов CheckedListBox и ButtonListBox. Если для них будет включен флаг возможности построения списков в несколько колонок, то объект ListBox будет располагаться снизу, иначе — справа от первых двух.

Также проверим работу групповых кнопок — возможность их работы в разных группах и возможность кнопки быть отжатой при повторном нажатии.

Во входные переменные советника добавим два новых параметра:

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

Первый будет указывать на возможность создания списка в несколько колонок (если все объекты не вмещаются по высоте панели), второй параметр будет задавать возможность множественного выбора кнопок в группе.

В обработчик OnInit() советника добавим блок кода для создания новых объектов (приведена только часть кода):

      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- получим указатель на объект GroupBox по его индексу в списке прикреплённых объектов с типом GroupBox
         gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1);
         if(gbox2!=NULL)
           {
            //--- установим тип рамки "вдавленная рамка", цвет рамки как цвет фона основной панели,
            //--- а цвет текста - затемнённый на 1 цвет фона последней прикреплённой панели
            gbox2.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox2.SetBorderColor(pnl.BackgroundColor(),true);
            gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox2.SetText("GroupBox2");
            //--- Создадим объект CheckedListBox
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false);
            //--- получим указатель на объект CheckedListBox по его индексу в списке прикреплённых объектов с типом CheckBox
            CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0);
            //--- Если CheckedListBox создан и указатель на него получен
            if(clbox!=NULL)
              {
               clbox.SetMultiColumn(InpListBoxMColumn);
               clbox.SetColumnWidth(0);
               clbox.CreateCheckBox(4,66);
              }
            //--- Создадим объект ButtonListBox
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,clbox.BottomEdgeRelative()+6,160,30,clrNONE,255,true,false);
            //--- получим указатель на объект ButtonListBox по его индексу в списке прикреплённых объектов с типом Button
            CButtonListBox *blbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0);
            //--- Если ButtonListBox создан и указатель на него получен
            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));
                 }
              }
            //--- Создадим объект ListBox
            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);
            //--- получим указатель на объект ListBox по его индексу в списке прикреплённых объектов с типом Button
            CListBox *lbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0);
            //--- Если ListBox создан и указатель на него получен
            if(lbox!=NULL)
              {
               lbox.CreateList(4);
              }
           }
        }
      //--- Перерисуем все объекты в порядке их иерархии
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Здесь мы создаём в объекте GroupBox2 два новых объекта-списка и, если они успешно созданы, то в каждом их них создаём по четыре объекта.

С полным кодом обработчика OnInit() советника можно ознакомиться в прилагаемых к статье файлах.


Скомпилируем советник и запустим его на графике:


Здесь мы видим, что две верхние кнопки объекта ButtonListBox работают немного не так, как две нижние. Это зависит от установленных флагов. В первом случае кнопкам запрещено выключаться при повторном нажатии. Отключить одну кнопку можно лишь нажатием на вторую. Во втором же случае, кнопку можно отключить как нажатием на вторую, так и повторным нажатием на уже включенную. На это влияет флаг групповой кнопки. Если он установлен, то кнопки полностью зависимы друг от друга — потому, что работают в группе.

Объект-список работает правильно. Но внешний вид оставляет желать лучшего — в оригинале в MS Visual Studio список более сжат — объекты находятся ближе друг у другу. Но здесь нам пока не даёт так сделать то обстоятельство, что если расположить объекты ближе друг к другу, то не всегда корректно работает изменение цвета фона объекта при взаимодействии его с мышкой. Как только найдём и поправим эту "неисправность", мы сможем подкорректировать внешний вид создаваемых объектов.


Что дальше

В следующей статье продолжим работу над графическими элементами GUI программ, создаваемых на основе библиотеки.

Ниже прикреплены все файлы текущей версии библиотеки, файлы тестового советника и индикатора контроля событий графиков для MQL5. Их можно скачать и протестировать всё самостоятельно. При возникновении вопросов, замечаний и пожеланий вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

DoEasy. Элементы управления (Часть 1): Первые шаги
DoEasy. Элементы управления (Часть 2): Продолжаем работу над классом CPanel
DoEasy. Элементы управления (Часть 3): Создание привязанных элементов управления
DoEasy. Элементы управления (Часть 4): Элемент управления "Панель", параметры Padding и Dock
DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize
DoEasy. Элементы управления (Часть 6): Элемент управления "Панель", автоизменение размеров контейнера под внутреннее содержимое
DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"
DoEasy. Элементы управления (Часть 8): Базовые WinForms-объекты по категориям, элементы управления "GroupBox" и "CheckBox
DoEasy. Элементы управления (Часть 9): Реорганизация методов WinForms-объектов, элементы управления "RadioButton" и "Button"
DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс
DoEasy. Элементы управления (Часть 11): WinForms-объекты — группы, WinForms-объект CheckedListBox



Прикрепленные файлы |
MQL5.zip (4486.27 KB)
Разработка торговой системы на основе индикатора OBV Разработка торговой системы на основе индикатора OBV
Это новая статья, продолжающая нашу серию для начинающих MQL5-программистов, в которой мы учимся строить торговые системы с использованием самых популярных индикаторов. На этот раз мы будем изучать индикатор балансового объема On Balance Volume (OBV) — узнаем, как его использовать и как создать торговую систему на его основе.
Разработка торгового советника с нуля (Часть 20): Новая система ордеров (III) Разработка торгового советника с нуля (Часть 20): Новая система ордеров (III)
Продолжим внедрение новой системы ордеров. Создание такой системы требует хорошего владения MQL5, а также понимания того, как на самом деле работает платформа MetaTrader 5 и какие ресурсы она нам предоставляет.
Разработка торговой системы на основе индикатора Накопления/Распределения - Accumulation/Distribution Разработка торговой системы на основе индикатора Накопления/Распределения - Accumulation/Distribution
Представляю вашему вниманию новую статью из серии, в которой мы учимся создавать торговые системы на основе популярных технических индикаторов. В этой статье мы будем изучать индикатор Накопления/Распределения (Accumulation/Distribution, A/D). Также мы разработаем торговую систему на языке MQL5 для работы в платформе MetaTrader 5, используя несколько простых стратегий.
Разработка торгового советника с нуля (Часть 19): Новая система ордеров (II) Разработка торгового советника с нуля (Часть 19): Новая система ордеров (II)
В данной статье мы будем разрабатывать графическую систему ордеров вида «посмотрите, что происходит». Следует сказать, что мы не начнем с нуля, а модифицируем существующую систему, добавив еще больше объектов и событий на график торгуемого нами актива.