English 中文 Español Deutsch 日本語 Português
preview
DoEasy. Элементы управления (Часть 1): Первые шаги

DoEasy. Элементы управления (Часть 1): Первые шаги

MetaTrader 5Примеры | 8 апреля 2022, 17:22
2 518 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

Данная статья является началом нового цикла, посвящённого созданию элементов управления в стиле Windows Forms. Конечно же, не все элементы, входящие в список элементов управления в MS Visual Studio, мы сможем воспроизвести. Но наиболее востребованные для построения GUI для программ на языке MQL5 мы сделаем.

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

Если открыть панель элементов в MS Visual Studio, то мы увидим список групп элементов управления:

  • Все формы Windows Forms — здесь находятся все доступные формы для создания в своём проекте
  • Стандартные элементы управления
  • Контейнеры
  • Меню и панели инструментов
  • Данные
  • Компоненты
  • Печать
  • Диалоговые окна

Это не все из доступных групп, находящихся в списке панели элементов MS Visual Studio. Каждая такая группа содержит в себе большой набор элементов. И не каждый из них мы будем делать для библиотеки. Но самые необходимые, конечно же, мы сделаем.

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

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

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

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


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

В последнем обновлении бета-версии терминала (3245) были добавлены новые свойства для символа и аккаунта:

  • MQL5: Добавлено значение SYMBOL_SUBSCRIPTION_DELAY в перечисление ENUM_SYMBOL_INFO_INTEGER — размер задержки у котировок, передаваемых по символу.
    Используется только для инструментов, работающих по подписке, как правило при трансляции данных в бесплатном тестовом режиме.
    Свойство можно запрашивать только для символов, выбранных в Обзоре рынка. В ином случае вы получите ошибку ERR_MARKET_NOT_SELECTED (4302).

  • MQL5: Добавлено свойство ACCOUNT_HEDGE_ALLOWED в перечисление ENUM_ACCOUNT_INFO_INTEGER — разрешение на открытие встречных позиций и отложенных ордеров. Используется только для хеджинговых счетов, позволяя реализовать требования некоторых регуляторов, когда на счете запрещается иметь встречные позиции, но разрешается иметь несколько позиций по одному символу в одном направлении.
    Если эта опция отключена, на счетах запрещается одновременно иметь разнонаправленные позиции и ордера по одному и тому же инструменту. Например, если на счете имеется позиция Buy, то пользователь не может открыть позицию Sell или выставить отложенный ордер на продажу. При попытке сделать это пользователю будет выдаваться ошибка TRADE_RETCODE_HEDGE_PROHIBITED.

Внесём эти свойства и в объекты символа и аккаунта библиотеки.

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

//+------------------------------------------------------------------+
//| Список индексов текстовых сообщений библиотеки                   |
//+------------------------------------------------------------------+
enum ENUM_MESSAGES_LIB
  {
   MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST,      // Начало списка параметров
   MSG_LIB_PARAMS_LIST_END,                           // Конец списка параметров
   MSG_LIB_PROP_NOT_SUPPORTED,                        // Свойство не поддерживается
   MSG_LIB_PROP_NOT_SUPPORTED_MQL4,                   // Свойство не поддерживается в MQL4
   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_2155,          // Свойство не поддерживается в MetaTrader5 версии ниже 2155
   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245,          // Свойство не поддерживается в MetaTrader5 версии ниже 3245
   MSG_LIB_PROP_NOT_SUPPORTED_POSITION,               // Свойство не поддерживается у позици

...

  
   MSG_SYM_PROP_BACKGROUND_COLOR,                     // Цвет фона символа в Market Watch
   MSG_SYM_PROP_SUBSCRIPTION_DELAY,                   // Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке
   //---

...

   MSG_ACC_PROP_FIFO_CLOSE,                           // Признак закрытия позиций только по правилу FIFO
   MSG_ACC_PROP_HEDGE_ALLOWED,                        // Разрешение на открытие встречных позиций и отложенных ордеров
   //---
   MSG_ACC_PROP_BALANCE,                              // Баланс счета

...

   MSG_GRAPH_ELEMENT_TYPE_FORM,                       // Форма
   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Окно
   MSG_GRAPH_ELEMENT_TYPE_PANEL,                      // Элемент управления Panel
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе
//---

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

   {"Свойство не поддерживается в MetaTrader5 версии ниже 2155","The property is not supported in MetaTrader5, build lower than 2155"},
   {"Свойство не поддерживается в MetaTrader5 версии ниже 3245","The property is not supported in MetaTrader5, build lower than 3245"},
   {"Свойство не поддерживается у позиции","Property not supported for position"},

...

   {"Цвет фона символа в Market Watch","Background color of the symbol in Market Watch"},
   {"Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке","Delay size for quotes transmitted per symbol for instruments working by subscription"},
   {"Максимальный Bid за день","Maximal Bid of the day"},

...

   {"Тип торгового сервера","Type of trading server"},
   {"Признак закрытия позиций только по правилу FIFO","Sign of closing positions only according to the FIFO rule"},
   {"Разрешение на открытие встречных позиций и отложенных ордеров","Permission to open opposite positions and pending orders"},
   //---
   {"Баланс счета","Account balance"},

...

   {"Форма","Form"},
   {"Окно","Window"},
   {"Элемент управления \"Panel\"","Control element \"Panel\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},


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

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

//--- Параметры канваса
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Частота обновления канваса
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Ноль для канваса с альфа-каналом
#define CLR_FORE_COLOR                 (C'0x2D,0x43,0x48')        // Цвет по умолчанию для текстов объектов на канвасе
#define DEF_FONT                       ("Calibri")                // Шрифт по умолчанию
#define DEF_FONT_SIZE                  (8)                        // Размер шрифта по умолчанию
#define OUTER_AREA_SIZE                (16)                       // Размер одной стороны внешней области вокруг рабочего пространства формы
//--- Параметры графических объектов

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

//+------------------------------------------------------------------+
//| Список типов объектов библиотеки                                 |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Графика
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // Тип объекта "Базовый объект всех графических объектов библиотеки"
   OBJECT_DE_TYPE_GELEMENT,                                       // Тип объекта "Графический элемент"
   OBJECT_DE_TYPE_GFORM,                                          // Тип объекта "Форма"
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // Тип объекта "Форма управления опорными точками графического объекта"
   OBJECT_DE_TYPE_GSHADOW,                                        // Тип объекта "Тень"
   //--- WinForms
   OBJECT_DE_TYPE_GWF_PANEL,                                      // Тип объекта "WinForms Panel"
//--- Анимация
   OBJECT_DE_TYPE_GFRAME,                                         // Тип объекта "Один кадр анимации"
   OBJECT_DE_TYPE_GFRAME_TEXT,                                    // Тип объекта "Один кадр текстовой анимации"
   OBJECT_DE_TYPE_GFRAME_QUAD,                                    // Тип объекта "Один кадр прямоугольной анимации"
   OBJECT_DE_TYPE_GFRAME_GEOMETRY,                                // Тип объекта "Один кадр геометрической анимации"
   OBJECT_DE_TYPE_GANIMATIONS,                                    // Тип объекта "Анимации"
//--- Управление графическими объектами

В этом разделе (WinForms) мы будем добавлять всё новые типы объектов по мере их создания.

В перечисление целочисленных свойств аккаунта впишем новое свойство и увеличим количество целочисленных свойств объекта с 11 до 12:

//+------------------------------------------------------------------+
//| Целочисленные свойства аккаунта                                  |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_INTEGER
  {
   ...
   ACCOUNT_PROP_FIFO_CLOSE,                                 // Признак закрытия позиций только по правилу FIFO
   ACCOUNT_PROP_HEDGE_ALLOWED                               // Разрешение на открытие встречных позиций и отложенных ордеров
  };
#define ACCOUNT_PROP_INTEGER_TOTAL    (12)                  // Общее количество целочисленных свойств счетов
#define ACCOUNT_PROP_INTEGER_SKIP     (0)                   // Количество неиспользуемых в сортировке целочисленных свойств счетов
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки аккаунтов                          |
//+------------------------------------------------------------------+
#define FIRST_ACC_DBL_PROP            (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP)
#define FIRST_ACC_STR_PROP            (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_ACCOUNT_MODE
  {
   ... 
   SORT_BY_ACCOUNT_FIFO_CLOSE,                              // Сортировать по признаку закрытия позиций только по правилу FIFO
   SORT_BY_ACCOUNT_HEDGE_ALLOWED,                           // Сортировать по разрешению на открытие встречных позиций и отложенных ордеров
//--- Сортировка по вещественным свойствам
   SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP,            // Сортировать по балансу счета в валюте депозита
   SORT_BY_ACCOUNT_CREDIT,                                  // Сортировать по размеру предоставленного кредита в валюте депозита
   ... 
   SORT_BY_ACCOUNT_COMPANY                                  // Сортировать по имени компании, обслуживающей счет
  };
//+------------------------------------------------------------------+


В перечисление целочисленных свойств символа добавим новое свойство и увеличим количество целочисленных свойств с 40 до 41:

//+------------------------------------------------------------------+
//| Целочисленные свойства символа                                   |
//+------------------------------------------------------------------+
enum ENUM_SYMBOL_PROP_INTEGER
  {
   //--- ...
   SYMBOL_PROP_OPTION_MODE,                                 // Тип опциона (из перечисления ENUM_SYMBOL_OPTION_MODE)
   SYMBOL_PROP_OPTION_RIGHT,                                // Право опциона (Call/Put) (из перечисления ENUM_SYMBOL_OPTION_RIGHT)
   SYMBOL_PROP_SUBSCRIPTION_DELAY,                          // Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке
   //--- пропускаемое свойство
   SYMBOL_PROP_BACKGROUND_COLOR                             // Цвет фона, которым подсвечивается символ в Market Watch
  }; 
#define SYMBOL_PROP_INTEGER_TOTAL    (41)                   // Общее количество целочисленных свойств
#define SYMBOL_PROP_INTEGER_SKIP     (1)                    // Количество неиспользуемых в сортировке целочисленных свойств символа
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки символов                           |
//+------------------------------------------------------------------+
#define FIRST_SYM_DBL_PROP          (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP)
#define FIRST_SYM_STR_PROP          (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP)
enum ENUM_SORT_SYMBOLS_MODE
  {
   ... 
   SORT_BY_SYMBOL_OPTION_MODE,                              // Сортировать по типу опциона (из перечисления ENUM_SYMBOL_OPTION_MODE)
   SORT_BY_SYMBOL_OPTION_RIGHT,                             // Сортировать по праву опциона (Call/Put) (из перечисления ENUM_SYMBOL_OPTION_RIGHT)
   SORT_BY_SYMBOL_SUBSCRIPTION_DELAY,                       // Сортировать по размеру задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке


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

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Стандартный графический объект
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Расширенный стандартный графический объект
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Элемент
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Объект тени
   GRAPH_ELEMENT_TYPE_FORM,                           // Форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
   //--- WinForms
   GRAPH_ELEMENT_TYPE_PANEL,                          // Windows Forms Panel
  };
//+------------------------------------------------------------------+

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

Если в объект-панель добавить другой объект, больший по размеру, чем панель, являющаяся для него контейнером, и если для панели разрешено автоматическое изменение её размеров, то есть два варианта изменения размера:

  1. только увеличение размера панели
  2. увеличение и уменьшение размера панели

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

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

//+------------------------------------------------------------------+
//| Режим автоматического изменения размера элемента интерфейса      |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE
  {
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                  // Только увеличение
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,           // Увеличение и уменьшение
  };
//+------------------------------------------------------------------+

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

Сразу же после перечисления режимов автоматического изменения размера, впишем новое перечисление:

//+------------------------------------------------------------------+
//| Границы элемента управления, привязанные к контейнеру            |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_DOCK_MODE
  {
   CANV_ELEMENT_DOCK_MODE_TOP,                        // Присоединение сверху и растягивание на ширину контейнера
   CANV_ELEMENT_DOCK_MODE_BOTTOM,                     // Присоединение снизу и растягивание на ширину контейнера
   CANV_ELEMENT_DOCK_MODE_LEFT,                       // Присоединение слева и растягивание на высоту контейнера
   CANV_ELEMENT_DOCK_MODE_RIGHT,                      // Присоединение справа и растягивание на высоту контейнера
   CANV_ELEMENT_DOCK_MODE_FILL,                       // Растягивание на ширину и высоту всего контейнера
   CANV_ELEMENT_DOCK_MODE_NONE,                       // Прикреплён к указанным координатам, размеры не меняются
  };
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   ... 
   CANV_ELEMENT_PROP_ZORDER,                          // Приоритет графического объекта на получение события нажатия мышки на графике
   CANV_ELEMENT_PROP_ENABLED,                         // Флаг доступности элемента
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (25)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
   ... 
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Сортировать по приоритету графического объекта на получение события нажатия мышки на графике
   SORT_BY_CANV_ELEMENT_ENABLED,                      // Сортировать по флагу доступности элемента
   ... 
  };
//+------------------------------------------------------------------+


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

Откроем файл \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh и внесём доработки в класс объекта-аккаунта.

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

//+------------------------------------------------------------------+
//| Класс аккаунта                                                   |
//+------------------------------------------------------------------+
class CAccount : public CBaseObjExt
  {
private:
   struct SData
     {
      //--- Целочисленные свойства счёта
      ... 
      bool           fifo_close;                   // ACCOUNT_FIFO_CLOSE (Признак того, что позиции можно закрывать только по правилу FIFO)
      bool           hedge_allowed;                // ACCOUNT_HEDGE_ALLOWED (Разрешение на открытие встречных позиций и отложенных ордеров)
      ... 
     };
   SData             m_struct_obj;                                      // Структура объекта-аккаунта
   uchar             m_uchar_array[];                                   // uchar-массив структуры объекта-аккаунта
   
//--- Свойства объекта


В блок методов упрощённого доступа к свойствам объекта-аккаунта впишем новый метод:

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-аккаунта          |
//+------------------------------------------------------------------+
//--- Возвращает целочисленные свойства аккаунта
   ... 
   bool              FIFOClose(void)                                 const { return (bool)this.GetProperty(ACCOUNT_PROP_FIFO_CLOSE);                              }
   bool              IsHedge(void)                                   const { return this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;                        }
   bool              HedgeAllowed(void)                              const { return (bool)this.GetProperty(ACCOUNT_PROP_HEDGE_ALLOWED);                           }
   ... 

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

В конструкторе класса в массив свойств объекта впишем это значение:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CAccount::CAccount(void)
  {
   this.m_type=OBJECT_DE_TYPE_ACCOUNT;
//--- Инициализация контролируемых данных
   this.SetControlDataArraySizeLong(ACCOUNT_PROP_INTEGER_TOTAL);
   this.SetControlDataArraySizeDouble(ACCOUNT_PROP_DOUBLE_TOTAL);
   this.ResetChangesParams();
   this.ResetControlsParams();
   ... 
   this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]                        = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader 5" ? 5 : 4);
   this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE]                         = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif );
   this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED]                      = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif );  
   ... 
