Графика в библиотеке DoEasy (Часть 86): Коллекция графических объектов - контролируем модификацию свойств

Artyom Trishkin | 30 октября, 2021

Содержание


Концепция

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

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

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

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

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


Отслеживание модификации свойств графических объектов

В обработчике OnChartEvent() нас интересуют события:

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

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

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

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

Откроем файл \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh класса абстрактного стандартного графического объекта и в приватной секции впишем новые массивы для хранения "прошлых" свойств объекта:

//+------------------------------------------------------------------+
//| Класс абстрактного стандартного графического объекта             |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   long              m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL];         // Целочисленные свойства
   double            m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL];        // Вещественные свойства
   string            m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL];        // Строковые свойства
   
   long              m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL];    // Целочисленные свойства до изменения
   double            m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL];   // Вещественные свойства до изменения
   string            m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL];   // Строковые свойства до изменения

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

public:

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

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                         }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;       }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;       }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property)        const { return this.m_long_prop[property];                        }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];      }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];      }
   
//--- Устанавливает прошлое (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop_prev[property]=value;                  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value){ this.m_double_prop_prev[this.IndexProp(property)]=value;}
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,string value){ this.m_string_prop_prev[this.IndexProp(property)]=value;}
//--- Возвращает из массива прошлых свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property)    const { return this.m_long_prop_prev[property];                   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property)     const { return this.m_double_prop_prev[this.IndexProp(property)]; }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property)     const { return this.m_string_prop_prev[this.IndexProp(property)]; }
   
//--- Возвращает себя
   CGStdGraphObj    *GetObject(void)                                                { return &this;}


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

public:
//+-------------------------------------------------------------------+ 
//|Методы упрощённого доступа и установки свойств графического объекта|
//+-------------------------------------------------------------------+
//--- Номер объекта в списке
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM);                              }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,number);                                   }
//--- Идентификатор объекта
   long              ObjectID(void)                const { return this.GetProperty(GRAPH_OBJ_PROP_ID);                                    }
   void              SetObjectID(const long obj_id)
                       {
                        CGBaseObj::SetObjectID(obj_id);
                        this.SetProperty(GRAPH_OBJ_PROP_ID,obj_id);
                        this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,obj_id);
                       }
//--- Тип графичесеого объекта


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

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

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

//--- Возвращает описание видимости объекта на таймфреймах
   string            VisibleOnTimeframeDescription(void);

//--- Перезаписывает все свойства графического объекта
   void              PropertiesRefresh(void);
//--- Проверяет изменения свойств объекта
   void              PropertiesCheckChanged(void);
   
private:
//--- Получает и сохраняет (1) целочисленные, (2) вещественные и (3) строковые свойства
   void              GetAndSaveINT(void);
   void              GetAndSaveDBL(void);
   void              GetAndSaveSTR(void);
//--- Копирует текущие данные в прошлые
   void              PropertiesCopyToPrevData(void);
   
  };
//+------------------------------------------------------------------+

Упростим защищённый параметрический конструктор. Ранее в нём получались все свойства, присущие всем графическим объектам, от графического объекта и записывались в объект класса:

//+------------------------------------------------------------------+
//| Защищённый параметрический конструктор                           |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- Устанавливаем объекту (1) его тип, тип графического (2) объекта, (3) елемента, (4) принадлежность и (5) номер подокна, (6) Digits символа графика
   this.m_type=obj_type;
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Сохранение целочисленных свойств
   //--- свойства, присущие всем графическим объектам, но не имеющиеся у графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Идентификатор графика
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Номер подокна графика
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Тип графического объекта (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Принадлежность графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Группа графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Идентификатор объекта
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Номер объекта в списке
   //--- Свойства, присущие всем графическим объектам, имеющиеся у графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(chart_id,name,OBJPROP_CREATETIME);  // Время создания объекта
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(chart_id,name,OBJPROP_TIMEFRAMES);  // Видимость объекта на таймфреймах
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(chart_id,name,OBJPROP_BACK);        // Объект на заднем плане
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(chart_id,name,OBJPROP_ZORDER);      // Приоритет графического объекта на получение события нажатия мышки на графике
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(chart_id,name,OBJPROP_HIDDEN);      // Запрет на показ имени графического объекта в списке объектов терминала
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTED);    // Выделенность объекта
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTABLE);  // Доступность объекта
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(chart_id,name,OBJPROP_TIME);        // Координата времени первой точки
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(chart_id,name,OBJPROP_COLOR);       // Цвет
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(chart_id,name,OBJPROP_STYLE);       // Стиль
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(chart_id,name,OBJPROP_WIDTH);       // Толщина линии
   //--- Свойства, принадлежащие разным графическим объектам
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]                          = 0;  // Заливка объекта цветом
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]                      = 0;  // Возможность редактирования текста в объекте Edit
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]                        = 0;  // Количество уровней
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]                    = 0;  // Цвет линии-уровня
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]                    = 0;  // Стиль линии-уровня
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]                    = 0;  // Толщина линии-уровня
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]                         = 0;  // Горизонтальное выравнивание текста в объекте "Поле ввода" (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]                      = 0;  // Размер шрифта
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]                      = 0;  // Луч продолжается влево
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]                     = 0;  // Луч продолжается вправо
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]                           = 0;  // Вертикальная линия продолжается на все окна графика
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]                       = 0;  // Отображение полного эллипса для объекта "Дуги Фибоначчи"
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]                     = 0;  // Код стрелки для объекта "Стрелка"
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]                        = 0;  // Положение точки привязки графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]                     = 0;  // Дистанция в пикселях по оси X от угла привязки
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]                     = 0;  // Дистанция в пикселях по оси Y от угла привязки
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]                     = 0;  // Тренд объекта Ганна
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]                        = 0;  // Уровень волновой разметки Эллиотта
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]                     = 0;  // Отображение линий для волновой разметки Эллиотта
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]                         = 0;  // Состояние кнопки (Нажата/Отжата)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]            = 0;  // Идентификатор объекта "График" (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]              = 0;  // Период для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE]          = 0;  // Признак отображения шкалы времени для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]         = 0;  // Признак отображения ценовой шкалы для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]         = 0;  // Масштаб для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]                         = 0;  // Ширина объекта по оси X в пикселях.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]                         = 0;  // Высота объекта по оси Y в пикселях.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]                       = 0;  // X-координата левого верхнего угла прямоугольной области видимости.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]                       = 0;  // Y-координата левого верхнего угла прямоугольной области видимости.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]                       = 0;  // Цвет фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]                        = 0;  // Угол графика для привязки графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE]                   = 0;  // Тип рамки для объекта "Прямоугольная рамка"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]                  = 0;  // Цвет рамки для объекта OBJ_EDIT и OBJ_BUTTON
   
//--- Сохранение вещественных свойств
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(chart_id,name,OBJPROP_PRICE);  // Координата цены
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = 0;                                               // Значение уровня
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = 0;                                               // Масштаб (свойство объектов Ганна и объекта "Дуги Фибоначчи")
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = 0;                                               // Угол
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = 0;                                               // Отклонение для канала стандартного отклонения
   
//--- Сохранение строковых свойств
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_NAME)]        = name;                                            // Имя объекта
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]        = ::ObjectGetString(chart_id,name,OBJPROP_TEXT);   // Описание объекта (текст, содержащийся в объекте)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]     = ::ObjectGetString(chart_id,name,OBJPROP_TOOLTIP);// Текст всплывающей подсказки
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]   = "";                                              // Описание уровня
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]        = "";                                              // Шрифт
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]     = "";                                              // Имя BMP-файла для объекта "Графическая метка"
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]= "";                                          // Символ для объекта "График" 
   
//--- Сохранение базовых свойств в родительском объекте
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);
   this.m_name=this.GetProperty(GRAPH_OBJ_PROP_NAME);

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

Теперь же все эти строки мы перенесём в отдельные методы, которые будут разом вызываться из метода PropertiesRefresh().
Поэтому удалим эти строки, и теперь конструктор будет таким:

