English 中文 Español Deutsch 日本語 Português
preview
DoEasy. Элементы управления (Часть 14): Новый алгоритм именования графических элементов. Продолжаем работу над WinForms-объектом TabControl

DoEasy. Элементы управления (Часть 14): Новый алгоритм именования графических элементов. Продолжаем работу над WinForms-объектом TabControl

MetaTrader 5Примеры | 5 августа 2022, 14:39
943 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

Например, при создании элементов GUI для тестовой программы этой статьи у нас получился такой список графических элементов (видна только первая часть из всех построенных элементов, но и этого достаточно для понимания принятой концепции):


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

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

Кроме создания нового алгоритма именования графических элементов, сегодня продолжим разработку элемента управления TabControl. Создадим для него объект TabHeader, описывающий заголовок вкладки. Этот объект должен будет уметь работать в группе с другими такими же объектами — заголовками других объектов-вкладок. При выборе этого элемента он должен уметь немного увеличиваться в размерах, и при этом должен учитывать расположение набора заголовков всех вкладок на элементе управления TabControl — сверху, снизу, слева или справа, и в зависимости от их расположения, рисовать рамку только в нужном месте объекта. Например, если объект-заголовок вкладки расположен сверху элемента управления TabControl, то рамка, очерчивающая заголовок вкладки, должна рисоваться только с трёх сторон — слева, сверху и справа. Нижняя же сторона заголовка вкладки будет соприкасаться с полем вкладки, на котором должны размещаться объекты этой вкладки. И место соприкосновения не должно иметь нарисованной границы — чтобы заголовок вкладки и поле вкладки были одним целым — без видимого разделения.

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


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

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

В файле \MQL5\Include\DoEasy\Defines.mqh в перечислении типов графических элементов добавим новый тип объекта-контейнера TabControl и добавим два вспомогательных элемента управления ListBoxItem и TabHeader в новой категории:

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
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_TAB_CONTROL,                 // Windows Forms TabControl
   //--- Ниже нужно вписывать типы объектов "стандартный элемент управления"
   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
   //--- Вспомогательные элементы WinForms-объектов
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,               // Windows Forms ListBoxItem
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
  };
//+------------------------------------------------------------------+


В перечислении строковых свойств графического элемента на канвасе добавим новое свойство — описание графического элемента, и увеличим общее количество строковых свойств с 3 до 4:

//+------------------------------------------------------------------+
//| Строковые свойства графического элемента на канвасе              |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_STRING
  {
   CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), // Имя объекта-графического элемента
   CANV_ELEMENT_PROP_NAME_RES,                        // Имя графического ресурса
   CANV_ELEMENT_PROP_TEXT,                            // Текст графического элемента
   CANV_ELEMENT_PROP_DESCRIPTION,                     // Описание графического элемента
  };
#define CANV_ELEMENT_PROP_STRING_TOTAL  (4)           // Общее количество строковых свойств
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#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_TAB_ALIGNMENT,                // Сортировать по местоположению вкладок внутри элемента управления
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Сортировать по местоположению объекта внутри элемента управления
//--- Сортировка по вещественным свойствам

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

Теперь мы сможем выбирать, сортировать и фильтровать графические элементы по новому свойству.


В файле \MQL5\Include\DoEasy\Data.mqh добавим индексы новых сообщений, удалим индекс ненужного сообщения:

//--- 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_LIST_BOX_ITEM,           // Объект коллекции элемента управления ListBox
   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,        // Элемент управления CheckedListBox
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // Элемент управления ButtonListBox
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,              // Заголовок вкладки
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE,                // Элемент управления TabPage
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // Элемент управления TabControl
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе

...

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


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

//--- 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\""},
   {"Объект коллекции элемента управления ListBox","Collection object of the ListBox control"},
   {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""},
   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   {"Заголовок вкладки","Tab header"},
                                                                                             
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

//--- Строковые свойства графических элементов
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},
   {"Описание графического элемента","Description of the graphic element"},
  };
//+---------------------------------------------------------------------+


Чтобы нам можно было получить описание типа графического элемента в приемлемом виде для последующего использования в библиотеке, в файле сервисных функций \MQL5\Include\DoEasy\Services\DELib.mqh создадим функцию, которая из имени константы перечисления типов графических элементов создаст и вернёт описание типа графического элемента:

//+------------------------------------------------------------------+
//| Возвращает тип графического объекта как string                   |
//+------------------------------------------------------------------+
string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   ushort array[];
   int total=StringToShortArray(StringSubstr(::EnumToString(type),18),array);
   for(int i=0;i<total-1;i++)
     {
      if(array[i]==95)
        {
         i+=1;
         continue;
        }
      else
         array[i]+=0x20;
     }
   string txt=ShortArrayToString(array);
   StringReplace(txt,"_Wf_Base","WFBase");
   StringReplace(txt,"_Wf_","");
   StringReplace(txt,"_Obj","");
   StringReplace(txt,"_","");
   StringReplace(txt,"Groupbox","GroupBox");
   return txt;
  }
//+------------------------------------------------------------------+

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

int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);

получаем количество символов подстроки, выделенной из наименования константы перечисления типа.

Разберём на примере константы GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX.

Преобразуем константу перечисления в текст "GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX":

EnumToString(type)

Из полученного текста "GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX" выделяем строку "_WF_CHECKED_LIST_BOX", начиная с символа 18:

StringSubstr(EnumToString(type),18)

и полученную строку "_WF_CHECKED_LIST_BOX" посимвольно копируем в ushort-массив, получая при этом количество скопированных символов:

int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);

В итоге у нас в массиве array[] содержатся коды каждого символа строки "_WF_CHECKED_LIST_BOX".


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

Это делаем в цикле по массиву символов:

   for(int i=0;i<total-1;i++)
     {
      if(array[i]==95)
        {
         i+=1;
         continue;
        }
      else
         array[i]+=0x20;
     }

Первый символ строки и, соответственно, и в массиве, у нас "_", и как только мы встречаем код этого символа (95) в массиве, нам нужно установить индекс цикла на следующий символ за ним.

В строке "_WF_CHECKED_LIST_BOX" это будут отмеченные цветом символы.
После установки индекса цикла на следующий код символа в массиве, сразу же идём на следующую итерацию, тем самым пропуская символ, который нужно оставить без изменений. И попадаем на оператор else, где прибавляем к коду символа в массиве значение 32, что сделает этот символ строчным.

Таким образом, в массиве после всего цикла будут содержаться коды символов строки "_Wf_Checked_List_Box", которые мы преобразуем в строку:

string txt=ShortArrayToString(array);

И далее мы просто заменяем в полученной строке указанные вхождения строк в нужные нам и возвращаем итоговую строку:

   StringReplace(txt,"_Wf_Base","WFBase");
   StringReplace(txt,"_Wf_","");
   StringReplace(txt,"_Obj","");
   StringReplace(txt,"_","");
   StringReplace(txt,"Groupbox","GroupBox");
   return txt;

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


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

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
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_TAB_HEADER          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)         :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE)           :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)        :
      //--- Стандартные элементы управления
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)        :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)              :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)        :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)             :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)      :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)   :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)    :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+


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

