Графика в библиотеке DoEasy (Часть 92): Класс памяти стандартных графических объектов. История изменения свойств объекта

Artyom Trishkin | 31 декабря, 2021

Содержание


Концепция

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


Где, и как это может пригодиться — тоже не берусь утверждать, но это будет дополнительным инструментом для проведения теханализа. Например, именем объекта мы можем отмечать день, неделю, месяц или ещё какой-то промежуток времени, на котором построен графический объект. При наступлении нового отрезка времени старый объект переименовывается так, чтобы его имя соответствовало новому временному отрезку, и объект перестраивается на графике. Соответственно, все его свойства будут сохраняться в его памяти (сегодня сделаем такой функционал), в том числе и история его переименований. Если мы каждый день что-либо отмечаем на графике при помощи графического объекта, и каждый день его перестраиваем, то можно и переименовывать его для соответствия новой дате. Затем, при прокрутке графика вручную, мы можем узнать время видимого бара и просто "вытащить" из памяти текущего графического объекта его состояние, соответствующее времени баров на прокручиваемом графике и применить его свойства к текущему объекту. Таким образом мы сможем создать некий smart-объект, который будет сам изменять своё состояние в зависимости от того, какое время имеют видимые бары на графике.

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

Опять-таки, для чего это нужно? Ну, мне кажется это удобным для проведения анализа своей прошедшей торговой недели. Да и это всего лишь один пример, сразу пришедший мне в голову и показавшийся полезным.


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

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


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

В файле \MQL5\Include\DoEasy\Defines.mqh изменим перечисление "Группа графического объекта" :

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Стандартный графический объект
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Элемент
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Объект тени
   GRAPH_ELEMENT_TYPE_FORM,                           // Форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
  };
//+------------------------------------------------------------------+
//| Группа графического объекта                                      |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_GROUP
  {
   GRAPH_OBJ_GROUP_LINES,                             // Линии
   GRAPH_OBJ_GROUP_CHANNELS,                          // Каналы
   GRAPH_OBJ_GROUP_GANN,                              // Ганн
   GRAPH_OBJ_GROUP_FIBO,                              // Фибоначчи
   GRAPH_OBJ_GROUP_ELLIOTT,                           // Эллиотт
   GRAPH_OBJ_GROUP_SHAPES,                            // Фигуры
   GRAPH_OBJ_GROUP_ARROWS,                            // Стрелки
   GRAPH_OBJ_GROUP_GRAPHICAL,                         // Графические объекты
  };
//+------------------------------------------------------------------+
//| Целочисленные свойства стандартного графического объекта         |
//+------------------------------------------------------------------+

на перечисление "Вид графического объекта":

//+------------------------------------------------------------------+
//| Вид графического объекта                                         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_SPECIES
  {
   GRAPH_OBJ_SPECIES_LINES,                             // Линии
   GRAPH_OBJ_SPECIES_CHANNELS,                          // Каналы
   GRAPH_OBJ_SPECIES_GANN,                              // Ганн
   GRAPH_OBJ_SPECIES_FIBO,                              // Фибоначчи
   GRAPH_OBJ_SPECIES_ELLIOTT,                           // Эллиотт
   GRAPH_OBJ_SPECIES_SHAPES,                            // Фигуры
   GRAPH_OBJ_SPECIES_ARROWS,                            // Стрелки
   GRAPH_OBJ_SPECIES_GRAPHICAL,                         // Графические объекты
  };
//+------------------------------------------------------------------+

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

   //--- Дополнительные свойства
   GRAPH_OBJ_PROP_ID = 0,                             // Идентификатор объекта
   GRAPH_OBJ_PROP_TYPE,                               // Тип графичесеого объекта (ENUM_OBJECT)
   GRAPH_OBJ_PROP_ELEMENT_TYPE,                       // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
   GRAPH_OBJ_PROP_GROUP,                              // Группа графического объекта (ENUM_GRAPH_OBJ_GROUP)
   GRAPH_OBJ_PROP_BELONG,                             // Принадлежность графического объекта
   GRAPH_OBJ_PROP_CHART_ID,                           // Идентификатор графика
   GRAPH_OBJ_PROP_WND_NUM,                            // Номер подокна графика
   GRAPH_OBJ_PROP_NUM,                                // Номер объекта в списке

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