//+------------------------------------------------------------------+
//| Защищённый параметрический конструктор                           |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const string name)
  {
//--- Устанавливаем объекту (1) его тип, тип графического (2) объекта, (3) елемента, (4) принадлежность и (5) номер подокна, (6) Digits символа графика
   this.m_type=obj_type;
   this.SetName(name);
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Сохранение целочисленных свойств
   //--- свойства, присущие всем графическим объектам, но не имеющиеся у графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID]    = CGBaseObj::ChartID();          // Идентификатор графика
   this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM]     = CGBaseObj::SubWindow();        // Номер подокна графика
   this.m_long_prop[GRAPH_OBJ_PROP_TYPE]        = CGBaseObj::TypeGraphObject();  // Тип графического объекта (ENUM_OBJECT)
   this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
   this.m_long_prop[GRAPH_OBJ_PROP_BELONG]      = CGBaseObj::Belong();           // Принадлежность графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_GROUP]       = CGBaseObj::Group();            // Группа графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_ID]          = 0;                             // Идентификатор объекта
   this.m_long_prop[GRAPH_OBJ_PROP_NUM]         = 0;                             // Номер объекта в списке

//--- Сохранение свойств, присущих всем графическим объектам, имеющиеся у графического объекта
   this.PropertiesRefresh();

//--- Сохранение базовых свойств в родительском объекте
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN);

//--- Сохранение текущих свойств в прошлые
   this.PropertiesCopyToPrevData();
   
  }
//+-------------------------------------------------------------------+

Для того, чтобы метод PropertiesRefresh() правильно отработал, он должен знать имя графического объекта, от которого получать данные. Ранее имя вписывалось почти в самом конце — в блоке чтения строковых параметров. Теперь же мы имя графического объекта считаем и запишем в свойства объекта-класса сразу при входе в конструктор. В самом конце — после записи всех свойств в объект-класс, вызовем метод PropertiesCopyToPrevData(), который запишет все уже сохранённые свойства объекта в массивы "прошлых" свойств — для контроля их изменений.

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

//+------------------------------------------------------------------+
//| Получает и сохраняет целочисленные свойства                      |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Свойства, присущие всем графическим объектам, имеющиеся у графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME); // Время создания объекта
   this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES); // Видимость объекта на таймфреймах
   this.m_long_prop[GRAPH_OBJ_PROP_BACK]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK);       // Объект на заднем плане
   this.m_long_prop[GRAPH_OBJ_PROP_ZORDER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER);     // Приоритет графического объекта на получение события нажатия мышки на графике
   this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN);     // Запрет на показ имени графического объекта в списке объектов терминала
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTED]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED);   // Выделенность объекта
   this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE); // Доступность объекта
   this.m_long_prop[GRAPH_OBJ_PROP_TIME]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME);       // Координата времени первой точки
   this.m_long_prop[GRAPH_OBJ_PROP_COLOR]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR);      // Цвет
   this.m_long_prop[GRAPH_OBJ_PROP_STYLE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE);      // Стиль
   this.m_long_prop[GRAPH_OBJ_PROP_WIDTH]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH);      // Толщина линии
   //--- Свойства, принадлежащие разным графическим объектам
   this.m_long_prop[GRAPH_OBJ_PROP_FILL]        = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL);       // Заливка объекта цветом
   this.m_long_prop[GRAPH_OBJ_PROP_READONLY]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY);   // Возможность редактирования текста в объекте Edit
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELS]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS);     // Количество уровней
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR); // Цвет линии-уровня
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE); // Стиль линии-уровня
   this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH]  = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH); // Толщина линии-уровня
   this.m_long_prop[GRAPH_OBJ_PROP_ALIGN]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN);      // Горизонтальное выравнивание текста в объекте "Поле ввода" (OBJ_EDIT)
   this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE);   // Размер шрифта
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT]    = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT);   // Луч продолжается влево
   this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT);  // Луч продолжается вправо
   this.m_long_prop[GRAPH_OBJ_PROP_RAY]         = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY);        // Вертикальная линия продолжается на все окна графика
   this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE);    // Отображение полного эллипса для объекта "Дуги Фибоначчи"
   this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE);  // Код стрелки для объекта "Стрелка"
   this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR);     // Положение точки привязки графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE);  // Дистанция в пикселях по оси X от угла привязки
   this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE);  // Дистанция в пикселях по оси Y от угла привязки
   this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION);  // Тренд объекта Ганна
   this.m_long_prop[GRAPH_OBJ_PROP_DEGREE]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE);     // Уровень волновой разметки Эллиотта
   this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES);  // Отображение линий для волновой разметки Эллиотта
   this.m_long_prop[GRAPH_OBJ_PROP_STATE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE);      // Состояние кнопки (Нажата/Отжата)
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID]   = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID);   // Идентификатор объекта "График" (OBJ_CHART).
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD);     // Период для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE); // Признак отображения шкалы времени для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE);// Признак отображения ценовой шкалы для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE);// Масштаб для объекта "График"
   this.m_long_prop[GRAPH_OBJ_PROP_XSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE);      // Ширина объекта по оси X в пикселях.
   this.m_long_prop[GRAPH_OBJ_PROP_YSIZE]       = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE);      // Высота объекта по оси Y в пикселях.
   this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET);    // X-координата левого верхнего угла прямоугольной области видимости.
   this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET);    // Y-координата левого верхнего угла прямоугольной области видимости.
   this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR]     = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR);    // Цвет фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.m_long_prop[GRAPH_OBJ_PROP_CORNER]      = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER);     // Угол графика для привязки графического объекта
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE);// Тип рамки для объекта "Прямоугольная рамка"
   this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR);// Цвет рамки для объекта OBJ_EDIT и OBJ_BUTTON
  }
