English 中文 Español Deutsch 日本語 Português
Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas

Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas

MetaTrader 5Примеры | 4 июня 2021, 08:51
2 747 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

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

  • Базовый графический объект — наследник CObject, содержит свойства, присущие графическим объектам, которые можно построить в терминале;
  • Объект-элемент на канвасе — имеет свойства объекта, построенного на основе объекта-канваса;
  • Объект-форма — имеет дополнительные свойства и функционал для оформления внешнего вида объекта-элемента;
  • Объект-окно — составной объект на основе объектов-элементов и объектов-форм;
  • и т.д.

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


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

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

   MSG_LIB_SYS_FAILED_CREATE_STORAGE_FOLDER,          // Не удалось создать папку хранения файлов. Ошибка: 
   MSG_LIB_SYS_FAILED_ADD_ACC_OBJ_TO_LIST,            // Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию
   MSG_LIB_SYS_FAILED_CREATE_CURR_ACC_OBJ,            // Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта
   MSG_LIB_SYS_FAILED_OPEN_FILE_FOR_WRITE,            // Не удалось открыть для записи файл
   MSG_LIB_SYS_INPUT_ERROR_NO_SYMBOL,                 // Ошибка входных данных: нет символа
   MSG_LIB_SYS_FAILED_CREATE_SYM_OBJ,                 // Не удалось создать объект-символ
   MSG_LIB_SYS_FAILED_ADD_SYM_OBJ,                    // Не удалось добавить символ
   MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ,                 // Не удалось создать объект-графический элемент

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

   {"Не удалось создать папку хранения файлов. Ошибка: ","Could not create file storage folder. Error: "},
   {"Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию","Error. Failed to add current account object to collection list"},
   {"Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта","Error. Failed to create an account object with current account data"},
   {"Не удалось открыть для записи файл ","Could not open file for writing: "},
   {"Ошибка входных данных: нет символа ","Input error: no "},
   {"Не удалось создать объект-символ ","Failed to create symbol object "},
   {"Не удалось добавить символ ","Failed to add "},
   {"Не удалось создать объект-графический элемент ","Failed to create graphic element object "},

Для нового объекта "графический элемент" в файл \MQL5\Include\DoEasy\Defines.mqh добавим его тип в список-перечисление типов графических объектов, а также его целочисленные и строковые свойства:

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Элемент
   GRAPH_ELEMENT_TYPE_FORM,                           // Форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
  };
//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+
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_OPACITY,                         // Непрозрачность формы
   CANV_ELEMENT_PROP_COLOR_BG,                        // Цвет фона формы
   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 (23)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+
//| Вещественные свойства окна графического элемента на канвасе      |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_DOUBLE
  {
   CANV_ELEMENT_PROP_DUMMY = CANV_ELEMENT_PROP_INTEGER_TOTAL, // DBL-заглушка
  };
#define CANV_ELEMENT_PROP_DOUBLE_TOTAL  (1)           // Общее количество вещественных свойств
#define CANV_ELEMENT_PROP_DOUBLE_SKIP   (1)           // Количество неиспользуемых в сортировке вещественных свойств
//+------------------------------------------------------------------+
//| Строковые свойства окна графического элемента на канвасе         |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_STRING
  {
   CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), // Имя объекта-формы
   CANV_ELEMENT_PROP_NAME_RES,                        // Имя графического ресурса
  };
#define CANV_ELEMENT_PROP_STRING_TOTAL  (2)           // Общее количество строковых свойств
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#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_OPACITY,                      // Сортировать по непрозрачности формы
   SORT_BY_CANV_ELEMENT_COLOR_BG,                     // Сортировать по цвету фона формы
   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,                     // Сортировать по имени графического ресурса
  };
//+------------------------------------------------------------------+

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

Прежде чем начнём создавать объект "графический элемент", переработаем класс базового объекта всех графических объектов библиотеки в файле MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh.

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

Нам будет удобнее полностью пересоздать этот класс, чем исправлять уже имеющийся. Поэтому просто удалим в этом файле всё и впишем новое:

//+------------------------------------------------------------------+
//|                                                     GBaseObj.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 "..\..\Services\DELib.mqh"
#include <Graphics\Graphic.mqh>
//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
private:
   int               m_type;                             // Тип объекта
protected:
   string            m_name_prefix;                      // Префикс имени объекта
   string            m_name;                             // Имя объекта
   long              m_chart_id;                         // Идентификатор графика
   int               m_subwindow;                        // Номер подокна
   int               m_shift_y;                          // Смещение координаты Y для подокна
   
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; }
//--- Виртуальный метод, возвращающий тип объекта
   virtual int       Type(void)                          const { return this.m_type;      }

//--- Конструктор/деструктор
                     CGBaseObj();
                    ~CGBaseObj();
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(0), m_type(0), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_")
  {
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CGBaseObj::~CGBaseObj()
  {
  }
//+------------------------------------------------------------------+

Итак. К файлу сразу подключен файл сервисных функций библиотеки и файл класса CGraphic Стандартной библиотеки, к которому уже подключен файл класса CCanvas. В то же время, класс CGraphic имеет широкий набор методов для рисования различных графиков, что тоже нам потребуется в дальнейшем.

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

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

   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(0);      }

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

Защищённые переменные класса:

  • m_name_prefix — здесь будем хранить префикс имён объектов для идентификации графических объектов по принадлежности программе. Соответственно, сюда будем записывать имя программы, созданной на основе данной библиотеки.
  • m_name — хранит имя графического объекта. Полное же имя объекта будет создаваться при помощи сложения префикса и имени. Таким образом, при создании объектов, нам необходимо будет указывать лишь уникальное имя вновь создаваемому объекту, а класс объекта "графический элемент" сам допишет к имени префикс, по которому можно будет идентифицировать объект с программой, его создавшей.
  • m_chart_id — сюда будем записывать идентификатор графика, на котором будет создаваться графический объект.
  • m_subwindow — подокно графика, в котором строится графический объект.
  • m_shift_y — значение смещения координаты Y объекта, созданного в подокне графика.

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

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

В конструкторе класса, в его списке инициализации сразу же зададим значение смещения координаты Y, тип объекта (0 по умолчанию) и префикс имени, состоящий из имени программы и нижнего подчёркивания:

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


Базовый объект всех графических объектов библиотеки на основе канваса

Приступим к созданию класса объекта "графический элемент" на основе класса CCanvas.
В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\ создадим новый файл GCnvElement.mqh класса CGCnvElement.

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

//+------------------------------------------------------------------+
//|                                                  GCnvElement.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 "GBaseObj.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
  }

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

protected:
   CCanvas           m_canvas;                                 // Объект класса CCanvas
   CPause            m_pause;                                  // Объект класса "Пауза"
//--- Возвращает положение курсора относительно (1) всего элемента, (2) активной зоны элемента
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);

private:

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

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];   // Строковые свойства

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство ордера
   int               IndexProp(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL;                                 }
   int               IndexProp(ENUM_CANV_ELEMENT_PROP_STRING property)  const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL;  }

public:

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

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;    }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property)        const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];   }

//--- Возвращает флаг поддержания объектом данного свойства
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property)          { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)           { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)           { return true; }

//--- Сравнивает объекты CGCnvElement между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CGCnvElement между собой по всем свойствам (для поиска равных объектов)
   bool              IsEqual(CGCnvElement* compared_obj) const;
//--- Создаёт элемент

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

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

//--- Создаёт элемент
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const color colour,
                            const uchar opacity,
                            const bool redraw=false);
                                
//--- Возвращает указатель на объект-канвас
   CCanvas          *CanvasObj(void)                                                   { return &this.m_canvas;               }
//--- Устанавливает частоту обновления канваса
   void              SetFrequency(const ulong value)                                   { this.m_pause.SetWaitingMSC(value);   }
//--- Обновляет координаты (сдвигает канвас)
   bool              Move(const int x,const int y,const bool redraw=false);
   
//--- Конструкторы/Деструктор
                     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(){;}
                    ~CGCnvElement();
     
//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту элемента,
   bool              SetCoordX(const int coord_x);
   bool              SetCoordY(const int coord_y);
   bool              SetWidth(const int width);
   bool              SetHeight(const int height);
//--- Устанавливает смещение (1) левого, (2) верхнего, (3) правого, (4) нижнего края активной зоны относительно элемента,
//--- (5) все смещения краёв активной зоны относительно элемента, (6) непрозрачность элемента
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));    }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));   }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));     }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));  }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetOpacity(const uchar value,const bool redraw=false);
   
//--- Возвращает смещение (1) левого, (2) правого, (3) верхнего, (4) нижнего края активной зоны элемента
   int               ActiveAreaLeftShift(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);    }
   int               ActiveAreaRightShift(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);   }
   int               ActiveAreaTopShift(void)            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);     }
   int               ActiveAreaBottomShift(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);  }
//--- Возвращает координату (1) левого, (2) правого, (3) верхнего, (4) нижнего края активной зоны элемента
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());              }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());          }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());               }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());        }
//--- Возвращает (1) непрозрачность, координату (2) правого, (3) нижнего края элемента
   uchar             Opacity(void)                       const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_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) высоту элемента,
   int               CoordX(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X);           }
   int               CoordY(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y);           }
   int               Width(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH);             }
   int               Height(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT);            }
//--- Возвращает флаг (1) перемещаемости, (2) активности элемента
   bool              Movable(void)                       const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);          }
   bool              Active(void)                        const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);           }
//--- Возвращает (1) имя объекта, (2) имя графического ресурса, (3) идентификатор графика, (4) номер подокна графика
   string            NameObj(void)                       const { return this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ);               }
   string            NameRes(void)                       const { return this.GetProperty(CANV_ELEMENT_PROP_NAME_RES);               }
   long              ChartID(void)                       const { return this.GetProperty(CANV_ELEMENT_PROP_CHART_ID);               }
   int               WindowNum(void)                     const { return (int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM);           }
                    
  };
//+------------------------------------------------------------------+

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

Параметрический конструктор класса:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
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)
                                          
  {
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   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_OPACITY,opacity);                       // Непрозрачность элемента
      this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,colour);                       // Цвет элемента
      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);
     }
  }
//+------------------------------------------------------------------+

Здесь сначала создаём имя объекта, состоящее из префикса имён объектов, создаваемого в родительском классе, и имени, переданного в параметрах конструктора. Таким образом уникальное имя объекта будет выглядеть как "Префикс_Имя_Объекта".
Далее записываем в переменные родительского класса идентификатор графика и номер подокна, переданные в параметрах.
Затем вызываем метод создания графического объекта на канвасе. При успешном создании объекта записываем все данные в свойства объекта-элемента. Если же графический объект класса CCanvas создать не удалось — сообщаем об этом в журнал. При этом имя с префиксом уже будет создано и установлены идентификатор графика и его подокно. Таким образом, можно будет попробовать ещё раз создать объект класса CCanvas повторным вызовом метода Create(). По умолчанию при создании объекта отступ активной зоны устанавливается с каждой стороны равным нулю, т.е. активная зона объекта будет совпадать с размером созданного графического элемента. После его создания размер и положение активной зоны можно всегда изменить при помощи соответствующих методов, которые рассмотрим далее.

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

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

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