//+------------------------------------------------------------------+
//| Целочисленные свойства стандартного графического объекта         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_PROP_INTEGER
  {
   //--- Дополнительные свойства
   GRAPH_OBJ_PROP_ID = 0,                             // Идентификатор объекта
   GRAPH_OBJ_PROP_TYPE,                               // Тип графичесеого объекта (ENUM_OBJECT)
   GRAPH_OBJ_PROP_ELEMENT_TYPE,                       // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
   GRAPH_OBJ_PROP_SPECIES,                            // Вид графического объекта (ENUM_GRAPH_OBJ_SPECIES)
   GRAPH_OBJ_PROP_BELONG,                             // Принадлежность графического объекта
   GRAPH_OBJ_PROP_CHART_ID,                           // Идентификатор графика
   GRAPH_OBJ_PROP_WND_NUM,                            // Номер подокна графика
   GRAPH_OBJ_PROP_NUM,                                // Номер объекта в списке
   GRAPH_OBJ_PROP_CHANGE_HISTORY,                     // Флаг хранения истории изменений
   GRAPH_OBJ_PROP_GROUP,                              // Группа объектов, к которой принадлежит графический объект
   //--- Общие свойства всех графических объектов

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

Так как мы добавили два новых свойства в перечисление целочисленных свойств графического объекта, то укажем их новое количество (вместо 52 теперь количество целочисленных свойств равно 54):

#define GRAPH_OBJ_PROP_INTEGER_TOTAL (54)             // Общее количество целочисленных свойств
#define GRAPH_OBJ_PROP_INTEGER_SKIP  (0)              // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+
//| Вещественные свойства стандартного графического объекта          |
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических объектов               |
//+------------------------------------------------------------------+
#define FIRST_GRAPH_OBJ_DBL_PROP  (GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_INTEGER_SKIP)
#define FIRST_GRAPH_OBJ_STR_PROP  (GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_INTEGER_SKIP+GRAPH_OBJ_PROP_DOUBLE_TOTAL-GRAPH_OBJ_PROP_DOUBLE_SKIP)
enum ENUM_SORT_GRAPH_OBJ_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_GRAPH_OBJ_ID = 0,                             // Сортировать по идентификатору объекта
   SORT_BY_GRAPH_OBJ_TYPE,                               // Сортировать по типу объекта
   SORT_BY_GRAPH_OBJ_ELEMENT_TYPE,                       // Сортировать по типу графического элемента
   SORT_BY_GRAPH_OBJ_SPECIES,                            // Сортировать по виду графического объекта
   SORT_BY_GRAPH_OBJ_BELONG,                             // Сортировать по принадлежности графического объекта
   SORT_BY_GRAPH_OBJ_CHART_ID,                           // Сортировать по идентификатору графика
   SORT_BY_GRAPH_OBJ_WND_NUM,                            // Сортировать по номеру подокна графика
   SORT_BY_GRAPH_OBJ_NUM,                                // Сортировать по номеру объекта в списке
   SORT_BY_GRAPH_OBJ_CHANGE_HISTORY,                     // Сортировать по флагу хранения истории изменений
   SORT_BY_GRAPH_OBJ_GROUP,                              // Сортировать по группе объектов, к которой принадлежит графический объект
   SORT_BY_GRAPH_OBJ_CREATETIME,                         // Сортировать по времени создания объекта
   SORT_BY_GRAPH_OBJ_TIMEFRAMES,                         // Сортировать по видимости объекта на таймфреймах
   SORT_BY_GRAPH_OBJ_BACK,                               // Сортировать по свойству "Объект на заднем плане"
   SORT_BY_GRAPH_OBJ_ZORDER,                             // Сортировать по приоритету графического объекта на получение события нажатия мышки на графике
   SORT_BY_GRAPH_OBJ_HIDDEN,                             // Сортировать по запрету на показ имени графического объекта в списке объектов терминала
   SORT_BY_GRAPH_OBJ_SELECTED,                           // Сортировать по свойству"Выделенность объекта"
   SORT_BY_GRAPH_OBJ_SELECTABLE,                         // Сортировать по свойству "Доступность объекта"
   SORT_BY_GRAPH_OBJ_TIME,                               // Сортировать по координате времени
   SORT_BY_GRAPH_OBJ_COLOR,                              // Сортировать по цвету
   SORT_BY_GRAPH_OBJ_STYLE,                              // Сортировать по стилю
   SORT_BY_GRAPH_OBJ_WIDTH,                              // Сортировать по толщине линии
   SORT_BY_GRAPH_OBJ_FILL,                               // Сортировать по свойству"Заливка объекта цветом"
   SORT_BY_GRAPH_OBJ_READONLY,                           // Сортировать по возможности редактирования текста в объекте Edit
   SORT_BY_GRAPH_OBJ_LEVELS,                             // Сортировать по количеству уровней
   SORT_BY_GRAPH_OBJ_LEVELCOLOR,                         // Сортировать по цвету линии-уровня
   SORT_BY_GRAPH_OBJ_LEVELSTYLE,                         // Сортировать по стилю линии-уровня
   SORT_BY_GRAPH_OBJ_LEVELWIDTH,                         // Сортировать по толщине линии-уровня
   SORT_BY_GRAPH_OBJ_ALIGN,                              // Сортировать по свойству "Горизонтальное выравнивание текста в объекте "Поле ввода""
   SORT_BY_GRAPH_OBJ_FONTSIZE,                           // Сортировать по размеру шрифта
   SORT_BY_GRAPH_OBJ_RAY_LEFT,                           // Сортировать по свойству "Луч продолжается влево"
   SORT_BY_GRAPH_OBJ_RAY_RIGHT,                          // Сортировать по свойству"Луч продолжается вправо"
   SORT_BY_GRAPH_OBJ_RAY,                                // Сортировать по свойству "Вертикальная линия продолжается на все окна графика"
   SORT_BY_GRAPH_OBJ_ELLIPSE,                            // Сортировать по свойству "Отображение полного эллипса для объекта "Дуги Фибоначчи"
   SORT_BY_GRAPH_OBJ_ARROWCODE,                          // Сортировать по коду стрелки для объекта "Стрелка"
   SORT_BY_GRAPH_OBJ_ANCHOR,                             // Сортировать по положению точки привязки графического объекта
   SORT_BY_GRAPH_OBJ_XDISTANCE,                          // Сортировать по дистанции в пикселях по оси X от угла привязки
   SORT_BY_GRAPH_OBJ_YDISTANCE,                          // Сортировать по дистанции в пикселях по оси Y от угла привязки
   SORT_BY_GRAPH_OBJ_DIRECTION,                          // Сортировать по свойству "Тренд объекта Ганна"
   SORT_BY_GRAPH_OBJ_DEGREE,                             // Сортировать по свойству "Уровень волновой разметки Эллиотта"
   SORT_BY_GRAPH_OBJ_DRAWLINES,                          // Сортировать по свойству "Отображение линий для волновой разметки Эллиотта"
   SORT_BY_GRAPH_OBJ_STATE,                              // Сортировать по состоянию кнопки (Нажата/Отжата)
   SORT_BY_GRAPH_OBJ_OBJ_CHART_ID,                       // Сортировать по идентификатору объекта "График".
   SORT_BY_GRAPH_OBJ_CHART_OBJ_PERIOD,                   // Сортировать по периоду для объекта "График"
   SORT_BY_GRAPH_OBJ_CHART_OBJ_DATE_SCALE,               // Сортировать по признаку отображения шкалы времени для объекта "График"
   SORT_BY_GRAPH_OBJ_CHART_OBJ_PRICE_SCALE,              // Сортировать по признаку отображения ценовой шкалы для объекта "График"
   SORT_BY_GRAPH_OBJ_CHART_OBJ_CHART_SCALE,              // Сортировать по масштабу для объекта "График"
   SORT_BY_GRAPH_OBJ_XSIZE,                              // Сортировать по ширине объекта по оси X в пикселях
   SORT_BY_GRAPH_OBJ_YSIZE,                              // Сортировать по высоте объекта по оси Y в пикселях
   SORT_BY_GRAPH_OBJ_XOFFSET,                            // Сортировать по X-координате левого верхнего угла прямоугольной области видимости
   SORT_BY_GRAPH_OBJ_YOFFSET,                            // Сортировать по Y-координате левого верхнего угла прямоугольной области видимости
   SORT_BY_GRAPH_OBJ_BGCOLOR,                            // Сортировать по цвету фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   SORT_BY_GRAPH_OBJ_CORNER,                             // Сортировать по углу графика для привязки графического объекта
   SORT_BY_GRAPH_OBJ_BORDER_TYPE,                        // Сортировать по типу рамки для объекта "Прямоугольная рамка"
   SORT_BY_GRAPH_OBJ_BORDER_COLOR,                       // Сортировать по цвету рамки для объекта OBJ_EDIT и OBJ_BUTTON
//--- Сортировка по вещественным свойствам
   SORT_BY_GRAPH_OBJ_PRICE = FIRST_GRAPH_OBJ_DBL_PROP,   // Сортировать по координате цены
   SORT_BY_GRAPH_OBJ_LEVELVALUE,                         // Сортировать по значению уровня
   SORT_BY_GRAPH_OBJ_SCALE,                              // Сортировать по масштабу (свойство объектов Ганна и объекта "Дуги Фибоначчи")
   SORT_BY_GRAPH_OBJ_ANGLE,                              // Сортировать по углу
   SORT_BY_GRAPH_OBJ_DEVIATION,                          // Сортировать по отклонению для канала стандартного отклонения
//--- Сортировка по строковым свойствам
   SORT_BY_GRAPH_OBJ_NAME = FIRST_GRAPH_OBJ_STR_PROP,    // Сортировать по имени объекта
   SORT_BY_GRAPH_OBJ_TEXT,                               // Сортировать по описанию объекта
   SORT_BY_GRAPH_OBJ_TOOLTIP,                            // Сортировать по тексту всплывающей подсказки
   SORT_BY_GRAPH_OBJ_LEVELTEXT,                          // Сортировать по описанию уровня
   SORT_BY_GRAPH_OBJ_FONT,                               // Сортировать по шрифту
   SORT_BY_GRAPH_OBJ_BMPFILE,                            // Сортировать по имени BMP-файла для объекта "Графическая метка"
   SORT_BY_GRAPH_OBJ_CHART_OBJ_SYMBOL,                   // Сортировать по символу для объекта "График" 
  };
//+------------------------------------------------------------------+


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

//--- GStdGraphObj
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ,     // Не удалось создать объект класса для графического объекта 
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ, // Не удалось создать графический объект 
   MSG_GRAPH_STD_OBJ_ERR_NOT_FIND_SUBWINDOW,          // Не удалось найти подокно графика
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_SNAPSHOT,      // Не удалось создать снимок истории изменений графического объекта
   MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT,         // Создан снимок истории изменений графического объекта

...

   MSG_GRAPH_OBJ_PROP_ID,                             // Идентификатор объекта
   MSG_GRAPH_OBJ_PROP_TYPE,                           // Тип графического объекта (ENUM_OBJECT)
   MSG_GRAPH_OBJ_PROP_ELEMENT_TYPE,                   // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
   MSG_GRAPH_OBJ_PROP_BELONG,                         // Принадлежность графического объекта
   MSG_GRAPH_OBJ_PROP_CHART_ID,                       // Идентификатор графика
   MSG_GRAPH_OBJ_PROP_WND_NUM,                        // Номер подокна графика
   MSG_GRAPH_OBJ_PROP_CHANGE_MEMORY,                  // История изменений
   MSG_GRAPH_OBJ_PROP_CREATETIME,                     // Время создания
   MSG_GRAPH_OBJ_PROP_TIMEFRAMES,                     // Видимость объекта на таймфреймах

...

   MSG_GRAPH_OBJ_PROP_SPECIES,                        // Вид графического объекта
   MSG_GRAPH_OBJ_PROP_SPECIES_LINES,                  // Линии
   MSG_GRAPH_OBJ_PROP_SPECIES_CHANNELS,               // Каналы
   MSG_GRAPH_OBJ_PROP_SPECIES_GANN,                   // Ганн
   MSG_GRAPH_OBJ_PROP_SPECIES_FIBO,                   // Фибоначчи
   MSG_GRAPH_OBJ_PROP_SPECIES_ELLIOTT,                // Эллиотт
   MSG_GRAPH_OBJ_PROP_SPECIES_SHAPES,                 // Фигуры
   MSG_GRAPH_OBJ_PROP_SPECIES_ARROWS,                 // Стрелки
   MSG_GRAPH_OBJ_PROP_SPECIES_GRAPHICAL,              // Графические объекты
   MSG_GRAPH_OBJ_PROP_GROUP,                          // Группа объектов
   MSG_GRAPH_OBJ_TEXT_CLICK_COORD,                    // (Координата щелчка по графику)
   MSG_GRAPH_OBJ_TEXT_ANCHOR_TOP,                     // Точка привязки для стрелки находится сверху
   MSG_GRAPH_OBJ_TEXT_ANCHOR_BOTTOM,                  // Точка привязки для стрелки находится снизу

...

//--- CDataPropObj
   MSG_DATA_PROP_OBJ_OUT_OF_PROP_RANGE,               // Переданное свойство находится за пределами диапазона свойств объекта
   MSG_GRAPH_OBJ_FAILED_CREATE_NEW_HIST_OBJ,          // Не удалось создать объект истории изменений графического объекта
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_HIST_LIST,         // Не удалось добавить объект истории изменений в список
   MSG_GRAPH_OBJ_FAILED_GET_HIST_OBJ,                 // Не удалось получить объект истории изменений
   MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE,               // Не удалось увеличить размер массива

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

//--- GStdGraphObj
   {"Не удалось создать объект класса для графического объекта ","Failed to create class object for graphic object"},
   {"Не удалось создать графический объект ","Failed to create graphic object "},
   {"Не удалось найти подокно графика","Could not find chart subwindow"},
   {"Не удалось создать снимок истории изменений графического объекта","Failed to create a snapshot of the change history of a graphic object"},
   {"Создан снимок истории изменений графического объекта","A snapshot of the history of changes to a graphical object has been created"},

...

   {"Идентификатор объекта","Object ID"},
   {"Тип объекта","Object type"},
   {"Тип графического элемента","Graphic element type"},
   {"Принадлежность объекта","Object belongs to"},
   {"Идентификатор графика объекта","Object chart ID"},
   {"Номер подокна графика","Chart subwindow number"},
   {"История изменений","Change history"},
   {"Время создания","Time of creation"},
   {"Видимость объекта на таймфреймах","Visibility of an object at timeframes"},

...

   {"Вид графического объекта","Graphic object species"},
   {"Линии","Lines"},
   {"Каналы","Channels"},
   {"Ганн","Gann"},
   {"Фибоначчи","Fibonacci"},
   {"Эллиотт","Elliott"},
   {"Фигуры","Shapes"},
   {"Стрелки","Arrows"},
   {"Графические объекты","Graphical"},
   {"Группа объектов","Object group"},

...

//--- CDataPropObj
   {"Переданное свойство находится за пределами диапазона свойств объекта","The passed property is outside the range of the object's properties"},
   {"Не удалось создать объект истории изменений графического объекта","Failed to create a graphical object change history object"},
   {"Не удалось добавить объект истории изменений в список","Failed to add change history object to the list"},
   {"Не удалось получить объект истории изменений","Failed to get change history object"},
   {"Не удалось увеличить размер массива","Failed to increase array size"},


Во все объекты-наследники абстрактного стандартного графического объекта, находящиеся в папке \MQL5\Include\DoEasy\Objects\Graph\Standard\, нам нужно внести некоторые изменения (на примере файла GStdArrowBuyObj.mqh).

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

   //--- Конструктор
                     CGStdArrowBuyObj(const long chart_id,const string name) : 
                        CGStdGraphObj(OBJECT_DE_TYPE_GSTD_ARROW_BUY,GRAPH_OBJ_BELONG_NO_PROGRAM,GRAPH_OBJ_SPECIES_ARROWS,chart_id,1,name)
                          {
                           //--- Укажем свойство объекта
                           CGStdGraphObj::SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,ANCHOR_TOP);
                          }

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

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

//+------------------------------------------------------------------+
//| Возвращает истину, если объект поддерживает переданное           |
//| целочисленное свойство, возвращает ложь в противном случае       |
//+------------------------------------------------------------------+
bool CGStdArrowBuyObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property)
  {
   switch((int)property)
     {
      //--- Поддерживаемые свойства
      case GRAPH_OBJ_PROP_ID           :
      case GRAPH_OBJ_PROP_TYPE         :
      case GRAPH_OBJ_PROP_ELEMENT_TYPE : 
      case GRAPH_OBJ_PROP_GROUP        : 
      case GRAPH_OBJ_PROP_BELONG       :
      case GRAPH_OBJ_PROP_CHART_ID     :
      case GRAPH_OBJ_PROP_WND_NUM      :
      case GRAPH_OBJ_PROP_NUM          :
      case GRAPH_OBJ_PROP_CREATETIME   :
      case GRAPH_OBJ_PROP_CHANGE_HISTORY:
      case GRAPH_OBJ_PROP_TIMEFRAMES   :
      case GRAPH_OBJ_PROP_BACK         :
      case GRAPH_OBJ_PROP_ZORDER       :
      case GRAPH_OBJ_PROP_HIDDEN       :
      case GRAPH_OBJ_PROP_SELECTED     :
      case GRAPH_OBJ_PROP_SELECTABLE   :
      case GRAPH_OBJ_PROP_TIME         :
      case GRAPH_OBJ_PROP_COLOR        :
      case GRAPH_OBJ_PROP_STYLE        :
      case GRAPH_OBJ_PROP_WIDTH        :
      case GRAPH_OBJ_PROP_ANCHOR       : return true;
      //--- Остальные свойства не поддерживаются
      //--- По умолчанию false
      default: break;
     }
   return false;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
protected:
   CArrayObj         m_list_events;                      // Список событий объекта
   ENUM_OBJECT       m_type_graph_obj;                   // Тип графического объекта
   ENUM_GRAPH_ELEMENT_TYPE m_type_element;               // Тип графического элемента
   ENUM_GRAPH_OBJ_BELONG m_belong;                       // Принадлежность программе
   ENUM_GRAPH_OBJ_SPECIES m_species;                     // Вид графического объекта
   string            m_name_prefix;                      // Префикс имени объекта
   string            m_name;                             // Имя объекта
   long              m_chart_id;                         // Идентификатор графика объекта
   long              m_object_id;                        // Идентификатор объекта
   long              m_zorder;                           // Приоритет графического объекта на получение события нажатия мышки
   int               m_subwindow;                        // Номер подокна
   int               m_shift_y;                          // Смещение координаты Y для подокна
   int               m_type;                             // Тип объекта
   int               m_timeframes_visible;               // Видимость объекта на таймфреймах (набор флагов)
   int               m_digits;                           // Количество знаков после запятой в котировке символа чарта
   int               m_group;                            // Группа графических объектов
   bool              m_visible;                          // Видимость объекта
   bool              m_back;                             // Флаг "Объект на заднем плане"
   bool              m_selected;                         // Флаг "Выделенность объекта"
   bool              m_selectable;                       // Флаг "Доступность объекта"
   bool              m_hidden;                           // Флаг "Запрет на показ имени графического объекта в списке объектов терминала"
   datetime          m_create_time;                      // Время создания объекта

и методы для установки и возврата значений этих переменных:

public:
//--- Установка значений переменных класса
   void              SetObjectID(const long value)             { this.m_object_id=value;              }
   void              SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong;             }
   void              SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj;           }
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type;   }
   void              SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species;        }
   void              SetGroup(const int group)                 { this.m_group=group;                  }
   void              SetName(const string name)                { this.m_name=name;                    }
   void              SetChartID(const long chart_id)           { this.m_chart_id=chart_id;            }
   void              SetDigits(const int value)                { this.m_digits=value;                 }

...

//--- Возврат значений переменных класса
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)        const { return this.m_type_element;       }
   ENUM_GRAPH_OBJ_BELONG   Belong(void)                  const { return this.m_belong;             }
   ENUM_GRAPH_OBJ_SPECIES  Species(void)                 const { return this.m_species;            }
   ENUM_OBJECT       TypeGraphObject(void)               const { return this.m_type_graph_obj;     }
   datetime          TimeCreate(void)                    const { return this.m_create_time;        }
   string            Name(void)                          const { return this.m_name;               }
   long              ChartID(void)                       const { return this.m_chart_id;           }
   long              ObjectID(void)                      const { return this.m_object_id;          }
   long              Zorder(void)                        const { return this.m_zorder;             }
   int               SubWindow(void)                     const { return this.m_subwindow;          }
   int               ShiftY(void)                        const { return this.m_shift_y;            }
   int               VisibleOnTimeframes(void)           const { return this.m_timeframes_visible; }
   int               Digits(void)                        const { return this.m_digits;             }
   int               Group(void)                         const { return this.m_group;              }
   bool              IsBack(void)                        const { return this.m_back;               }
   bool              IsSelected(void)                    const { return this.m_selected;           }
   bool              IsSelectable(void)                  const { return this.m_selectable;         }
   bool              IsHidden(void)                      const { return this.m_hidden;             }
   bool              IsVisible(void)                     const { return this.m_visible;            }

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

//--- Возвращает описание типа графического (1) объекта, (2) элемента, (3) принадлежности, (4) вида графического объекта
string               TypeGraphObjectDescription(void);
string               TypeElementDescription(void);
string               BelongDescription(void);
string               SpeciesDescription(void);

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

//+------------------------------------------------------------------+
//| Возвращает описание группы графического объекта                  |
//+------------------------------------------------------------------+
string CGBaseObj::SpeciesDescription(void)
  {
   return
     (
      this.Species()==GRAPH_OBJ_SPECIES_LINES      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_LINES)      :
      this.Species()==GRAPH_OBJ_SPECIES_CHANNELS   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_CHANNELS)   :
      this.Species()==GRAPH_OBJ_SPECIES_GANN       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_GANN)       :
      this.Species()==GRAPH_OBJ_SPECIES_FIBO       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_FIBO)       :
      this.Species()==GRAPH_OBJ_SPECIES_ELLIOTT    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_ELLIOTT)    :
      this.Species()==GRAPH_OBJ_SPECIES_SHAPES     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_SHAPES)     :
      this.Species()==GRAPH_OBJ_SPECIES_ARROWS     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_ARROWS)     :
      this.Species()==GRAPH_OBJ_SPECIES_GRAPHICAL  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES_GRAPHICAL)  :
      "Unknown"
     );
  }

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(0),m_visible(false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_"),m_belong(GRAPH_OBJ_BELONG_PROGRAM)
  {
   this.m_list_events.Clear();                  // Очистка списка событий
   this.m_list_events.Sort();                   // Флаг сортированного списка
   this.m_type=OBJECT_DE_TYPE_GBASE;            // Тип объекта
   this.m_type_graph_obj=WRONG_VALUE;           // Тип графического объекта
   this.m_type_element=WRONG_VALUE;             // Тип графического элемента
   this.m_belong=WRONG_VALUE;                   // Принадлежность программе/терминалу
   this.m_group=0;                              // Группа графических объектов
   this.m_name_prefix="";                       // Префикс имени объекта
   this.m_name="";                              // Имя объекта
   this.m_chart_id=0;                           // Идентификатор графика объекта
   this.m_object_id=0;                          // Идентификатор объекта
   this.m_zorder=0;                             // Приоритет графического объекта на получение события нажатия мышки
   this.m_subwindow=0;                          // Номер подокна
   this.m_shift_y=0;                            // Смещение координаты Y для подокна
   this.m_timeframes_visible=OBJ_ALL_PERIODS;   // Видимость объекта на таймфреймах (набор флагов)
   this.m_visible=true;                         // Видимость объекта
   this.m_back=false;                           // Флаг "Объект на заднем плане"
   this.m_selected=false;                       // Флаг "Выделенность объекта"
   this.m_selectable=false;                     // Флаг "Доступность объекта"
   this.m_hidden=true;                          // Флаг "Запрет на показ имени графического объекта в списке объектов терминала"
   this.m_create_time=0;                        // Время создания объекта
  }
//+------------------------------------------------------------------+


Класс памяти стандартных графических объектов

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

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

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

Оба эти класса разместим в уже имеющемся у нас файле класса свойств объекта \MQL5\Include\DoEasy\Services\Properties.mqh.

Подключим к нему файл сервисных функций библиотеки:

//+------------------------------------------------------------------+
//|                                                   Properties.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 "DELib.mqh"
#include "XDimArray.mqh"
//+------------------------------------------------------------------+

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

//--- Возвращает количество (1) целочисленных, (2) вещественных, (3) строковых параметров
   int               TotalLong(void)   const { return this.m_total_int; }
   int               TotalDouble(void) const { return this.m_total_dbl; }
   int               TotalString(void) const { return this.m_total_str; }

//--- Конструктор

Эти методы нам пригодятся для установки количества свойств объектам класса истории изменений.

Сразу же за классом свойств объекта напишем класс снимка изменённых свойств объекта:

//+------------------------------------------------------------------+
//| Класс снимка изменённых свойств объекта                          |
//+------------------------------------------------------------------+
class CChangedProps : public CDataPropObj
  {
private:
  long               m_time_change;                         // Время модификации свойств
  string             m_symbol;                              // Символ окна графика
  int                m_digits;                              // Digits символа
public:
//--- Устанавливает (1) значение времени изменения, (2) символ, (3) Digits символа
   void              SetTimeChanged(const long time)        { this.m_time_change=time;                   }
   void              SetSymbol(const string symbol)         { this.m_symbol=symbol;                      }
   void              SetDigits(const int digits)            { this.m_digits=digits;                      }
//--- Возвращает (1) значение времени изменения, (2) время изменения, (3) символ, (4) Digits символа
   long              TimeChanged(void)                const { return this.m_time_change;                 }
   string            TimeChangedToString(void)        const { return TimeMSCtoString(this.m_time_change);}
   string            Symbol(void)                     const { return this.m_symbol;                      }
   int               Digits(void)                     const { return this.m_digits;                      }
//--- Конструктор/Деструктор
                     CChangedProps (const int prop_total_integer,const int prop_total_double,const int prop_total_string,const long time_changed) : 
                        CDataPropObj(prop_total_integer,prop_total_double,prop_total_string) { this.m_time_change=time_changed;}
                    ~CChangedProps (void){;}
  };
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Класс истории изменений свойств графического объекта             |
//+------------------------------------------------------------------+
class CChangeHistory
  {
private:
  CArrayObj          m_list_changes;                  // Список истории изменения свойств
public:
//--- Возвращает (1) указатель на объект истории изменений свойств, (2) количество изменений
   CChangedProps    *GetChangedPropsObj(const string source,const int index)
                       {
                        CChangedProps *props=this.m_list_changes.At(index<0 ? 0 : index);
                        if(props==NULL)
                           CMessage::ToLog(source,MSG_GRAPH_OBJ_FAILED_GET_HIST_OBJ);
                        return props;
                       }
   int               TotalChanges(void)               { return this.m_list_changes.Total();  }
//--- Создаёт новый объект истории изменений свойств графического объекта
   bool              CreateNewElement(CDataPropObj *element,const long time_change)
                       {
                        //--- Создаём новый объект снимка свойств графического объекта
                        CChangedProps *obj=new CChangedProps(element.TotalLong(),element.TotalDouble(),element.TotalString(),time_change);
                        //--- Если объект создать не удалось - сообщаем об этом и возвращаем false
                        if(obj==NULL)
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_NEW_HIST_OBJ);
                           return false;
                          }
                        //--- Если объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем false
                        if(!this.m_list_changes.Add(obj))
                          {
                           CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_HIST_LIST);
                           delete obj;
                           return false;
                          }
                        //--- Получем идентификатор графика, на котором расположен графический объект
                        long chart_id=element.GetLong(GRAPH_OBJ_PROP_CHART_ID,0);
                        //--- Устанавливаем объекту снимка свойств графического объекта символ графика и Digits символа
                        obj.SetSymbol(::ChartSymbol(chart_id));
                        obj.SetDigits((int)::SymbolInfoInteger(obj.Symbol(),SYMBOL_DIGITS));
                        //--- Копируем все целочисленные свойства
                        for(int i=0;i<element.TotalLong();i++)
                          {
                           int total=element.Long().Size(i);
                           if(obj.SetSizeRange(i,total))
                             {
                              for(int r=0;r<total;r++)
                                 obj.Long().Set(i,r,element.Long().Get(i,r));
                             }
                           else
                              CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE);
                          }
                        //--- Копируем все вещественные свойства
                        for(int i=0;i<element.TotalDouble();i++)
                          {
                           int total=element.Double().Size(i);
                           if(obj.Double().SetSizeRange(i,total))
                             {
                              for(int r=0;r<total;r++)
                                 obj.Double().Set(i,r,element.Double().Get(i,r));
                             }
                           else
                              CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE);
                          }
                        //--- Копируем все строковые свойства
                        for(int i=0;i<element.TotalString();i++)
                          {
                           int total=element.String().Size(i);
                           if(obj.String().SetSizeRange(i,total))
                             {
                              for(int r=0;r<total;r++)
                                 obj.String().Set(i,r,element.String().Get(i,r));
                             }
                           else
                              CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE);
                          }
                        return true;
                       }
//--- Возвращает по индексу в списке объекта истории изменений графического объекта
//--- значение из указанного индекса (1) long-, (2) double-, (3) string-массива
   long              GetLong(const int time_index,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const int index)
                       {
                        CChangedProps *properties=this.GetChangedPropsObj(DFUN,time_index);
                        if(properties==NULL)
                           return 0;
                        return properties.GetLong(prop,index);
                       }
   double            GetDouble(const int time_index,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const int index)
                       {
                        CChangedProps *properties=this.GetChangedPropsObj(DFUN,time_index);
                        if(properties==NULL)
                           return 0;
                        return properties.GetDouble(prop,index);
                       }
   string            GetString(const int time_index,const ENUM_GRAPH_OBJ_PROP_STRING prop,const int index)
                       {
                        CChangedProps *properties=this.GetChangedPropsObj(DFUN,time_index);
                        if(properties==NULL)
                           return "";
                        return properties.GetString(prop,index);
                       }
//--- Конструктор/Деструктор
                     CChangeHistory(void){;}
                    ~CChangeHistory(void){;}
  };
//+------------------------------------------------------------------+

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| Класс данных текущих и прошлых свойств                           |
//+------------------------------------------------------------------+
class CProperties : public CObject
  {
private:
   CArrayObj         m_list;  // Список для хранения указателей на объекты свойств
public:
   CDataPropObj     *Curr;    // Указатель на объект текущих свойств
   CDataPropObj     *Prev;    // Указатель на объект прошлых свойств
   CChangeHistory   *History; // Указатель на объект истории изменений
//--- Устанавливает размера массива (size) в указанном измерении (range)
   bool              SetSizeRange(const int range,const int size)
                       {
                        return(this.Curr.SetSizeRange(range,size) && this.Prev.SetSizeRange(range,size) ? true : false);
                       }
//--- Возвращает размер указанного массива (1) текущих, (2) прошлых данных первого измерения
   int               CurrSize(const int range)  const { return Curr.Size(range); }
   int               PrevSize(const int range)  const { return Prev.Size(range); }
//--- Копирование текущих данных в прошлые
   void              CurrentToPrevious(void)
                       {
                        //--- Копируем все целочисленные свойства
                        for(int i=0;i<this.Curr.Long().Total();i++)
                           for(int r=0;r<this.Curr.Long().Size(i);r++)
                              this.Prev.Long().Set(i,r,this.Curr.Long().Get(i,r));
                        //--- Копируем все вещественные свойства
                        for(int i=0;i<this.Curr.Double().Total();i++)
                           for(int r=0;r<this.Curr.Double().Size(i);r++)
                              this.Prev.Double().Set(i,r,this.Curr.Double().Get(i,r));
                        //--- Копируем все строковые свойства
                        for(int i=0;i<this.Curr.String().Total();i++)
                           for(int r=0;r<this.Curr.String().Size(i);r++)
                              this.Prev.String().Set(i,r,this.Curr.String().Get(i,r));
                       }
//--- Возвращает количество изменений графического объекта с момента началаих записи
   int               TotalChanges(void)   { return this.History.TotalChanges();  }
                       
//--- Конструктор
                     CProperties(const int prop_int_total,const int prop_double_total,const int prop_string_total)
                       {
                        //--- Создаём новые объекты текущих и прошлых свойств
                        this.Curr=new CDataPropObj(prop_int_total,prop_double_total,prop_string_total);
                        this.Prev=new CDataPropObj(prop_int_total,prop_double_total,prop_string_total);
                        //--- Добавляем созданные объекты в список
                        this.m_list.Add(this.Curr);
                        this.m_list.Add(this.Prev);
                        //--- Создаём объект истории изменений
                        this.History=new CChangeHistory();
                       }
//--- Деструктор
                    ~CProperties()
                       {
                        this.m_list.Clear();
                        this.m_list.Shutdown();
                        if(this.History!=NULL)
                           delete this.History;
                       }
  };
//+------------------------------------------------------------------+

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

Чтобы мы могли взаимодействовать с историей изменения графического объекта, нам нужно в классе абстрактнго стандартного графического объекта в файле \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh добавить метод, возвращающий указатель на список истории изменений, находящийся в свойствах объекта:

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)     { this.Prop.Curr.SetLong(property,index,value);    }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)    { this.Prop.Curr.SetDouble(property,index,value);  }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)    { this.Prop.Curr.SetString(property,index,value);  }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)          const { return this.Prop.Curr.GetLong(property,index);   }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)           const { return this.Prop.Curr.GetDouble(property,index); }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index)           const { return this.Prop.Curr.GetString(property,index); }
//--- Устанавливает прошлое (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.SetLong(property,index,value);    }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.SetDouble(property,index,value);  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.SetString(property,index,value);  }
//--- Возвращает из массива прошлых свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)      const { return this.Prop.Prev.GetLong(property,index);   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)       const { return this.Prop.Prev.GetDouble(property,index); }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index)       const { return this.Prop.Prev.GetString(property,index); }
   
//--- Возвращает (1) себя, (2) свойства, (3) историю изменений
   CGStdGraphObj    *GetObject(void)                                       { return &this;            }
   CProperties      *Properties(void)                                      { return this.Prop;        }
   CChangeHistory   *History(void)                                         { return this.Prop.History;}
   
//--- Возвращает флаг поддержания объектом данного свойства

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

//--- Конструктор по умолчанию
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; }
//--- Деструктор
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                       }
protected:
//--- Защищённый параметрический конструктор
                     CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                                   const ENUM_GRAPH_OBJ_BELONG belong,
                                   const ENUM_GRAPH_OBJ_SPECIES species,
                                   const long chart_id, const int pivots,
                                   const string name);

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

public:
//+-------------------------------------------------------------------+ 
//|Методы упрощённого доступа и установки свойств графического объекта|
//+-------------------------------------------------------------------+
//--- Номер объекта в списке
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM,0);                            }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,0,number);                                 }
//--- Флаг хранения истории изменений
   bool              AllowChangeHistory(void)      const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0);                }
   void              SetAllowChangeMemory(const bool flag){ this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,flag);                       }
//--- Идентификатор объекта
   long              ObjectID(void)                const { return this.GetProperty(GRAPH_OBJ_PROP_ID,0);                                  }
   void              SetObjectID(const long obj_id)
                       {
                        CGBaseObj::SetObjectID(obj_id);
                        this.SetProperty(GRAPH_OBJ_PROP_ID,0,obj_id);
                        this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,0,obj_id);
                       }
//--- Тип графичесеого объекта
   ENUM_OBJECT       GraphObjectType(void)         const { return (ENUM_OBJECT)this.GetProperty(GRAPH_OBJ_PROP_TYPE,0);                   }
   void              SetGraphObjectType(const ENUM_OBJECT obj_type)
                       {
                        CGBaseObj::SetTypeGraphObject(obj_type);
                        this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,obj_type);
                       }
//---Тип графического элемента
   ENUM_GRAPH_ELEMENT_TYPE GraphElementType(void)  const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0);}
   void              SetGraphElementType(const ENUM_GRAPH_ELEMENT_TYPE elm_type)
                       {
                        CGBaseObj::SetTypeElement(elm_type);
                        this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,elm_type);
                       }
//--- Принадлежность графического объекта
   ENUM_GRAPH_OBJ_BELONG Belong(void)              const { return (ENUM_GRAPH_OBJ_BELONG)this.GetProperty(GRAPH_OBJ_PROP_BELONG,0);       }
   void              SetBelong(const ENUM_GRAPH_OBJ_BELONG belong)
                       {
                        CGBaseObj::SetBelong(belong);
                        this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,belong);
                       }
//--- Группа графических объектов
   int               Group(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_GROUP,0);                          }
   void              SetGroup(const int group)
                       {
                        CGBaseObj::SetGroup(group);
                        this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,group);
                       }
//--- Идентификатор графика

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

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

//--- Перезаписывает все свойства графического объекта
   void              PropertiesRefresh(void);
//--- Проверяет изменения свойств объекта
   void              PropertiesCheckChanged(void);
//--- Копирует текущие данные в прошлые
   void              PropertiesCopyToPrevData(void);
//--- Возвращает (1) количество изменений свойств в истории, указанные (2) по индексу свойства, (3) последнего, (4) первого изменённого объекта
   int               HistoryChangesTotal(void)                          { return this.History().TotalChanges();                                          }
   CChangedProps    *GetHistoryChangedProps(const string source,const int index) { return this.History().GetChangedPropsObj(source,index);               }
   CChangedProps    *GetHistoryChangedPropsLast(const string source)    { return this.History().GetChangedPropsObj(source,this.HistoryChangesTotal()-1); }
   CChangedProps    *GetHistoryChangedPropsFirst(const string source)   { return this.History().GetChangedPropsObj(source,0);                            }
//--- Возвращает из указанного по индексу в списке объектов истории изменений
//--- указанное значение (1) целочисленного, (2) вещественного и (3) строкового свойства
   long              HistoryChangedObjGetLong(const int time_index,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const int prop_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.GetLong(prop,prop_index) : 0);
                       }
   double            HistoryChangedObjGetDouble(const int time_index,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const int prop_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.GetDouble(prop,prop_index) : 0);
                       }
   string            HistoryChangedObjGetString(const int time_index,const ENUM_GRAPH_OBJ_PROP_STRING prop,const int prop_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.GetString(prop,prop_index) : "ERROR");
                       }
//--- Возвращает (1) символ, (2) Digits символа, (3) время изменения объекта истории изменений
   string            HistoryChangedObjSymbol(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.Symbol() : "ERROR");
                       }
   int               HistoryChangedObjDigits(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.Digits() : 0);
                       }
   long              HistoryChangedObjTimeChanged(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.TimeChanged() : 0);
                       }
   string            HistoryChangedObjTimeChangedToString(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        return(obj!=NULL ? obj.TimeChangedToString() : "ERROR");
                       }
//--- Устанавливает параметры объекта из указанного снимка истории
   bool              SetPropertiesFromHistory(const int time_index)
                       {
                        CChangedProps *obj=this.GetHistoryChangedProps(DFUN,time_index);
                        if(obj==NULL)
                           return false;
                        int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
                        for(int i=begin; i<end; i++)
                          {
                           ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
                           for(int j=0;j<this.Prop.CurrSize(prop);j++)
                              if(this.GetProperty(prop,j)!=obj.GetLong(prop,j))
                                 this.SetHistoryINT(prop,obj.GetLong(prop,j),j);
                          }
                        begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
                        for(int i=begin; i<end; i++)
                          {
                           ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
                           for(int j=0;j<this.Prop.CurrSize(prop);j++)
                             {
                              if(this.GetProperty(prop,j)!=obj.GetDouble(prop,j))
                                 this.SetHistoryDBL(prop,obj.GetDouble(prop,j),j);
                             }
                          }
                        begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
                        for(int i=begin; i<end; i++)
                          {
                           ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
                           for(int j=0;j<this.Prop.CurrSize(prop);j++)
                              if(this.GetProperty(prop,j)!=obj.GetString(prop,j))
                                 this.SetHistorySTR(prop,obj.GetString(prop,j),j);
                          }
                        return true;
                       }

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

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

И добавим методы в приватную секцию класса:

private:
//--- Получает и сохраняет (1) целочисленные, (2) вещественные и (3) строковые свойства
   void              GetAndSaveINT(void);
   void              GetAndSaveDBL(void);
   void              GetAndSaveSTR(void);
   
//--- Создаёт новый объект истории изменений графического объекта
   bool              CreateNewChangeHistoryObj(const bool first)
                       {
                        bool res=true;
                        if(first)
                           res &=this.History().CreateNewElement(this.Prop.Prev,this.GetMarketWatchTime());
                        res &=this.History().CreateNewElement(this.Prop.Curr,this.GetMarketWatchTime());
                        if(!res)
                           CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_SNAPSHOT);
                        return res;
                       }
//--- Устанавливает графическому объекту (1) целочисленные, (2) вещественные и (3) строковые значения свойств из истории изменений
   void              SetHistoryINT(const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetHistoryDBL(const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetHistorySTR(const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);
//--- Возвращает время последнего тика символа
   long              GetSymbolTime(const string symbol)
                       {
                        MqlTick tick;
                        return(::SymbolInfoTick(symbol,tick) ? tick.time_msc : 0);
                       }
//--- Возвращает время последнего тика Обзора рынка
   long              GetMarketWatchTime(void)
                       {
                        long res=0;
                        for(int i=::SymbolsTotal(true)-1;i>WRONG_VALUE;i--)
                          {
                           const long time=this.GetSymbolTime(::SymbolName(i,true));
                           if(time>res)
                              res=time;
                          }
                        return res;
                       }   
  };
//+------------------------------------------------------------------+

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

//--- Создаёт новый объект истории изменений графического объекта
   bool              CreateNewChangeHistoryObj(const bool first)
                       {
                        bool res=true;
                        if(first)
                           res &=this.History().CreateNewElement(this.Prop.Prev,this.GetMarketWatchTime());
                        res &=this.History().CreateNewElement(this.Prop.Curr,this.GetMarketWatchTime());
                        if(!res)
                           CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_SNAPSHOT);
                        return res;
                       }

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

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

//--- Возвращает время последнего тика Обзора рынка
   long              GetMarketWatchTime(void)
                       {
                        long res=0;
                        for(int i=::SymbolsTotal(true)-1;i>WRONG_VALUE;i--)
                          {
                           const long time=this.GetSymbolTime(::SymbolName(i,true));
                           if(time>res)
                              res=time;
                          }
                        return res;
                       }   

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

//+------------------------------------------------------------------+
//| Защищённый параметрический конструктор                           |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_SPECIES species,
                             const long chart_id,const int pivots,
                             const string name)
  {
   //--- Создаём объект свойств со значениями по умолчанию
   this.Prop=new CProperties(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL);
   
//--- Устанавливаем количество опорных точек и уровней объекта
   this.m_pivots=pivots;
   int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS);

//--- Устанавливаем размерности массивов свойств в соответствии с количеством опорных точек и уровней
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2);
   
//--- Устанавливаем объекту (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::SetSpecies(species);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Сохранение целочисленных свойств, присущих всем графическим объектам, но не имеющиеся у графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID());                // Идентификатор графика
   this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());               // Номер подокна графика
   this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject());            // Тип графического объекта (ENUM_OBJECT)
   this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement());   // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
   this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong());                   // Принадлежность графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_SPECIES,0,CGBaseObj::Species());                 // Вид графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,0);                                      // Группа графических объектов
   this.SetProperty(GRAPH_OBJ_PROP_ID,0,0);                                         // Идентификатор объекта
   this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0);                                        // Номер объекта в списке
   this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,false);                         // Флаг хранения истории изменений
   
//--- Сохранение свойств, присущих всем графическим объектам, имеющимся у графического объекта
   this.PropertiesRefresh();
   
//--- Сохранение базовых свойств в родительском объекте
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME,0);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);

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


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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства объекта              |
//+------------------------------------------------------------------+
string CGStdGraphObj::GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER property)
  {
   return
     (
      property==GRAPH_OBJ_PROP_ID         ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_TYPE       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.TypeDescription()
         )  :
      property==GRAPH_OBJ_PROP_ELEMENT_TYPE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ELEMENT_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+CGBaseObj::TypeElementDescription()
         )  :
      property==GRAPH_OBJ_PROP_SPECIES    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SPECIES)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+CGBaseObj::SpeciesDescription()
         )  :
      property==GRAPH_OBJ_PROP_GROUP    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(CGBaseObj::Group()>0 ? (string)this.GetProperty(property,0) : CMessage::Text(MSG_LIB_PROP_EMPTY))
         )  :
      property==GRAPH_OBJ_PROP_BELONG     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BELONG)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+CGBaseObj::BelongDescription()
         )  :
      property==GRAPH_OBJ_PROP_CHART_ID   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_WND_NUM    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_WND_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_CHANGE_HISTORY ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHANGE_MEMORY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_CREATETIME   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CREATETIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property,0),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==GRAPH_OBJ_PROP_TIMEFRAMES ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_TIMEFRAMES)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.VisibleOnTimeframeDescription()
         )  :
      property==GRAPH_OBJ_PROP_BACK       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BACK)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsBack() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_ZORDER     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_HIDDEN     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_HIDDEN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsHidden() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_SELECTED   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SELECTED)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsSelected() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_SELECTABLE ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_SELECTABLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IsSelectable() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_NUM        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_TIME       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_TIME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+"\n"+this.TimesDescription()
         )  :
      property==GRAPH_OBJ_PROP_COLOR      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property,0),true)
         )  :
      property==GRAPH_OBJ_PROP_STYLE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_STYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+LineStyleDescription((ENUM_LINE_STYLE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_WIDTH     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_WIDTH)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_FILL       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_FILL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_READONLY   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_READONLY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_LEVELS     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_LEVELCOLOR ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELCOLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ":\n"+this.LevelsColorDescription()
         )  :
      property==GRAPH_OBJ_PROP_LEVELSTYLE ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELSTYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ":\n"+this.LevelsStyleDescription()
         )  :
      property==GRAPH_OBJ_PROP_LEVELWIDTH ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELWIDTH)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ":\n"+this.LevelsWidthDescription()
         )  :
      property==GRAPH_OBJ_PROP_ALIGN      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ALIGN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignModeDescription((ENUM_ALIGN_MODE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_FONTSIZE   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_FONTSIZE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_RAY_LEFT   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_RAY_LEFT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_RAY_RIGHT  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_RAY_RIGHT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_RAY        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_RAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_ELLIPSE    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ELLIPSE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_ARROWCODE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ARROWCODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_ANCHOR     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ANCHOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.AnchorDescription()
         )  :
      property==GRAPH_OBJ_PROP_XDISTANCE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_XDISTANCE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_YDISTANCE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_YDISTANCE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_DIRECTION  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_DIRECTION)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+GannDirectDescription((ENUM_GANN_DIRECTION)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_DEGREE     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_DEGREE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ElliotWaveDegreeDescription((ENUM_ELLIOT_WAVE_DEGREE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_DRAWLINES  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_DRAWLINES)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_STATE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_STATE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_BUTTON_STATE_PRESSED) : CMessage::Text(MSG_LIB_TEXT_BUTTON_STATE_DEPRESSED))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID  ?  CMessage::Text(MSG_CHART_OBJ_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_PERIOD ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_PERIOD)+
         (!this.SupportProperty(property)       ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE)+
         (!this.SupportProperty(property)             ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE)+
         (!this.SupportProperty(property)             ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property,0) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE)+
         (!this.SupportProperty(property)             ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_XSIZE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_XSIZE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_YSIZE      ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_YSIZE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_XOFFSET    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_XOFFSET)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_YOFFSET    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_YOFFSET)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property,0)
         )  :
      property==GRAPH_OBJ_PROP_BGCOLOR    ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BGCOLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property,0),true)
         )  :
      property==GRAPH_OBJ_PROP_CORNER     ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_CORNER)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+BaseCornerDescription((ENUM_BASE_CORNER)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_BORDER_TYPE   ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BORDER_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+BorderTypeDescription((ENUM_BORDER_TYPE)this.GetProperty(property,0))
         )  :
      property==GRAPH_OBJ_PROP_BORDER_COLOR  ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_BORDER_COLOR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property,0),true)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Проверяет изменения свойств объекта                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   CGBaseObj::ClearEventsList();
   bool changed=false;
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME)
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      if(this.AllowChangeHistory())
        {
         int total=HistoryChangesTotal();
         if(this.CreateNewChangeHistoryObj(total<1))
            ::Print
              (
               DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total),
               ": ",this.HistoryChangedObjTimeChangedToString(total-1)
              );
        }
      this.PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

Здесь: получаем количество изменений графического объекта и передаём их в метод создания нового объекта-снимка свойств объекта в виде bool-флага (если total меньше 1, то передаваемое значение будет true, что означает, что это первое изменение графического объекта). Если объект создать и добавить в список изменений удалось, то об этом выводится сообщение в журнал с указанием номера изменения. Если это первое изменение, то в сообщении указывается "0-1", что означает, что созданы сразу два объекта (0 — состояние графического объекта до изменения его свойств, 1 — его текущее состояние).

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

//+------------------------------------------------------------------+
//| Устанавливает графическому объекту целочисленные                 |
//| значения свойств из истории изменений                            |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetHistoryINT(const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier)
  {
   switch(prop)
     {
      case GRAPH_OBJ_PROP_TIMEFRAMES            :  this.SetVisibleOnTimeframes((int)value,false);        break;  // Видимость объекта на таймфреймах
      case GRAPH_OBJ_PROP_BACK                  :  this.SetFlagBack(value,false);                        break;   // Объект на заднем плане
      case GRAPH_OBJ_PROP_ZORDER                :  this.SetZorder(value,false);                          break;   // Приоритет графического объекта на получение события нажатия мышки на графике
      case GRAPH_OBJ_PROP_HIDDEN                :  this.SetFlagHidden(value,false);                      break;   // Запрет на показ имени графического объекта в списке объектов терминала
      case GRAPH_OBJ_PROP_SELECTED              :  this.SetFlagSelected(value,false);                    break;   // Выделенность объекта
      case GRAPH_OBJ_PROP_SELECTABLE            :  this.SetFlagSelectable(value,false);                  break;   // Доступность объекта
      case GRAPH_OBJ_PROP_TIME                  :  this.SetTime(value,modifier);                         break;   // Координата времени
      case GRAPH_OBJ_PROP_COLOR                 :  this.SetColor((color)value);                          break;   // Цвет
      case GRAPH_OBJ_PROP_STYLE                 :  this.SetStyle((ENUM_LINE_STYLE)value);                break;   // Стиль
      case GRAPH_OBJ_PROP_WIDTH                 :  this.SetWidth((int)value);                            break;   // Толщина линии
      case GRAPH_OBJ_PROP_FILL                  :  this.SetFlagFill(value);                              break;   // Заливка объекта цветом
      case GRAPH_OBJ_PROP_READONLY              :  this.SetFlagReadOnly(value);                          break;   // Возможность редактирования текста в объекте Edit
      case GRAPH_OBJ_PROP_LEVELS                :  this.SetLevels((int)value);                           break;   // Количество уровней
      case GRAPH_OBJ_PROP_LEVELCOLOR            :  this.SetLevelColor((color)value,modifier);            break;   // Цвет линии-уровня
      case GRAPH_OBJ_PROP_LEVELSTYLE            :  this.SetLevelStyle((ENUM_LINE_STYLE)value,modifier);  break;   // Стиль линии-уровня
      case GRAPH_OBJ_PROP_LEVELWIDTH            :  this.SetLevelWidth((int)value,modifier);              break;   // Толщина линии-уровня
      case GRAPH_OBJ_PROP_ALIGN                 :  this.SetAlign((ENUM_ALIGN_MODE)value);                break;   // Горизонтальное выравнивание текста в объекте "Поле ввода" (OBJ_EDIT)
      case GRAPH_OBJ_PROP_FONTSIZE              :  this.SetFontSize((int)value);                         break;   // Размер шрифта
      case GRAPH_OBJ_PROP_RAY_LEFT              :  this.SetFlagRayLeft(value);                           break;   // Луч продолжается влево
      case GRAPH_OBJ_PROP_RAY_RIGHT             :  this.SetFlagRayRight(value);                          break;   // Луч продолжается вправо
      case GRAPH_OBJ_PROP_RAY                   :  this.SetFlagRay(value);                               break;   // Вертикальная линия продолжается на все окна графика
      case GRAPH_OBJ_PROP_ELLIPSE               :  this.SetFlagEllipse(value);                           break;   // Отображение полного эллипса для объекта "Дуги Фибоначчи"
      case GRAPH_OBJ_PROP_ARROWCODE             :  this.SetArrowCode((uchar)value);                      break;   // Код стрелки для объекта "Стрелка"
      case GRAPH_OBJ_PROP_ANCHOR                :  this.SetAnchor((int)value);                           break;   // Положение точки привязки графического объекта
      case GRAPH_OBJ_PROP_XDISTANCE             :  this.SetXDistance((int)value);                        break;   // Дистанция в пикселях по оси X от угла привязки
      case GRAPH_OBJ_PROP_YDISTANCE             :  this.SetYDistance((int)value);                        break;   // Дистанция в пикселях по оси Y от угла привязки
      case GRAPH_OBJ_PROP_DIRECTION             :  this.SetDirection((ENUM_GANN_DIRECTION)value);        break;   // Тренд объекта Ганна
      case GRAPH_OBJ_PROP_DEGREE                :  this.SetDegree((ENUM_ELLIOT_WAVE_DEGREE)value);       break;   // Уровень волновой разметки Эллиотта
      case GRAPH_OBJ_PROP_DRAWLINES             :  this.SetFlagDrawLines(value);                         break;   // Отображение линий для волновой разметки Эллиотта
      case GRAPH_OBJ_PROP_STATE                 :  this.SetFlagState(value);                             break;   // Состояние кнопки (Нажата/Отжата)
      case GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID    :  this.SetChartObjChartID(value);                       break;   // Идентификатор объекта "График" (OBJ_CHART)
      case GRAPH_OBJ_PROP_CHART_OBJ_PERIOD      :  this.SetChartObjPeriod((ENUM_TIMEFRAMES)value);       break;   // Период для объекта "График"
      case GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE  :  this.SetChartObjChartScale((int)value);               break;   // Признак отображения шкалы времени для объекта "График"
      case GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE :  this.SetFlagChartObjPriceScale(value);                break;   // Признак отображения ценовой шкалы для объекта "График"
      case GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE :  this.SetFlagChartObjDateScale(value);                 break;   // Масштаб для объекта "График"
      case GRAPH_OBJ_PROP_XSIZE                 :  this.SetXSize((int)value);                            break;   // Ширина объекта по оси X в пикселях
      case GRAPH_OBJ_PROP_YSIZE                 :  this.SetYSize((int)value);                            break;   // Высота объекта по оси Y в пикселях
      case GRAPH_OBJ_PROP_XOFFSET               :  this.SetXOffset((int)value);                          break;   // X-координата левого верхнего угла прямоугольной области видимости
      case GRAPH_OBJ_PROP_YOFFSET               :  this.SetYOffset((int)value);                          break;   // Y-координата левого верхнего угла прямоугольной области видимости
      case GRAPH_OBJ_PROP_BGCOLOR               :  this.SetBGColor((color)value);                        break;   // Цвет фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
      case GRAPH_OBJ_PROP_CORNER                :  this.SetCorner((ENUM_BASE_CORNER)value);              break;   // Угол графика для привязки графического объекта
      case GRAPH_OBJ_PROP_BORDER_TYPE           :  this.SetBorderType((ENUM_BORDER_TYPE)value);          break;   // Тип рамки для объекта "Прямоугольная рамка"
      case GRAPH_OBJ_PROP_BORDER_COLOR          :  this.SetBorderColor((color)value);                    break;   // Цвет рамки для объекта OBJ_EDIT и OBJ_BUTTON
      case GRAPH_OBJ_PROP_ID                    :  // Идентификатор объекта
      case GRAPH_OBJ_PROP_TYPE                  :  // Тип графичесеого объекта (ENUM_OBJECT)
      case GRAPH_OBJ_PROP_ELEMENT_TYPE          :  // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
      case GRAPH_OBJ_PROP_SPECIES               :  // Вид графического объекта (ENUM_GRAPH_OBJ_SPECIES)
      case GRAPH_OBJ_PROP_GROUP                 :  // Группа графических объектов
      case GRAPH_OBJ_PROP_BELONG                :  // Принадлежность графического объекта
      case GRAPH_OBJ_PROP_CHART_ID              :  // Идентификатор графика
      case GRAPH_OBJ_PROP_WND_NUM               :  // Номер подокна графика
      case GRAPH_OBJ_PROP_NUM                   :  // Номер объекта в списке
      case GRAPH_OBJ_PROP_CHANGE_HISTORY        :  // Флаг хранения истории изменений
      case GRAPH_OBJ_PROP_CREATETIME            :  // Время создания объекта
      default  : break;
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает графическому объекту вещественные                  |
//| значения свойств из истории изменений                            |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetHistoryDBL(const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier)
  {
   switch(prop)
     {
      case GRAPH_OBJ_PROP_PRICE                 : this.SetPrice(value,modifier);          break;   // Координата цены
      case GRAPH_OBJ_PROP_LEVELVALUE            : this.SetLevelValue(value,modifier);     break;   // Значение уровня
      case GRAPH_OBJ_PROP_SCALE                 : this.SetScale(value);                   break;   // Масштаб (свойство объектов Ганна и объекта "Дуги Фибоначчи")
      case GRAPH_OBJ_PROP_ANGLE                 : this.SetAngle(value);                   break;   // Угол
      case GRAPH_OBJ_PROP_DEVIATION             : this.SetDeviation(value);               break;   // Отклонение для канала стандартного отклонения
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает графическому объекту строковые                     |
//| значения свойств из истории изменений                            |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetHistorySTR(const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier)
  {
   switch(prop)
     {
      case GRAPH_OBJ_PROP_TEXT                  : this.SetText(value);                    break;   // Описание объекта (текст, содержащийся в объекте)
      case GRAPH_OBJ_PROP_TOOLTIP               : this.SetTooltip(value);                 break;   // Текст всплывающей подсказки
      case GRAPH_OBJ_PROP_LEVELTEXT             : this.SetLevelText(value,modifier);      break;   // Описание уровня
      case GRAPH_OBJ_PROP_FONT                  : this.SetFont(value);                    break;   // Шрифт
      case GRAPH_OBJ_PROP_BMPFILE               : this.SetBMPFile(value,modifier);        break;   // Имя BMP-файла для объекта "Графическая метка"
      case GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL      : this.SetChartObjSymbol(value);          break;   // Символ для объекта "График"
      case GRAPH_OBJ_PROP_NAME                  : // Имя объекта
      default :  break;
     }
  }
//+------------------------------------------------------------------+

Здесь: в зависимости от переданного в метод свойства, в операторе-переключателе switch выбирается соответствующий метод, устанавливающий значение этому свойству объекта как в объект-класса, так и в графический объект. Те свойства, которые устанавливать не нужно для объекта истории изменения свойств, не имеют своего обработчика case, поэтому выполнение кода доходит до метки default, где и завершается оператором break.

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

//--- Возвращает (1) последний удалённый графический объект, (2) размер массива свойств графического объекта
   CGStdGraphObj    *GetLastDeletedGraphObj(void)                 const { return this.m_list_deleted_obj.At(this.m_list_deleted_obj.Total()-1); }
   int               GetSizeProperty(const string name,const long chart_id,const int prop)
                       {
                        CGStdGraphObj *obj=this.GetStdGraphObject(name,chart_id);
                        return(obj!=NULL ? obj.Properties().CurrSize(prop) : 0);
                       }
//--- Возвращает список объектов по идентификатору графика и группе
   CArrayObj        *GetListStdGraphObjByGroup(const long chart_id,const int group)
                       {
                        CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
                        return CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,group,EQUAL);
                       }
   
//--- Конструктор

Метод получает список всех графических объектов по идентификатору графика, и из полученного списка возвращает список объектов с указанным значение группы.

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

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

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

//--- Возвращает (1) коллекцию графических объектов, список (2) существующих, (3) удалённых графических объектов
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects;                       }
   CArrayObj           *GetListStdGraphObj(void)                        { return this.m_graph_objects.GetListGraphObj();      }
   CArrayObj           *GetListDeletedObj(void)                         { return this.m_graph_objects.GetListDeletedObj();    }
//--- Возвращает (1) количество удалённых графических объектов, (2) размер массива свойства
   int                  TotalDeletedGraphObjects(void)                  { return this.GetListDeletedObj().Total();            }
   int                  GraphGetSizeProperty(const string name,const long chart_id,const int prop)
                          {
                           return this.m_graph_objects.GetSizeProperty(name,chart_id,prop);
                          }
//--- Возвращает класс объекта стандартного графического объекта по имени и идентификатору графика
   CGStdGraphObj       *GraphGetStdGraphObject(const string name,const long chart_id)
                          {
                           return this.m_graph_objects.GetStdGraphObject(name,chart_id);
                          }
   
//--- Заполняет массив идентификаторами графиков, открытых в терминале

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

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


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

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

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

Для управления просмотром истории изменений, зададим клавиши на клавиатуре.

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

Зададим для указанных кнопок макроподстановки:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart92.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 <DoEasy\Engine.mqh>
//--- defines
#define        FORMS_TOTAL (4)   // Количество создаваемых форм
#define        START_X     (4)   // Начальная координата X фигуры
#define        START_Y     (4)   // Начальная координата Y фигуры
#define KEY_LEFT           (188) // Влево
#define KEY_RIGHT          (190) // Вправо
#define KEY_ORIGIN         (191) // Изначальные свойства
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CEngine        engine;
CArrayObj      list_forms;  
color          array_clr[];
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| 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();
        }
     }
   */
   //--- Если нажата клавиша на клавиатуре
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- Объявим индекс текущего объекта истории изменений графического объекта
      static int index=0;
      //--- Получаем список всех графических объектов с указанным номеромгруппы (1)
      CArrayObj *list=engine.GetListStdGraphObj();
      list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,1,EQUAL);
      if(list==NULL || list.Total()==0)
         return;
      //--- Если нажата клавиша "/" (EN-раскладка)
      if(lparam==KEY_ORIGIN)
        {
         //--- Указываем индекс 0 в списке для объекта истории изменений графического объекта
         index=0;
         //--- В цикле по количеству объектов на графике с группой 1
         for(int i=0;i<list.Total();i++)
           {
            //--- Получаем очередной объект из списка и устанавливаем ему свойства (начальные) из истории изменений
            CGStdGraphObj *obj=list.At(i);
            if(obj==NULL)
               continue;
            obj.SetPropertiesFromHistory(index);
           }
        }
      //--- Если нажата клавиша "." (EN-раскладка)
      if(lparam==KEY_RIGHT)
        {
         //--- Объявляем переменные для поиска максимального количества изменений всех графических объектов с группой 1
         int change_max=0, changes_total=0;
         //--- Увеличиваем индекс объекта в списке истории изменений графического объекта
         index++;
         //--- В цикле по количеству объектов на графике с группой 1
         for(int i=0;i<list.Total();i++)
           {
            //--- Получаем очередной объект из списка
            CGStdGraphObj *obj=list.At(i);
            if(obj==NULL)
               continue;
            //--- Расчитываем максимальное количество изменений у всех графических объектов с группой 1
            changes_total=obj.HistoryChangesTotal();
            if(changes_total>change_max)
               change_max=changes_total;
            //--- Устанавливаем объекту свойства (по индексу index из списка) из истории изменений
            obj.SetPropertiesFromHistory(index>obj.HistoryChangesTotal()-1 ? obj.HistoryChangesTotal()-1 : index);
           }
         //--- Если индекс объекта истории изменений стал больше максимального числа изменений всех объектов,
         //--- устанавливаем индекс равным максимальному числу изменений всех графических объектов
         if(index>change_max-1)
            index=change_max-1;
        }
      //--- Если нажата клавиша "," (EN-раскладка)
      if(lparam==KEY_LEFT)
        {
         //--- Уменьшаем индекс объекта в списке истории изменений графического объекта
         index--;
         //--- Если индекс стал меньше нуля, устанавливаем ему значение 0
         if(index<0)
            index=0;
         //--- В цикле по количеству объектов на графике с группой 1
         for(int i=0;i<list.Total();i++)
           {
            //--- Получаем очередной объект из списка
            CGStdGraphObj *obj=list.At(i);
            if(obj==NULL)
               continue;
            //--- Устанавливаем объекту свойства (по индексу index из списка) из истории изменений
            obj.SetPropertiesFromHistory(index);
           }
        }
      //--- Перерисовываем график для отображения изменений графических объектов
      ChartRedraw();
     }
   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         long array[];
         engine.GraphGetArrayChartsID(array);
         for(int i=0;i<ArraySize(array);i++)
            engine.CreateLineVertical(array[i],"LineVertical",0,time);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);