//+------------------------------------------------------------------+
//| Получает и сохраняет вещественные свойства                       |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveDBL(void)
  {
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE);       // Координата цены
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)]  = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE);  // Значение уровня
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE);       // Масштаб (свойство объектов Ганна и объекта "Дуги Фибоначчи")
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)]       = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE);       // Угол
   this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)]   = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION);   // Отклонение для канала стандартного отклонения
  }
//+------------------------------------------------------------------+
//| Получает и сохраняет строковые свойства                          |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveSTR(void)
  {
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT);     // Описание объекта (текст, содержащийся в объекте)
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP);  // Текст всплывающей подсказки
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)]         = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT);// Описание уровня
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)]              = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT);     // Шрифт
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)]           = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE);  // Имя BMP-файла для объекта "Графическая метка"
   this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]  = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL);   // Символ для объекта "График" 
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Перезаписывает все свойства графического объекта                 |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesRefresh(void)
  {
   this.GetAndSaveINT();
   this.GetAndSaveDBL();
   this.GetAndSaveSTR();
  }
//+------------------------------------------------------------------+

Здесь поочерёдно вызываются все три вышерассмотренных метода.

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

//+------------------------------------------------------------------+
//| Копирует текущие данные в прошлые                                |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCopyToPrevData(void)
  {
   ::ArrayCopy(this.m_long_prop_prev,this.m_long_prop);
   ::ArrayCopy(this.m_double_prop_prev,this.m_double_prop);
   ::ArrayCopy(this.m_string_prop_prev,this.m_string_prop);
  }
//+------------------------------------------------------------------+

Здесь при помощи функции копирования массивов поочерёдно копируем массивы целочисленных, вещественных и строковых свойств в соответствующие массивы прошлых свойств.

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

//+------------------------------------------------------------------+
//| Проверяет изменения свойств объекта                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   bool changed=false;
   int beg=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   beg=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+

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

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


Отслеживание удаления графических объектов

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

Откроем файл класса-коллекции графических объектов \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh и внесём в него все необходимые доработки.

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