//--- Обновление данных в базовом объекте и поиск изменений
   CBaseObjExt::Refresh();
  }
//+-------------------------------------------------------------------+

Здесь: только для MQL5 — если версия терминала ниже 3245, то такого свойства нет, и записываем значение false. Если же версия терминала больше, либо равна 3245, то получаем это значение из нового свойства аккаунта и записываем его в массив целочисленных свойств объекта. Для MQL4 всегда записываем false — там нет таких и многих других свойств.

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

//+------------------------------------------------------------------+
//| Обновляет все данные аккаунта                                    |
//+------------------------------------------------------------------+
void CAccount::Refresh(void)
  {
//--- Инициализация событийных данных
   this.m_is_event=false;
   this.m_hash_sum=0;
   ... 
   this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE]                            = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif );
   this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED]                         = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif );
   ... 
   CBaseObjExt::Refresh();
   this.CheckEvents();
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт структуру объекта-аккаунта                               |
//+------------------------------------------------------------------+
bool CAccount::ObjectToStruct(void)
  {
//--- Сохранение целочисленных свойств
   ... 
   this.m_struct_obj.server_type=(int)this.ServerType();
   this.m_struct_obj.fifo_close=this.FIFOClose();
   this.m_struct_obj.hedge_allowed=this.HedgeAllowed();
   ... 
   //--- Сохранение структуры в uchar-массив
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Здесь мы вписываем в целочисленные поля структуры объекта новое свойство и свойство аккаунта FIFOClose, которое было добавлено в версии 2155, но здесь мы этот момент как-то упустили...

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

//+------------------------------------------------------------------+
//| Создаёт объект-аккаунт из структуры                              |
//+------------------------------------------------------------------+
void CAccount::StructToObject(void)
  {
//--- Сохранение целочисленных свойств
   ... 
   this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE]                         = this.m_struct_obj.fifo_close;
   this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED]                      = this.m_struct_obj.hedge_allowed;
   ... 
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства аккаунта             |
//+------------------------------------------------------------------+
string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property)
  {
   return
     (
      ... 
      property==ACCOUNT_PROP_FIFO_CLOSE      ?  CMessage::Text(MSG_ACC_PROP_FIFO_CLOSE)+": "+
                                                   (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))  :
      property==ACCOUNT_PROP_HEDGE_ALLOWED   ?  CMessage::Text(MSG_ACC_PROP_HEDGE_ALLOWED)+": "+
                                                   (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))  :
      ""
     );
  }
//+------------------------------------------------------------------+


Аналогичные доработки впишем в файл объекта-символа в \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh.

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

protected:
//--- Защищённый параметрический конструктор
                     CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index);

//--- Получает и возвращает целочисленные свойства выбранного символа из его параметров
   ... 
   long              SymbolCalcMode(void)                const;
   long              SymbolSwapMode(void)                const;
   long              SymbolSubscriptionDelay(void)       const;
   long              SymbolDigitsLot(void);
   int               SymbolDigitsBySwap(void);
   ... 
//--- Ищет символ и возвращает флаг его наличия на сервере
   bool              Exist(void)                         const;

public:


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

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-символа           |
//+------------------------------------------------------------------+
//--- Целочисленные свойства
   ... 
   ENUM_SYMBOL_OPTION_MODE OptionMode(void)                       const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE);        }
   ENUM_SYMBOL_OPTION_RIGHT OptionRight(void)                     const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT);      }
   long              SubscriptionDelay(void)                      const { return this.GetProperty(SYMBOL_PROP_SUBSCRIPTION_DELAY);                          }
//--- Вещественные свойства

Здесь мы просто возвращаем методом GetProperty() значение, записанное в массиве целочисленных свойств объекта-символа.

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

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index)
  {
//--- Сохранение целочисленных свойств
   ... 
   this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]                                    = this.m_book_subscribed;
   this.m_long_prop[SYMBOL_PROP_SUBSCRIPTION_DELAY]                                 = this.SymbolSubscriptionDelay();
   ... 
