Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas
Содержание
- Концепция
- Доработка классов библиотеки
- Базовый объект всех графических объектов библиотеки на основе канваса
- Тестирование
- Что дальше
Концепция
С прошлой статьи мы открыли большой раздел библиотеки по работе с графикой и начали создавать объект-форму — как основной объект всех графических объектов библиотеки, создаваемых на основе класса стандартной библиотеки 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): Объект-форма графического элемента
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования