Графика в библиотеке DoEasy (Часть 76): Объект Форма и предопределённые цветовые темы

Artyom Trishkin | 17 июня, 2021

Содержание


Концепция

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

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

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


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

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

   MSG_LIB_SYS_FAILED_ADD_SYM_OBJ,                    // Не удалось добавить символ
   MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ,                 // Не удалось создать объект-графический элемент
   MSG_LIB_SYS_OBJ_ALREADY_IN_LIST,                   // Такой объект уже есть в списке
   MSG_LIB_SYS_FAILED_GET_DATA_GRAPH_RES,             // Не удалось получить данные графического ресурса

...

   MSG_LIB_SYS_FAILED_ADD_BUFFER,                     // Не удалось добавить объект-буфер в список
   MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ,              // Не удалось создать объект \"Индикаторный буфер\"
   MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST,                // Не удалось добавить объект в список

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

   {"Не удалось добавить символ ","Failed to add "},
   {"Не удалось создать объект-графический элемент ","Failed to create graphic element object "},
   {"Такой объект уже есть в списке","Such an object is already in the list"},
   {"Не удалось получить данные графического ресурса","Failed to get graphic resource data"},

...

   {"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"},
   {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""},
   {"Не удалось добавить объект в список","Failed to add object to the list"},

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

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

Откроем файл \MQL5\Include\DoEasy\Defines.mqh и внесём в него озвученные доработки:

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

//--- Параметры серий снимков стакана цен
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // Требуемое количество дней для снимков стакана цен в сериях по умолчанию
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Максимальное количество хранимых снимков стакана цен одного символа
//--- Параметры канваса
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Частота обновления канваса
#define NULL_COLOR                     (0x00FFFFFF)               // Ноль для канваса с альфа-каналом
#define OUTER_AREA_SIZE                (5)                        // Размер одной стороны внешней области вокруг рабочего пространства
//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+

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

   CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,                // Отступ активной зоны от нижнего края элемента
   CANV_ELEMENT_PROP_OPACITY,                         // Непрозрачность элемента
   CANV_ELEMENT_PROP_COLOR_BG,                        // Цвет фона элемента
   CANV_ELEMENT_PROP_MOVABLE,                         // Флаг перемещаемости элемента

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

//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Идентификатор элемента
   CANV_ELEMENT_PROP_TYPE,                            // Тип графического элемента
   CANV_ELEMENT_PROP_NUM,                             // Номер элемента в списке
   CANV_ELEMENT_PROP_CHART_ID,                        // Идентификатор графика
   CANV_ELEMENT_PROP_WND_NUM,                         // Номер подокна графика
   CANV_ELEMENT_PROP_COORD_X,                         // X-координата формы на графике
   CANV_ELEMENT_PROP_COORD_Y,                         // Y-координата формы на графике
   CANV_ELEMENT_PROP_WIDTH,                           // Ширина элемента
   CANV_ELEMENT_PROP_HEIGHT,                          // Высота элемента
   CANV_ELEMENT_PROP_RIGHT,                           // Правая граница элемента
   CANV_ELEMENT_PROP_BOTTOM,                          // Нижняя граница элемента
   CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,                  // Отступ активной зоны от левого края элемента
   CANV_ELEMENT_PROP_ACT_SHIFT_TOP,                   // Отступ активной зоны от верхнего края элемента
   CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,                 // Отступ активной зоны от правого края элемента
   CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,                // Отступ активной зоны от нижнего края элемента
   CANV_ELEMENT_PROP_MOVABLE,                         // Флаг перемещаемости элемента
   CANV_ELEMENT_PROP_ACTIVE,                          // Флаг активности элемента
   CANV_ELEMENT_PROP_COORD_ACT_X,                     // X-координата активной зоны элемента
   CANV_ELEMENT_PROP_COORD_ACT_Y,                     // Y-координата активной зоны элемента
   CANV_ELEMENT_PROP_ACT_RIGHT,                       // Правая граница активной зоны элемента
   CANV_ELEMENT_PROP_ACT_BOTTOM,                      // Нижняя граница активной зоны элемента
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (21)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+

Общее количество целочисленных свойств уменьшим на 2 — вместо 23 впишем 21.

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

   SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM,             // Сортировать по отступу активной зоны от нижнего края элемента
   SORT_BY_CANV_ELEMENT_OPACITY,                      // Сортировать по непрозрачности элемента
   SORT_BY_CANV_ELEMENT_COLOR_BG,                     // Сортировать по цвету фона элемента
   SORT_BY_CANV_ELEMENT_MOVABLE,                      // Сортировать по флагу перемещаемости элемента

Полный список теперь будет таким:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Сортировать по идентификатору элемента
   SORT_BY_CANV_ELEMENT_TYPE,                         // Сортировать по типу графического элемента
   SORT_BY_CANV_ELEMENT_NUM,                          // Сортировать по номеру формы в списке
   SORT_BY_CANV_ELEMENT_CHART_ID,                     // Сортировать по идентификатору графика
   SORT_BY_CANV_ELEMENT_WND_NUM,                      // Сортировать по номеру окна графика
   SORT_BY_CANV_ELEMENT_COORD_X,                      // Сортировать по X-координате элемента на графике
   SORT_BY_CANV_ELEMENT_COORD_Y,                      // Сортировать по Y-координате элемента на графике
   SORT_BY_CANV_ELEMENT_WIDTH,                        // Сортировать по ширине элемента
   SORT_BY_CANV_ELEMENT_HEIGHT,                       // Сортировать по высоте элемента
   SORT_BY_CANV_ELEMENT_RIGHT,                        // Сортировать по правой границе элемента
   SORT_BY_CANV_ELEMENT_BOTTOM,                       // Сортировать по нижней границе элемента
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT,               // Сортировать по отступу активной зоны от левого края элемента
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP,                // Сортировать по отступу активной зоны от верхнего края элемента
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT,              // Сортировать по отступу активной зоны от правого края элемента
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM,             // Сортировать по отступу активной зоны от нижнего края элемента
   SORT_BY_CANV_ELEMENT_MOVABLE,                      // Сортировать по флагу перемещаемости элемента
   SORT_BY_CANV_ELEMENT_ACTIVE,                       // Сортировать по флагу активности элемента
   SORT_BY_CANV_ELEMENT_COORD_ACT_X,                  // Сортировать по X-координате активной зоны элемента
   SORT_BY_CANV_ELEMENT_COORD_ACT_Y,                  // Сортировать по Y-координате активной зоны элемента
   SORT_BY_CANV_ELEMENT_ACT_RIGHT,                    // Сортировать по правой границе активной зоны элемента
   SORT_BY_CANV_ELEMENT_ACT_BOTTOM,                   // Сортировать по нижней границе активной зоны элемента
//--- Сортировка по вещественным свойствам

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
private:

protected:
   string            m_name_prefix;                      // Префикс имени объекта
   string            m_name;                             // Имя объекта
   long              m_chart_id;                         // Идентификатор графика
   int               m_subwindow;                        // Номер подокна
   int               m_shift_y;                          // Смещение координаты Y для подокна
   int               m_type;                             // Тип объекта
   bool              m_visible;                          // Видимость объекта
   
//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void)                      { return true; }
   virtual void      StructToObject(void){;}

public:

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

public:
//--- Возврат значений переменных класса
   string            Name(void)                          const { return this.m_name;      }
   long              ChartID(void)                       const { return this.m_chart_id;  }
   int               SubWindow(void)                     const { return this.m_subwindow; }
//--- (1) Устанавливает, (2) возвращает видимость объекта
   void              SetVisible(const bool flag)   
                       { 
                        long value=(flag ? OBJ_ALL_PERIODS : 0);
                        if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value))
                           this.m_visible=flag;
                       }
   bool              IsVisible(void)                     const { return this.m_visible;   }

//--- Виртуальный метод, возвращающий тип объекта
   virtual int       Type(void)                          const { return this.m_type;      }

Метод, устанавливающий видимость объекта, сначала проверяет значение флага, и в зависимости от переданного значения (true или false) отсылает запрос на установку объекту значения либо OBJ_ALL_PERIODS — для показа объекта на графике, либо 0 — для его сокрытия. При успешной постановке запроса в очередь событий графика в переменную m_visible записывается переданное в метод значение флага, которое можно узнать при помощи метода IsVisible(), возвращающего значение этой переменной.

В конструкторе класса, в его списке инициализации инициализируем новую переменную значением false:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(0), m_type(0),m_visible(false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_")
  {
  }
//+------------------------------------------------------------------+


Доработаем класс объекта-графического элемента в файле \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;                         // Цвет фона графика
//--- Возвращает положение курсора относительно (1) всего элемента, (2) активной зоны элемента
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);

private:

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

   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];    // Целочисленные свойства
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];   // Вещественные свойства
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];   // Строковые свойства
   
   ENUM_TEXT_ANCHOR  m_text_anchor;                            // Текущее выравнивание текста
   color             m_color_bg;                               // Цвет фона элемента
   uchar             m_opacity;                                // Непрозрачность элемента
   
//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство ордера

Теперь свойства "цвет фона элемента" и "непрозрачность элемента" будем записывать в эти переменные. Из них же и читать.

Для оформления внешнего вида графических объектов нам потребуется метод, позволяющий изменять светлоту цвета.
Это одна из составляющих цветовой модели HSL:

HSL, HLS или HSI (от англ. hue, saturation, lightness (intensity)) — цветовая модель, в которой цветовыми координатами являются тон, насыщенность и светлота. Следует отметить, что HSV и HSL — две разные цветовые модели (lightness — светлота, что отличается от яркости).

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

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

//--- Обновляет координаты (сдвигает канвас)
   bool              Move(const int x,const int y,const bool redraw=false);

//--- Изменяет светлоту цвета на указанную величину
   uint              ChangeColorLightness(const uint clr,const double change_value);

protected:

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

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

protected:
//--- Защищённый конструктор
                     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);
public:

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

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

public:
//--- Параметрический конструктор
                     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);
//--- Конструктор по умолчанию/Деструктор
                     CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) {;}


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

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правый, (6) нижний край элемента,
   bool              SetCoordX(const int coord_x);
   bool              SetCoordY(const int coord_y);
   bool              SetWidth(const int width);
   bool              SetHeight(const int height);
   void              SetRightEdge(void)                        { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());           }
   void              SetBottomEdge(void)                       { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());         }
//--- Устанавливает смещение (1) левого, (2) верхнего, (3) правого, (4) нижнего края активной зоны относительно элемента,
//--- (5) все смещения краёв активной зоны относительно элемента, (6) цвет фона элемента, (7) непрозрачность
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));       }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));      }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));        }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));     }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetColorBackground(const color colour)    { this.m_color_bg=colour;                                               }
   void              SetOpacity(const uchar value,const bool redraw=false);

//--- Устанавливает флаг (1) перемещаемости, (2) активности объекта, (3) идентификатор элемента, (4) номер элемента в списке, (5) наличие тени
   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              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetShadow(const bool flag);
   
//--- Возвращает смещение (1) левого, (2) правого, (3) верхнего, (4) нижнего края активной зоны элемента

Методы для возврата цвета фона и непрозрачности теперь возвращают значения, записанные в новые объявленные переменные:

//--- Возвращает (1) цвет фона, (2) непрозрачность, координату (3) правого, (4) нижнего края элемента
   color             ColorBackground(void)               const { return this.m_color_bg;                                               }
   uchar             Opacity(void)                       const { return this.m_opacity;                                                }
   int               RightEdge(void)                     const { return this.CoordX()+this.m_canvas.Width();                           }
   int               BottomEdge(void)                    const { return this.CoordY()+this.m_canvas.Height();                          }
//--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту элемента,

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

//--- Возвращает (1) идентификатор элемента, (2) номер элемента в списке, (3) флаг наличия тени у формы, (4) цвет фона чарта
   int               ID(void)                            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID);                   }
   int               Number(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                  }
   bool              IsShadow(void)                      const { return this.m_shadow;                                                 }
   color             ChartColorBackground(void)          const { return this.m_chart_color_bg;                                         }
//--- Устанавливает объект выше всех
   void              BringToTop(void)                          { CGBaseObj::SetVisible(false); CGBaseObj::SetVisible(true);            }
//+------------------------------------------------------------------+

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

Из параметрического конструктора уберём строки с установкой удалённых теперь свойств:

      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0);                    // Отступ активной зоны от нижнего края элемента
      this.SetProperty(CANV_ELEMENT_PROP_OPACITY,opacity);                       // Непрозрачность элемента
      this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,colour);                       // Цвет элемента
      this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable);                       // Флаг перемещаемости элемента