//--- Инициализация умолчательных значений торгового объекта
   this.m_trade.Init(this.Name(),0,this.LotsMin(),5,0,0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возвращает размер задержки у котировок, передаваемых по символу, |
//| для инструментов, работающих по подписке                         |
//+------------------------------------------------------------------+
long CSymbol::SymbolSubscriptionDelay(void) const
  {
   return
     (
      #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)>=3245 ? ::SymbolInfoInteger(this.m_name,SYMBOL_SUBSCRIPTION_DELAY) : 0)
      #else 0
      #endif 
     );
  }
//+------------------------------------------------------------------+

Здесь: для MQL5, если версия терминала равна или выше 3245, то возвращаем значение нового свойства символа, иначе — возвращаем ноль.
Для MQL4 всегда возвращаем ноль
— там нет такого свойства.

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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства символа              |
//+------------------------------------------------------------------+
string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER property)
  {
   return
     (
      ... 
      property==SYMBOL_PROP_BACKGROUND_COLOR    ?  CMessage::Text(MSG_SYM_PROP_BACKGROUND_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (this.GetProperty(property)==CLR_MW_DEFAULT || this.GetProperty(property)==CLR_NONE ?  ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+::ColorToString((color)this.GetProperty(property),true))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SUBSCRIPTION_DELAY  ?  CMessage::Text(MSG_SYM_PROP_SUBSCRIPTION_DELAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3245 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245)+")" : ": "+(string)this.GetProperty(property))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


Для цветовых схем элементов GUI в файле \MQL5\Include\DoEasy\GraphINI.mqh добавим значение цвета текста, увеличим количество параметров в цветовой схеме с 4 до 5 и в массивы значений цветовых схем добавим значения цвета текста:

//+------------------------------------------------------------------+
//| Список индексов параметров цветов цветовой схемы                 |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME_COLORS
  {
   COLOR_THEME_COLOR_FORM_BG,                   // Цвет фона формы
   COLOR_THEME_COLOR_FORM_FRAME,                // Цвет рамки формы
   COLOR_THEME_COLOR_FORM_RECT_OUTER,           // Цвет очерчивающего прямоугольника формы
   COLOR_THEME_COLOR_FORM_SHADOW,               // Цвет тени формы
   COLOR_THEME_COLOR_FORM_TEXT,                 // Цвет текстов на форме
  };
#define TOTAL_COLOR_THEME_COLORS       (5)      // Количество параметров в цветовой теме
//+------------------------------------------------------------------+
//| Массив, содержащий цветовые схемы                                |
//+------------------------------------------------------------------+
color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]=
  {
//--- Параметры цветовой схемы "Голубая сталь"
   {
      C'134,160,181',                           // Цвет фона формы
      C'134,160,181',                           // Цвет рамки формы
      clrDimGray,                               // Цвет очерчивающего прямоугольника формы
      clrGray,                                  // Цвет тени формы
      C'0x3E,0x3E,0x3E',                        // Цвет текстов на форме
   },
//--- Параметры цветовой схемы "Светлый серо-циановый"
   {
      C'181,196,196',                           // Цвет фона формы
      C'181,196,196',                           // Цвет рамки формы
      clrGray,                                  // Цвет очерчивающего прямоугольника формы
      clrGray,                                  // Цвет тени формы
      C'0x3E,0x3E,0x3E',                        // Цвет текстов на форме
   },
  };
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Стили рамок                                                      |
//+------------------------------------------------------------------+
enum ENUM_FRAME_STYLE
  {
   FRAME_STYLE_NONE,                            // Нет рамки
   FRAME_STYLE_SIMPLE,                          // Простая рамка
   FRAME_STYLE_FLAT,                            // Плоская рамка
   FRAME_STYLE_BEVEL,                           // Рельефная (выпуклая)
   FRAME_STYLE_STAMP,                           // Рельефная (вдавленная)
  };
//+------------------------------------------------------------------+


Доработаем класс базового объекта всех графических объектов библиотеки в файле \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh.

Чтобы можно было в своих программах при указании идентификатора текущего графика указывать 0 или NULL вместо указания числового значения идентификатора или передачи функции ChartID(), впишем проверку значения, переданного в метод SetChartID():

public:
//--- Возвращает префикс имени
   string            NamePrefix(void)                    const { return this.m_name_prefix;           }
//--- Установка значений переменных класса
   void              SetObjectID(const long value)             { this.m_object_id=value;              }
   void              SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong;             }
   void              SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj;           }
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type;   }
   void              SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species;        }
   void              SetGroup(const int group)                 { this.m_group=group;                  }
   void              SetName(const string name)                { this.m_name=name;                    }
   void              SetDigits(const int value)                { this.m_digits=value;                 }
   void              SetChartID(const long chart_id)
                       { this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);    }
   
//--- Устанавливает флаг "Объект на заднем плане"

Здесь мы проверяем, какое значение передано в метод и, если это 0 или NULL, то присваиваем переменной значение идентификатора текущего графика, иначе — переданное в метод значение.

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

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(void)
  {
   return
     (
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)           :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)  :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)         :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)               :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)             :
      //---
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_PANEL              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_PANEL)              :
      "Unknown"
     );
  }
//+------------------------------------------------------------------+


В классе объекта графического элемента в файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh в структуру объекта впишем новое поле для свойства доступности элемента:

//+------------------------------------------------------------------+
//| Класс объекта графического элемента                              |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // Объект класса CCanvas
   CPause            m_pause;                                  // Объект класса "Пауза"
   bool              m_shadow;                                 // Наличие тени
   color             m_chart_color_bg;                         // Цвет фона графика
   uint              m_duplicate_res[];                        // Массив для хранения копии данных ресурса

//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
private:
   struct SData
     {
      //--- Целочисленные свойства объекта
      ... 
      int            coord_act_bottom;                         // Нижняя граница активной зоны элемента
      long           zorder;                                   // Приоритет графического объекта на получение события нажатия мышки на графике
      bool           enabled;                                  // Флаг доступности элемента
      //--- Вещественные свойства объекта

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


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

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
   ... 
//--- Устанавливает флаг (1) перемещаемости, (2) активности объекта, (3) флаг взаимодействия,
//--- (4) идентификатор элемента, (5) номер элемента в списке, (6) флаг доступности, (7) наличие тени
   void              SetMovable(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag);                     }
   void              SetActive(const bool flag)                { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag);                      }
   void              SetInteraction(const bool flag)           { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag);                 }
   void              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetEnabled(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag);                     }
   void              SetShadow(const bool flag)                { this.m_shadow=flag;                                                   } 
   ... 
//--- Возвращает флаг (1) перемещаемости, (2) активности, (3) взаимодействия, (4) доступности элемента
   bool              Movable(void)                       const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);             }
   bool              Active(void)                        const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);              }
   bool              Interaction(void)                   const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION);         }
   bool              Enabled(void)                       const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);             }
//--- Возвращает (1) имя объекта, (2) имя графического ресурса, (3) идентификатор графика, (4) номер подокна графика


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

//+------------------------------------------------------------------+
//| Методы заполнения, очистки и обновления растровых данных         |
//+------------------------------------------------------------------+
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   void              Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   void              Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Полностью очищает элемент
   void              Erase(const bool redraw=false);
//--- Обновляет элемент
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }

ранее метод был таким:

void              Erase(color &colors[],const uchar opacity,const bool vgradient=true,const bool cycle=false,const bool redraw=false);

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

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
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   name,
                           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.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_color_bg=colour;
   this.m_opacity=opacity;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {
      ... 
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity);                       // Флаг активности элемента
      this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false);                     // Флаг взаимодействия элемента с внешней средой
      this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true);                          // Флаг доступности элемента
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Правая граница элемента
   ... 
  }
//+------------------------------------------------------------------+