В файле класса объекта-графического элемента \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, в его защищённой секции, объявим два метода — возвращающий количество графических элементов по типу и создающий и возвращающий имя графического элемента по его типу:

//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
//--- Копирует массив цветов в указанный массив цветов фона
   void              CopyArraysColors(color &array_dst[],const color &array_src[],const string source);
   
//--- Возвращает количество графических элементов по типу
   int               GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const;
//--- Создаёт и возвращает имя графического элемента по его типу
   string            CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type);
   
private:

Реализацию методов рассмотрим чуть позже.

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

private:
   int               m_shift_coord_x;                          // Смещение координаты X относительно базового объекта
   int               m_shift_coord_y;                          // Смещение координаты Y относительно базового объекта
   struct SData
     {
      //--- Целочисленные свойства объекта
      int            id;                                       // Идентификатор элемента
      int            type;                                     // Тип графического элемента
      //---...
      //---...
      bool           button_toggle;                            // Флаг "Переключатель" элемента управления, имеющего кнопку
      bool           button_state;                             // Состояние элемента управления "Переключатель", имеющего кнопку
      bool           button_group_flag;                        // Флаг группы кнопки
      bool           multicolumn;                              // Горизонтальное отображение столбцов в элементе управления ListBox
      int            column_width;                             // Ширина каждого столбца элемента управления ListBox
      bool           tab_multiline;                            // Несколько рядов вкладок в элементе управления TabControl
      int            tab_alignment;                            // Местоположение вкладок внутри элемента управления
      int            alignment;                                // Местоположение объекта внутри элемента управления
      //--- Вещественные свойства объекта

      //--- Строковые свойства объекта
      uchar          name_obj[64];                             // Имя объекта-графического элемента
      uchar          name_res[64];                             // Имя графического ресурса
      uchar          text[256];                                // Текст графического элемента
      uchar          descript[256];                            // Описание графического элемента
     };
   SData             m_struct_obj;                             // Структура объекта
   uchar             m_uchar_array[];                          // uchar-массив структуры объекта

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

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

//--- Создаёт элемент
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const bool redraw=false);

Теперь имя объекта будет не передаваться в метод, а создаваться в нём, исходя из типа создаваемого объекта.

Абсолютно во всех ранее написанных нами классах объектов-графических элементов, в формальных параметрах всех их конструкторов, уже заменены переменные "name" на переменные "descript". Для примера, здесь, в этом файле, это:

protected:
//--- Защищённый конструктор
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  descript,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h);
public:
//--- (1) Устанавливает, (2) возвращает смещение координаты X относительно базового объекта
   void              SetCoordXRelative(const int value)                                { this.m_shift_coord_x=value;                }
   int               CoordXRelative(void)                                        const { return this.m_shift_coord_x;               }
//--- (1) Устанавливает, (2) возвращает смещение координаты Y относительно базового объекта
   void              SetCoordYRelative(const int value)                                { this.m_shift_coord_y=value;                }
   int               CoordYRelative(void)                                        const { return this.m_shift_coord_y;               }
   
//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Параметрический конструктор
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const int     element_id,
                                  const int     element_num,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  descript,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h,
                                  const color   colour,
                                  const uchar   opacity,
                                  const bool    movable=true,
                                  const bool    activity=true,
                                  const bool    redraw=false);

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

Точно так же доработке подверглась и установка типа графического элемента. Ранее во всех конструкторах мы дважды прописывали тип элемента в каждом конструкторе каждого класса WinForms-объектов — сначала тип записывали в базовый объект графических элементов библиотеки (в его переменную m_type_element):

void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type;   }

а затем, второй строкой, прописывали этот же тип в свойства объекта.

Упростим это, создав публичный метод для записи (и возврата) типа объекта сразу в оба значения — в переменную и в свойство:

//--- Устанавливает смещение (1) левого, (2) верхнего, (3) правого, (4) нижнего края активной зоны относительно элемента,
//--- (5) все смещения краёв активной зоны относительно элемента, (6) непрозрачность
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));       }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));      }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));        }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));     }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetOpacity(const uchar value,const bool redraw=false);
   
//--- (1) Устанавливает, (2) возвращает тип графического элемента
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type)
                       {
                        CGBaseObj::SetTypeElement(type);
                        this.SetProperty(CANV_ELEMENT_PROP_TYPE,type);
                       }
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)  const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE);   }
   
//--- Устанавливает основной цвет фона

Теперь в каждом конструкторе каждого класса WinForms-объекта вместо двух строк установки одного свойства в разные родительские классы будем вписывать одну строку с вызовом этого метода для установки свойства — он запишет его в оба родительских класса.


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

//--- Группа графического объекта
   virtual int       Group(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP);                }
   virtual void      SetGroup(const int value)
                       {
                        CGBaseObj::SetGroup(value);
                        this.SetProperty(CANV_ELEMENT_PROP_GROUP,value);
                       }
//--- Описание графического элемента
   string            Description(void)                   const { return this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION);               }
   void              SetDescription(const string descr)        { this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descr);                }
   
//+------------------------------------------------------------------+
//| Методы получения растровых данных                                |
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(colour,true);
   this.SetOpacity(opacity);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Имя графического ресурса
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Идентификатор графика

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

      this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,CANV_ELEMENT_CHEK_STATE_UNCHECKED);  // Состояние элемента управления, имеющего флажок проверки
      this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,true);                        // Автоматическое изменение состояния флажка при его выборе
      
      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,false);                                        // Флаг "Переключатель" элемента управления, имеющего кнопку
      this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,false);                                         // Состояние элемента управления "Переключатель", имеющего кнопку
      this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,false);                                         // Флаг группы кнопки
      this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,false);                                // Горизонтальное отображение столбцов в элементе управления ListBox
      this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,0);                                    // Ширина каждого столбца элемента управления ListBox
      this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,false);                                        // Несколько рядов вкладок в элементе управления TabControl
      this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                   // Местоположение вкладок внутри элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                       // Местоположение объекта внутри элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                    // Текст графического элемента
      this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                       // Описание графического элемента
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт структуру объекта                                        |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Сохранение целочисленных свойств
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                               // Идентификатор элемента
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                           // Тип графического элемента
   
   //---...
   //---...

   this.m_struct_obj.button_toggle=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE);                             // Флаг "Переключатель" элемента управления, имеющего кнопку
   this.m_struct_obj.button_state=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE);                               // Состояние элемента управления "Переключатель", имеющего кнопку
   
   this.m_struct_obj.button_group_flag=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP);                          // Флаг группы кнопки
   this.m_struct_obj.multicolumn=(bool)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN);                       // Горизонтальное отображение столбцов в элементе управления ListBox
   this.m_struct_obj.column_width=(int)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH);                       // Ширина каждого столбца элемента управления ListBox
   this.m_struct_obj.tab_multiline=(bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE);                             // Несколько рядов вкладок в элементе управления TabControl
   this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);                              // Местоположение вкладок внутри элемента управления
   this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT);                                      // Местоположение объекта внутри элемента управления
//--- Сохранение вещественных свойств