Теперь эти и новые свойства будем записывать в новые переменные:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
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_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND);
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.SetFont("Calibri",8);
   this.m_text_anchor=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_NAME_RES,this.m_canvas.ResourceName()); // Имя графического ресурса
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Идентификатор графика
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Номер подокна графика
      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Имя объекта-элемента
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Тип графического элемента
      this.SetProperty(CANV_ELEMENT_PROP_ID,element_id);                         // Идентификатор элемента
      this.SetProperty(CANV_ELEMENT_PROP_NUM,element_num);                       // Номер элемента в списке
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x);                             // X-координата элемента на графике
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y);                             // Y-координата элемента на графике
      this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w);                               // Ширина элемента
      this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h);                              // Высота элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0);                      // Отступ активной зоны от левого края элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0);                       // Отступ активной зоны от верхнего края элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0);                     // Отступ активной зоны от правого края элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0);                    // Отступ активной зоны от нижнего края элемента
      this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable);                       // Флаг перемещаемости элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity);                       // Флаг активности элемента
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Правая граница элемента
      this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());              // Нижняя граница элемента
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft());     // X-координата активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop());      // Y-координата активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Правая граница активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Нижняя граница активной зоны элемента
     }
   else
     {
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name);
     }
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Защищённый конструктор                                           |
//+------------------------------------------------------------------+
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_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND);
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.SetFont("Calibri",8);
   this.m_text_anchor=0;
   this.m_color_bg=NULL_COLOR;
   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_NAME_RES,this.m_canvas.ResourceName()); // Имя графического ресурса
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Идентификатор графика
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Номер подокна графика
      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Имя объекта-элемента
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Тип графического элемента
      this.SetProperty(CANV_ELEMENT_PROP_ID,0);                                  // Идентификатор элемента
      this.SetProperty(CANV_ELEMENT_PROP_NUM,0);                                 // Номер элемента в списке
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x);                             // X-координата элемента на графике
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y);                             // Y-координата элемента на графике
      this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w);                               // Ширина элемента
      this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h);                              // Высота элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0);                      // Отступ активной зоны от левого края элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0);                       // Отступ активной зоны от верхнего края элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0);                     // Отступ активной зоны от правого края элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0);                    // Отступ активной зоны от нижнего края элемента
      this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,false);                         // Флаг перемещаемости элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false);                          // Флаг активности элемента
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Правая граница элемента
      this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());              // Нижняя граница элемента
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft());     // X-координата активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop());      // Y-координата активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Правая граница активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Нижняя граница активной зоны элемента
     }
   else
     {
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name);
     }
  }
//+------------------------------------------------------------------+

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

Из метода создания структуры объекта удалим ненужные теперь строки:

   this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Отступ активной зоны от нижнего края элемента
   this.m_struct_obj.opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY);                // Непрозрачность элемента
   this.m_struct_obj.color_bg=(color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG);              // Цвет фона элемента
   this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);                 // Флаг перемещаемости элемента

и впишем ниже сохранение этих параметров из новых переменных:

//+------------------------------------------------------------------+
//| Создаёт структуру объекта                                        |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Сохранение целочисленных свойств
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                            // Идентификатор элемента
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                        // Тип графического элемента
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                       // Номер элемента в списке
   this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID);                     // Идентификатор графика
   this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM);                // Номер подокна графика
   this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X);                  // X-координата формы на графике
   this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y);                  // Y-координата формы на графике
   this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH);                      // Ширина элемента
   this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT);                    // Высота элемента
   this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT);                 // Правая граница элемента
   this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM);               // Нижняя граница элемента
   this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);    // Отступ активной зоны от левого края элемента
   this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);      // Отступ активной зоны от верхнего края элемента
   this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);  // Отступ активной зоны от правого края элемента
   this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Отступ активной зоны от нижнего края элемента


   this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);                 // Флаг перемещаемости элемента
   this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);                   // Флаг активности элемента
   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-координата активной зоны элемента
   this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT);        // Правая граница активной зоны элемента
   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);      // Нижняя граница активной зоны элемента
   this.m_struct_obj.color_bg=this.m_color_bg;                                                  // Цвет фона элемента
   this.m_struct_obj.opacity=this.m_opacity;                                                    // Непрозрачность элемента
//--- Сохранение вещественных свойств

//--- Сохранение строковых свойств
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Имя объекта-графического элемента
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Имя графического ресурса
   //--- Сохранение структуры в 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;
  }
//+------------------------------------------------------------------+

То же самое сделаем и в методе создания объекта из структуры:

//+------------------------------------------------------------------+
//| Создаёт объект из структуры                                      |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Сохранение целочисленных свойств
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                 // Идентификатор элемента
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                             // Тип графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                            // Номер элемента в списке
   this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id);                     // Идентификатор графика
   this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow);                     // Номер подокна графика
   this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x);                       // X-координата формы на графике
   this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y);                       // Y-координата формы на графике
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width);                           // Ширина элемента
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height);                         // Высота элемента
   this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right);                      // Правая граница элемента
   this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom);                    // Нижняя граница элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left);         // Отступ активной зоны от левого края элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top);           // Отступ активной зоны от верхнего края элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right);       // Отступ активной зоны от правого края элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom);     // Отступ активной зоны от нижнего края элемента
                                                                                           
                                                                                           
   this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable);                       // Флаг перемещаемости элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active);                         // Флаг активности элемента
   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.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) // Флаг необходимости перерисовки
                         
  {
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(NULL_COLOR);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Устанавливает непрозрачность элемента                            |
//+------------------------------------------------------------------+
void CGCnvElement::SetOpacity(const uchar value,const bool redraw=false)
  {
   this.m_canvas.TransparentLevelSet(value);
   this.m_opacity=value;
   this.m_canvas.Update(redraw);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Изменяет светлоту цвета на указанную величину                    |
//+------------------------------------------------------------------+
uint CGCnvElement::ChangeColorLightness(const uint clr,const double change_value)
  {
   if(change_value==0.0)
      return clr;
   double a=GETRGBA(clr);
   double r=GETRGBR(clr);
   double g=GETRGBG(clr);
   double b=GETRGBB(clr);
   double h=0,s=0,l=0;
   CColors::RGBtoHSL(r,g,b,h,s,l);
   double nl=l+change_value;
   if(nl>1.0) nl=1.0;
   if(nl<0.0) nl=0.0;
   CColors::HSLtoRGB(h,s,nl,r,g,b);
   return ARGB(a,r,g,b);
  }
//+------------------------------------------------------------------+

Здесь:
Проверяем переданное в метод значение, на которое требуется изменить светлоту, и если передан ноль, то ничего изменять не требуется — возвращаем цвет без изменений.
Далее получаем по отдельности каждую из компонент ARGB цвета, переданного в метод и преобразуем RGB-составляющие в цветовую модель HSL.
После преобразования значения каждой из компонент модели HSL будут записаны в соответствующие переменные (нам нужна l-составляющая).
Прибавляем к ней значение, переданное в метод (значения change_value могут быть от -1.0 до 1.0) и корректируем его при выходе за допустимые диапазоны значений.
Затем преобразуем HSL-модель обратно в RGB и возвращаем ARGB-модель, полученную из новых компонент цвета, образованных при конвертации из HSL-модели в RGB.


Цветовые темы и типы форм

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

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

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

//+------------------------------------------------------------------+
//|                                                     GraphINI.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"
//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define TOTAL_COLOR_THEMES             (2)      // Количество цветовых схем
//+------------------------------------------------------------------+

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

Ниже впишем индексы цветовых тем и индексы параметров одной темы — у каждой темы будет одно и то же количество параметров:

//+------------------------------------------------------------------+
//|                                                     GraphINI.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"
//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define TOTAL_COLOR_THEMES             (2)      // Количество цветовых схем
//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список индексов цветовых схем                                    |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEMES
  {
   COLOR_THEME_BLUE_STEEL,                      // Голубая сталь
   COLOR_THEME_LIGHT_CYAN_GRAY,                 // Светлый серо-циановый
  };
//+------------------------------------------------------------------+
//| Список индексов параметров цветов цветовой схемы                 |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME_COLORS
  {
   COLOR_THEME_COLOR_FORM_BG,                   // Цвет фона формы
   COLOR_THEME_COLOR_FORM_FRAME,                // Цвет рамки формы
   COLOR_THEME_COLOR_FORM_FRAME_OUTER,          // Цвет внешней рамки формы
   COLOR_THEME_COLOR_FORM_SHADOW,               // Цвет тени формы
  };
#define TOTAL_COLOR_THEME_COLORS       (4)      // Количество параметров в цветовой теме
//+------------------------------------------------------------------+

По именам констант этих перечислений будет удобно обращаться к каждой конкретной цветовой теме и требуемому её параметру.

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

//+------------------------------------------------------------------+
//|                                                     GraphINI.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"
//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define TOTAL_COLOR_THEMES             (2)      // Количество цветовых схем
//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список индексов цветовых схем                                    |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEMES
  {
   COLOR_THEME_BLUE_STEEL,                      // Голубая сталь
   COLOR_THEME_LIGHT_CYAN_GRAY,                 // Светлый серо-циановый
  };
//+------------------------------------------------------------------+
//| Список индексов параметров цветов цветовой схемы                 |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME_COLORS
  {
   COLOR_THEME_COLOR_FORM_BG,                   // Цвет фона формы
   COLOR_THEME_COLOR_FORM_FRAME,                // Цвет рамки формы
   COLOR_THEME_COLOR_FORM_FRAME_OUTER,          // Цвет внешней рамки формы
   COLOR_THEME_COLOR_FORM_SHADOW,               // Цвет тени формы
  };
#define TOTAL_COLOR_THEME_COLORS       (4)      // Количество параметров в цветовой теме
//+------------------------------------------------------------------+
//| Массив, содержащий цветовые схемы                                |
//+------------------------------------------------------------------+
color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]=
  {
//--- Параметры цветовой схемы "Голубая сталь"
   {
      C'134,160,181',                           // Цвет фона формы
      C'134,160,181',                           // Цвет рамки формы
      clrDimGray,                               // Цвет внешней рамки формы
      C'46,85,117',                             // Цвет тени формы
   },
//--- Параметры цветовой схемы "Светлый серо-циановый"
   {
      C'181,196,196',                           // Цвет фона формы
      C'181,196,196',                           // Цвет рамки формы
      clrGray,                                  // Цвет внешней рамки формы
      C'130,147,153',                           // Цвет тени формы
   },
  };
//+------------------------------------------------------------------+

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

Далее впишем перечисления типов сглаживания при рисовании примитивов, стили рамок, типы и стили форм, и далее — такие же перечисления индексов свойств стилей форм и их параметров, как и для цветовых тем:

//+------------------------------------------------------------------+
//| Типы сглаживания                                                 |
//+------------------------------------------------------------------+
enum ENUM_SMOOTHING_TYPE
  {
   SMOOTHING_TYPE_NONE,                         // Без сглаживания
   SMOOTHING_TYPE_AA,                           // Anti-aliasing
   SMOOTHING_TYPE_WU,                           // Wu
   SMOOTHING_TYPE_THICK,                        // Thick
   SMOOTHING_TYPE_DUAL,                         // Dual
  };
//+------------------------------------------------------------------+
//| Стили рамок                                                      |
//+------------------------------------------------------------------+
enum ENUM_FRAME_STYLE
  {
   FRAME_STYLE_SIMPLE,                          // Простая рамка
   FRAME_STYLE_FLAT,                            // Плоская рамка
   FRAME_STYLE_BEVEL,                           // Рельефная (выпуклая)
   FRAME_STYLE_STAMP,                           // Рельефная (вдавленная)
  };
//+------------------------------------------------------------------+
//| Типы форм                                                        |
//+------------------------------------------------------------------+
enum ENUM_FORM_TYPE
  {
   FORM_TYPE_SQUARE,                            // Прямоугольная
  };
//+------------------------------------------------------------------+
//| Стили форм                                                       |
//+------------------------------------------------------------------+
enum ENUM_FORM_STYLE
  {
   FORM_STYLE_FLAT,                             // Плоская форма
   FORM_STYLE_BEVEL,                            // Рельефная форма
  };
#define TOTAL_FORM_STYLES
//+------------------------------------------------------------------+
//| Список индексов параметров стилей форм                           |
//+------------------------------------------------------------------+
enum ENUM_FORM_STYLE_PARAMS
  {
   FORM_STYLE_FRAME_WIDTH_LEFT,                 // Ширина рамки формы слева
   FORM_STYLE_FRAME_WIDTH_RIGHT,                // Ширина рамки формы справа
   FORM_STYLE_FRAME_WIDTH_TOP,                  // Ширина рамки формы сверху
   FORM_STYLE_FRAME_WIDTH_BOTTOM,               // Ширина рамки формы снизу
   FORM_STYLE_FRAME_SHADOW_OPACITY,             // Непрозрачность тени
  };
#define TOTAL_FORM_STYLE_PARAMS        (5)      // Количество параметров стиля формы
//+------------------------------------------------------------------+
//| Массив, содержащий параметры стилей форм                         |
//+------------------------------------------------------------------+
int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]=
  {
//--- Параметры стиля формы "Плоская форма"
   {
      3,                                        // Ширина рамки формы слева
      3,                                        // Ширина рамки формы справа
      3,                                        // Ширина рамки формы сверху
      3,                                        // Ширина рамки формы снизу
      80,                                       // Непрозрачность тени
   },
//--- Параметры стиля формы "Рельефная форма"
   {
      4,                                        // Ширина рамки формы слева
      4,                                        // Ширина рамки формы справа
      4,                                        // Ширина рамки формы сверху
      4,                                        // Ширина рамки формы снизу
      100,                                      // Непрозрачность тени
   },
  };
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//|                                                      InpData.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "GraphINI.mqh"
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//|                                                      InpData.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "GraphINI.mqh"
//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define COMPILE_EN // Закомментировать строку для компиляции на русском языке 
//+------------------------------------------------------------------+
//| Перечисления входных параметров                                  |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Английский язык входных параметров                               |
//+------------------------------------------------------------------+
#ifdef COMPILE_EN
//+------------------------------------------------------------------+
//| Режимы работы с символами                                        |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work only with the current Symbol
   SYMBOLS_MODE_DEFINES,                              // Work with a given list of Symbols
   SYMBOLS_MODE_MARKET_WATCH,                         // Working with Symbols from the "Market Watch" window
   SYMBOLS_MODE_ALL                                   // Work with a complete list of Symbols
  };
//+------------------------------------------------------------------+
//| Режимы работы с таймфреймами                                     |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Work only with the current timeframe
   TIMEFRAMES_MODE_LIST,                              // Work with a given list of timeframes
   TIMEFRAMES_MODE_ALL                                // Work with a complete list of timeframes
  };
//+------------------------------------------------------------------+
//| Выбор "Да"/"Нет"                                                 |
//+------------------------------------------------------------------+
enum ENUM_INPUT_YES_NO
  {
   INPUT_NO  = 0,                                     // No
   INPUT_YES = 1                                      // Yes
  };
//+------------------------------------------------------------------+
//| Выбор цветовых тем                                               |
//+------------------------------------------------------------------+
enum ENUM_INPUT_COLOR_THEME
  {
   INPUT_COLOR_THEME_BLUE_STEEL,                      // Blue steel
   INPUT_COLOR_THEME_LIGHT_CYAN_GRAY,                 // Light cyan gray
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Русский язык входных параметров                                  |
//+------------------------------------------------------------------+
#else  
//+------------------------------------------------------------------+
//| Режимы работы с символами                                        |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Работа только с текущим символом
   SYMBOLS_MODE_DEFINES,                              // Работа с заданным списком символов
   SYMBOLS_MODE_MARKET_WATCH,                         // Работа с символами из окна "Обзор рынка"
   SYMBOLS_MODE_ALL                                   // Работа с полным списком символов
  };
//+------------------------------------------------------------------+
//| Режимы работы с таймфреймами                                     |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Работа только с текущим таймфреймом
   TIMEFRAMES_MODE_LIST,                              // Работа с заданным списком таймфреймов
   TIMEFRAMES_MODE_ALL                                // Работа с полным списком таймфреймов
  };
//+------------------------------------------------------------------+
//| Выбор "Да"/"Нет"                                                 |
//+------------------------------------------------------------------+
enum ENUM_INPUT_YES_NO
  {
   INPUT_NO  = 0,                                     // Нет
   INPUT_YES = 1                                      // Да
  };
//+------------------------------------------------------------------+
//| Выбор цветовых тем                                               |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME
  {
   COLOR_THEME_BLUE_STEEL,                            // Голубая сталь
   COLOR_THEME_LIGHT_CYAN_GRAY,                       // Светлый серо-циановый
  };
//+------------------------------------------------------------------+
#endif 
//+------------------------------------------------------------------+

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

Класс объекта "Форма"

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

В папке E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Objects\Graph\ создадим новый файл Form.mqh класса CForm. Класс должен быть унаследован от объекта-графического элемента, соответственно, и файл объекта-элемента должен быть подключен:

//+------------------------------------------------------------------+
//|                                                         Form.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 "GCnvElement.mqh"
//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
  }

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

//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // Список присоединённых элементов
   CGCnvElement     *m_shadow_obj;                             // Указатель на объект тени
   color             m_color_frame;                            // Цвет рамки формы
   color             m_color_shadow;                           // Цвет тени формы
   int               m_frame_width_left;                       // Ширина рамки формы слева
   int               m_frame_width_right;                      // Ширина рамки формы справа
   int               m_frame_width_top;                        // Ширина рамки формы сверху
   int               m_frame_width_bottom;                     // Ширина рамки формы снизу

//--- Инициализирует переменные
   void              Initialize(void);

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

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

public:
   //--- Конструкторы
                     CForm(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm(const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm(const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm() { this.Initialize(); }
//--- Деструктор
                    ~CForm();
                           
//--- Поддерживаемые свойства формы (1) целочисленные, (2) строковые
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Возвращает (1) список присоединённых объектов, (2) объект тени
   CArrayObj        *GetList(void)                                            { return &this.m_list_elements;  }
   CGCnvElement     *GetShadowObj(void)                                       { return this.m_shadow_obj;      }

Далее объявлены методы для работы с объектом-формой:

//--- Устанавливает (1) цветовую схему, (2) стиль формы
   virtual void      SetColorTheme(const ENUM_COLOR_THEMES theme,const uchar opacity);
   virtual void      SetFormStyle(const ENUM_FORM_STYLE style,
                                  const ENUM_COLOR_THEMES theme,
                                  const uchar opacity,
                                  const bool shadow=false,
                                  const bool redraw=false);
   
//--- Создаёт новый присоединённый элемент
   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);

//--- Создаёт объект тени
   void              CreateShadow(const uchar opacity);
//--- Рисует тень объекта
   void              DrawShadow(const uchar opacity);

//--- Рисует рамку формы
   void              DrawFormFrame(const int wd_top,                          // Ширина верхнего сегмента рамки
                                   const int wd_bottom,                       // Ширина нижнего сегмента рамки
                                   const int wd_left,                         // Ширина левого сегмента рамки
                                   const int wd_right,                        // Ширина правого сегмента рамки
                                   const color colour,                        // Цвет рамки
                                   const uchar opacity,                       // Непрозрачность рамки
                                   const ENUM_FRAME_STYLE style);             // Стиль рамки
//--- Рисует простую рамку
   void              DrawFrameSimple(const int x,                             // Координата X относительно формы
                                     const int y,                             // Координата Y относительно формы
                                     const int width,                         // Ширина рамки
                                     const int height,                        // Высота рамки
                                     const int wd_top,                        // Ширина верхнего сегмента рамки
                                     const int wd_bottom,                     // Ширина нижнего сегмента рамки
                                     const int wd_left,                       // Ширина левого сегмента рамки
                                     const int wd_right,                      // Ширина правого сегмента рамки
                                     const color colour,                      // Цвет рамки
                                     const uchar opacity);                    // Непрозрачность рамки
//--- Рисует плоскую рамку
   void              DrawFrameFlat(const int x,                               // Координата X относительно формы
                                   const int y,                               // Координата Y относительно формы
                                   const int width,                           // Ширина рамки
                                   const int height,                          // Высота рамки
                                   const int wd_top,                          // Ширина верхнего сегмента рамки
                                   const int wd_bottom,                       // Ширина нижнего сегмента рамки
                                   const int wd_left,                         // Ширина левого сегмента рамки
                                   const int wd_right,                        // Ширина правого сегмента рамки
                                   const color colour,                        // Цвет рамки
                                   const uchar opacity);                      // Непрозрачность рамки

//--- Рисует рельефную (выпуклую) рамку
   void              DrawFrameBevel(const int x,                              // Координата X относительно формы
                                    const int y,                              // Координата Y относительно формы
                                    const int width,                          // Ширина рамки
                                    const int height,                         // Высота рамки
                                    const int wd_top,                         // Ширина верхнего сегмента рамки
                                    const int wd_bottom,                      // Ширина нижнего сегмента рамки
                                    const int wd_left,                        // Ширина левого сегмента рамки
                                    const int wd_right,                       // Ширина правого сегмента рамки
                                    const color colour,                       // Цвет рамки
                                    const uchar opacity);                     // Непрозрачность рамки

//--- Рисует рельефную (вдавленную) рамку
   void              DrawFrameStamp(const int x,                              // Координата X относительно формы
                                    const int y,                              // Координата Y относительно формы
                                    const int width,                          // Ширина рамки
                                    const int height,                         // Высота рамки
                                    const int wd_top,                         // Ширина верхнего сегмента рамки
                                    const int wd_bottom,                      // Ширина нижнего сегмента рамки
                                    const int wd_left,                        // Ширина левого сегмента рамки
                                    const int wd_right,                       // Ширина правого сегмента рамки
                                    const color colour,                       // Цвет рамки
                                    const uchar opacity);                     // Непрозрачность рамки

//--- Рисует простое поле
   void              DrawFieldFlat(const int x,                               // Координата X относительно формы
                                   const int y,                               // Координата Y относительно формы
                                   const int width,                           // Ширина поля
                                   const int height,                          // Высота поля
                                   const color colour,                        // Цвет поля
                                   const uchar opacity);                      // Непрозрачность поля

//--- Рисует рельефное (выпуклое) поле
   void              DrawFieldBevel(const int x,                              // Координата X относительно формы
                                    const int y,                              // Координата Y относительно формы
                                    const int width,                          // Ширина поля
                                    const int height,                         // Высота поля
                                    const color colour,                       // Цвет поля
                                    const uchar opacity);                     // Непрозрачность поля

//--- Рисует рельефное (вдавленное) поле
   void              DrawFieldStamp(const int x,                              // Координата X относительно формы
                                    const int y,                              // Координата Y относительно формы
                                    const int width,                          // Ширина поля
                                    const int height,                         // Высота поля
                                    const color colour,                       // Цвет поля
                                    const uchar opacity);                     // Непрозрачность поля
   
//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- (1) Устанавливает, (2) возвращает цвет рамки формы
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;  }
   color             ColorFrame(void)                                   const { return this.m_color_frame;  }
//--- (1) Устанавливает, (2) возвращает цвет тени формы
   void              SetColorShadow(const color colour)                       { this.m_color_shadow=colour; }
   color             ColorShadow(void)                                  const { return this.m_color_shadow; }

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

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

Конструктор с указанием идентификатора чарта и подокна:

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h)
  {
   this.Initialize();
  }
//+------------------------------------------------------------------+

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

Конструктор на текущем чарте с указанием подокна:

//+------------------------------------------------------------------+
//| Конструктор на текущем чарте с указанием подокна                 |
//+------------------------------------------------------------------+
CForm::CForm(const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h)
  {
   this.Initialize();
  }
//+------------------------------------------------------------------+

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

Конструктор на текущем чарте в главном окне графика:

//+------------------------------------------------------------------+
//| Конструктор на текущем чарте в главном окне графика              |
//+------------------------------------------------------------------+
CForm::CForm(const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h)
  {
   this.Initialize();
  }
//+------------------------------------------------------------------+

В конструктор передаются имя объекта-формы, координаты верхнего левого угла формы и её размеры. В списке инициализации вызываем конструктор класса объекта-элемента с указанием типа объекта "Форма" и идентификатора текущего графика и номера главного окна (0). В теле класса вызываем метод инициализации.

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

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

Метод инициализации переменных:

//+------------------------------------------------------------------+
//| Инициализирует переменные                                        |
//+------------------------------------------------------------------+
void CForm::Initialize(void)
  {
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
   this.m_frame_width_right=2;
   this.m_frame_width_left=2;
   this.m_frame_width_top=2;
   this.m_frame_width_bottom=2;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string obj_name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity)
  {
   int pos=::StringLen(::MQLInfoString(MQL_PROGRAM_NAME));
   string pref=::StringSubstr(NameObj(),pos+1);
   string name=pref+"_"+obj_name;
   CGCnvElement *element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

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

В теле класса извлекаем из имени объекта его окончание (имя состоит из имени программы и имени объекта при его создании). Нам нужно извлечь имя объекта при его создании и добавить к нему имя, переданное в метод.
Таким образом из, например, такого имени "Имя_программы_Форма01" мы извлекаем подстроку "Форма01" и добавляем к этой строке имя, переданное в метод. Если мы создаём объект тени и передаём имя "Тень", то имя объекта получится "Форма01_ Тень ", а окончательное имя созданного объекта будет таким: "Имя_программы_Форма01_Тень".

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

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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
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;
   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());
      delete obj;
      return false;
     }
   if(!this.m_list_elements.Add(obj))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj());
      delete obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

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

Метод, создающий объект тени:

//+------------------------------------------------------------------+
//| Создаёт объект тени                                              |
//+------------------------------------------------------------------+
void CForm::CreateShadow(const uchar opacity)
  {
//--- Если флаг тени выключен - уходим
   if(!this.m_shadow)
      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=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,-1,"Shadow",x,y,w,h,this.m_chart_color_bg,opacity,Movable(),false);
   if(this.m_shadow_obj==NULL)
      return;
//--- Объект-форму перемещаем на передний план
   this.BringToTop();
  }
//+------------------------------------------------------------------+

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

Метод рисования тени:

//+------------------------------------------------------------------+
//| Рисует тень                                                      |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const uchar opacity)
  {
//--- Если флаг тени выключен - уходим
   if(!this.m_shadow)
      return;
//--- Рассчитываем координаты прямоугольника относительно границ объекта тени
   int x=OUTER_AREA_SIZE+1;
   int y=OUTER_AREA_SIZE+1;
//--- Рисуем закрашенный прямоугольник с началом от рассчитанных координат и размером текущего объекта-формы
   m_shadow_obj.DrawRectangleFill(x,y,x+Width(),y+Height(),this.ColorShadow(),opacity);
//--- Обновляем объект тени для отображения изменений
   m_shadow_obj.Update();
   return;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает цветовую схему                                     |
//+------------------------------------------------------------------+
void CForm::SetColorTheme(const ENUM_COLOR_THEMES theme,const uchar opacity)
  {
   this.SetOpacity(opacity);
   this.SetColorBackground(array_color_themes[theme][COLOR_THEME_COLOR_FORM_BG]);
   this.SetColorFrame(array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME]);
   this.SetColorShadow(array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW]);
  }
//+------------------------------------------------------------------+

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

Метод, устанавливающий стиль формы:

//+------------------------------------------------------------------+
//| Устанавливает стиль формы                                        |
//+------------------------------------------------------------------+
void CForm::SetFormStyle(const ENUM_FORM_STYLE style,
                         const ENUM_COLOR_THEMES theme,
                         const uchar opacity,
                         const bool shadow=false,
                         const bool redraw=false)
  {
//--- Устанавливаем параметры непрозрачности и размера сторон рамки формы
   this.m_shadow=shadow;
   this.m_frame_width_top=array_form_style[style][FORM_STYLE_FRAME_WIDTH_TOP];
   this.m_frame_width_bottom=array_form_style[style][FORM_STYLE_FRAME_WIDTH_BOTTOM];
   this.m_frame_width_left=array_form_style[style][FORM_STYLE_FRAME_WIDTH_LEFT];
   this.m_frame_width_right=array_form_style[style][FORM_STYLE_FRAME_WIDTH_RIGHT];
//--- Устанавливаем цветовую схему
   this.SetColorTheme(theme,opacity);
//--- Создаём объект тени и рисуем простую неразмытую тень
   this.CreateShadow((uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]);
   this.DrawShadow((uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]);
//--- Заполняем фон формы цветом и непрозрачностью
   this.Erase(this.ColorBackground(),this.Opacity());
//--- В зависимости от выбранного стиля формы рисуем соответствующую рамку формы и внешнюю очерчивающую рамку
   switch(style)
     {
      case FORM_STYLE_BEVEL   :
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_BEVEL);
        this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME_OUTER],this.Opacity());
        break;
      //---FORM_STYLE_FLAT
      default:
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_FLAT);
        this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME_OUTER],this.Opacity());
        break;
     }
  }
//+------------------------------------------------------------------+

Логика метода прокомментирована в его листинге.

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

Метод, рисующий рамку формы:

//+------------------------------------------------------------------+
//| Рисует рамку формы                                               |
//+------------------------------------------------------------------+
void CForm::DrawFormFrame(const int wd_top,              // Ширина верхнего сегмента рамки
                          const int wd_bottom,           // Ширина нижнего сегмента рамки
                          const int wd_left,             // Ширина левого сегмента рамки
                          const int wd_right,            // Ширина правого сегмента рамки
                          const color colour,            // Цвет рамки
                          const uchar opacity,           // Непрозрачность рамки
                          const ENUM_FRAME_STYLE style)  // Стиль рамки
  {
//--- В зависимости от переданного стиля рамки
   switch(style)
     {
      //--- рисуем объёмную (выпуклую) рамку
      case FRAME_STYLE_BEVEL :
         DrawFrameBevel(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
      //--- рисуем объёмную (вдавленную) рамку
      case FRAME_STYLE_STAMP :
         DrawFrameStamp(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
      //--- рисуем плоскую рамку
      case FRAME_STYLE_FLAT :
         DrawFrameFlat(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
      //--- рисуем простую рамку
      default:
        //---FRAME_STYLE_SIMPLE
         DrawFrameSimple(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
     }
  }
//+------------------------------------------------------------------+

В зависимости от стиля рамки рисуем соответствующую рамку формы.

Метод, рисующий простую рамку:

//+------------------------------------------------------------------+
//| Рисует простую рамку                                             |
//+------------------------------------------------------------------+
void CForm::DrawFrameSimple(const int x,           // Координата X относительно формы
                            const int y,           // Координата Y относительно формы
                            const int width,       // Ширина рамки
                            const int height,      // Высота рамки
                            const int wd_top,      // Ширина верхнего сегмента рамки
                            const int wd_bottom,   // Ширина нижнего сегмента рамки
                            const int wd_left,     // Ширина левого сегмента рамки
                            const int wd_right,    // Ширина правого сегмента рамки
                            const color colour,    // Цвет рамки
                            const uchar opacity)   // Непрозрачность рамки
  {
//--- Задаём координаты прямоугольника
   int x1=x, y1=y;
   int x2=x1+width-1;
   int y2=y1+height-1;
//--- Рисуем первый прямоугольник
   CGCnvElement::DrawRectangle(x1,y1,x2,y2,colour,opacity);
//--- Если ширина рамки со всех сторон больше 1 - рисуем второй прямоугольник
   if(wd_left>1 || wd_right>1 || wd_top>1 || wd_bottom>1)
      CGCnvElement::DrawRectangle(x1+wd_left-1,y1+wd_top-1,x2-wd_right+1,y2-wd_bottom+1,colour,opacity);
//--- Ищем "пустоты" между линиями двух прямоугольников и заливаем их цветом
   if(wd_left>2 && wd_right>2 && wd_top>2 && wd_bottom>2)
      this.Fill(x1+1,y1+1,colour,opacity);
   else if(wd_left>2 && wd_top>2)
      this.Fill(x1+1,y1+1,colour,opacity);
   else if(wd_right>2 && wd_bottom>2)
      this.Fill(x2-1,y2-1,colour,opacity);
   else if(wd_left<3 && wd_right<3)
     {
      if(wd_top>2)
         this.Fill(x1+1,y1+1,colour,opacity);
      if(wd_bottom>2)
         this.Fill(x1+1,y2-1,colour,opacity);
     }
   else if(wd_top<3 && wd_bottom<3)
     {
      if(wd_left>2)
         this.Fill(x1+1,y1+1,colour,opacity);
      if(wd_right>2)
         this.Fill(x2-1,y1+1,colour,opacity);
     }
  }
//+------------------------------------------------------------------+

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

Метод, рисующий плоскую рамку:

//+------------------------------------------------------------------+
//| Рисует плоскую рамку                                             |
//+------------------------------------------------------------------+
void CForm::DrawFrameFlat(const int x,
                          const int y,
                          const int width,
                          const int height,
                          const int wd_top,
                          const int wd_bottom,
                          const int wd_left,
                          const int wd_right,
                          const color colour,
                          const uchar opacity)
  {
//--- Рисуем простую рамку
   this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
//--- Если ширина верха и низа рамки больше одного пикселя
   if(wd_top>1 && wd_bottom>1)
     {
      //--- Затемняем горизонтальные стороны рамки
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.05));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.07));
        }
     }
//--- Если ширина слева и справа рамки больше одного пикселя
   if(wd_left>1 && wd_right>1)
     {
      //--- Затемняем вертикальные стороны рамки
      for(int i=1;i<height-1;i++)
        {
         this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.01));
         this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.02));
        }
     }
  }
//+------------------------------------------------------------------+

Метод, рисующий рельефную (выпуклую) рамку:

//+------------------------------------------------------------------+
//| Рисует рельефную (выпуклую) рамку                                |
//+------------------------------------------------------------------+
void CForm::DrawFrameBevel(const int x,
                           const int y,
                           const int width,
                           const int height,
                           const int wd_top,
                           const int wd_bottom,
                           const int wd_left,
                           const int wd_right,
                           const color colour,
                           const uchar opacity)
  {
//--- Рисуем простую рамку
   this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
//--- Если ширина верха и низа рамки больше одного пикселя
   if(wd_top>1 && wd_bottom>1)
     {
      //--- Осветляем и затемняем нужные стороны граней рамки
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),0.25));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.2));
        }
      for(int i=wd_left;i<width-wd_right;i++)
        {
         this.m_canvas.PixelSet(x+i,y+wd_top-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+wd_top-1),-0.2));
         this.m_canvas.PixelSet(x+i,y+height-wd_bottom,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-wd_bottom),0.1));
        }
     }
//--- Если ширина слева и справа рамки больше одного пикселя
   if(wd_left>1 && wd_right>1)
     {
      //--- Осветляем и затемняем нужные стороны граней рамки
      for(int i=1;i<height-1;i++)
        {
         this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+i),0.1));
         this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+i),-0.1));
        }
      for(int i=wd_top;i<height-wd_bottom;i++)
        {
         this.m_canvas.PixelSet(x+wd_left-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+wd_left-1,y+i),-0.1));
         this.m_canvas.PixelSet(x+width-wd_right,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-wd_right,y+i),0.1));
        }
     }
  }
//+------------------------------------------------------------------+

Метод, рисующий рельефную (вдавленную) рамку:

//+------------------------------------------------------------------+
//| Рисует рельефную (вдавленную) рамку                              |
//+------------------------------------------------------------------+
void CForm::DrawFrameStamp(const int x,
                           const int y,
                           const int width,
                           const int height,
                           const int wd_top,
                           const int wd_bottom,
                           const int wd_left,
                           const int wd_right,
                           const color colour,
                           const uchar opacity)
  {
//--- Рисуем простую рамку
   this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
//--- Если ширина верха и низа рамки больше одного пикселя
   if(wd_top>1 && wd_bottom>1)
     {
      //--- Осветляем и затемняем нужные стороны граней рамки
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.25));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),0.2));
        }
      for(int i=wd_left;i<width-wd_right;i++)
        {
         this.m_canvas.PixelSet(x+i,y+wd_top-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+wd_top-1),0.2));
         this.m_canvas.PixelSet(x+i,y+height-wd_bottom,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-wd_bottom),-0.25));
        }
     }
//--- Если ширина слева и справа рамки больше одного пикселя
   if(wd_left>1 && wd_right>1)
     {
      //--- Осветляем и затемняем нужные стороны граней рамки
      for(int i=1;i<height-1;i++)
        {
         this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+i),-0.1));
         this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+i),0.2));
        }
      for(int i=wd_top;i<height-wd_bottom;i++)
        {
         this.m_canvas.PixelSet(x+wd_left-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+wd_left-1,y+i),0.2));
         this.m_canvas.PixelSet(x+width-wd_right,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-wd_right,y+i),-0.2));
        }
     }
  }
//+------------------------------------------------------------------+

Методы, рисующие поля (простое и рельефные):

//+------------------------------------------------------------------+
//| Рисует простое поле                                              |
//+------------------------------------------------------------------+
void CForm::DrawFieldFlat(const int x,const int y,const int width,const int height,const color colour,const uchar opacity)
  {
//--- Рисуем закрашенный прямоугольник
   CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity);
//--- Затемняем все его грани
   for(int i=0;i<width;i++)
     {
      this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.05));
      this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.05));
     }
   for(int i=1;i<height-1;i++)
     {
      this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.05));
      this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.05));
     }
  }
//+------------------------------------------------------------------+
//| Рисует рельефное (выпуклое) поле                                 |
//+------------------------------------------------------------------+
void CForm::DrawFieldBevel(const int x,const int y,const int width,const int height,const color colour,const uchar opacity)
  {
//--- Рисуем закрашенный прямоугольник
   CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity);
//--- Осветляем его верх и лево и затемняем его низ и право
   for(int i=0;i<width;i++)
     {
      this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),0.1));
      this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.1));
     }
   for(int i=1;i<height-1;i++)
     {
      this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),0.05));
      this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.05));
     }
  }
//+------------------------------------------------------------------+
//| Рисует рельефное (вдавленное) поле                               |
//+------------------------------------------------------------------+
void CForm::DrawFieldStamp(const int x,const int y,const int width,const int height,const color colour,const uchar opacity)
  {
//--- Рисуем закрашенный прямоугольник
   CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity);
//--- Затемняем его верх и лево и осветляем его низ и право
   for(int i=0;i<width;i++)
     {
      this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.1));
      this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),0.1));
     }
   for(int i=1;i<height-1;i++)
     {
      this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.05));
      this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),0.05));
     }
  }
//+------------------------------------------------------------------+

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

На этом создание объекта-формы на сегодня завершено.


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

Сегодня не будет "фееричного" тестирования :) Сегодня просто создадим две разные формы с разными стилями построения и цветовыми темами. После создания добавим на каждую поле — на верхнюю форму добавим объёмное вдавленное поле, на вторую форму — полупрозрачное объёмное вдавленное поле.

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

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

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart76.mq5 |
//|                                  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"
//--- includes
#include <Arrays\ArrayObj.mqh>
#include <DoEasy\Services\Select.mqh>
#include <DoEasy\Objects\Graph\Form.mqh>
//--- defines
#define        FORMS_TOTAL (2)   // Количество создаваемых форм
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
CArrayObj      list_forms;
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка разрешений на отсылку событий перемещения курсора и прокрутки колёсика мышки
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Установка глобальных переменных советника

//--- Создадим заданное количество объектов-форм
   list_forms.Clear();
   int total=FORMS_TOTAL;
   for(int i=0;i<total;i++)
     {
      //--- При создании объекта передаём в него все требуемые параметры
      CForm *form=new CForm("Form_0"+(string)(i+1),300,40+(i*80),100,70);
      if(form==NULL)
         continue;
      //--- Установим форме флаги активности, и перемещаемости
      form.SetActive(true);
      form.SetMovable(false);
      //--- Установим форме её идентификатор, равный индексу цикла и номер всписке объектов
      form.SetID(i);
      form.SetNumber(0);   // (0 - означает главный объект-форма) К главному объекту могут прикрепляться второстепенные, которыми он будет управлять
      //--- Установим полную непрозрачность для верхней формы и частичную - для нижней
      uchar opacity=(i==0 ? 255 : 250);
      //--- Указываем стиль формы и её цветовую тему в зависимости от индекса цикла
      ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i;
      ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i;
      //--- Устанавливаем форме её стиль и тему
      form.SetFormStyle(style,theme,opacity,true);
      //--- Если это первая (верхняя) форма
      if(i==0)
        {
         //--- Нарисуем на ней вдавленное поле, немного смещённое от центра формы книзу
         form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity());
         form.Update(true);
        }
      //--- Если это вторая (нижняя) форма
      if(i==1)
        {
         //--- Нарисуем на ней вдавленное полупрозрачное поле по центру в виде "затемнённого стекла"
         form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200);
         form.Update(true);
        }
      //--- Добавим объекты в список
      if(!list_forms.Add(form))
        {
         delete form;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Из обработчика OnChartEvent() удалим всю обработку щелчков мышки по объектам:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Если щелчок по объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
     {

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

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


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

Что дальше

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

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

К содержанию

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

Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента
Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas
Графика в библиотеке DoEasy (Часть 75): Методы работы с примитивами и текстом в базовом графическом элементе