В защищённом конструкторе сделаем то же самое:

//+------------------------------------------------------------------+
//| Защищённый конструктор                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  name,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_color_bg=CLR_CANV_NULL;
   this.m_opacity=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false))
     {
      ... 
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false);                          // Флаг активности элемента
      this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false);                     // Флаг взаимодействия элемента с внешней средой
      this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true);                          // Флаг доступности элемента
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Правая граница элемента
      ... 

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


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

//+------------------------------------------------------------------+
//| Создаёт структуру объекта                                        |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Сохранение целочисленных свойств
   ... 
   this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);                   // Флаг активности элемента
   this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION);         // Флаг взаимодействия элемента с внешней средой
   this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);                 // Флаг доступности элемента
   this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X);          // X-координата активной зоны элемента
   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);          // Y-координата активной зоны элемента
   ... 
   return true;
  }
//+------------------------------------------------------------------+

 

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

//+------------------------------------------------------------------+
//| Создаёт объект из структуры                                      |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Сохранение целочисленных свойств
   ... 
   this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active);                         // Флаг активности элемента
   this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction);               // Флаг взаимодействия элемента с внешней средой
   this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled);                       // Флаг доступности элемента
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x);               // X-координата активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y);               // Y-координата активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right);             // Правая граница активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);           // Нижняя граница активной зоны элемента
   this.m_color_bg=this.m_struct_obj.color_bg;                                                  // Цвет фона элемента
   this.m_opacity=this.m_struct_obj.opacity;                                                    // Непрозрачность элемента
   this.m_zorder=this.m_struct_obj.zorder;                                                      // Приоритет графического объекта на получение события нажатия мышки на графике
   ... 
//--- Сохранение строковых свойств
   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));// Имя графического ресурса
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт графический объект-элемент                               |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Идентификатор графика
                          const int wnd_num,       // Подокно графика
                          const string name,       // Имя элемента
                          const int x,             // Координата X
                          const int y,             // Координата Y
                          const int w,             // Ширина
                          const int h,             // Высота
                          const color colour,      // Цвет фона
                          const uchar opacity,     // Непрозрачность
                          const bool redraw=false) // Флаг необходимости перерисовки
                         
  {
   ::ResetLastError();
   if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,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,::GetLastError(),true);
   return false;
  }
//+------------------------------------------------------------------+


Доработаем класс объекта-формы в файле \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

Приватный метод инициализации переменных

//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // Список присоединённых элементов
   CAnimations      *m_animations;                             // Указатель на объект анимаций
   CShadowObj       *m_shadow_obj;                             // Указатель на объект тени
   CMouseState       m_mouse;                                  // Объект класса "Состояния мышки"
   ENUM_MOUSE_FORM_STATE m_mouse_form_state;                   // Состояние мышки относительно формы
   ushort            m_mouse_state_flags;                      // Флаги состояния мышки
   color             m_color_frame;                            // Цвет рамки формы
   int               m_frame_width_left;                       // Ширина рамки формы слева
   int               m_frame_width_right;                      // Ширина рамки формы справа
   int               m_frame_width_top;                        // Ширина рамки формы сверху
   int               m_frame_width_bottom;                     // Ширина рамки формы снизу
   int               m_offset_x;                               // Смещение координаты X относительно курсора
   int               m_offset_y;                               // Смещение координаты Y относительно курсора
   
//--- Инициализирует переменные
   void              Initialize(void);
//--- Сбрасывает размер массива (1) текстовых, (2) прямоугольных, (3) геометрических кадров анимаций
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);

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

//--- Сбрасывает размер массива (1) текстовых, (2) прямоугольных, (3) геометрических кадров анимаций
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Возвращает имя зависимого объекта
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
  
//--- Создаёт новый графический объект
   CGCnvElement     *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);

//--- Создаёт объект для тени
   void              CreateShadowObj(const color colour,const uchar opacity);
   
protected:
//--- Инициализирует переменные
   void              Initialize(void);
   void              Deinitialize(void);
   
public:
//--- Возвращает (1) состояние мышки относительно формы, координату (2) X, (3) Y курсора


Метод GetList(), возвращающий список присоединённых объектов, переименуем в GetListElements(), что более подходит по его смыслу и назначению:

//--- Возвращает (1) себя, список (2) присоединённых объектов, (3) объект тени
   CForm            *GetObject(void)                                          { return &this;                  }
   CArrayObj        *GetListElements(void)                                    { return &this.m_list_elements;  }
   CGCnvElement     *GetShadowObj(void)                                       { return this.m_shadow_obj;      }
//--- Возвращает указатель на (1) объект анимаций, список (2) текстовых, (3) прямоугольных кадров анимаций


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

//--- Создаёт новый присоединённый элемент
   bool              CreateNewElement(const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);
//--- Добавляет новый присоединённый элемент
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

//--- Рисует тень объекта
   void              DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4);


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

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   if(this.m_shadow_obj!=NULL)
      delete this.m_shadow_obj;
   if(this.m_animations!=NULL)
      delete this.m_animations;
  }
//+------------------------------------------------------------------+

в новый метод деинициализации:

//+------------------------------------------------------------------+
//| Деинициализирует переменные                                      |
//+------------------------------------------------------------------+
void CForm::Deinitialize(void)
  {
   if(this.m_shadow_obj!=NULL)
      delete this.m_shadow_obj;
   if(this.m_animations!=NULL)
      delete this.m_animations;
  }  
//+------------------------------------------------------------------+