//+------------------------------------------------------------------+
//| Класс управления объектами чарта                                 |
//+------------------------------------------------------------------+
class CChartObjectsControl : public CObject
  {
private:
   CArrayObj         m_list_new_graph_obj;      // Список добавленных графических объектов
   ENUM_TIMEFRAMES   m_chart_timeframe;         // Период графика
   long              m_chart_id;                // Идентификатор графика
   string            m_chart_symbol;            // Символ графика
   bool              m_is_graph_obj_event;      // Флаг события в списке графических объектов
   int               m_total_objects;           // Количество графических объектов
   int               m_last_objects;            // Количество графических объектов на прошлой проверке
   int               m_delta_graph_obj;         // Разница в количестве графических объектов по сравнению с прошлой проверкой
   
//--- Возвращает имя последнего добавленного на график графического объекта
   string            LastAddedGraphObjName(void);
//--- Устанавливает для графика разрешение отслеживания событий мышки и графических объектов
   void              SetMouseEvent(void);
   
public:
//--- Возврат значений переменных
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_chart_timeframe;    }
   long              ChartID(void)                             const { return this.m_chart_id;           }
   string            Symbol(void)                              const { return this.m_chart_symbol;       }
   bool              IsEvent(void)                             const { return this.m_is_graph_obj_event; }
   int               TotalObjects(void)                        const { return this.m_total_objects;      }
   int               Delta(void)                               const { return this.m_delta_graph_obj;    }
//--- Создаёт новый объект стандартного графического объекта
   CGStdGraphObj    *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name);
//--- Возвращает список вновь добавленных объектов
   CArrayObj        *GetListNewAddedObj(void)                        { return &this.m_list_new_graph_obj;}
//--- Проверяет объекты на чарте
   void              Refresh(void);
//--- Конструкторы
                     CChartObjectsControl(void)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=::ChartID();
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     CChartObjectsControl(const long chart_id)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=chart_id;
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.SetMouseEvent();
                       }
                     
//--- Сравнивает объекты CChartObjectsControl между собой по идентификатору графика (для сортировки списка по свойству объекта)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CChartObjectsControl *obj_compared=node;
                        return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0);
                       }

//--- Обработчик событий
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

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

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

//+------------------------------------------------------------------+
//| Устанавливает для графика разрешение                             |
//| отслеживания событий мышки и графических объектов                |
//+------------------------------------------------------------------+
void CChartObjectsControl::SetMouseEvent(void)
  {
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_CREATE,true);
   ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_DELETE,true);
  }
//+------------------------------------------------------------------+


Теперь займёмся классом-коллекцией графических объектов.

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

//+------------------------------------------------------------------+
//| Коллекция графических объектов                                   |
//+------------------------------------------------------------------+
class CGraphElementsCollection : public CBaseObj
  {
private:
   CArrayObj         m_list_charts_control;     // Список объектов управления чартами
   CListObj          m_list_all_canv_elm_obj;   // Список всех графических элементов на канвасе
   CListObj          m_list_all_graph_obj;      // Список всех графических объектов
   bool              m_is_graph_obj_event;      // Флаг события в списке графических объектов
   int               m_total_objects;           // Количество графических объектов
   int               m_delta_graph_obj;         // Разница в количестве графических объектов по сравнению с прошлой проверкой
   
//--- Возвращает флаг наличия объекта-графического элемента в списке-коллекции графических элементов
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
//--- Возвращает флаг наличия объекта-графического объекта в списке-коллекции графических объектов
   bool              IsPresentGraphObjInList(const long chart_id,const string name);
//--- Возвращает флаг наличия графического объекта на графике по имени
   bool              IsPresentGraphObjOnChart(const long chart_id,const string name);
//--- Возвращает указатель на объект управления объектами указанного чарта
   CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id);
//--- Создаёт новый объект управления графическими объектами указанного чарта и добавляет его в список
   CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id);
//--- Обновляет список графических объектов по идентификатору чарта
   CChartObjectsControl *RefreshByChartID(const long chart_id);
//--- Возвращает первый свободный идентификатор графического (1) объекта, (2) элемента на канвасе
   long              GetFreeGraphObjID(void);
   long              GetFreeCanvElmID(void);
//--- Добавляет графический объект в коллекцию
   bool              AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control);
//--- Находит объект, имеющийся в коллекции, но отсутствующий на графике
   CGStdGraphObj    *FindMissingObj(const long chart_id);
//--- Находит графический объект, имеющийся на графике, но отсутствующий в коллекции
   string            FindExtraObj(const long chart_id);