//--- Сохранение строковых свойств
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);   // Имя объекта-графического элемента
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);   // Имя графического ресурса
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text);           // Текст графического элемента
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Описание графического элемента
   //--- Сохранение структуры в uchar-массив
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт объект из структуры                                      |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Сохранение целочисленных свойств
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                    // Идентификатор элемента
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                                // Тип графического элемента
   
   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,this.m_struct_obj.button_toggle);                             // Флаг "Переключатель" элемента управления, имеющего кнопку
   this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,this.m_struct_obj.button_state);                               // Состояние элемента управления "Переключатель", имеющего кнопку

   this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,this.m_struct_obj.button_group_flag);                          // Флаг группы кнопки
   this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,this.m_struct_obj.multicolumn);                       // Горизонтальное отображение столбцов в элементе управления ListBox
   this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,this.m_struct_obj.column_width);                      // Ширина каждого столбца элемента управления ListBox
   this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,this.m_struct_obj.tab_multiline);                             // Несколько рядов вкладок в элементе управления TabControl
   this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment);                             // Местоположение вкладок внутри элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment);                                     // Местоположение объекта внутри элемента управления
//--- Сохранение вещественных свойств

//--- Сохранение строковых свойств
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));   // Имя объекта-графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));   // Имя графического ресурса
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text));           // Текст графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Описание графического элемента
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт графический объект-элемент                               |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Идентификатор графика
                          const int wnd_num,       // Подокно графика
                          const int x,             // Координата X
                          const int y,             // Координата Y
                          const int w,             // Ширина
                          const int h,             // Высота
                          const bool redraw=false) // Флаг необходимости перерисовки
                         
  {
   ::ResetLastError();
   if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,this.m_name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(CLR_CANV_NULL);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   CMessage::ToLog(DFUN_ERR_LINE,::GetLastError(),true);
   return false;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возвращает количество графических элементов по типу              |
//+------------------------------------------------------------------+
int CGCnvElement::GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const
  {
//--- Объявляем переменную с количеством графических объектов и
//--- получаем общее количество графических объектов на графике и подокне, где создаётся графический элемент
   int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow());
//--- Создаём имя графического объекта по его типу
   string name=TypeGraphElementAsString(type);
//--- В цикле по всем объектам графика и подокна
   for(int i=0;i<total;i++)
     {
      //--- получаем имя очередного объекта
      string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow());
      //--- если в имени объекта нет установленного префикса имён графических объектов библиотеки, идём далее - этот объект не наш
      if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE)
         continue;
      //--- Если в имени выбранного в цикле графического объекта есть подстрока с созданным именем объекта по его типу -
      //--- значит есть графический объект такого типа - увеличиваем счётчик объектов этого типа
      if(::StringFind(name_obj,name)>0)
         n++;
     }
//--- Возвращаем количество найденных объектов по их типу
   return n;
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Создаёт и возвращает имя графического элемента по его типу       |
//+------------------------------------------------------------------+
string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return this.NamePrefix()+TypeGraphElementAsString(type)+(string)this.GetNumGraphElements(type);
  }
//+------------------------------------------------------------------+

В метод передаётся тип объекта, для которого нужно создать имя.
Далее к префиксу графических объектов библиотеки ("Имя_программы"+"_") добавляем имя объекта по его типу и количество объектов такого типа.
Полученный результат возвращаем из метода.

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


Чтобы мы могли правильно указать для создаваемого объекта его тип, нам нужно понять каким образом этот тип "доходит" до класса-графического элемента CGCnvElement, который является одним из родительских классов для WinForms-объектов, и в котором создаются эти объекты. Нам нужно до этого класса "донести" тип создаваемого графического элемента. Сейчас во всех конструкторах наследуемых от него классов явно указывается тот тип, к классу которого принадлежит класс-наследник. Таким образом мы всегда будем создавать только тот тип, который указан в классе, расположенном в иерархии наследования следующим после класса CGCnvElement. А это класс объекта-формы.

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

И не забываем, что во всех файлах всех классов WInForms-объектов формальные переменные "name" уже переименованы в "descript" в конструкторах и методах создания графических элементов. Сообщаю об этом ещё раз — чтобы более к этому вопросу не возвращаться и не описывать уже сделанные одинаковые изменения для каждого WinForms-объекта из всех имеющихся.

В файле объекта-формы \MQL5\Include\DoEasy\Objects\Graph\Form.mqh удалим объявление более не нужного метода, возвращающего имя зависимого объекта (все имена теперь создаются автоматически в классе CGCnvElement):

//--- Создаёт объект для тени
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- Возвращает имя зависимого объекта
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
//--- Обновляет координаты привязанных объектов
   virtual bool      MoveDependentObj(const int x,const int y,const bool redraw=false);

Также удалим и код реализации этого метода из листинга класса.


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

//--- Обработчик последнего события мышки
   virtual void      OnMouseEventPostProcessing(void);

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CForm(const ENUM_GRAPH_ELEMENT_TYPE type,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
public:                     
//--- Конструкторы

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

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

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CForm::CForm(const ENUM_GRAPH_ELEMENT_TYPE type,
             const long chart_id,
             const int subwindow,
             const string descript,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+

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

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

Из метода, создающего новый графический объект, удалим строку с созданием имени зависимого объекта и при создании новых объектов передаём в них не имя, а параметр "descript" (описание), передаваемый в формальных параметрах метода:

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity)
  {
   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(),descript,x,y,w,h,colour,opacity,movable,activity);
        break;
      //--- создаём объект-форму
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(type,this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

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

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

//--- Создаём имя графического элемента
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;

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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//| и добавляет его в список присоединённых объектов                 |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(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)
  {
//--- Если тип создаваемого графического элемента меньше, чем "элемент" - сообщаем об этом и возвращаем false
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return NULL;
     }
//--- Задаём номер элемента в списке
   int num=this.m_list_elements.Total();
//--- Создаём описание графического элемента по умолчанию
   string descript=TypeGraphElementAsString(element_type);
//--- Получаем экранные координаты объекта относительно системы координат базового объекта
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Создаём новый графический элемент
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return NULL;
//--- и добавляем его в список привязанных графических элементов

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

   return obj;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт объект тени                                              |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- Если флаг тени выключен, или объект тени уже существует - уходим
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;

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

//--- Создаём новый объект тени и записываем указатель на него в переменную
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.NameObj()+"Shadow",x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
   this.m_list_tmp.Add(this.m_shadow_obj);
//--- Устанавливаем свойства созданному объекту-тени
   
   //---...
   //---...
   //---...

//--- Объект-форму перемещаем на передний план
   this.BringToTop();
  }
//+------------------------------------------------------------------+

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


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

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
public:
//--- Конструкторы
                     CWinFormBase(const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
                     CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
                       }

//--- (1) Устанавливает, (2) возвращает цвет текста по умолчанию всех объектов на панели


Реализация защищённого конструктора практически полностью повторяет реализацию публичного параметрического:

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Инициализируем все переменные
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

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

//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Инициализируем все переменные

вместо двух, ранее в нём записанных, и делающих то же самое:

//--- Установим тип графического элемента и тип объекта библиотеки как базовый WinForms-объект
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Инициализируем все переменные

Аналогичные доработки внесны во все классы всех WinForms-объектов, и далее уже рассматриваться не будут.


В методе, возвращающем описание строкового свойства элемента, впишем возврат описания нового свойства:

//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства элемента                 |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_STRING property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_NAME_OBJ      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_OBJ)+": \""+this.GetProperty(property)+"\""     :
      property==CANV_ELEMENT_PROP_NAME_RES      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_RES)+": \""+this.GetProperty(property)+"\""     :
      property==CANV_ELEMENT_PROP_TEXT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TEXT)+": \""+this.GetProperty(property)+"\""         :
      property==CANV_ELEMENT_PROP_DESCRIPTION   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DESCRIPTION)+": \""+this.GetProperty(property)+"\""  :
      ""
     );
  }
//+------------------------------------------------------------------+


Все вышеозвученные доработки сделаны в классах WinForms-объектов в файлах:

\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh,

В файле объекта-кнопки \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, помимо таких же общих для всех WinForms-объектов доработок, сделаем метод для установки состояния виртуальным, так как в наследуемых классах его потребуется переопределить:

   bool              Toggle(void)                        const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE);                               }
//--- (1) Устанавливает, (2) возвращает состояние элемента управления "Переключатель"
   virtual void      SetState(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag);
                        if(this.State())
                          {
                           this.SetBackgroundColor(this.BackgroundStateOnColor(),false);
                           this.SetForeColor(this.ForeStateOnColor(),false);
                           this.UnpressOtherAll();
                          }
                        else
                          {
                           this.SetBackgroundColor(this.BackgroundColorInit(),false);
                           this.SetForeColor(this.ForeColorInit(),false);
                           this.SetBorderColor(this.BorderColorInit(),false);
                          }
                       }
   bool              State(void)                         const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE);                                }

 

В классе объекта ListBox у нас для вывода строк используются объекты-кнопки. Но для вывода текста на кнопках с условием, что текст всегда прижат к левому краю кнопки при его выравниванию по левому краю, нам нужно внести параметр, указывающий на сколько символов нужно сдвинуть текст вправо.
У объекта-кнопки нет такого свойства. Поэтому создадим вспомогательный объект ListBoxItem.

В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\ создадим новый файл ListBoxItem.mqh класса CListBoxItem. Класс должен быть унаследован от класса объекта-кнопки, и его файл должен быть подключен к файлу создаваемого класса:

//+------------------------------------------------------------------+
//|                                                  ListBoxItem.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 "Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Label элементов управления WForms                  |
//+------------------------------------------------------------------+
class CListBoxItem : public CButton
  {
  }


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

//+------------------------------------------------------------------+
//| Класс объекта Label элементов управления WForms                  |
//+------------------------------------------------------------------+
class CListBoxItem : public CButton
  {
private:
   uchar             m_text_shift;                          // Смещение текста элемента вправо от левого края в символах
   string            m_shift_space;                         // Строка смещения
protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
public:
//--- Возвращает смещение текста элемента вправо от левого края в символах
   uchar             TextShift(void)                        const { return this.m_text_shift;   }
//--- (1) Создаёт, (2) возвращает строку, состоящую из количества символов смещения
   void              SetTextShiftSpace(const uchar value);
   string            GetTextShiftSpace(void)                const { return this.m_shift_space;  }
//--- Устанавливает текст элемента
   virtual void      SetText(const string text);
//--- Конструктор
                     CListBoxItem(const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
  };
//+------------------------------------------------------------------+


Конструкторы класса:

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetTextAlign(ANCHOR_LEFT);
   this.SetTextShiftSpace(1);
  }
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetTextAlign(ANCHOR_LEFT);
   this.SetTextShiftSpace(1);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Создаёт строку, состоящую из количества символов смещения        |
//+------------------------------------------------------------------+
void CListBoxItem::SetTextShiftSpace(const uchar value)
  {
   this.m_text_shift=value;
   this.m_shift_space="";
   switch(this.TextAlign())
     {
      case ANCHOR_LEFT        :
      case ANCHOR_LEFT_LOWER  :
      case ANCHOR_LEFT_UPPER  :
        for(int i=0;i<(int)this.m_text_shift;i++)
           this.m_shift_space+=" ";
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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


Метод, устанавливающий текст элемента:

//+------------------------------------------------------------------+
//| Устанавливает текст элемента                                     |
//+------------------------------------------------------------------+
void CListBoxItem::SetText(const string text)
  {
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,this.GetTextShiftSpace()+text);
  }
//+------------------------------------------------------------------+

Здесь в свойство объекта вписывается текст объекта, к которому слева прибавлено количество пробелов, установленное методом SetTextShiftSpace(), рассмотренным нами выше.


В классе объекта ListBox в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ListBox.mqh, в его приватной секции, добавим переменную, хранящую смещение текста объектов-коллекции этого элемента (класса объекта ListBoxItem, рассмотренного нами выше), в публичной секции объявим методы для работы с новой переменной, а в защищённой секции объявим защищённый конструктор:

//+------------------------------------------------------------------+
//| Класс объекта ListBox элементов управления WForms                |
//+------------------------------------------------------------------+
class CListBox : public CElementsListBox
  {
private:
   uchar             m_text_shift;                       // Смещение текста элементов ListBoxItem вправо от левого края в символах
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
public:
//--- (1) Устанавливает, (2) возвращает смещение текста элемента вправо от левого края в символах
   void              SetTextShift(const uchar value);
   uchar             TextShift(void)                  const { return this.m_text_shift;   }
//--- Создаёт список из указанного количества строк (объектов Label)
   void              CreateList(const int line_count,const int new_column_width=0,const bool autosize=true);
   
protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CListBox(const ENUM_GRAPH_ELEMENT_TYPE type,
                              const long chart_id,
                              const int subwindow,
                              const string descript,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
public:
//--- Конструктор
                     CListBox(const long chart_id,
                              const int subwindow,
                              const string descript,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
  };
//+------------------------------------------------------------------+

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

Доработаем метод, создающий список из указанного количества строк (ListBoxItem).

Теперь вместо объекта класса CButton, будем работать с новым классом CListBoxItem, соответственно, и создавать будем объект этого класса. После создания объекта сразу зададим смещение его текста и установим текст созданного элемента коллекции по умолчанию:

//+------------------------------------------------------------------+
//| Создаёт список из указанного количества строк (ListBoxItem)      |
//+------------------------------------------------------------------+
void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true)
  {
//--- Создаём указатель на объект CListBoxItem
   CListBoxItem *obj=NULL;
//--- Рассчитываем ширину создаваемого объекта в зависимости от указанной ширины столбца
   int width=(new_column_width>0 ? new_column_width : this.Width()-this.BorderSizeLeft()-this.BorderSizeRight());
//--- Создаём указанное количество объектов ListBoxItem
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,count,0,0,width,15,new_column_width,autosize);
//--- В цикле по созданному количеству объектов
   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_LIST_BOX_ITEM));
         continue;
        }
      //--- Устанавливаем выравнивание текста слева по центру
      obj.SetTextAlign(ANCHOR_LEFT);
      obj.SetTextShiftSpace(this.TextShift());
      //--- Устанавливаем текст объекта
      obj.SetFontSize(8);
      obj.SetText(TypeGraphElementAsString(obj.TypeGraphElement())+string(i+1));
      //--- Устанавливаем цвета фона, текста и рамки
      obj.SetBackgroundStateOnColor(clrDodgerBlue,true);
      obj.SetBackgroundStateOnColorMouseOver(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-5));
      obj.SetBackgroundStateOnColorMouseDown(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-10));
      obj.SetForeStateOnColor(this.BackgroundColor(),true);
      obj.SetForeStateOnColorMouseOver(obj.ChangeColorLightness(obj.ForeStateOnColor(),-5));
      obj.SetForeStateOnColorMouseDown(obj.ChangeColorLightness(obj.ForeStateOnColor(),-10));
      obj.SetBorderColor(obj.BackgroundColor(),true);
      obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown());
      obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver());
      //--- Ставим флаги кнопки-переключателя и групповой кнопки
      obj.SetToggleFlag(true);
      obj.SetGroupButtonFlag(true);
     }