В деструкторе будем вызывать этот метод:

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   this.Deinitialize();
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Добавляет новый присоединённый элемент                           |
//+------------------------------------------------------------------+
bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y)
  {
   if(obj==NULL)
      return false;
   this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ);
   int index=this.m_list_elements.Search(obj);
   if(index>WRONG_VALUE)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
      return false;
     }
   if(!this.m_list_elements.Add(obj))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const int element_num,
                             const string element_name,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool movable,
                             const bool activity)
  {
   CGCnvElement *obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,element_num,element_name,x,y,w,h,colour,opacity,movable,activity);
   if(obj==NULL)
      return false;
   if(!this.AddNewElement(obj,x,y))
     {
      delete obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт объект тени                                              |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- Если флаг тени выключен, или объект тени уже существует - уходим
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;
//--- Рассчитываем координаты объекта тени в соответствии с отступом сверху и слева
   int x=this.CoordX()-OUTER_AREA_SIZE;
   int y=this.CoordY()-OUTER_AREA_SIZE;
//--- Рассчитываем ширину и высоту в соответствии с отступом сверху, снизу, слева и справа
   int w=this.Width()+OUTER_AREA_SIZE*2;
   int h=this.Height()+OUTER_AREA_SIZE*2;
//--- Создаём новый объект тени и записываем указатель на него в переменную
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("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_shadow_obj.SetID(this.ID());
   this.m_shadow_obj.SetNumber(-1);
   this.m_shadow_obj.SetOpacityShadow(opacity);
   this.m_shadow_obj.SetColorShadow(colour);
   this.m_shadow_obj.SetMovable(this.Movable());
   this.m_shadow_obj.SetActive(false);
   this.m_shadow_obj.SetVisible(false,false);
//--- Объект-форму перемещаем на передний план
   this.BringToTop();
  }
//+------------------------------------------------------------------+

Все подготовительные этапы мы завершили.


Класс объекта WinForms Panel

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

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

Для всех элементов управления WinForms определим новый каталог в библиотеке.

Создадим новую папку \MQL5\Include\DoEasy\Objects\Graph\WForms\, а в ней создадим подпапки по наименованиям групп элементов управления в MS Visual Studio в том количестве, которое мы определили в начале статьи:

  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Components\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Data\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Dialogs\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Menu & Toolbars\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Printing     

Так как панель является контейнером для других объектов, то и файл класса этого объекта будет расположен в соответствующей папке \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\.

Создадим в указанной папке новый файл Panel.mqh класса CPanel, унаследованного от класса CForm, файл которого должен быть подключен:

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

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

//+------------------------------------------------------------------+
//| Класс объекта Panel элементов управления WForms                  |
//+------------------------------------------------------------------+
class CPanel : public CForm
  {
private:
   color             m_fore_color;                                   // Цвет текста по умолчанию для всех объектов на панели
   ENUM_FRAME_STYLE  m_border_style;                                 // Стиль рамки панели
   bool              m_autoscroll;                                   // Флаг автоматического появления полосы прокрутки
   int               m_autoscroll_margin[2];                         // Массив полей вокруг элемента управления при автоматической прокрутке
   bool              m_autosize;                                     // Флаг автоматического изменения размера элемента под содержимое
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Режим автоматического изменения размера элемента под содержимое
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Режим привязки границ элемента к контейнеру
   int               m_margin[4];                                    // Массив промежутков всех сторон между полями данного и другого элемента управления
   int               m_padding[4];                                   // Массив промежутков всех сторон внутри элемента управления
public:

Для пояснения что такое Margin, Padding и AutoSize, приведу пример из справки по MS Windows Forms .NET Framework 4.X:

... три наиболее важных свойства: Margin , Padding и AutoSize, которые имеются во всех элементах управления Windows Forms.

Свойство Margin определяет поле вокруг элемента управления, благодаря которому обеспечивается определенное расстояние между границами этого элемента и другими элементами.

Свойство Padding определяет поле внутри элемента управления, благодаря которому обеспечивается определенное расстояние между содержимым элемента управления (например, значением свойства Text) и его границами.

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


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

public:
//--- (1) Устанавливает, (2) возвращает цвет текста по умолчанию всех объектов на панели
   void              ForeColor(const color clr)                      { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }

//--- (1) Устанавливает, (2) возвращает стиль рамки
   void              BorderStyle(const ENUM_FRAME_STYLE style)       { this.m_border_style=style;           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return this.m_border_style;          }

//--- (1) Устанавливает, (2) возвращает флаг автоматического появления полосы прокрутки
   void              AutoScroll(const bool flag)                     { this.m_autoscroll=flag;              }
   bool              AutoScroll(void)                                { return this.m_autoscroll;            }
//--- Устанавливает (1) ширину, (2) высоту поля, (3) всех полей вокруг элемента управления при автоматической прокрутке
   void              AutoScrollMarginWidth(const int value)          { this.m_autoscroll_margin[0]=value;   }
   void              AutoScrollMarginHeight(const int value)         { this.m_autoscroll_margin[1]=value;   }
   void              AutoScrollMarginAll(const int value)
                       {
                        this.AutoScrollMarginWidth(value); this.AutoScrollMarginHeight(value);
                       }
//--- Возвращает (1) ширину, (2) высоту поля вокруг элемента управления при автоматической прокрутке
   int               AutoScrollMarginWidth(void)               const { return this.m_autoscroll_margin[0];  }
   int               AutoScrollMarginHeight(void)              const { return this.m_autoscroll_margin[1];  }
   
//--- (1) Устанавливает, (2) возвращает флаг автоматического изменения размера элемента под содержимое
   void              AutoSize(const bool flag)                       { this.m_autosize=flag;                }
   bool              AutoSize(void)                                  { return this.m_autosize;              }
//--- (1) Устанавливает, (2) возвращает режим автоматического изменения размера элемента под содержимое
   void              AutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode) { this.m_autosize_mode=mode; }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)         const { return this.m_autosize_mode;         }
   
//--- (1) Устанавливает, (2) возвращает режим привязки границ элемента к контейнеру
   void              DockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode){ this.m_dock_mode=mode;               }
   ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void)                  const { return this.m_dock_mode;             }

//--- Устанавливает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу, (5) со всех сторон между полями данного и другого элемента управления
   void              MarginLeft(const int value)                     { this.m_margin[0]=value;              }
   void              MarginTop(const int value)                      { this.m_margin[1]=value;              }
   void              MarginRight(const int value)                    { this.m_margin[2]=value;              }
   void              MarginBottom(const int value)                   { this.m_margin[3]=value;              }
   void              MarginAll(const int value)
                       {
                        this.MarginLeft(value); this.MarginTop(value); this.MarginRight(value); this.MarginBottom(value);
                       }
//--- Возвращает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу между полями данного и другого элемента управления
   int               MarginLeft(void)                          const { return this.m_margin[0];             }
   int               MarginTop(void)                           const { return this.m_margin[1];             }
   int               MarginRight(void)                         const { return this.m_margin[2];             }
   int               MarginBottom(void)                        const { return this.m_margin[3];             }

//--- Устанавливает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу, (5) со всех сторон внутри элемента управления
   void              PaddingLeft(const int value)                     { this.m_padding[0]=value;            }
   void              PaddingTop(const int value)                      { this.m_padding[1]=value;            }
   void              PaddingRight(const int value)                    { this.m_padding[2]=value;            }
   void              PaddingBottom(const int value)                   { this.m_padding[3]=value;            }
   void              PaddingAll(const int value)
                       {
                        this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value);
                       }
//--- Возвращает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу между полями внутри элемента управления
   int               PaddingLeft(void)                          const { return this.m_padding[0];           }
   int               PaddingTop(void)                           const { return this.m_padding[1];           }
   int               PaddingRight(void)                         const { return this.m_padding[2];           }
   int               PaddingBottom(void)                        const { return this.m_padding[3];           }
   
//--- Конструкторы
                     CPanel(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.m_fore_color=CLR_FORE_COLOR;
                        this.MarginAll(3);
                        this.PaddingAll(0);
                        this.Initialize(); 
                       }