//--- Удаляет графический объект из списка-коллекции графических объектов
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   
public:

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

public:
//--- Возвращает себя
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Возвращает полный список-коллекцию стандартных графических объектов "как есть"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Возвращает полный список-коллекцию графических элеменов на канвасе "как есть"
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
//--- Возвращает список графических элементов по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
//--- Возвращает список графических объектов по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)    { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); }
//--- Возвращает количество новых графических объектов, (3) флаг произошедшего изменения в списке графических объектов
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   bool              IsEvent(void) const                                                                 { return this.m_is_graph_obj_event;    }
//--- Возвращает графический объект по имени и идентификатору графика
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
//--- Конструктор
                     CGraphElementsCollection();
//--- Выводит в журнал описание свойств объекта (full_prop=true - все свойства, false - только поддерживаемые - реализуется в наследниках класса)
   virtual void      Print(const bool full_prop=false,const bool dash=false);
//--- Выводит в журнал краткое описание объекта
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);

//--- Создаёт список объектов управления чартами, возвращает количество чартов
   int               CreateChartControlList(void);
//--- Обновляет список (1) всех графических объектов, (2) на указанном чарте, заполняет данные о количестве новых и устанавливает флаг события
   void              Refresh(void);
   void              Refresh(const long chart_id);
//--- Обработчик событий
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Обновляет список всех графических объектов                       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
//--- Объявим переменные для поиска графиков
   long chart_id=0;
   int i=0;
//--- В цикле по всем открытым графикам терминала (не более 100)
   while(i<CHARTS_MAX)
     {
      //--- Получим идентификатор графика
      chart_id=::ChartNext(chart_id);
      if(chart_id<0)
         break;
      //--- Получаем указатель на объект управления графическими объектами
      //--- и обновляем список графических объектов по идентификатору чарта
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      //--- Если указатель получить не удалось - идём к следующему графику
      if(obj_ctrl==NULL)
         continue;
      //--- Если есть событие изменения количества объектов на графике
      if(obj_ctrl.IsEvent())
        {
         //--- Если добавлен графический объект на график
         if(obj_ctrl.Delta()>0)
           {
            //--- Получаем список добавленных графических объектов и перемещаем их в список-коллекцию
            //--- (если объект поместить в коллекцию не удалось - идём к следующему объекту)
            if(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- Если удалён графический объект
         else if(obj_ctrl.Delta()<0)
           {
            // Найдём лишний объект в списке
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);
            if(obj!=NULL)
              {
               //--- Выведем в журнал короткое описание найденного объекта, который удалён с графика
               obj.PrintShort();
               //--- Удалим объект класса удалённого графического объекта из списка-коллекции
               if(!this.DeleteGraphObjFromList(obj))
                  CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
              }
           }
         //--- иначе
         else
           {
            
           }
        }
      //--- Увеличиваем индекс цикла
      i++;
     }
  }
//+------------------------------------------------------------------+

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

Метод, ищущий объект, имеющийся в коллекции, но отсутствующий на графике:

//+------------------------------------------------------------------+
//|Находит объект, имеющийся в коллекции, но отсутствующий на графике|
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   if(list==NULL)
      return NULL;
   for(int i=0;i<list.Total();i++)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name()))
         return obj;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

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

Метод, ищущий объект, имеющийся на графике, но отсутствующий в коллекции:

//+------------------------------------------------------------------+
//|Находит объект, имеющийся на графике, но отсутствующий в коллекции|
//+------------------------------------------------------------------+
string CGraphElementsCollection::FindExtraObj(const long chart_id)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
     {
      string name=::ObjectName(chart_id,i);
      if(!this.IsPresentGraphObjInList(chart_id,name))
         return name;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Здесь: в цикле по всем объектам в списке терминала получаем имя очередного объекта и, если объекта с таким именем и идентификатором графика нет в списке-коллекции, — возвращаем имя графического объекта. По завершении цикла возвращаем NULL.

Метод, возвращающий флаг наличия объекта класса графического объекта в списке-коллекции графических объектов:

//+------------------------------------------------------------------+
//| Возвращает флаг наличия объекта класса графического объекта      |
//| в списке-коллекции графических объектов                          |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list==NULL || list.Total()==0 ? false : true);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает флаг наличия графического объекта на графике по имени |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjOnChart(const long chart_id,const string name)
  {
   int total=::ObjectsTotal(chart_id);
   for(int i=0;i<total;i++)
      if(::ObjectName(chart_id,i)==name)
         return true;
   return false;
  }
//+-------------------------------------------------------------------+

В цикле по общему количеству всех графических объектов на указанном по идентификатору графике получаем имя очередного объекта и, если имя совпадает с искомым, возвращаем true. По окончании цикла возвращаем false.

Метод, удаляющий графический объект из списка-коллекции графических объектов:

//+-------------------------------------------------------------------+
//|Удаляет графический объект из списка-коллекции графических объектов|
//+-------------------------------------------------------------------+
bool CGraphElementsCollection::DeleteGraphObjFromList(CGStdGraphObj *obj)
  {
   this.m_list_all_graph_obj.Sort();
   int index=this.m_list_all_graph_obj.Search(obj);
   return(index==WRONG_VALUE ? false : this.m_list_all_graph_obj.Delete(index));
  }
//+------------------------------------------------------------------+

В метод передаётся указатель на объект, который необходимо удалить из списка.
Устанавливаем списку флаг сортированного списка
(поиск ведётся только в сортированных списках),
получаем индекс объекта при помощи метода Search()
Стандартной Библиотеки.
Если индекс объекта не найден — возвращаем false
,
иначе — возвращаем результат удаления объекта из списка методом Delete()
Стандартной Библиотеки.

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

//+------------------------------------------------------------------+
//| Возвращает графический объект по имени и идентификатору графика  |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id)
  {
   CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,chart_id);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Получаем список объектов, идентификатор графика которых равен переданному в метод. Из полученного списка получаем список, содержащий объект, имя которого совпадает с искомым (должен быть всего один такой объект). Если список получить удалось, и он не пустой, — возвращаем указатель на первый (и единственный) объект в списке. Иначе — возвращаем NULL.

Обработчик событий.

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

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

В коде метода вся логика расписана в комментариях. Просто рассмотрим метод для самостоятельного изучения:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- Если объект, свойства которого изменены, или который был перемещён,
      //--- успешно получен из списка-коллекции по его имени, записанном в sparam
      obj=this.GetStdGraphObject(sparam,::ChartID());
      if(obj!=NULL)
        {
         //--- Обновим свойства полученного объекта
         //--- и проверим их изменение
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
      //--- Если объект не удалось получить по имени - он отсутствует в списке,
      //--- а значит его имя было изменено
      else
        {
         //--- Найдём в списке объект, которого нет на графике
         obj=this.FindMissingObj(::ChartID());
         if(obj==NULL)
            return;
         //--- Получим имя переименованного  графического объекта на графике, которого нет в списке-коллекции
         string name_new=this.FindExtraObj(::ChartID());
         //--- Установим новое имя объекту в списке-коллекции, которому не соответствует ни один графический объект на графике,
         //--- обновим свойства объекта и проверим их изменение
         obj.SetName(name_new);
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
     }
  }
//+------------------------------------------------------------------+


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

//--- Запускает новый отсчёт паузы
   void                 Pause(const ulong pause_msc,const datetime time_start=0)
                          {
                           this.PauseSetWaitingMSC(pause_msc);
                           this.PauseSetTimeBegin(time_start*1000);
                           while(!this.PauseIsCompleted() && !::IsStopped()){}
                          }

//--- Возвращает коллекцию графических объектов
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects; }

//--- Конструктор/Деструктор
                        CEngine();
                       ~CEngine();

private:


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

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

Из обработчика OnInit() удалим строки с установкой разрешений на отслеживание событий мышки:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка разрешений на отсылку событий перемещения курсора и прокрутки колёсика мышки
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Установка глобальных переменных советника
   ArrayResize(array_clr,2);        // Массив цветов градиентной заливки
   array_clr[0]=C'246,244,244';     // Исходный ≈бледно-серый
   array_clr[1]=C'249,251,250';     // Конечный ≈бледный серо-зелёный