//+------------------------------------------------------------------+
//|Сравнивает объекты CGCnvElement между собой по указанному свойству|
//+------------------------------------------------------------------+
int CGCnvElement::Compare(const CObject *node,const int mode=0) const
  {
   const CGCnvElement *obj_compared=node;
//--- сравнение целочисленных свойств двух объектов
   if(mode<CANV_ELEMENT_PROP_INTEGER_TOTAL)
     {
      long value_compared=obj_compared.GetProperty((ENUM_CANV_ELEMENT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_CANV_ELEMENT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение вещественных свойств двух объектов
   else if(mode<CANV_ELEMENT_PROP_DOUBLE_TOTAL+CANV_ELEMENT_PROP_INTEGER_TOTAL)
     {
      double value_compared=obj_compared.GetProperty((ENUM_CANV_ELEMENT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_CANV_ELEMENT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение строковых свойств двух объектов
   else if(mode<ORDER_PROP_DOUBLE_TOTAL+ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_STRING_TOTAL)
     {
      string value_compared=obj_compared.GetProperty((ENUM_CANV_ELEMENT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_CANV_ELEMENT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Сравнивает объекты CGCnvElement между собой по всем свойствам    |
//+------------------------------------------------------------------+
bool CGCnvElement::IsEqual(CGCnvElement *compared_obj) const
  {
   int beg=0, end=CANV_ELEMENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_CANV_ELEMENT_PROP_INTEGER prop=(ENUM_CANV_ELEMENT_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=CANV_ELEMENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_CANV_ELEMENT_PROP_DOUBLE prop=(ENUM_CANV_ELEMENT_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=CANV_ELEMENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_CANV_ELEMENT_PROP_STRING prop=(ENUM_CANV_ELEMENT_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

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

Метод, создающий графический объект-элемент:

//+------------------------------------------------------------------+
//| Создаёт графический объект-элемент                               |
//+------------------------------------------------------------------+
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.m_canvas.Erase(::ColorToARGB(colour,opacity));
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

В метод передаются все необходимые для построения параметры, и вызывается вторая форма метода CreateBitmapLabel() класса CCanvas. Если графический ресурс, привязанный к объекту чарта, создан успешно, то графический элемент заливается цветом и вызывается метод Update() для отображения на экране сделанных изменений. При этом в метод передаётся флаг необходимости перерисовки экрана — если мы обновляем составной объект, состоящий из нескольких графических элементов, график перерисовывать нужно после осуществления изменений во всех элементах составного объекта — чтобы не вызывать многократное обновление графика после изменения каждого элемента. Далее записываем в переменную родительского класса m_shift значение смещения координаты Y для подокна и возвращаем true. Если объект класса CCanvas не создан, возвращаем false.

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

//+------------------------------------------------------------------+
//| Возвращает положение курсора относительно элемента               |
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideElement(const int x,const int y)
  {
   return(x>=this.CoordX() && x<=this.RightEdge() && y>=this.CoordY() && y<=this.BottomEdge());
  }
//+------------------------------------------------------------------+

В метод передаются целочисленные координаты курсора X и Y и возвращается положение переданных координат относительно габаритов элемента — значение true будет возвращено, только если курсор находится внутри элемента.

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

//+------------------------------------------------------------------+
//| Возвращает положение курсора относительно активной зоны элемента |
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideActiveArea(const int x,const int y)
  {
   return(x>=this.ActiveAreaLeft() && x<=this.ActiveAreaRight() && y>=this.ActiveAreaTop() && y<=this.ActiveAreaBottom());
  }
//+------------------------------------------------------------------+

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

Метод, обновляющий координаты элемента:

//+------------------------------------------------------------------+
//| Обновляет координаты элемента                                    |
//+------------------------------------------------------------------+
bool CGCnvElement::Move(const int x,const int y,const bool redraw=false)
  {
//--- Если элемент не перемещаемый или неактивный - уходим
   if(!this.Movable())
      return false;
//--- Если не удалось записать новые значения координат в свойства графического объекта - возвращаем false
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
   //--- Если стоит флаг обновления - перерисовываем график.
   if(redraw)
      ::ChartRedraw(this.ChartID());
   //--- Возвращаем true
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает новую координату X                                 |
//+------------------------------------------------------------------+
bool CGCnvElement::SetCoordX(const int coord_x)
  {
   int x=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE);
   if(coord_x==x)
     {
      if(coord_x==GetProperty(CANV_ELEMENT_PROP_COORD_X))
         return true;
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x);
      return true;
     }
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,coord_x))
     {
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает новую координату Y                                 |
//+------------------------------------------------------------------+
bool CGCnvElement::SetCoordY(const int coord_y)
  {
   int y=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE);
   if(coord_y==y)
     {
      if(coord_y==GetProperty(CANV_ELEMENT_PROP_COORD_Y))
         return true;
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y);
      return true;
     }
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,coord_y))
     {
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Логика метода аналогична вышерассмотренному методу установки координаты X.

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

//+------------------------------------------------------------------+
//| Устанавливает новую ширину                                       |
//+------------------------------------------------------------------+
bool CGCnvElement::SetWidth(const int width)
  {
   return this.m_canvas.Resize(width,this.m_canvas.Height());
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает новую высоту                                       |
//+------------------------------------------------------------------+
bool CGCnvElement::SetHeight(const int height)
  {
   return this.m_canvas.Resize(this.m_canvas.Width(),height);
  }
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает все смещения активной зоны относительно элемента   |
//+------------------------------------------------------------------+
void CGCnvElement::SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift)
  {
   this.SetActiveAreaLeftShift(left_shift);
   this.SetActiveAreaBottomShift(bottom_shift);
   this.SetActiveAreaRightShift(right_shift);
   this.SetActiveAreaTopShift(top_shift);
  }
//+------------------------------------------------------------------+

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

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

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

В метод передаётся требуемое значение непрозрачности объекта (0 — полностью прозрачный, 255 — полностью непрозрачный) и флаг перерисовки чарта.
Далее вызываем метод TransparentLevelSet() класса CCanvas, записываем в свойства объекта новое значение свойства и обновляем объект с переданным флагом его перерисовки.

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

Откроем файл \MQL5\Include\DoEasy\Services\Select.mqh и впишем в него подключение файла класса объекта "графический элемент" и в конец тела класса объявление методов сортировки и поиска объектов "графический элемент" по их свойствам:

//+------------------------------------------------------------------+
//|                                                       Select.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"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
#include "..\Objects\Series\SeriesDE.mqh"
#include "..\Objects\Indicators\Buffer.mqh"
#include "..\Objects\Indicators\IndicatorDE.mqh"
#include "..\Objects\Indicators\DataInd.mqh"
#include "..\Objects\Ticks\DataTick.mqh"
#include "..\Objects\Book\MarketBookOrd.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
#include "..\Objects\Chart\ChartObj.mqh"
#include "..\Objects\Graph\GCnvElement.mqh"
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Методы работы с данными графических элементов на канвасе         |
//+------------------------------------------------------------------+
   //--- Возвращает список объектов, где одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByGraphCanvElementProperty(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByGraphCanvElementProperty(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByGraphCanvElementProperty(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс чарта в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   static int        FindGraphCanvElementMax(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_INTEGER property);
   static int        FindGraphCanvElementMax(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_DOUBLE property);
   static int        FindGraphCanvElementMax(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_STRING property);
   //--- Возвращает индекс чарта в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   static int        FindGraphCanvElementMin(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_INTEGER property);
   static int        FindGraphCanvElementMin(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_DOUBLE property);
   static int        FindGraphCanvElementMin(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Методы работы с данными графических элементов на канвасе         |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает список объектов, где одно из целочисленных            |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByGraphCanvElementProperty(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CGCnvElement *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список объектов, где одно из вещественных             |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByGraphCanvElementProperty(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CGCnvElement *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      double obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список объектов, где одно из строковых                |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByGraphCanvElementProperty(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CGCnvElement *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      string obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс объекта в списке                               |
//| с максимальным значением целочисленного свойства                 |
//+------------------------------------------------------------------+
int CSelect::FindGraphCanvElementMax(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CGCnvElement *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CGCnvElement *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      long obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс объекта в списке                               |
//| с максимальным значением вещественного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindGraphCanvElementMax(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CGCnvElement *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CGCnvElement *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      double obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс объекта в списке                               |
//| с максимальным значением строкового свойства                     |
//+------------------------------------------------------------------+
int CSelect::FindGraphCanvElementMax(CArrayObj *list_source,ENUM_CANV_ELEMENT_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CGCnvElement *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CGCnvElement *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      string obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс объекта в списке                               |
//| с минимальным значением целочисленного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindGraphCanvElementMin(CArrayObj* list_source,ENUM_CANV_ELEMENT_PROP_INTEGER property)
  {
   int index=0;
   CGCnvElement *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CGCnvElement *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      long obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс объекта в списке                               |
//| с минимальным значением вещественного свойства                   |
//+------------------------------------------------------------------+
int CSelect::FindGraphCanvElementMin(CArrayObj* list_source,ENUM_CANV_ELEMENT_PROP_DOUBLE property)
  {
   int index=0;
   CGCnvElement *min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CGCnvElement *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      double obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс объекта в списке                               |
//| с минимальным значением строкового свойства                      |
//+------------------------------------------------------------------+
int CSelect::FindGraphCanvElementMin(CArrayObj* list_source,ENUM_CANV_ELEMENT_PROP_STRING property)
  {
   int index=0;
   CGCnvElement *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CGCnvElement *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      string obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

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

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


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

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

Подключим к советнику файл класса динамического массива указателей на экземпляры класса CObject и его наследников, стандартной библиотеки и
файлы классов CSelect и CGCnvElement библиотеки,
укажем количество создаваемых объектов "графический элемент" и объявим список, в который будем размещать создаваемые графические элементы:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart74.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\GCnvElement.mqh>
//--- defines
#define        FORMS_TOTAL (2)
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
CArrayObj      list_elements;
//+------------------------------------------------------------------+

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

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

//--- Создадим заданное количество графических элементов на канвасе
   int total=FORMS_TOTAL;
   for(int i=0;i<total;i++)
     {
      //--- При создании объекта передаём в него все требуемые параметры
      CGCnvElement *element=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,i,0,ChartID(),0,"Element_0"+(string)(i+1),300,40+(i*80),100,70,clrSilver,200,InpMovable,true,true);
      if(element==NULL)
         continue;
      //--- Добавим объекты в список
      if(!list_elements.Add(element))
        {
         delete element;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

В обработчике OnDeinit() удалим все комментарии с графика:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   Comment("");
  }
//+------------------------------------------------------------------+

В обработчике OnChartEvent() отловим щелчок по объекту, получим из списка объектов объект-элемент с именем, соответствующим имени объекта, по которому щёлкнули мышкой, записанном в параметре sparam обработчика, и увеличим уровень его непрозрачности на 5 единиц. В комментарии на чарт выведем сообщение с именем обрабатываемого объекта и значением его уровня непрозрачности:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Если щелчок по объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Получим в новый список объект-элемент с именем, соответствующим значению строкового параметра sparam обработчика OnChartEvent()
      CArrayObj *obj_list=CSelect::ByGraphCanvElementProperty(GetPointer(list_elements),CANV_ELEMENT_PROP_NAME_OBJ,sparam,EQUAL);
      if(obj_list!=NULL && obj_list.Total()>0)
        {
         //--- Получим указатель на объект в списке
         CGCnvElement *obj=obj_list.At(0);
         //--- и зададим для него новый уровень непрозрачности
         uchar opasity=obj.Opacity();
         if((opasity+5)>255)
            opasity=0;
         else 
            opasity+=5;
         //--- Установим новую непрозрачность объекту и выведем в журнал имя объекта и уровень непрозрачности
         obj.SetOpacity(opasity);
         Comment(DFUN,"Object name: ",obj.NameObj(),", opasity=",opasity);
        }
     }
  }
//+------------------------------------------------------------------+

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



Что дальше

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

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

К содержанию

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

Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента

Прикрепленные файлы |
MQL5.zip (4001.76 KB)
Графика в библиотеке DoEasy (Часть 75): Методы работы с примитивами и текстом в базовом графическом элементе Графика в библиотеке DoEasy (Часть 75): Методы работы с примитивами и текстом в базовом графическом элементе
В статье продолжим развитие базового класса-графического элемента всех графических объектов библиотеки, создаваемых на основе класса Стандартной библиотеки CCanvas. Мы создадим методы для рисования графических примитивов и методы вывода текста на объект-графический элемент.
Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы
В данной серии статей будем искать практическое применение теории вероятностей для описания процесса торговли и ценообразования. В первой статье мы познакомимся с основами комбинаторики и теории вероятностей, и разберем первый пример применения фракталов в рамках теории вероятности.
Графика в библиотеке DoEasy (Часть 76): Объект Форма и предопределённые цветовые темы Графика в библиотеке DoEasy (Часть 76): Объект Форма и предопределённые цветовые темы
В статье опишем концепцию построения различных тем оформления GUI в библиотеке, создадим объект "Форма", являющийся потомком объекта класса графического элемента, подготовим данные для создания теней графических объектов библиотеки и дальнейшего развития функционала.
Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента
В статье начинаем новый большой раздел библиотеки по работе с графикой. Сегодня создадим объект состояний мышки, базовый объект всех графических элементов и класс объекта-формы графических элементов библиотеки.