//--- Деструктор
                    ~CPanel();
  };
//+------------------------------------------------------------------+

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

Например, для значения Margin в MS Visual Studio предусмотрена возможность установить как каждое свойство раздельно, так и все четыре одновременно:


У нас есть четыре конструктора класса: с указанием (1) идентификатора чарта, подокна графика, имени объекта и координат с размерами, (2) подокна графика текущего чарта, имени объекта и координат с размерами, (3) имени объекта и координат с размерами, (4) имени объекта с нулевыми координатами и размерами:

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL;
   this.m_fore_color=CLR_FORE_COLOR;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Конструктор на текущем чарте с указанием подокна                 |
//+------------------------------------------------------------------+
CPanel::CPanel(const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Конструктор на текущем чарте в главном окне графика              |
//+------------------------------------------------------------------+
CPanel::CPanel(const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),0,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+

В каждом конструкторе в строке инициализации передаём необходимые параметры в конструктор родительского класса.
Затем в теле конструктора устанавливаем тип графического элемента, тип объекта библиотеки, цвет текстов для панели по уполчанию, устанавливаем значение Margin для всех строн равное 3, Padding равное 0 и инициализируем переменные родительского класса.

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

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

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CPanel::~CPanel()
  {
   CForm::Deinitialize();
  }
//+------------------------------------------------------------------+


Теперь нам необходимо доработать класс-коллекцию графических элементов \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

Вместо файла объекта-формы подключим файл объекта-панели:

//+------------------------------------------------------------------+
//|                                      GraphElementsCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\WForms\Containers\Panel.mqh"
#include "..\Objects\Graph\Standard\GStdVLineObj.mqh"

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

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

//--- Возвращает список графических объектов по идентификатору графика и группе
   CArrayObj        *GetListStdGraphObjByGroup(const long chart_id,const int group)
                       {
                        CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
                        return CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,group,EQUAL);
                       }
//--- Возвращает список графических элементов по идентификатору графика и идентификатору объекта
   CArrayObj        *GetListCanvElementByID(const long chart_id,const int element_id)
                       {
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                        return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);;
                       }
//--- Возвращает список графических элементов по идентификатору графика и имени объекта
   CArrayObj        *GetListCanvElementByName(const long chart_id,const string name)
                       {
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                        return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,name,EQUAL);;
                       }
   
//--- Конструктор

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

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

//--- Создаёт объект-графический элемент на канвасе на указанном графике и подокне
   int               CreateElement(const long chart_id,
                                   const int subwindow,
                                   const string name,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   const color clr,
                                   const uchar opacity,
                                   const bool movable,
                                   const bool activity,
                                   const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический элемент на канвасе на указанном графике и подокне с заливкой вертикальным градиентом
   int               CreateElementVGradient(const long chart_id,
                                            const int subwindow,
                                            const string name,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            color &clr[],
                                            const uchar opacity,
                                            const bool movable,
                                            const bool activity,
                                            const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,true,false,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический элемент на канвасе на указанном графике и подокне с заливкой горизонтальным градиентом
   int               CreateElementHGradient(const long chart_id,
                                            const int subwindow,
                                            const string name,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            color &clr[],
                                            const uchar opacity,
                                            const bool movable,
                                            const bool activity,
                                            const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,false,false,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический элемент на канвасе на указанном графике и подокне с заливкой циклическим вертикальным градиентом
   int               CreateElementVGradientCicle(const long chart_id,
                                                 const int subwindow,
                                                 const string name,
                                                 const int x,
                                                 const int y,
                                                 const int w,
                                                 const int h,
                                                 color &clr[],
                                                 const uchar opacity,
                                                 const bool movable,
                                                 const bool activity,
                                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,true,true,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический элемент на канвасе на указанном графике и подокне с заливкой циклическим горизонтальным градиентом
   int               CreateElementHGradientCicle(const long chart_id,
                                                 const int subwindow,
                                                 const string name,
                                                 const int x,
                                                 const int y,
                                                 const int w,
                                                 const int h,
                                                 color &clr[],
                                                 const uchar opacity,
                                                 const bool movable,
                                                 const bool activity,
                                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,false,true,redraw);
                        return obj.ID();
                       }
 
//--- Создаёт объект-графический объект-форму на канвасе на указанном графике и подокне
   int               CreateForm(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color clr,
                                const uchar opacity,
                                const bool movable,
                                const bool activity,
                                const bool shadow=false,
                                const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический объект-форму на канвасе на указанном графике и подокне с заливкой вертикальным градиентом
   int               CreateFormVGradient(const long chart_id,
                                         const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         color &clr[],
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity,
                                         const bool shadow=false,
                                         const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,true,false,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический объект-форму на канвасе на указанном графике и подокне с заливкой горизонтальным градиентом
   int               CreateFormHGradient(const long chart_id,
                                         const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         color &clr[],
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity,
                                         const bool shadow=false,
                                         const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,false,false,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический объект-форму на канвасе на указанном графике и подокне с заливкой циклическим вертикальным градиентом
   int               CreateFormVGradientCicle(const long chart_id,
                                              const int subwindow,
                                              const string name,
                                              const int x,
                                              const int y,
                                              const int w,
                                              const int h,
                                              color &clr[],
                                              const uchar opacity,
                                              const bool movable,
                                              const bool activity,
                                              const bool shadow=false,
                                              const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,true,true,redraw);
                        return obj.ID();
                       }
//--- Создаёт объект-графический объект-форму на канвасе на указанном графике и подокне с заливкой циклическим горизонтальным градиентом
   int               CreateFormHGradientCicle(const long chart_id,
                                              const int subwindow,
                                              const string name,
                                              const int x,
                                              const int y,
                                              const int w,
                                              const int h,
                                              color &clr[],
                                              const uchar opacity,
                                              const bool movable,
                                              const bool activity,
                                              const bool shadow=false,
                                              const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,false,true,redraw);
                        return obj.ID();
                       }
 
//--- Создаёт объект-графический объект WinForms Panel на канвасе на указанном графике и подокне
   int               CreatePanel(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h,
                                 const color clr,
                                 const uchar opacity,
                                 const bool movable,
                                 const bool activity,
                                 const bool shadow=false,
                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }

  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Сбрасывает всем формам флаги взаимодействия кроме указанной      |
//+------------------------------------------------------------------+
void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept)
  {
   //--- В цикле по всем объектам класса-коллекции графических элементов
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем указатель на объект
      CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i);
      //--- если указатель получить не удалось, или это не форма, или это та форма, указатель на которую передан в метод - идём далее
      if(obj==NULL || obj.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID()))
         continue;
      //--- Сбрасываем флаг взаимодействия текущей форме в цикле
      obj.SetInteraction(false);
     }
  }
//+------------------------------------------------------------------+


Так как теперь у нас есть методы для создания графических элементов, форм и панели, то метод, добавляющий графический элемент в список коллекцию в файле \MQL5\Include\DoEasy\Engine.mqh главного объекта библиотеки CEngine, нам больше не нужен — удалим его:

//--- Возвращает список графических элементов на канвасе
   CArrayObj           *GetListCanvElement(void)                           { return this.m_graph_objects.GetListCanvElm();                }
//--- Добавляет графический элемент на канвасе в коллекцию
   bool                 GraphAddCanvElmToCollection(CGCnvElement *element) { return this.m_graph_objects.AddCanvElmToCollection(element); }
   
//--- Заполняет массив идентификаторами графиков, открытых в терминале
   void              GraphGetArrayChartsID(long &array_charts_id[])

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

//--- Возвращает список графических элементов на канвасе
   CArrayObj           *GetListCanvElement(void)                           { return this.m_graph_objects.GetListCanvElm();                }
//--- Возвращает список графических элементов по идентификатору графика и идентификатору объекта
   CArrayObj           *GetListCanvElementByID(const long chart_id,const int element_id)
                          {
                           return this.m_graph_objects.GetListCanvElementByID(chart_id,element_id);
                          }
//--- Возвращает список графических элементов по идентификатору графика и имени объекта
   CArrayObj           *GetListCanvElementByName(const long chart_id,const string name)
                          {
                           return this.m_graph_objects.GetListCanvElementByName(chart_id,name);
                          }
   
//--- Заполняет массив идентификаторами графиков, открытых в терминале
   void              GraphGetArrayChartsID(long &array_charts_id[])

Эти два метода просто возвращают результат запроса от одноимённых методов класса-коллекции графических элементов, рассмотренных нами выше.

Теперь у нас всё готово для тестирования.


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

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

Что будем тестировать? Оставим создание трёх объектов-форм из прошлой статьи, добавим три объекта-элемента и один объект-панель.

На каждом из объектов выведем текст с названием типа этого объекта и его идентификатором в коллекции графических элементов.

В обработчике OnInit() напишем создание всех объектов при помощи добавленных сегодня в класс-коллекцию методов:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка глобальных переменных советника
   ArrayResize(array_clr,2);        // Массив цветов градиентной заливки
   array_clr[0]=C'26,100,128';      // Исходный ≈Тёмно-лазурный цвет
   array_clr[1]=C'35,133,169';      // Осветлённый исходный цвет
//--- Создадим массив с текущим символом и установим его для использования в библиотеке
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Создадим объект-таймсерию для текущего символа и периода и выведем его описание в журнал
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания
//--- Создадим объекты-формы
   int obj_id=WRONG_VALUE;
   CArrayObj *list=NULL;
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      obj_id=engine.GetGraphicObjCollection().CreateFormVGradient(ChartID(),0,"Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true,true);
      list=engine.GetListCanvElementByID(ChartID(),obj_id);
      form=list.At(0);
      if(form==NULL)
         continue;
      //--- Установим ZOrder в ноль и выведем текст с описанием типа градиента и обновим форму
      //--- Параметры текста: координаты текста в центре формы и точка привязки - тоже по центру
      //--- Создаём новый кадр текстовой анимации с идентификатором 0 и выводим текст на форму
      form.SetZorder(0,false);
      form.TextOnBG(0,"Form: ID "+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false);
     }
//--- Создадим четыре графических элемента
   CGCnvElement *elm=NULL;
   array_clr[0]=C'0x65,0xA4,0xA9';
   array_clr[1]=C'0x48,0x75,0xA2';
//--- Вертикальный градиент
   obj_id=engine.GetGraphicObjCollection().CreateElementVGradient(NULL,0,"CElmVG",form.RightEdge()+50,20,200,50,array_clr,127,true,true,true);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Вертикальный циклический градиент
   obj_id=engine.GetGraphicObjCollection().CreateElementVGradientCicle(NULL,0,"CElmVGC",form.RightEdge()+50,80, 200,50,array_clr,127,true,true,true);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Горизонтальный градиент
   obj_id=engine.GetGraphicObjCollection().CreateElementHGradient(NULL,0,"CElmHG",form.RightEdge()+50,140,200,50,array_clr,127,true,true,true);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Горизонтальный циклический градиент
   obj_id=engine.GetGraphicObjCollection().CreateElementHGradientCicle(NULL,0,"CElmHGC",form.RightEdge()+50,200,200,50,array_clr,127,true,true,false);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }

//--- Создадим объект WinForms Panel
   CPanel *pnl=NULL;
   obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+50,50,150,150,array_clr[0],200,true,true,false,true);
   list=engine.GetListCanvElementByID(ChartID(),obj_id);
   pnl=list.At(0);
   if(pnl!=NULL)
     {
      pnl.SetFontSize(10);
      pnl.TextOnBG(0,"WinForm Panel: ID "+(string)pnl.ID(),4,2,FRAME_ANCHOR_LEFT_TOP,pnl.ForeColor(),pnl.Opacity());
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

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


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


Что дальше

В следующей статье продолжим разработку класса объекта WinForms Panel.

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

К содержанию

*Последняя статья предыдущей серии:

Графика в библиотеке DoEasy (Часть 100): Устраняем недочёты при работе с расширенными стандартными графическими объектам


Прикрепленные файлы |
MQL5.zip (4458.36 KB)
Несколько индикаторов на графике (Часть 02): Первые эксперименты Несколько индикаторов на графике (Часть 02): Первые эксперименты
В предыдущей статье "Несколько индикаторов на графике" я представил концепции и основы того, как мы можем использовать несколько индикаторов на графике. В данной статье я представлю и детально объясню исходный код.
Несколько индикаторов на графике (Часть 01): Понимание концепций Несколько индикаторов на графике (Часть 01): Понимание концепций
Сегодня разберем, как можно добавить несколько индикаторов в график одновременно, не занимая при этом отдельную его область. При торговле много трейдеров чувствуют себя более уверенно, если одновременно смотрят на несколько индикаторов (например, RSI, STOCASTIC, MACD, ADX и другие), а в некоторых случаях даже на разные активы, составляющие тот или иной индекс.
Как сделать график более интересным: добавление фона Как сделать график более интересным: добавление фона
Многие рабочие терминалы содержат некое репрезентативное изображение, которое показывает что-то о пользователе, эти изображения делают рабочий стол более красивым и разнообразным. Давайте посмотрим, как сделать графики более интересными, добавив фон.
Анализируем причины неудач торговых советников Анализируем причины неудач торговых советников
В этой статье мы проанализируем данные по валютам, чтобы понять, почему советники могут показывать хорошие результаты на одних интервалах и при этом плохо работают на других.