//--- Если в метод передан флаг автоматического изменения размеров базового объекта -
//--- устанавливаем режим автоизменения размера как "увиличить и уменьшить"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Устанавливает смещение текста элемента                           |
//| вправо от левого края в символах                                 |
//+------------------------------------------------------------------+
void CListBox::SetTextShift(const uchar value)
  {
   this.m_text_shift=value;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CListBoxItem *obj=this.GetElement(i);
      if(obj==NULL || obj.TextShift()==value)
         continue;
      obj.SetTextShiftSpace(value);
      obj.SetText(obj.Text());
      obj.Update(false);
     }
  }
//+------------------------------------------------------------------+

Здесь: записываем количество символов смещения, переданное в метод, в переменную, затем в цикле по списку привязанных объектов получаем очередной объект и устанавливаем для него текст со смещением при помощи метода объекта SetText(), рассмотренного нами выше при обсуждении методов класса CListBoxItem.

В классе базового объекта-контейнера в файле MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh удалим лишний конструктор:

                     CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR,true);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_NONE);
                        this.SetAutoScroll(false,false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                       }
//--- Деструктор
                    ~CContainer();
  };
//+------------------------------------------------------------------+


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

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CContainer(const ENUM_GRAPH_ELEMENT_TYPE type,
                                const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);
public:
//--- Конструктор
                     CContainer(const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

Реализация защищённого конструктора идентична всем, ранее добавленным защищённым конструкторам в других классах.


В методе, устанавливающем параметры присоединённому объекту, добавим обработку объекта ListBoxItem такую же, как и для объектов Button и TabHeader:

      //--- Для 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", "TabHeader", "ListBoxItem"
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM     :
        //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод:
        //--- цвет фона устанавливаем  в зависимости от переданного в метод:
        //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод.
        //--- Цвет рамки устанавливаем равным цвету текста
        obj.SetForeColor(this.ForeColor(),true);
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- Для 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;


Обработку теперь отсутствующего объекта TabPage удалим из метода:

        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
        //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод:
        //--- цвет фона устанавливаем  в зависимости от переданного в метод:
        //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод.
        //--- Цвет рамки устанавливаем равным цвету текста
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR : colour,true);
        obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
        obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
        obj.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
        obj.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY);
        obj.SetBorderSizeAll(1);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
  


К файлу класса базового объекта-списка элементов управления \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh подключим файл класса CListBoxItem:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Containers\Container.mqh"
#include "..\ListBoxItem.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта списка элементов управления WForms        |
//+------------------------------------------------------------------+

Теперь класс коллекции списка элементов управления будет доступен в других объектах библиотеки.

Так же, как и в остальных объектах, объявим защищённый конструктор:

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);
                                    
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CElementsListBox(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const long chart_id,
                                      const int subwindow,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
public:
//--- Конструктор

Реализация защищённого конструктора и доработка параметрического идентичны остальным WinForms-объектам.


Класс TabHeader — заголовок вкладки объекта TabControl

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

Это всё могут делать кнопки. Но кнопки не могут изменять свои размеры в зависимости от состояния. А заголовки вкладок должны. Выбранная вкладка имеет заголовок чуть больше, чем не выбранная. При этом есть ещё одно: заголовки вкладок могут размещаться в элементе управления с четырёх сторон — сверху, снизу, слева и справа. Соответственно, и рамка должна быть не прорисована с той стороны, которая соприкасается с полем вкладки. Значит, нужно для кнопки сделать новый метод её перерисовки — где рамка будет рисоваться в соответствии с расположением заголовка на элементе управления, и новый метод обработки нажатия — где кнопка будет увеличиваться в размерах и смещаться на новые координаты так, чтобы она всегда оставалась прижатой к полю вкладки.

В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\ создадим новый файл TabHeader.mqh класса TabHeader. Класс должен быть унаследован от класса объекта-кнопки, а его файл должен быть подключен к файлу создаваемого класса:

//+------------------------------------------------------------------+
//|                                                    TabHeader.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 "Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Класс объекта TabHeader элемента управления WForms TabControl    |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
  }


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

//+------------------------------------------------------------------+
//| Класс объекта TabHeader элемента управления WForms TabControl    |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
private:
   int               m_width_off;                        // Ширина объекта в состоянии "не выбран"
   int               m_height_off;                       // Высота объекта в состоянии "не выбран"
   int               m_width_on;                         // Ширина объекта в состоянии "выбран"
   int               m_height_on;                        // Высота объекта в состоянии "выбран"
   int               m_col;                              // Номер колонки заголовка
   int               m_row;                              // Номер строки заголовка
//--- Устанавливает ширину, высоту и смещение элемента в зависимости от состояния
   void              SetWH(void);
//--- Подстраивает размер и расположение элемента в зависимости от состояния
   void              WHProcessStateOn(void);
   void              WHProcessStateOff(void);
//--- Русует рамку элемента в зависимости от расположения
   void              DrawFrame(void);
   
protected:
//--- Обработчик события  Курсор в пределах активной области, отжата кнопка мышки (левая)
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

public:
//---Устанавливает размеры элемента управления в состоянии (1) "не выбран", (2) "выбран"
   bool              SetSizeOff(void)  { return(CGCnvElement::SetWidth(this.m_width_off) && CGCnvElement::SetHeight(this.m_height_off) ? true : false);  }
   bool              SetSizeOn(void)   { return(CGCnvElement::SetWidth(this.m_width_on) && CGCnvElement::SetHeight(this.m_height_on)   ? true : false);  }
//--- Устанавливает размеры элемента управления
   void              SetWidthOff(const int value)                                            { this.m_width_off=value;  }
   void              SetHeightOff(const int value)                                           { this.m_height_off=value; }
   void              SetWidthOn(const int value)                                             { this.m_width_on=value;   }
   void              SetHeightOn(const int value)                                            { this.m_height_on=value;  }