//--- Создадим массив с текущим символом и установим его для использования в библиотеке
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Создадим объект-таймсерию для текущего символа и периода и выведем его описание в журнал
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

В самом конце обработчика OnChartEvent() впишем вызов обработчика событий класса-коллекции графических объектов:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Если работа в тестере - выход
   if(MQLInfoInteger(MQL_TESTER))
      return;
//--- Если перемещаем мышь
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      CForm *form=NULL;
      datetime time=0;
      double price=0;
      int wnd=0;
      
      //--- Если клавиша Ctrl не нажата
      if(!IsCtrlKeyPressed())
        {
         //--- очищаем список созданных объектов-форм и разрешаем прокручивать график мышкой и показывать контекстное меню
         list_forms.Clear();
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,true);
         return;
        }
      
      //--- Если координаты  X и Y графика успешно преобразованы в значения время и цена
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- получим индекс бара, над которым находится курсор
         int index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         if(index==WRONG_VALUE)
            return;
         
         //--- Получим объект-бар по индексу
         CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index);
         if(bar==NULL)
            return;
         
         //--- Преобразуем координаты графика из представления время/цена объекта-бара в координаты по оси X и Y
         int x=(int)lparam,y=(int)dparam;
         if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y))
            return;
         
         //--- Запретим перемещать график мышкой и показывать контекстное меню
         ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false);
         ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,false);
         
         //--- Создадим имя объекта-формы и скроем все объекты кроме одного с таким именем
         string name="FormBar_"+(string)index;
         HideFormAllExceptOne(name);
         
         //--- Если ещё нет объекта-формы с таким именем
         if(!IsPresentForm(name))
           {
            //--- создадим новый объект-форму
            form=bar.CreateForm(index,name,x,y,114,16);   
            if(form==NULL)
               return;
            
            //--- Установим форме флаги активности и неперемещаемости
            form.SetActive(true);
            form.SetMovable(false);
            //--- Установим непрозрачность 200
            form.SetOpacity(200);
            //--- Цвет фона формы зададим как первый цвет из массива цветов
            form.SetColorBackground(array_clr[0]);
            //--- Цвет очерчивающей рамки формы
            form.SetColorFrame(C'47,70,59');
            //--- Установим флаг рисования тени
            form.SetShadow(true);
            //--- Рассчитаем цвет тени как цвет фона графика, преобразованный в монохромный
            color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
            //--- Если в настройках задано использовать цвет фона графика, то затемним монохромный цвет на 20 единиц
            //--- Иначе - будем использовать для рисования тени заданный в настройках цвет
            color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
            //--- Нарисуем тень формы со смещением от формы вправо-вниз на три пикселя по всем осям
            //--- Непрозрачность тени при этом установим равной 200, а радиус размытия равный 4
            form.DrawShadow(2,2,clr,200,3);
            //--- Зальём фон формы вертикальным градиентом
            form.Erase(array_clr,form.Opacity());
            //--- Нарисуем очерчивающий прямоугольник по краям формы
            form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
            //--- Если объект-форму не удалось добавить в список - удаляем форму и уходим из обработчика
            if(!list_forms.Add(form))
              {
               delete form;
               return;
              }
            //--- Зафиксируем внешний вид формы
            form.Done();
           }
         //--- Если объект-форма существует
         if(form!=NULL)
           {
            //--- нарисуем на нём текст с описанием типа бара, соответствующего положению курсора мышки и покажем форму
            form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21');
            form.Show();
           }
         //--- Перерисуем чарт
         ChartRedraw();
        }
     }
   
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
   
  }
//+------------------------------------------------------------------+

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


Пока что это просто записи в журнал, но ...

Что дальше

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

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

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

К содержанию

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

Графика в библиотеке DoEasy (Часть 83): Класс абстрактного стандартного графического объекта
Графика в библиотеке DoEasy (Часть 84): Классы-наследники абстрактного стандартного графического объекта
Графика в библиотеке DoEasy (Часть 85): Коллекция графических объектов - добавляем вновь создаваемые