//--- Обработка событий стандартных графических объектов
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   CGStdGraphObj *obj=NULL;
   if(idx>GRAPH_OBJ_EVENT_NO_EVENT && idx<GRAPH_OBJ_EVENTS_NEXT_CODE)
     {
      CChartObjectsControl *chart_ctrl=NULL;
      int end=0;
      string evn="";
      //--- В зависимости от типа события выводим сообщение о нём в журнал
      switch(idx)
        {
         //--- Событие создания графического объекта
         case GRAPH_OBJ_EVENT_CREATE   :
           //--- Выводим сообщение о создании нового графического объекта
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE),":");
           //--- Получаем указатель на объект по имени и идентификатору графика, переданных, соответственно, в sparam и lparam,
           //--- выводим в журнал краткое описание вновь созданного объекта и устанавливаем флаг хранения истории изменений
           obj=engine.GraphGetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              obj.PrintShort();
              obj.SetAllowChangeMemory(true);
              obj.SetGroup(1);
             }
           break;
         //--- Событие изменения свойства графического объекта
         case GRAPH_OBJ_EVENT_CHANGE   :
           //--- Выводим сообщение об изменении свойства графического объекта
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE),":");
           //--- Получаем указатель на объект по имени и идентификатору графика, переданных, соответственно, в sparam и lparam
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              //--- Выводим в журнал краткое описание изменённого объекта
              obj.PrintShort();
              //--- рассчитываем код изменённого свойства, которое было передано в dparam и получаем описание этого свойства
              if(dparam<GRAPH_OBJ_PROP_INTEGER_TOTAL)
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_INTEGER)dparam);
              else if(dparam<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL)
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_DOUBLE)dparam);
              else
                 evn=obj.GetPropertyDescription((ENUM_GRAPH_OBJ_PROP_STRING)dparam);
              //--- Выводим в журнал описание изменённого свойства графического объекта
              Print(DFUN,evn);
             }
           break;
         //--- Событие переименования графического объекта
         case GRAPH_OBJ_EVENT_RENAME   :
           //--- Выводим сообщение о переименовании графического объекта
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME));
           //--- Получаем указатель на объект по имени и идентификатору графика, переданных, соответственно, в sparam и lparam
           obj=engine.GetGraphicObjCollection().GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              //--- Выводим в журнал прошлое и новое имя объекта и всю историю его переименований
              Print(DFUN,obj.GetProperty(GRAPH_OBJ_PROP_NAME,obj.Properties().CurrSize(GRAPH_OBJ_PROP_NAME)-1)," >>> ",obj.GetProperty(GRAPH_OBJ_PROP_NAME,0));
              obj.PrintRenameHistory();
             }
           break;
         //--- Событие удаления графического объекта
         case GRAPH_OBJ_EVENT_DELETE   :
           //--- Выводим сообщение об удалении графического объекта
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE),":");
           //--- Получаем указатель на удалённый объект по имени и идентификатору графика, переданных, соответственно, в sparam и lparam
           //--- и выводим в журнал краткое описание удалённого объекта
           obj=engine.GetGraphicObjCollection().GetStdDelGraphObject(sparam,lparam);
           if(obj!=NULL)
             {
              obj.PrintShort();
             }
           break;
         //--- Событие удаления графического объекта вместе с окном графика
         case GRAPH_OBJ_EVENT_DEL_CHART:
           //--- Выводим сообщение об удалении графических объектов вместе с окном графика, идентификатор и символ которого переданы в lparam и sparam
           Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART),": #",lparam,", ",sparam,":");
           //--- Рассчитываем конечное значение для цикла по списку удалённых графических объектов
           end=engine.TotalDeletedGraphObjects()-(int)dparam;
           if(end<0)
              end=0;
           //--- В цикле от конца списка удалённых графических объектов до рассчитанного значения в переменной end
           for(int i=engine.TotalDeletedGraphObjects()-1;i>=end;i--)
             {
              //--- получаем из списка очередной удалённый графический объект
              obj=engine.GetListDeletedObj().At(i);
              if(obj==NULL)
                 continue;
              //--- и выводим в журнал его краткое описание
              obj.PrintShort();
             }
           break;
         //---
         default:
           break;
        }
     }
  }
//+------------------------------------------------------------------+

Вся логика обработки нажатия клавиш расписана в комментариях к коду.

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


 

Что дальше

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

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

К содержанию

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

Графика в библиотеке DoEasy (Часть 89): Программное создание стандартных графических объектов. Базовый функционал
Графика в библиотеке DoEasy (Часть 90): События стандартных графических объектов. Базовый функционал
Графика в библиотеке DoEasy (Часть 91): События стандартных графических объектов в программе. История изменения имени объекта