//--- Возвращает размеры элемента управления
   int               WidthOff(void)                                                    const { return this.m_width_off; }
   int               HeightOff(void)                                                   const { return this.m_height_off;}
   int               WidthOn(void)                                                     const { return this.m_width_on;  }
   int               HeightOn(void)                                                    const { return this.m_height_on; }
//--- (1) Устанавливает, (2) возвращает номер строки заголовка вкладки
   void              SetRow(const int value)                                                 { this.m_row=value;        }
   int               Row(void)                                                         const { return this.m_row;       }
//--- (1) Устанавливает, (2) возвращает номер колонки заголовка вкладки
   void              SetColumn(const int value)                                              { this.m_col=value;        }
   int               Column(void)                                                      const { return this.m_col;       }
//--- Устанавливает местоположение вкладки
   void              SetTabLocation(const int index,const int row,const int col)
                       {
                        this.SetRow(row);
                        this.SetColumn(col);
                       }
//--- (1) Устанавливает, (2) возвращает местоположение объекта на элементе управления
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
                           this.SetBorderSize(1,1,1,0);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
                           this.SetBorderSize(1,0,1,1);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
                           this.SetBorderSize(1,1,0,1);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
                           this.SetBorderSize(0,1,1,1);
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }
   

//--- Устанавливает состояние элемента управления
   virtual void      SetState(const bool flag);

//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);

//--- Обработчик последнего события мышки
   virtual void      OnMouseEventPostProcessing(void);

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type,
                                const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);
public:
//--- Конструктор
                     CTabHeader(const long chart_id,
                             const int subwindow,
                             const string descript,
                             const int x,
                             const int y,
                             const int w,
                             const int h);
  };
//+------------------------------------------------------------------+

Из описаний переменных и методов должно быть понятно их назначение. Рассмотрим их подробнее.

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

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);
   this.SetState(false);
  }
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);
   this.SetState(false);
  }
//+------------------------------------------------------------------+

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

Метод, устанавливающий состояние элемента управления:

//+------------------------------------------------------------------+
//| Устанавливает состояние элемента управления                      |
//+------------------------------------------------------------------+
void CTabHeader::SetState(const bool flag)
  {
   bool state=this.State();
   CButton::SetState(flag);
   if(state!=flag)
      this.SetWH();
  }
//+------------------------------------------------------------------+

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


Метод, устанавливающий ширину, высоту и смещение элемента в зависимости от его состояния:

//+------------------------------------------------------------------+
//| Устанавливает ширину, высоту и смещение элемента                 |
//| в зависимости от его состояния                                   |
//+------------------------------------------------------------------+
void CTabHeader::SetWH(void)
  {
   if(this.State())
      this.WHProcessStateOn();
   else
      this.WHProcessStateOff();
  }
//+------------------------------------------------------------------+

Если состояние "включено", то вызываем метод изменения размера и расположения для состояния "включено", иначе — вызываем метод изменения размера и расположения для состояния "выключено".


Метод, подстраивающий размер и расположение элемента в состоянии "выбран" в зависимости от его расположения:

//+------------------------------------------------------------------+
//| Подстраивает размер и расположение элемента                      |
//| в состоянии "выбран" в зависимости от его расположения           |
//+------------------------------------------------------------------+
void CTabHeader::WHProcessStateOn(void)
  {
//--- Еси не удалось установить новый размер - уходим
   if(!this.SetSizeOn())
      return;
//--- В зависимости от расположения заголовка
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP :
        //--- смещаем его в нужное место и устанавливаем новые относительные координаты
        if(this.Move(this.CoordX()-2,this.CoordY()-2))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative()-2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM :
        //--- смещаем его в нужное место и устанавливаем новые относительные координаты
        if(this.Move(this.CoordX()-2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      default:
        break;
     }
   this.Update(false);
  }
//+------------------------------------------------------------------+

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


Метод, подстраивающий размер и расположение элемента в состоянии "не выбран" в зависимости от его расположения:

//+------------------------------------------------------------------+
//| Подстраивает размер и расположение элемента                      |
//| в состоянии "не выбран" в зависимости от его расположения        |
//+------------------------------------------------------------------+
void CTabHeader::WHProcessStateOff(void)
  {
//--- Еси не удалось установить новый размер - уходим
   if(!this.SetSizeOff())
      return;
//--- В зависимости от расположения заголовка
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP :
        //--- смещаем его в нужное место и устанавливаем новые относительные координаты
        if(this.Move(this.CoordX()+2,this.CoordY()+2))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative()+2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM :
        //--- смещаем его в нужное место и устанавливаем новые относительные координаты
        if(this.Move(this.CoordX()+2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      default:
        break;
     }
   this.Update(false);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Рисует рамку элемента в зависимости от расположения              |
//+------------------------------------------------------------------+
void CTabHeader::DrawFrame(void)
  {
//--- Устанавливаемначальные координаты
   int x1=0;
   int x2=this.Width()-1;
   int y1=0;
   int y2=this.Height()-1;
//--- В зависимости от расположения заголовка рисуем рамку так,
//--- чтобы прилегающая к полю грань рисуемой рамки выходила за пределы объекта
//--- таким образом визуально грань не будет нарисована на прилегающейстороне
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        this.DrawRectangle(x1,y1,x2,y2+1,this.BorderColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        this.DrawRectangle(x1,y1-1,x2,y2,this.BorderColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        this.DrawRectangle(x1,y1,x2+1,y2,this.BorderColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        this.DrawRectangle(x1-1,y1,x2,y2,this.BorderColor(),this.Opacity());
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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


Метод, очищающий элемент с заполнением его цветом и непрозрачностью:

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CTabHeader::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Закрашиваем элемент с указанным цветом и флагом необходимости перерисовки
   CGCnvElement::Erase(colour,opacity,redraw);
//--- Если у объекта есть рамка - рисуем её
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFrame();
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Метод виртуальный, переопределяет метод родительского объекта, где рисуется рамка в пределах объекта. Здесь же мы вызываем вышенаписанный метод для отрисовки рамки только с трёх сторон объекта.


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

//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CTabHeader::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Закрашиваем элемент с указанным массивом цветов и флагом необходимости перерисовки
   CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw);
//--- Если у объекта есть рамка - рисуем её
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFrame();
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Метод идентичен вышерассмотренному, но заливает фон градиентным цветом из массива цветов, переданного в метод.


Обработчик события Курсор в пределах активной области, отжата кнопка мышки (левая):

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| отжата кнопка мышки (левая)                                      |
//+------------------------------------------------------------------+
void CTabHeader::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);
         this.SetForeColor(this.ForeColorInit(),false);
        }
      //--- Если это кнопка-переключатель - устанавливаем изначальный цвет фона и текста в зависимости от того нажата кнопка или нет
      else
        {
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false);
         this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false);
        }
      //--- Устанавливаем изначальный цвет рамки
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Выводим тестовое сообщение в журнал
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- Если кнопка мышки отпущена в пределах элемента - это щелчок по элементу управления
   else
     {
      //--- Если это простая кнопка - устанавливаем цвет фона и текста для состояния "Курсор мышки над активной зоной"
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.ForeColorMouseOver(),false);
        }
      //--- Если это кнопка-переключатель -
      else
        {
         //--- если кнопка не работает в группе - устанавливаем её состояние на противоположное,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- иначе - если кнопка ещё не нажата - устанавливаем её в нажатое состояние
         else if(!this.State())
            this.SetState(true);
         //--- устанавливаем цвет фона и текста для состояния "Курсор мышки над активной зоной" в зависимости от того нажата кнопка или нет
         this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),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);
  }
//+------------------------------------------------------------------+


Обработчик последнего события мышки:

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CTabHeader::OnMouseEventPostProcessing(void)
  {
   ENUM_MOUSE_FORM_STATE state=GetMouseState();
   switch(state)
     {
      //--- Курсор за пределами формы, кнопки мышки не нажаты
      //--- Курсор за пределами формы, нажата кнопка мышки (любая)
      //--- Курсор за пределами формы, прокручивается колёсико мышки
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED     :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL       :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false);
           this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(false);
          }
        break;

      //--- Курсор в пределах формы, кнопки мышки не нажаты
      //--- Курсор в пределах формы, нажата кнопка мышки (любая)
      //--- Курсор в пределах формы, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, кнопки мышки не нажаты
      //--- Курсор в пределах активной области, нажата кнопка мышки (любая)
      //--- Курсор в пределах активной области, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, отжата кнопка мышки (левая)
      //--- Курсор в пределах области прокрутки окна, кнопки мышки не нажаты
      //--- Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области прокрутки окна, прокручивается колёсико мышки
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL :
        break;
      //---MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Оба метода идентичны методам родительского класса.
Сюда перенесены для возможного их переопределения при дальнейшей разработке объекта TabControl.


Класс TabControl — продолжаем разработку

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

Подключим к файлу элемента управления файл класса объекта-заголовка вкладки:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\TabHeader.mqh"
//+------------------------------------------------------------------+


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

private:
   int                  m_item_width;                 // Фиксированная ширина заголовков вкладок
   int                  m_item_height;                // Фиксированная высота заголовков вкладок
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Устанавливает вкладку выбранной
   void              SetSelected(const int index);
//--- Устанавливает вкладку не выбранной
   void              SetUnselected(const int index);
   
public:


Публичный метод CreateTabPage() переименуем в CreateTabPages(), сделаем его с возвращаемым типом bool и с иным набором формальных параметров, а также добавим два метода, возвращающие указатели на заголовок и поле вкладки по индексу:

public:
//--- Создаёт указанное количество вкладок
   bool              CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="");
   
//--- Возвращает указатель на (1) заголовок, (2) поле вкладки
   CTabHeader       *GetHeader(const int index)          { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); }
   CContainer       *GetField(const int index)           { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index);  }


Метод SetAlignment() сделаем таким, чтобы он не просто устанавливал значение в свойство объекта, но и устанавливал его для всех заголовков вкладок, созданных в элементе управления:

//--- (1) Устанавливает, (2) возвращает местоположение вкладок на элементе управления
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                        CArrayObj *list=this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetAlignment(alignment);
                          }
                       }

Сначала устанавливаем значение в свойства объекта, затем получаем список всех созданных заголовков вкладок, и в цикле по полученному списку для каждого объекта устанавливаем такое же значение.


Объявим ещё три публичных метода:

//--- Устанавливает фиксированный размер вкладки
   void              SetItemSize(const int w,const int h)
                       {
                        if(this.ItemWidth()!=w)
                           this.SetItemWidth(w);
                        if(this.ItemHeight()!=h)
                           this.SetItemHeight(h);
                       }
//--- Устанавливает вкладку выбранной/не выбранной
   void              Select(const int index,const bool flag);

//--- Устанавливает текст заголовка (1) указанной вкладки, (2) по индексу
   void              SetHeaderText(CTabHeader *header,const string text);
   void              SetHeaderText(const int index,const string text);

//--- Конструктор


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

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CTabControl::CTabControl(const long chart_id,
                   const int subwindow,
                   const string descript,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(0,true);
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetBackgroundColorMouseDown(CLR_CANV_NULL);
   this.SetBackgroundColorMouseOver(CLR_CANV_NULL);
   this.SetBorderColor(CLR_CANV_NULL,true);
   this.SetBorderColorMouseDown(CLR_CANV_NULL);
   this.SetBorderColorMouseOver(CLR_CANV_NULL);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,18);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Создаёт указанное количество вкладок                             |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Рассчитываем размеры и начальные координаты заголовка вкладки
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- В цикле по количеству вкладок
   CTabHeader *header=NULL;
   for(int i=0;i<total;i++)
     {
      //--- В зависимости от расположения заголовков вкладок устанавливаем их начальные координаты
      int header_x=2;
      int header_y=0;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         header_y=0;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         header_y=this.Height()-h;
      //--- Устанавливаем текущую координату X
      header_x=(header==NULL ? header_x : header.RightEdgeRelative());
      //--- Создаём объект TabHeader
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,w,h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      
      //--- В зависимости от расположения заголовков вкладок устанавливаем начальные координаты полей вкладок
      int field_x=0;
      int field_y=0;
      int field_h=this.Height()-header.Height();
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         field_y=header.BottomEdgeRelative();
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         field_y=0;
      //--- Создаём объект Container (поле вкладки)
      CContainer *field=NULL;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,field_x,field_y,this.Width(),field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER),string(i+1));
         return false;
        }
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.Hide();
   //---
     }
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

Вкладки у нас состоят из двух объектов — заголовка вкладки и поля вкладки. Заголовок (кнопка) служит для выбора активной вкладки, а поле — для размещения на нём других элементов управления. Кнопки всегда должны отображаться, а поля вкладок — всегда скрываться, кроме активной.

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


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

//+------------------------------------------------------------------+
//| Устанавливает вкладку выбранной                                  |
//+------------------------------------------------------------------+
void CTabControl::SetSelected(const int index)
  {
   CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);
   CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index);
   if(header==NULL || field==NULL)
      return;
   field.Show();
   field.BringToTop();
   header.SetState(true);
   header.BringToTop();
  }
//+------------------------------------------------------------------+

Получаем указатели на заголовок и поле вкладки по индексу.
Отображаем поле и выводим его на передний план
.
Устанавливаем выбранным заголовок и выводим его на передний план.


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

//+------------------------------------------------------------------+
//| Устанавливает вкладку не выбранной                               |
//+------------------------------------------------------------------+
void CTabControl::SetUnselected(const int index)
  {
   CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);
   CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index);
   if(header==NULL || field==NULL)
      return;
   field.Hide();
   header.SetState(false);
  }
//+------------------------------------------------------------------+

Получаем указатели на заголовок и поле вкладки по индексу.
Скрываем поле и у
станавливаем заголовок как не выбранный.


Метод, устанавливающий вкладку выбранной/не выбранной:

//+------------------------------------------------------------------+
//| Устанавливает вкладку выбранной/не выбранной                     |
//+------------------------------------------------------------------+
void CTabControl::Select(const int index,const bool flag)
  {
   if(flag)
      this.SetSelected(index);
   else
      this.SetUnselected(index);
  }
//+------------------------------------------------------------------+

В метод передаётся индекс вкладки и флаг, и в зависимости от значения флага, вызываем один из двух вышерассмотренных методов.


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

//+------------------------------------------------------------------+
//| Устанавливает текст заголовка указанной вкладки                  |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderText(CTabHeader *header,const string text)
  {
   if(header==NULL)
      return;
   header.SetText(text);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает текст заголовка вкладки по индексу                 |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderText(const int index,const string text)
  {
   CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);
   this.SetHeaderText(header,text);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                         const int obj_num,
                                         const string descript,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const color colour,
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity)
  {

   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT              :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM                 :
         element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
         element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM     :
         element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
                                                                                     
                                                                                     
                                                                                     
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

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


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

На сегодня это все необходимые доработки.


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

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

Для отображения описаний констант перечисления на двух разных языках в настройках советника создадим дополнительное перечисление для английского языка компиляции и русского, и в настройках добавим новую переменную с типом этого перечисления, которая будет указывать с какой стороны — сверху или снизу (право-лево пока не работают) будут размещаться заголовки вкладок:

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // None
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (bevel)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (stamp)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Indeterminate
  };
enum ENUM_ELEMENT_ALIGNMENT
  {
   ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP,                    // Top
   ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM,              // Bottom
   ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT,                  // Left
   ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT,                // Right
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Только увеличение
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Увеличение и уменьшение
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // Нет рамки
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Простая рамка
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Плоская рамка
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Рельефная (выпуклая)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Рельефная (вдавленная)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Не установлен
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Установлен
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Неопределённый
  };
enum ENUM_ELEMENT_ALIGNMENT
  {
   ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP,                    // Сверху
   ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM,              // Снизу
   ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT,                  // Слева
   ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT,                // Справа
  };
#endif 
//--- 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
sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+


В обработчике OnInit() в блоке создания графических объектов закомментируем блок создания объекта TabControl из прошлой статьи (там мы его разместим позже) и код создания объекта ListBox (его мы будем размещать во вкладки будущего объекта TabControl). А после блока кода для создания объекта ButtonListBox разместим блок кода для создания объекта TabControl c тремя вкладками, первая из которых будет выбрана изначально:

      //--- Если прикреплённый объект GroupBox создан
      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");
            
            //--- Создадим объект TabControl
//            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false);
//            //--- получим указатель на объект TabControl по его индексу в списке прикреплённых объектов с типом TabControl
//            CTabControl *tctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
//            if(tctrl!=NULL)
//              {
//               //--- получим указатель на объект Container по его индексу в списке прикреплённых объектов с типом Container
//               CContainer *page=tctrl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,0);
//               if(page!=NULL)
//                 {
//                  // Здесь будем создавать объекты, прикреплённые к указанной вкладке объекта TabControl
//                  // К сожалению, в текущем состоянии создания имён графических объектов библиотеки
//                  // дальнейшее их создание упирается в предел количества символов в имени ресурса в классе CCanvas
//                 }
//               
//              }
            ///*
            //--- Создадим объект 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(true);
               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(true);
               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));
                 }
              }
            
            int lbx=6;
            int lby=blbox.BottomEdgeRelative()+6;
            int lbw=180;
            //--- Создадим объект TabControl
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,lbx,lby,lbw,78,clrNONE,255,true,false);
            //--- получим указатель на объект TabControl по его индексу в списке прикреплённых объектов с типом TabControl
            CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
            //--- Если TabControl создан и указатель на него получен
            if(tab_ctrl!=NULL)
              {
               //--- Установим расположение заголовков вкладок на элементе и текст вкладок
               tab_ctrl.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
               tab_ctrl.CreateTabPages(3,0,56,16,TextByLanguage("Вкладка","TabPage"));
              }
                        
            //--- Создадим объект 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,60,clrNONE,255,true,false);
            ////--- получим указатель на объект ListBox по его индексу в списке прикреплённых объектов с типом ListBox
            //CListBox *lbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0);
            ////--- Если ListBox создан и указатель на него получен
            //if(lbox!=NULL)
            //  {
            //   lbox.SetMultiColumn(true);
            //   lbox.CreateList(8,68);
            //  }
            //*/
           }
        }

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


Вот теперь вкладки ожили по сравнению с советником из прошлой статьи. Заголовки вкладок могут располагаться как сверху, так и снизу контейнера, но заметны и недоработки: если другие элементы вполне сносно себя ведут при взаимодействии с мышкой, то заголовки вкладок заметно "моргают". С этим разберёмся. И ещё: под конец я указал курсором на линию между заголовком и полем вкладки. Её там быть не должно. Но это в статье оговаривалось — будем делать в следующей статье так, чтобы вкладка — заголовок и поле были одним целым.


Что дальше

В следующей статье продолжим развитие элемента управления TabControl.

Ниже прикреплены все файлы текущей версии библиотеки, файлы тестового советника и индикатора контроля событий графиков для 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
DoEasy. Элементы управления (Часть 12): Базовый объект-список, WinForms-объекты ListBox и ButtonListBox
DoEasy. Элементы управления (Часть 13): Оптимизация взаимодействия WinForms-объектов с мышкой, начало разработки WinForms-объекта TabControl



Прикрепленные файлы |
MQL5.zip (4505.48 KB)
Машинное обучение и Data Science (Часть 03): Матричная регрессия Машинное обучение и Data Science (Часть 03): Матричная регрессия
В этот раз мы будем создавать модели с помощью матриц — они дают большую гибкость и позволяют создавать мощные модели, которые могут обрабатывать не только пять независимых переменных, но и множество других, насколько позволяют пределы вычислительных возможностей компьютера. Статья будет очень интересной, это точно.
Нейросети — это просто (Часть 23): Создаём инструмент для Transfer Learning Нейросети — это просто (Часть 23): Создаём инструмент для Transfer Learning
В данной серии статей мы уже не один раз упоминали о Transfer Learning. Но дальше упоминаний пока дело не шло. Я предлагаю заполнить этот пробел и посмотреть поближе на Transfer Learning.
Машинное обучение и Data Science (Часть 04): Предсказание биржевого краха Машинное обучение и Data Science (Часть 04): Предсказание биржевого краха
В этой статье я попытаюсь использовать нашу логистическую модель, чтобы спрогнозировать крах фондового рынка на основе главнейших акций для экономики США: NETFLIX и APPLE. Мы проанализируем эти акции, будем использовать информацию о предыдущих падениях рынка 2019 и 2020 годов. Посмотрим, как наша модель будет работать в нынешних мрачных условиях.
Разработка торговой системы на основе индикатора объемов Volumes Разработка торговой системы на основе индикатора объемов Volumes
Представляю вашему вниманию новую статью из серии, в которой мы учимся создавать торговые системы на основе популярных технических индикаторов. Данная статья будет посвящена индикатору Volumes. Объем как понятие является важным факторов в торговле на финансовых рынках, и поэтому обязательно надо его учитывать. В этой статье узнаем, как разработать торговую систему на основе показателей от индикатора объемов Volumes.