English 中文 Español Deutsch 日本語 Português
Прочие классы в библиотеке DoEasy (Часть 71): События коллекции объектов-чартов

Прочие классы в библиотеке DoEasy (Часть 71): События коллекции объектов-чартов

MetaTrader 5Примеры | 29 апреля 2021, 14:03
2 438 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

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

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


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

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

   MSG_CHART_OBJ_TEMPLATE_SAVED,                      // Шаблон графика сохранён
   MSG_CHART_OBJ_TEMPLATE_APPLIED,                    // Шаблон применён к графику
   
   MSG_CHART_OBJ_INDICATOR_ADDED,                     // Добавлен индикатор
   MSG_CHART_OBJ_INDICATOR_REMOVED,                   // Удалён индикатор
   MSG_CHART_OBJ_INDICATOR_CHANGED,                   // Изменён индикатор
   MSG_CHART_OBJ_WINDOW_ADDED,                        // Добавлено подокно
   MSG_CHART_OBJ_WINDOW_REMOVED,                      // Удалено подокно

//--- CChartObjCollection
   MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION,        // Коллекция чартов
   MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,  // Не удалось создать новый объект-чарт
   MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,         // Не удалось добавить объект-чарт в коллекцию
   MSG_CHART_COLLECTION_ERR_CHARTS_MAX,               // Нельзя открыть новый график, так как количество открытых графиков уже максимальное
   
   MSG_CHART_COLLECTION_CHART_OPENED,                 // Открыт график
   MSG_CHART_COLLECTION_CHART_CLOSED,                 // Закрыт график
  
  };
//+------------------------------------------------------------------+

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

   {"Шаблон графика сохранён","Chart template saved"},
   {"Шаблон применён к графику","Template applied to the chart"},
   
   {"Добавлен индикатор","Added indicator"},
   {"Удалён индикатор","Removed indicator"},
   {"Изменён индикатор","Changed indicator"},
   
   {"Добавлено подокно","Added subwindow"},
   {"Удалено подокно","Removed subwindow"},
   
//--- CChartObjCollection
   {"Коллекция чартов","Chart collection"},
   {"Не удалось создать новый объект-чарт","Failed to create new chart object"},
   {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"},
   {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"},
   
   {"Открыт график","Open chart"},
   {"Закрыт график","Closed chart"},
   
  };
//+---------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Данные для работы с чартами                                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список возможных событий чарта                                   |
//+------------------------------------------------------------------+
enum ENUM_CHART_OBJ_EVENT
  {
   CHART_OBJ_EVENT_NO_EVENT = SIGNAL_MQL5_EVENTS_NEXT_CODE, // Нет события
   CHART_OBJ_EVENT_CHART_OPEN,                        // Событие "Открытие нового чарта"
   CHART_OBJ_EVENT_CHART_CLOSE,                       // Событие "Закрытие чарта"
   CHART_OBJ_EVENT_CHART_WND_ADD,                     // Событие "Добавление нового окна на чарт"
   CHART_OBJ_EVENT_CHART_WND_DEL,                     // Событие "Удаление окна с чарта"
   CHART_OBJ_EVENT_CHART_WND_IND_ADD,                 // Событие "Добавление нового индикатора в окно чарта"
   CHART_OBJ_EVENT_CHART_WND_IND_DEL,                 // Событие "Удаление индикатора из окна чарта"
   CHART_OBJ_EVENT_CHART_WND_IND_CHANGE,              // Событие "Изменение параметров индикатора в окне чарта"
  };
#define CHART_OBJ_EVENTS_NEXT_CODE  (CHART_OBJ_EVENT_CHART_WND_IND_CHANGE+1)  // Код следующего события после последнего кода события чарта
//+------------------------------------------------------------------+

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

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

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

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

//+------------------------------------------------------------------+
//| Целочисленные свойства чарта                                     |
//+------------------------------------------------------------------+
enum ENUM_CHART_PROP_INTEGER
  {
   CHART_PROP_ID = 0,                                 // Идентификатор графика
   CHART_PROP_TIMEFRAME,                              // Таймфрейм графика
   CHART_PROP_SHOW,                                   // Признак отрисовки ценового графика
   CHART_PROP_IS_OBJECT,                              // Признак идентификации объекта "График" (OBJ_CHART)
   CHART_PROP_BRING_TO_TOP,                           // Показ графика поверх всех других
   CHART_PROP_CONTEXT_MENU,                           // Включение/отключение доступа к контекстному меню по нажатию правой клавиши мышки. 
   CHART_PROP_CROSSHAIR_TOOL,                         // Включение/отключение доступа к инструменту "Перекрестие" по нажатию средней клавиши мышки
   CHART_PROP_MOUSE_SCROLL,                           // Прокрутка графика левой кнопкой мышки по горизонтали
   CHART_PROP_EVENT_MOUSE_WHEEL,                      // Отправка всем mql5-программам на графике сообщений о событиях колёсика мышки (CHARTEVENT_MOUSE_WHEEL)
   CHART_PROP_EVENT_MOUSE_MOVE,                       // Отправка всем mql5-программам на графике сообщений о событиях перемещения и нажатия кнопок мышки (CHARTEVENT_MOUSE_MOVE)
   CHART_PROP_EVENT_OBJECT_CREATE,                    // Отправка всем mql5-программам на графике сообщений о событии создания графического объекта (CHARTEVENT_OBJECT_CREATE)
   CHART_PROP_EVENT_OBJECT_DELETE,                    // Отправка всем mql5-программам на графике сообщений о событии уничтожения графического объекта (CHARTEVENT_OBJECT_DELETE)
   CHART_PROP_MODE,                                   // Тип графика (свечи, бары или линия (ENUM_CHART_MODE))
   CHART_PROP_FOREGROUND,                             // Ценовой график на переднем плане
   CHART_PROP_SHIFT,                                  // Режим отступа ценового графика от правого края
   CHART_PROP_AUTOSCROLL,                             // Режим автоматического перехода к правому краю графика
   CHART_PROP_KEYBOARD_CONTROL,                       // Разрешение на управление графиком с помощью клавиатуры
   CHART_PROP_QUICK_NAVIGATION,                       // Разрешение на перехват графиком нажатий клавиш Space и Enter для активации строки быстрой навигации
   CHART_PROP_SCALE,                                  // Масштаб
   CHART_PROP_SCALEFIX,                               // Режим фиксированного масштаба
   CHART_PROP_SCALEFIX_11,                            // Режим масштаба 1:1
   CHART_PROP_SCALE_PT_PER_BAR,                       // Режим указания масштаба в пунктах на бар
   CHART_PROP_SHOW_TICKER,                            // Отображение в левом верхнем углу тикера символа
   CHART_PROP_SHOW_OHLC,                              // Отображение в левом верхнем углу значений OHLC
   CHART_PROP_SHOW_BID_LINE,                          // Отображение значения Bid горизонтальной линией на графике
   CHART_PROP_SHOW_ASK_LINE,                          // Отображение значения Ask горизонтальной линией на графике
   CHART_PROP_SHOW_LAST_LINE,                         // Отображение значения Last горизонтальной линией на графике
   CHART_PROP_SHOW_PERIOD_SEP,                        // Отображение вертикальных разделителей между соседними периодами
   CHART_PROP_SHOW_GRID,                              // Отображение сетки на графике
   CHART_PROP_SHOW_VOLUMES,                           // Отображение объемов на графике
   CHART_PROP_SHOW_OBJECT_DESCR,                      // Отображение текстовых описаний объектов
   CHART_PROP_VISIBLE_BARS,                           // Количество баров на графике, доступных для отображения
   CHART_PROP_WINDOWS_TOTAL,                          // Общее количество окон графика, включая подокна индикаторов
   CHART_PROP_WINDOW_HANDLE,                          // Хэндл окна графика
   CHART_PROP_WINDOW_YDISTANCE,                       // Дистанция в пикселях по вертикальной оси Y между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   CHART_PROP_FIRST_VISIBLE_BAR,                      // Номер первого видимого бара на графике
   CHART_PROP_WIDTH_IN_BARS,                          // Ширина графика в барах
   CHART_PROP_WIDTH_IN_PIXELS,                        // Ширина графика в пикселях
   CHART_PROP_HEIGHT_IN_PIXELS,                       // Высота графика в пикселях
   CHART_PROP_COLOR_BACKGROUND,                       // Цвет фона графика
   CHART_PROP_COLOR_FOREGROUND,                       // Цвет осей, шкалы и строки OHLC
   CHART_PROP_COLOR_GRID,                             // Цвет сетки
   CHART_PROP_COLOR_VOLUME,                           // Цвет объемов и уровней открытия позиций
   CHART_PROP_COLOR_CHART_UP,                         // Цвет бара вверх, тени и окантовки тела бычьей свечи
   CHART_PROP_COLOR_CHART_DOWN,                       // Цвет бара вниз, тени и окантовки тела медвежьей свечи
   CHART_PROP_COLOR_CHART_LINE,                       // Цвет линии графика и японских свечей "Доджи"
   CHART_PROP_COLOR_CANDLE_BULL,                      // Цвет тела бычьей свечи
   CHART_PROP_COLOR_CANDLE_BEAR,                      // Цвет тела медвежьей свечи
   CHART_PROP_COLOR_BID,                              // Цвет линии Bid-цены
   CHART_PROP_COLOR_ASK,                              // Цвет линии Ask-цены
   CHART_PROP_COLOR_LAST,                             // Цвет линии цены последней совершенной сделки (Last)
   CHART_PROP_COLOR_STOP_LEVEL,                       // Цвет уровней стоп-ордеров (Stop Loss и Take Profit)
   CHART_PROP_SHOW_TRADE_LEVELS,                      // Отображение на графике торговых уровней (уровни открытых позиций, Stop Loss, Take Profit и отложенных ордеров)
   CHART_PROP_DRAG_TRADE_LEVELS,                      // Разрешение на перетаскивание торговых уровней на графике с помощью мышки
   CHART_PROP_SHOW_DATE_SCALE,                        // Отображение на графике шкалы времени
   CHART_PROP_SHOW_PRICE_SCALE,                       // Отображение на графике ценовой шкалы
   CHART_PROP_SHOW_ONE_CLICK,                         // Отображение на графике панели быстрой торговли
   CHART_PROP_IS_MAXIMIZED,                           // Окно графика развернуто
   CHART_PROP_IS_MINIMIZED,                           // Окно графика свернуто
   CHART_PROP_IS_DOCKED,                              // Окно графика закреплено
   CHART_PROP_FLOAT_LEFT,                             // Левая координата открепленного графика относительно виртуального экрана
   CHART_PROP_FLOAT_TOP,                              // Верхняя координата открепленного графика относительно виртуального экрана
   CHART_PROP_FLOAT_RIGHT,                            // Правая координата открепленного графика  относительно виртуального экрана
   CHART_PROP_FLOAT_BOTTOM,                           // Нижняя координата открепленного графика  относительно виртуального экрана
   //--- CWndInd
   CHART_PROP_WINDOW_IND_HANDLE,                      // Хэндл индикатора в окне графика
   CHART_PROP_WINDOW_IND_INDEX,                       // Индекс индикатора в окне графика
   CHART_PROP_WINDOW_NUM,                             // Номер окна графика
  };
#define CHART_PROP_INTEGER_TOTAL (67)                 // Общее количество целочисленных свойств
#define CHART_PROP_INTEGER_SKIP  (0)                  // Количество неиспользуемых в сортировке целочисленных свойств стакана
//+------------------------------------------------------------------+

Так как количество целочисленных свойств увеличилось, то не забываем увеличить и указание их количества — с 66 до 67.

И, соответственно, в перечисление критериев сортировки объектов-чартов пропишем сортировку по номеру окна графика:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки чартов                             |
//+------------------------------------------------------------------+
#define FIRST_CHART_DBL_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP)
#define FIRST_CHART_STR_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP+CHART_PROP_DOUBLE_TOTAL-CHART_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CHART_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_CHART_ID = 0,                              // Сортировать по идентификатору графика
   SORT_BY_CHART_TIMEFRAME,                           // Сортировать по таймфрейму графика
   SORT_BY_CHART_SHOW,                                // Сортировать по признаку отрисовки ценового графика
   SORT_BY_CHART_IS_OBJECT,                           // Сортировать по признаку идентификации объекта "График" (OBJ_CHART)
   SORT_BY_CHART_BRING_TO_TOP,                        // Сортировать по флагу показа графика поверх всех других
   SORT_BY_CHART_CONTEXT_MENU,                        // Сортировать по флагу включения/отключения доступа к контекстному меню по нажатию правой клавиши мышки
   SORT_BY_CHART_CROSSHAIR_TOO,                       // Сортировать по флагу включения/отключения доступа к инструменту "Перекрестие" по нажатию средней клавиши мышки
   SORT_BY_CHART_MOUSE_SCROLL,                        // Сортировать по флагу прокрутки графика левой кнопкой мышки по горизонтали
   SORT_BY_CHART_EVENT_MOUSE_WHEEL,                   // Сортировать по флагу отправки всем mql5-программам на графике сообщений о событиях колёсика мышки
   SORT_BY_CHART_EVENT_MOUSE_MOVE,                    // Сортировать по флагу отправки всем mql5-программам на графике сообщений о событиях перемещения и нажатия кнопок мышки
   SORT_BY_CHART_EVENT_OBJECT_CREATE,                 // Сортировать по флагу отправки всем mql5-программам на графике сообщений о событии создания графического объекта
   SORT_BY_CHART_EVENT_OBJECT_DELETE,                 // Сортировать по флагу отправки всем mql5-программам на графике сообщений о событии уничтожения графического объекта
   SORT_BY_CHART_MODE,                                // Сортировать по типу графика
   SORT_BY_CHART_FOREGROUND,                          // Сортировать по флагу "Ценовой график на переднем плане"
   SORT_BY_CHART_SHIFT,                               // Сортировать по флагу "Режим отступа ценового графика от правого края"
   SORT_BY_CHART_AUTOSCROLL,                          // Сортировать по флагу "Режим автоматического перехода к правому краю графика"
   SORT_BY_CHART_KEYBOARD_CONTROL,                    // Сортировать по флагу разрешения на управление графиком с помощью клавиатуры
   SORT_BY_CHART_QUICK_NAVIGATION,                    // Сортировать по флагу разрешения на перехват графиком нажатий клавиш Space и Enter для активации строки быстрой навигации
   SORT_BY_CHART_SCALE,                               // Сортировать по масштабу
   SORT_BY_CHART_SCALEFIX,                            // Сортировать по флагу фиксированного масштаба
   SORT_BY_CHART_SCALEFIX_11,                         // Сортировать по флагу масштаба 1:1
   SORT_BY_CHART_SCALE_PT_PER_BAR,                    // Сортировать по флагу указания масштаба в пунктах на бар
   SORT_BY_CHART_SHOW_TICKER,                         // Сортировать по флагу отображения в левом верхнем углу тикера символа
   SORT_BY_CHART_SHOW_OHLC,                           // Сортировать по флагу отображения в левом верхнем углу значений OHLC
   SORT_BY_CHART_SHOW_BID_LINE,                       // Сортировать по флагу отображения значения Bid горизонтальной линией на графике
   SORT_BY_CHART_SHOW_ASK_LINE,                       // Сортировать по флагу отображения значения Ask горизонтальной линией на графике
   SORT_BY_CHART_SHOW_LAST_LINE,                      // Сортировать по флагу отображения значения Last горизонтальной линией на графике
   SORT_BY_CHART_SHOW_PERIOD_SEP,                     // Сортировать по флагу отображения вертикальных разделителей между соседними периодами
   SORT_BY_CHART_SHOW_GRID,                           // Сортировать по флагу отображения сетки на графике
   SORT_BY_CHART_SHOW_VOLUMES,                        // Сортировать по режиму отображения объемов на графике
   SORT_BY_CHART_SHOW_OBJECT_DESCR,                   // Сортировать по флагу отображения текстовых описаний объектов
   SORT_BY_CHART_VISIBLE_BARS,                        // Сортировать по количеству баров на графике, доступных для отображения
   SORT_BY_CHART_WINDOWS_TOTAL,                       // Сортировать по общему количеству окон графика, включая подокна индикаторов
   SORT_BY_CHART_WINDOW_HANDLE,                       // Сортировать по хэндлу графика
   SORT_BY_CHART_WINDOW_YDISTANCE,                    // Сортировать по дистанции в пикселях по вертикальной оси Y между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   SORT_BY_CHART_FIRST_VISIBLE_BAR,                   // Сортировать по номеру первого видимого бара на графике
   SORT_BY_CHART_WIDTH_IN_BARS,                       // Сортировать по ширине графика в барах
   SORT_BY_CHART_WIDTH_IN_PIXELS,                     // Сортировать по ширине графика в пикселях
   SORT_BY_CHART_HEIGHT_IN_PIXELS,                    // Сортировать по высоте графика в пикселях
   SORT_BY_CHART_COLOR_BACKGROUND,                    // Сортировать по цвету фона графика
   SORT_BY_CHART_COLOR_FOREGROUND,                    // Сортировать по цвету осей, шкалы и строки OHLC
   SORT_BY_CHART_COLOR_GRID,                          // Сортировать по цвету сетки
   SORT_BY_CHART_COLOR_VOLUME,                        // Сортировать по цвету объемов и уровней открытия позиций
   SORT_BY_CHART_COLOR_CHART_UP,                      // Сортировать по цвету бара вверх, тени и окантовки тела бычьей свечи
   SORT_BY_CHART_COLOR_CHART_DOWN,                    // Сортировать по цвету бара вниз, тени и окантовки тела медвежьей свечи
   SORT_BY_CHART_COLOR_CHART_LINE,                    // Сортировать по цвету линии графика и японских свечей "Доджи"
   SORT_BY_CHART_COLOR_CANDLE_BULL,                   // Сортировать по цвету тела бычьей свечи
   SORT_BY_CHART_COLOR_CANDLE_BEAR,                   // Сортировать по цвету тела медвежьей свечи
   SORT_BY_CHART_COLOR_BID,                           // Сортировать по цвету линии Bid-цены
   SORT_BY_CHART_COLOR_ASK,                           // Сортировать по цвету линии Ask-цены
   SORT_BY_CHART_COLOR_LAST,                          // Сортировать по цвету линии цены последней совершенной сделки (Last)
   SORT_BY_CHART_COLOR_STOP_LEVEL,                    // Сортировать по цвету уровней стоп-ордеров (Stop Loss и Take Profit)
   SORT_BY_CHART_SHOW_TRADE_LEVELS,                   // Сортировать по флагу отображения на графике торговых уровней
   SORT_BY_CHART_DRAG_TRADE_LEVELS,                   // Сортировать по флагу разрешения на перетаскивание торговых уровней на графике с помощью мышки
   SORT_BY_CHART_SHOW_DATE_SCALE,                     // Сортировать по флагу отображения на графике шкалы времени
   SORT_BY_CHART_SHOW_PRICE_SCALE,                    // Сортировать по флагу отображения на графике ценовой шкалы
   SORT_BY_CHART_SHOW_ONE_CLICK,                      // Сортировать по флагу отображения на графике панели быстрой торговли
   SORT_BY_CHART_IS_MAXIMIZED,                        // Сортировать по флагу "Окно графика развернуто"
   SORT_BY_CHART_IS_MINIMIZED,                        // Сортировать по флагу "Окно графика свернуто"
   SORT_BY_CHART_IS_DOCKED,                           // Сортировать по флагу "Окно графика закреплено"
   SORT_BY_CHART_FLOAT_LEFT,                          // Сортировать по левой координате открепленного графика относительно виртуального экрана
   SORT_BY_CHART_FLOAT_TOP,                           // Сортировать по верхней координате открепленного графика относительно виртуального экрана
   SORT_BY_CHART_FLOAT_RIGHT,                         // Сортировать по правой координате открепленного графика  относительно виртуального экрана
   SORT_BY_CHART_FLOAT_BOTTOM,                        // Сортировать по нижней координате открепленного графика  относительно виртуального экрана
   SORT_BY_CHART_WINDOW_IND_HANDLE,                   // Сортировать по хэндлу индикатора в окне графика
   SORT_BY_CHART_WINDOW_IND_INDEX,                    // Сортировать по индексу индикатора в окне графика
   SORT_BY_CHART_WINDOW_NUM,                          // Сортировать по номеру окна графика
//--- Сортировка по вещественным свойствам
   SORT_BY_CHART_SHIFT_SIZE = FIRST_CHART_DBL_PROP,   // Сортировать по размеру отступа нулевого бара от правого края в процентах
   SORT_BY_CHART_FIXED_POSITION,                      // Сортировать по положению фиксированной позиции графика от левого края в процентах
   SORT_BY_CHART_FIXED_MAX,                           // Сортировать по фиксированному максимуму графика
   SORT_BY_CHART_FIXED_MIN,                           // Сортировать по фиксированному минимуму графика
   SORT_BY_CHART_POINTS_PER_BAR,                      // Сортировать по значению масштаба в пунктах на бар
   SORT_BY_CHART_PRICE_MIN,                           // Сортировать по минимуму графика
   SORT_BY_CHART_PRICE_MAX,                           // Сортировать по максимуму графика
//--- Сортировка по строковым свойствам
   SORT_BY_CHART_COMMENT = FIRST_CHART_STR_PROP,      // Сортировать по тексту комментария на графике
   SORT_BY_CHART_EXPERT_NAME,                         // Сортировать по имени эксперта, запущенного на графике
   SORT_BY_CHART_SCRIPT_NAME,                         // Сортировать по имени скрипта, запущенного на графике
   SORT_BY_CHART_WINDOW_IND_NAME,                     // Сортировать по имени индикатора, запущенного в окне графика
   SORT_BY_CHART_SYMBOL,                              // Сортировать по символу графика
  };
//+------------------------------------------------------------------+


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

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

Внесём доработки в файл класса объекта-индикатора в окне чарта и объекта-окна чарта
(оба класса находятся в одном файле \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh).

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

//+------------------------------------------------------------------+
//| Класс объекта-индикатора окна графика                            |
//+------------------------------------------------------------------+
class CWndInd : public CObject
  {
private:
   long              m_chart_id;                         // Идентификатор графика
   string            m_name;                             // Короткое имя индикатора
   int               m_index;                            // Индекс индикатора в списке
   int               m_window_num;                       // Номер подокна индикатора
   int               m_handle;                           // Хэндл индикатора
public:
//--- Возвращает себя
   CWndInd          *GetObject(void)                     { return &this;               }
//--- Возвращает (1) имя индикатора, (2) индекс в списке, (3) хэндл индикатора, (4) номер подокна
   string            Name(void)                    const { return this.m_name;         }
   int               Index(void)                   const { return this.m_index;        }
   int               Handle(void)                  const { return this.m_handle;       }
   int               WindowNum(void)               const { return this.m_window_num;   }
//--- Устанавливает (1), имя, (2) индекс окна на графике, (3) хэндл, (4) номер подокна
   void              SetName(const string name)          { this.m_name=name;           }
   void              SetIndex(const int index)           { this.m_index=index;         }
   void              SetHandle(const int handle)         { this.m_handle=handle;       }
   void              SetWindowNum(const int win_num)     { this.m_window_num=win_num;  }
   
//--- Выводит в журнал описание свойств объекта (dash=true - дефис перед описанием, false - только описание)
   void              Print(const bool dash=false)        { ::Print((dash ? "- " : "")+this.Header());                      }
//--- Возвращает краткое наименование объекта
   string            Header(void)                  const { return CMessage::Text(MSG_CHART_OBJ_INDICATOR)+" "+this.Name(); }
   
//--- Сравнивает объекты CWndInd между собой по указанному свойству
   virtual int       Compare(const CObject *node,const int mode=0) const;
   
//--- Конструкторы
                     CWndInd(void){;}
                     CWndInd(const int handle,const string name,const int index,const int win_num) : m_handle(handle),
                                                                                                     m_name(name),
                                                                                                     m_index(index),
                                                                                                     m_window_num(win_num) {}
  };
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Класс объекта-окна графика                                       |
//+------------------------------------------------------------------+
class CChartWnd : public CBaseObjExt
  {
private:
   CArrayObj         m_list_ind;                                        // Список индикаторов
   CArrayObj        *m_list_ind_del;                                    // Указатель на список удалённых из окна индикаторов
   CArrayObj        *m_list_ind_param;                                  // Указатель на список изменённых индикаторов
   int               m_window_num;                                      // Номер подокна
   int               m_wnd_coord_x;                                     // Координата X для времени на графике в окне
   int               m_wnd_coord_y;                                     // Координата Y для цены на графике в окне
   string            m_symbol;                                          // Символ графика, к которому принадлежит окно
//--- Возвращает флаг наличия индикатора (1) из списка в окне, (2) из окна в списке
   bool              IsPresentInWindow(const CWndInd *ind);
   bool              IsPresentInList(const string name);
//--- Возвращает объект-индикатор, который есть в списке, но нет на графике
   CWndInd          *GetMissingInd(void);
//--- Удаляет из списка уже отсутствующие в окне индикаторы
   void              IndicatorsDelete(void);
//--- Добавляет в список новые индикаторы
   void              IndicatorsAdd(void);
//--- Проверяет изменение параметров существующих индикаторов
   void              IndicatorsChangeCheck(void);
   
public:

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

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

public:
//--- Возвращает себя
   CChartWnd        *GetObject(void)                                    { return &this;            }

//--- Возвращает флаг поддержания объектом данного свойства
   virtual bool      SupportProperty(ENUM_CHART_PROP_INTEGER property)  { return(property==CHART_PROP_WINDOW_YDISTANCE || property==CHART_PROP_HEIGHT_IN_PIXELS ? true : false); }
   virtual bool      SupportProperty(ENUM_CHART_PROP_DOUBLE property)   { return false; }
   virtual bool      SupportProperty(ENUM_CHART_PROP_STRING property)   { return (property==CHART_PROP_WINDOW_IND_NAME || property==CHART_PROP_SYMBOL ? true : false);  }

//--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства
   string            GetPropertyDescription(ENUM_CHART_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_CHART_PROP_DOUBLE property)  { return CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED);        }
   string            GetPropertyDescription(ENUM_CHART_PROP_STRING property);

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

//--- Возвращает краткое наименование объекта
   virtual string    Header(void);
   
//--- Создаёт и отправляет событие чарта на график управляющей программы
   void              SendEvent(ENUM_CHART_OBJ_EVENT event);
  
//--- Сравнивает объекты CChartWnd между собой по указанному свойству (для сортировки списка по свойству объекта-mql5-сигнала)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CChartWnd между собой по всем свойствам (для поиска равных объектов-mql5-сигналов)
   bool              IsEqual(CChartWnd* compared_obj) const;
   
//--- Конструкторы
                     CChartWnd(void){;}
                     CChartWnd(const long chart_id,const int wnd_num,const string symbol,CArrayObj *list_ind_del,CArrayObj *list_ind_param);
//--- Деструктор
                    ~CChartWnd(void);

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

//--- Возвращает (1) номер подокна, (2) количество индикаторов, прикреплённых к окну, (3) наименование символа графика
   int               WindowNum(void)                              const { return this.m_window_num;                                       }
   int               IndicatorsTotal(void)                        const { return this.m_list_ind.Total();                                 }
   string            Symbol(void)                                 const { return m_symbol;}
//--- Устанавливает (1) номер подокна, (2) символ графика
   void              SetWindowNum(const int num)                        { this.m_window_num=num;                                          }
   void              SetSymbol(const string symbol)                     { this.m_symbol=symbol;                                           }
   
//--- Возвращает (1) список индикаторов, объект-индикатор окна из списка по (2) индексу в списке, (3) по хэндлу
   CArrayObj        *GetIndicatorsList(void)                            { return &this.m_list_ind;                                        }
   CWndInd          *GetIndicatorByIndex(const int index);
   CWndInd          *GetIndicatorByHandle(const int handle);
//--- Возвращает (1) последний добавленный в окно, (2) последний удалённый из окна, (3) изменённый индикатор
   CWndInd          *GetLastAddedIndicator(void)                        { return this.m_list_ind.At(this.m_list_ind.Total()-1);           }
   CWndInd          *GetLastDeletedIndicator(void)                      { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);   }
   CWndInd          *GetLastChangedIndicator(void)                      { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);}


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

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CChartWnd::CChartWnd(const long chart_id,const int wnd_num,const string symbol,CArrayObj *list_ind_del,CArrayObj *list_ind_param) : m_window_num(wnd_num),
                                                                                                                                    m_symbol(symbol),
                                                                                                                                    m_wnd_coord_x(0),
                                                                                                                                    m_wnd_coord_y(0)
  {
   this.m_list_ind_del=list_ind_del;
   this.m_list_ind_param=list_ind_param;
   CBaseObj::SetChartID(chart_id);
   this.IndicatorsListCreate();
  }
//+------------------------------------------------------------------+

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

Деструктор класса:

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CChartWnd::~CChartWnd(void)
  {
   int total=this.m_list_ind.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      CWndInd *ind=this.m_list_ind.At(i);
      if(ind==NULL)
         continue;
      ::IndicatorRelease(ind.Handle());
      this.m_list_ind.Delete(i);
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Сравнивает объекты CChartWnd между собой по указанному свойству  |
//+------------------------------------------------------------------+
int CChartWnd::Compare(const CObject *node,const int mode=0) const
  {
   const CChartWnd *obj_compared=node;
   if(mode==CHART_PROP_WINDOW_YDISTANCE)
      return(this.YDistance()>obj_compared.YDistance() ? 1 : this.YDistance()<obj_compared.YDistance() ? -1 : 0);
   else if(mode==CHART_PROP_HEIGHT_IN_PIXELS)
      return(this.HeightInPixels()>obj_compared.HeightInPixels() ? 1 : this.HeightInPixels()<obj_compared.HeightInPixels() ? -1 : 0);
   else if(mode==CHART_PROP_WINDOW_NUM)
      return(this.WindowNum()>obj_compared.WindowNum() ? 1 : this.WindowNum()<obj_compared.WindowNum() ? -1 : 0);
   else if(mode==CHART_PROP_SYMBOL)
      return(this.Symbol()==obj_compared.Symbol() ? 0 : this.Symbol()>obj_compared.Symbol() ? 1 : -1);
   return -1;
  }
//+------------------------------------------------------------------+

Это необходимо для сортировки и поиска объектов в списках по символу графика и вновь введённому свойству объекта — номеру окна.

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

//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства объекта                  |
//+------------------------------------------------------------------+
string CChartWnd::GetPropertyDescription(ENUM_CHART_PROP_STRING property)
  {
   return
     (
      property==CHART_PROP_SYMBOL  ?  CMessage::Text(MSG_LIB_PROP_SYMBOL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.Symbol()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

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

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

      //--- по короткому имени индикатора получаем и сохраняем его хэндл
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Освобождаем хэндл индикатора
      ::IndicatorRelease(handle);
      //--- Создаём новый объект-индикатора в окне графика

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

//+------------------------------------------------------------------+
//| Создаёт список прикреплённых к окну индикаторов                  |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsListCreate(void)
  {
   //--- Очищаем списки индикаторов
   this.m_list_ind.Clear();
   //--- Получаем общее количество индикаторов в окне
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   //--- В цикле по количеству индикаторов
   for(int i=0;i<total;i++)
     {
      //--- получаем и сохраняем короткое имя индикатора,
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      //--- по короткому имени индикатора получаем и сохраняем его хэндл
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Создаём новый объект-индикатора в окне графика
      CWndInd *ind=new CWndInd(handle,name,i,this.WindowNum());
      if(ind==NULL)
         continue;
      //--- устанавливаем списку флаг сортированного списка
      this.m_list_ind.Sort();
      //--- Если объект добавить в список не удалось - удаляем его
      if(!this.m_list_ind.Add(ind))
         delete ind;
     }
  }
//+------------------------------------------------------------------+

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

Точно такие же манипуляции проведём и с методом, добавляющем в список новые индикаторы:

Удалим строку

      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Освобождаем хэндл индикатора
      ::IndicatorRelease(handle);
      //--- Создаём новый объект-индикатора в окне графика

и добавим передачу номера окна при создании нового объекта-индикатора в окне графика:

//+------------------------------------------------------------------+
//| Добавляет в список новые индикаторы                              |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsAdd(void)
  {
   //--- Получаем общее количество индикаторов в окне
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   //--- В цикле по количеству индикаторов
   for(int i=0;i<total;i++)
     {
      //--- получаем и сохраняем короткое имя индикатора,
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      //--- по короткому имени индикатора получаем и сохраняем его хэндл
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Создаём новый объект-индикатора в окне графика
      CWndInd *ind=new CWndInd(handle,name,i,this.WindowNum());
      if(ind==NULL)
         continue;
      //--- устанавливаем списку флаг сортированного списка
      this.m_list_ind.Sort();
      //--- Если объект уже есть в списке, или добавить его в список не удалось - удаляем его
      if(this.m_list_ind.Search(ind)>WRONG_VALUE || !this.m_list_ind.Add(ind))
         delete ind;
     }
  }
//+------------------------------------------------------------------+

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

      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      ::IndicatorRelease(handle);

Теперь и в этом методе мы не освобождаем хэндл индикатора:

//+------------------------------------------------------------------+
//| Возвращает флаг наличия индикатора из списка в окне              |
//+------------------------------------------------------------------+
bool CChartWnd::IsPresentInWindow(const CWndInd *ind)
  {
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   for(int i=0;i<total;i++)
     {
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      if(ind.Name()==name && ind.Handle()==handle)
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Проверяет изменение параметров существующих индикаторов          |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsChangeCheck(void)
  {
   //--- Получаем общее количество индикаторов в окне
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   //--- В цикле по всем индикаторам окна
   for(int i=0;i<total;i++)
     {
      //--- получаем имя индикатора, а по имени - его хэндл
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Если индикатор с таким именем есть в списке индикаторов объекта - идём к следующему
      if(this.IsPresentInList(name))
         continue;
      //--- Получаем объект-индикатор, который есть в списке, но его нет в окне
      CWndInd *ind=this.GetMissingInd();
      if(ind==NULL)
         continue;
      //--- Если индексы индикатора и найденного объекта совпадают, то это индикатор, у которого изменены параметры
      if(ind.Index()==i)
        {
         //--- На основании найденного объекта-индикатора создаём новый объект-индикатор,
         CWndInd *changed=new CWndInd(ind.Handle(),ind.Name(),ind.Index(),ind.WindowNum());
         if(changed==NULL)
            continue;
         //--- устанавливаем списку изменённых индикаторов флаг сортированного списка
         this.m_list_ind_param.Sort();
         //--- Если вновь созданный объект-индикатор не удалось добавить в список изменённых индикаторов,
         //--- удаляем созданный объект и перехоим к следующему индикатору в окне
         if(!this.m_list_ind_param.Add(changed))
           {
            delete changed;
            continue;
           }
         //--- Для найденного "потерянного" индикатора устанавливаем новые параметры - короткое имя и хэндл
         ind.SetName(name);
         ind.SetHandle(handle);
         //--- и вызываем метод отправки пользовательского события на график управляющей программы
         this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_CHANGE);
        }
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Удаляет из списка уже отсутствующие в окне индикаторы            |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsDelete(void)
  {
   //--- В цикле по списку объектов-индикаторов окна
   int total=this.m_list_ind.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      //--- получаем очередной объект-индикатор
      CWndInd *ind=this.m_list_ind.At(i);
      if(ind==NULL)
         continue;
      //--- Если такой индикатор есть в окне графика - идём к следующему объекту в списке
      if(this.IsPresentInWindow(ind))
         continue;
      //--- Создаём копию удалённого индикатора
      CWndInd *ind_del=new CWndInd(ind.Handle(),ind.Name(),ind.Index(),ind.WindowNum());
      if(ind_del==NULL)
         continue;
      //--- Если созданный объект не удалось поместить в список удалённых из окна индикаторов -
      //--- удаляем его и идём к следующему объекту в списке
      if(!this.m_list_ind_del.Add(ind_del))
        {
         delete ind_del;
         continue;
        }
      //--- Удалённый из окна индикатор удаляем из списка
      this.m_list_ind.Delete(i);
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает флаг наличия индикатора из окна в списке              |
//+------------------------------------------------------------------+
bool CChartWnd::IsPresentInList(const string name)
  {
   CWndInd *ind=new CWndInd();
   if(ind==NULL)
      return false;
   ind.SetName(name);
   this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_NAME);
   int index=this.m_list_ind.Search(ind);
   delete ind;
   return(index>WRONG_VALUE);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает объект-индикатор, который есть в списке,              |
//| но нет на графике                                                |
//+------------------------------------------------------------------+
CWndInd *CChartWnd::GetMissingInd(void)
  {
   for(int i=0;i<this.m_list_ind.Total();i++)
     {
      CWndInd *ind=this.m_list_ind.At(i);
      if(!this.IsPresentInWindow(ind))
         return ind;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает объект-индикатор по индексу в списке окна             |
//+------------------------------------------------------------------+
CWndInd *CChartWnd::GetIndicatorByIndex(const int index)
  {
   CWndInd *ind=new CWndInd();
   if(ind==NULL)
      return NULL;
   ind.SetIndex(index);
   this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_INDEX);
   int n=this.m_list_ind.Search(ind);
   delete ind;
   return this.m_list_ind.At(n);
  }
//+------------------------------------------------------------------+

Здесь:  создаём временный объект-индикатор и устанавливаем ему индекс, переданный в метод. Устанавливаем списку объектов-индикаторов флаг сортировки по индексу индикатора в окне на графике и получаем индекс индикатора в списке объекта-окна с таким индексом в списке окна графика. Обязательно удаляем временный объект и возвращаем указатель на объект по найденному индексу в списке объектов-индикаторов.
Если объект найден не был, то метод Search() вернёт -1, а метод At() при таком значении индекса вернёт NULL. Таким образом, метод возвращает либо указатель на найденный объект в списке, либо NULL, если объекта-индикатора с указанным индексом в окне графика нет в списке.

Метод, возвращающий объект-индикатор окна из списка по хэндлу:

//+------------------------------------------------------------------+
//| Возвращает объект-индикатор окна из списка по хэндлу             |
//+------------------------------------------------------------------+
CWndInd *CChartWnd::GetIndicatorByHandle(const int handle)
  {
   CWndInd *ind=new CWndInd();
   if(ind==NULL)
      return NULL;
   ind.SetHandle(handle);
   this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_HANDLE);
   int index=this.m_list_ind.Search(ind);
   delete ind;
   return this.m_list_ind.At(index);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Обновляет данные по прикреплённым индикаторам                    |
//+------------------------------------------------------------------+
void CChartWnd::Refresh(void)
  {
   //--- Рассчитываем значение изменения количества индикаторов в окне "сейчас и на прошлой проверке"
   int change=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num)-this.m_list_ind.Total();
   //--- Если нет изменения количества индикаторов в окне -
   if(change==0)
     {
      //--- проверяем изменение параметров всех индикаторов и выходим
      this.IndicatorsChangeCheck();
      return;
     }
   //--- Если добавлены индикаторы
   if(change>0)
     {
      //--- Вызываем метод добавления новых индикаторов в список
      this.IndicatorsAdd();
      //--- В цикле по количеству добавленных в окно индикаторов
      for(int i=0;i<change;i++)
        {
         //--- получаем новый индикатор в списке по рассчитанному от конца списка индексу
         int index=this.m_list_ind.Total()-(1+i);
         //--- и если объект получить не удалось - переходим к следующему
         CWndInd *ind=this.m_list_ind.At(index);
         if(ind==NULL)
            continue;
         //--- вызываем метод отправки события на график управляющей программы
         this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_ADD);
        }
     }
   //--- Если есть удалённые индикаторы
   if(change<0)
     {
      //--- Вызываем метод удаления лишних индикаторов из списка
      this.IndicatorsDelete();
      //--- В цикле по количеству удалённых из окна индикаторов
      for(int i=0;i<-change;i++)
        {
         //--- получаем новый удалённый индикатор в списке удалённых индикаторов по рассчитанному от конца списка индексу
         int index=this.m_list_ind_del.Total()-(1+i);
         //--- и если объект получить не удалось - переходим к следующему
         CWndInd *ind=this.m_list_ind_del.At(index);
         if(ind==NULL)
            continue;
         //--- вызываем метод отправки события на график управляющей программы
         this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_DEL);
        }
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт и отправляет событие окна графика                        |
//| на график управляющей программы                                  |
//+------------------------------------------------------------------+
void CChartWnd::SendEvent(ENUM_CHART_OBJ_EVENT event)
  {
   //--- Если добавлен индикатор
   if(event==CHART_OBJ_EVENT_CHART_WND_IND_ADD)
     {
      //--- Получаем последний добавленный в список объект-индикатор
      CWndInd *ind=this.GetLastAddedIndicator();
      if(ind==NULL)
         return;
      //--- Отправляем событие CHART_OBJ_EVENT_CHART_WND_IND_ADD на график управляющей программы
      //--- в lparam передаём идентификатор графика,
      //--- в dparam передаём номер окна графика,
      //--- в sparam передаём короткое имя добавленного индикатора
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
     }
   //--- Если удалён индикатор
   else if(event==CHART_OBJ_EVENT_CHART_WND_IND_DEL)
     {
      //--- Получаем последний объект-индикатор, добавленный в список удалённых индикаторов
      CWndInd *ind=this.GetLastDeletedIndicator();
      if(ind==NULL)
         return;
      //--- Отправляем событие CHART_OBJ_EVENT_CHART_WND_IND_DEL на график управляющей программы
      //--- в lparam передаём идентификатор графика,
      //--- в dparam передаём номер окна графика,
      //--- в sparam передаём короткое имя удалённого индикатора
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
     }
   //--- Если изменён индикатор
   else if(event==CHART_OBJ_EVENT_CHART_WND_IND_CHANGE)
     {
      //--- Получаем последний объект-индикатор, добавленный в список изменённых индикаторов
      CWndInd *ind=this.GetLastChangedIndicator();
      if(ind==NULL)
         return;
      //--- Отправляем событие CHART_OBJ_EVENT_CHART_WND_IND_CHANGE на график управляющей программы
      //--- в lparam передаём идентификатор графика,
      //--- в dparam передаём номер окна графика,
      //--- в sparam передаём короткое имя изменённого индикатора
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
     }
  }
//+------------------------------------------------------------------+

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

Теперь доработаем класс объекта-чарта в файле \MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqh.

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

//+------------------------------------------------------------------+
//| Класс объекта-чарта                                              |
//+------------------------------------------------------------------+
class CChartObj : public CBaseObjExt
  {
private:
   CArrayObj         m_list_wnd;                                  // Список объектов окон графика
   CArrayObj        *m_list_wnd_del;                              // Указатель на список удалённых объектов окон графика
   CArrayObj        *m_list_ind_del;                              // Указатель на список удалённых из окна индикаторов
   CArrayObj        *m_list_ind_param;                            // Указатель на список изменённых индикаторов
   long              m_long_prop[CHART_PROP_INTEGER_TOTAL];       // Целочисленные свойства
   double            m_double_prop[CHART_PROP_DOUBLE_TOTAL];      // Вещественные свойства
   string            m_string_prop[CHART_PROP_STRING_TOTAL];      // Строковые свойства
   int               m_digits;                                    // Digits() символа
   datetime          m_wnd_time_x;                                // Время для координаты X на графике в окне
   double            m_wnd_price_y;                               // Цена для координаты Y на графике в окне
   
//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство
   int               IndexProp(ENUM_CHART_PROP_DOUBLE property)   const { return(int)property-CHART_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_CHART_PROP_STRING property)   const { return(int)property-CHART_PROP_INTEGER_TOTAL-CHART_PROP_DOUBLE_TOTAL; }

//--- Методы установки флагов параметров 
   bool              SetShowFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetBringToTopFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetContextMenuFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetCrosshairToolFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetMouseScrollFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventMouseWhellFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventMouseMoveFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventObjectCreateFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventObjectDeleteFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetForegroundFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShiftFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetAutoscrollFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetKeyboardControlFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetQuickNavigationFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetScaleFixFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetScaleFix11Flag(const string source,const bool flag,const bool redraw=false);
   bool              SetScalePTPerBarFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowTickerFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowOHLCFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowBidLineFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowAskLineFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowLastLineFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowPeriodSeparatorsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowGridFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowObjectDescriptionsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowTradeLevelsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetDragTradeLevelsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowDateScaleFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowPriceScaleFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowOneClickPanelFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetDockedFlag(const string source,const bool flag,const bool redraw=false);

//--- Методы установки значений свойств
   bool              SetMode(const string source,const ENUM_CHART_MODE mode,const bool redraw=false);
   bool              SetScale(const string source,const int scale,const bool redraw=false);
   bool              SetModeVolume(const string source,const ENUM_CHART_VOLUME_MODE mode,const bool redraw=false);
   void              SetVisibleBars(void);
   void              SetWindowsTotal(void);
   void              SetFirstVisibleBars(void);
   void              SetWidthInBars(void);
   void              SetWidthInPixels(void);
   void              SetMaximizedFlag(void);
   void              SetMinimizedFlag(void);
   void              SetExpertName(void);
   void              SetScriptName(void);
   
//--- (1) Создаёт, (2) проверяет и пересоздаёт список окон графика
   void              CreateWindowsList(void);
   void              RecreateWindowsList(const int change);
//--- Добавляет расширение файлу скриншота при его отсутствии
   string            FileNameWithExtention(const string filename);
   
public:

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

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetProperty(ENUM_CHART_PROP_INTEGER property,long value)    { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_CHART_PROP_DOUBLE property,double value)   { this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_CHART_PROP_STRING property,string value)   { this.m_string_prop[this.IndexProp(property)]=value;    }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetProperty(ENUM_CHART_PROP_INTEGER property)         const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_CHART_PROP_DOUBLE property)          const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_CHART_PROP_STRING property)          const { return this.m_string_prop[this.IndexProp(property)];   }
//--- Возвращает (1) себя, (2) список объектов-окон, (3) список удалённых объектов-окон
   CChartObj        *GetObject(void)                                             { return &this;               }
   CArrayObj        *GetList(void)                                               { return &this.m_list_wnd;    }

//--- Возвращает последнее (1) добавленное (удалённое) окно графика
   CChartWnd        *GetLastAddedWindow(void)                                    { return this.m_list_wnd.At(this.m_list_wnd.Total()-1);           }
   CChartWnd        *GetLastDeletedWindow(void)                                  { return this.m_list_wnd_del.At(this.m_list_wnd_del.Total()-1);   }
   
//--- Возвращает (1) последний добавленный в окно, (2) последний удалённый из окна, (3) изменённый индикатор,
   CWndInd          *GetLastAddedIndicator(const int win_num);
   CWndInd          *GetLastDeletedIndicator(void)                               { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);   }
   CWndInd          *GetLastChangedIndicator(void)                               { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);}
//--- Возвращает индикатор по индексу из указанного окна графика
   CWndInd          *GetIndicator(const int win_num,const int ind_index);
   
//--- Возвращает флаг поддержания объектом данного свойства
   virtual bool      SupportProperty(ENUM_CHART_PROP_INTEGER property)           { return (property!=CHART_PROP_WINDOW_YDISTANCE ? true : false);  }
   virtual bool      SupportProperty(ENUM_CHART_PROP_DOUBLE property)            { return true; }
   virtual bool      SupportProperty(ENUM_CHART_PROP_STRING property)            { return true; }

//--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства
   string            GetPropertyDescription(ENUM_CHART_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_CHART_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_CHART_PROP_STRING property);

//--- Выводит в журнал описание свойств объекта (full_prop=true - все свойства, false - только поддерживаемые)
   void              Print(const bool full_prop=false);
//--- Выводит в журнал краткое описание объекта
   virtual void      PrintShort(const bool dash=false);
//--- Возвращает краткое наименование объекта
   virtual string    Header(void);
   
//--- Создаёт и отправляет событие чарта на график управляющей программы
   void              SendEvent(ENUM_CHART_OBJ_EVENT event);
  
//--- Сравнивает объекты CChartObj между собой по указанному свойству (для сортировки списка по свойству объекта-чарта)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CChartObj между собой по всем свойствам (для поиска равных объектов-чартов)
   bool              IsEqual(CChartObj* compared_obj) const;
   
//--- Обновляет объект-чарт и его список окон индикаторов
   void              Refresh(void);
   
//--- Конструкторы
                     CChartObj(){;}
                     CChartObj(const long chart_id,CArrayObj *list_wnd_del,CArrayObj *list_ind_del,CArrayObj *list_ind_param);
//+------------------------------------------------------------------+ 

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

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id,CArrayObj *list_wnd_del,CArrayObj *list_ind_del,CArrayObj *list_ind_param) : m_wnd_time_x(0),m_wnd_price_y(0)
  {
   this.m_list_wnd_del=list_wnd_del;
   this.m_list_ind_del=list_ind_del;
   this.m_list_ind_param=list_ind_param;
//--- Установка идентификатора графика в базовый объект

в конце листинга конструктора установим списку удалённых окон объекта-чарта флаг сортировки по номеру окна графика:

   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   this.m_list_wnd_del.Sort(SORT_BY_CHART_WINDOW_NUM);
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает последний добавленный в окно индикатор                |
//+------------------------------------------------------------------+
CWndInd *CChartObj::GetLastAddedIndicator(const int win_num)
  {
   CChartWnd *wnd=this.GetWindowByNum(win_num);
   return(wnd!=NULL ? wnd.GetLastAddedIndicator() : NULL);
  }
//+------------------------------------------------------------------+

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

Метод, возвращающий индикатор по индексу из указанного окна графика:

//+------------------------------------------------------------------+
//| Возвращает индикатор по индексу из указанного окна графика       |
//+------------------------------------------------------------------+
CWndInd *CChartObj::GetIndicator(const int win_num,const int ind_index)
  {
   CChartWnd *wnd=this.GetWindowByNum(win_num);
   return(wnd!=NULL ? wnd.GetIndicatorByIndex(ind_index) : NULL);
  }
//+------------------------------------------------------------------+

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

В методе, обновляющем объект-чарт и список его окон, заменим метод CreateWindowsList() на новый метод RecreateWindowsList():

//+------------------------------------------------------------------+
//| Обновляет объект-чарт и список его окон                          |
//+------------------------------------------------------------------+
void CChartObj::Refresh(void)
  {
   for(int i=0;i<this.m_list_wnd.Total();i++)
     {
      CChartWnd *wnd=this.m_list_wnd.At(i);
      if(wnd==NULL)
         continue;
      wnd.Refresh();
     }
   int change=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL)-this.WindowsTotal();
   if(change==0)
      return;
   this.RecreateWindowsList(change);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт список окон графика                                      |
//+------------------------------------------------------------------+
void CChartObj::CreateWindowsList(void)
  {
   //--- Очищаем список окон графика
   this.m_list_wnd.Clear();
   //--- Получаем общее количество окон графика из окружения
   int total=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL);
   //--- В цикле по общему количеству окон
   for(int i=0;i<total;i++)
     {
      //--- Создаём новый объект-окно чарта
      CChartWnd *wnd=new CChartWnd(this.m_chart_id,i,this.Symbol(),this.m_list_ind_del,this.m_list_ind_param);
      if(wnd==NULL)
         continue;
      //--- Если номер окна больше 0 (не главное окно графика) и в нём ещё нет индикатора,
      //--- то удаляем вновь созданный объект-окно чарта и идём на следующую итерацию цикла
      if(wnd.WindowNum()!=0 && wnd.IndicatorsTotal()==0)
        {
         delete wnd;
         continue;
        }
      //--- Если объект в список не был добавлен - удаляем этот объект
      this.m_list_wnd.Sort();
      if(!this.m_list_wnd.Add(wnd))
         delete wnd;
     }
   //--- Если количество объектов в списке соответствует количеству окон на графике,
   //--- то записываем это значение в свойство объекта-чарта
   //--- Если же количество объектов в списке не соответствует количеству окон на графике,
   //--- то в свойство объекта-чарта записываем значение количества объектов в списке.
   int value=int(this.m_list_wnd.Total()==total ? total : this.m_list_wnd.Total());
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,value);
  }
//+------------------------------------------------------------------+

Метод для проверки изменений количества окон графика и пересоздания их списка:

//+------------------------------------------------------------------+
//| Проверяет и пересоздаёт список окон графика                      |
//+------------------------------------------------------------------+
void CChartObj::RecreateWindowsList(const int change)
  {
//--- Если удалено окно
   if(change<0)
     {
      //--- Если на графике всего одно окно, то это значит, что имеем только основной график без подокон,
      //--- а изменение количества окон графика указывает нам на удаление графика символа в терминале.
      //--- Такая ситуация обрабатывается в классе-коллекции объектов-чартов - уходим из метода
      if(this.WindowsTotal()==1)
         return;
      //--- Получаем последний удалённый индикатор из списка удалённых индикаторов
      CWndInd *ind=this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);
      //--- Если индикатор удалось получить
      if(ind!=NULL)
        {
         //--- создаём новый объект-окно графика
         CChartWnd *wnd=new CChartWnd();
         if(wnd!=NULL)
           {
            //--- Устанавливаем для нового объекта номер подокна из последнего удалённого объекта-индикатора,
            //--- идентификатор и наименование символа данного объекта-чарта
            wnd.SetWindowNum(ind.WindowNum());
            wnd.SetChartID(this.ID());
            wnd.SetSymbol(this.Symbol());
            //--- Если не удалось добавить созданный объект в список удалённых объектов-окон чарта - удаляем его
            if(!this.m_list_wnd_del.Add(wnd))
               delete wnd;
           }
        }
      //--- Вызываем метод для отправки события на график управляющей программы и пересоздаём список окон графика
      this.SendEvent(CHART_OBJ_EVENT_CHART_WND_DEL);
      this.CreateWindowsList();
      return;
     }
//--- Если нет изменений - уходим
   else if(change==0)
      return;

//--- Если добавлено окно
   //--- Получаем общее количество окон графика из окружения
   int total=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL);
   //--- В цикле по общему количеству окон
   for(int i=0;i<total;i++)
     {
      //--- Создаём новый объект-окно чарта
      CChartWnd *wnd=new CChartWnd(this.m_chart_id,i,this.Symbol(),this.m_list_ind_del,this.m_list_ind_param);
      if(wnd==NULL)
         continue;
      this.m_list_wnd.Sort(SORT_BY_CHART_WINDOW_NUM);
      //--- Если номер окна больше 0 (не главное окно графика) и в нём ещё нет индикатора,
      //--- или такое окно уже есть в списке, или объект-окно не добавлен в список
      //--- то удаляем вновь созданный объект-окно чарта и идём на следующую итерацию цикла
      if((wnd.WindowNum()!=0 && wnd.IndicatorsTotal()==0) || this.m_list_wnd.Search(wnd)>WRONG_VALUE || !this.m_list_wnd.Add(wnd))
        {
         delete wnd;
         continue;
        }
      //--- Если добавлено окно - вызываем метод для отправки события на график управляющей программы
      this.SendEvent(CHART_OBJ_EVENT_CHART_WND_ADD);
     }
   //--- Если количество объектов в списке соответствует количеству окон на графике,
   //--- то записываем это значение в свойство объекта-чарта
   //--- Если же количество объектов в списке не соответствует количеству окон на графике,
   //--- то в свойство объекта-чарта записываем значение количества объектов в списке.
   int value=int(this.m_list_wnd.Total()==total ? total : this.m_list_wnd.Total());
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,value);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Создаёт и отправляет событие графика                             |
//| на график управляющей программы                                  |
//+------------------------------------------------------------------+
void CChartObj::SendEvent(ENUM_CHART_OBJ_EVENT event)
  {
   //--- Если добавлено окно
   if(event==CHART_OBJ_EVENT_CHART_WND_ADD)
     {
      //--- Получаем последний добавленный в список объект-окно графика
      CChartWnd *wnd=this.GetLastAddedWindow();
      if(wnd==NULL)
         return;
      //--- Отправляем событие CHART_OBJ_EVENT_CHART_WND_ADD на график управляющей программы
      //--- в lparam передаём идентификатор графика,
      //--- в dparam передаём номер окна графика,
      //--- в sparam передаём символ графика
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
     }
   //--- Если удалено окно
   else if(event==CHART_OBJ_EVENT_CHART_WND_DEL)
     {
      //--- Получаем последний объект-окно графика, добавленный в список удалённых окон
      CChartWnd *wnd=this.GetLastDeletedWindow();
      if(wnd==NULL)
         return;
      //--- Отправляем событие CHART_OBJ_EVENT_CHART_WND_DEL на график управляющей программы
      //--- в lparam передаём идентификатор графика,
      //--- в dparam передаём номер окна графика,
      //--- в sparam передаём символ графика
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
     }
  }
//+------------------------------------------------------------------+

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

Вопросы по методам можно задать в обсуждении к статье.

Доработаем класс коллекции объектов-чартов в файле \MQL5\Include\DoEasy\Collections\ChartObjCollection.mqh.

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

//+------------------------------------------------------------------+
//| Коллекция объектов-mql5-сигналов                                 |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
private:
   CListObj                m_list;                                   // Список объектов-чартов
   CListObj                m_list_del;                               // Список удалённых объектов-чартов
   CArrayObj               m_list_wnd_del;                           // Список удалённых объектов окон графика
   CArrayObj               m_list_ind_del;                           // Список удалённых из окна индикаторов
   CArrayObj               m_list_ind_param;                         // Список изменённых индикаторов
   int                     m_charts_total_prev;                      // Прошлое количество чартов в терминале
   //--- Возвращает количество чартов в терминале
   int                     ChartsTotal(void) const;
   //--- Возвращает флаг существования (1) объекта-чарта, (2) чарта
   bool                    IsPresentChartObj(const long chart_id);
   bool                    IsPresentChart(const long chart_id);
   //--- Создаёт новый объект-чарт и добавляет его в список
   bool                    CreateNewChartObj(const long chart_id,const string source);
   //--- Находит отсутствующий объект-чарт, создаёт его и добавляет в список-коллекцию
   bool                    FindAndCreateMissingChartObj(void);
   //--- Находит и удаляет из списка объект-чарт, отсутствующий в терминале
   void                    FindAndDeleteExcessChartObj(void);
public:

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

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

public:
//--- Возвращает (1) себя, (2) список-коллекцию объектов-чартов, (3) список удалённых объектов-чартов, 
//--- список (4) удалённых объектов-окон, (5) удалённых, (6) изменённых индикаторов
   CChartObjCollection    *GetObject(void)                                 { return &this;                  }
   CArrayObj              *GetList(void)                                   { return &this.m_list;           }
   CArrayObj              *GetListDeletedCharts(void)                      { return &this.m_list_del;       }
   CArrayObj              *GetListDeletedWindows(void)                     { return &this.m_list_wnd_del;   }
   CArrayObj              *GetListDeletedIndicators(void)                  { return &this.m_list_ind_del;   }
   CArrayObj              *GetListChangedIndicators(void)                  { return &this.m_list_ind_param; }
   //--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj              *GetList(ENUM_CHART_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
//--- Возвращает количество объектов-чартов в списке
   int                     DataTotal(void)                           const { return this.m_list.Total();    }
//--- Выводит в журнал (1) полное, (2) краткое описание коллекции
   void                    Print(void);
   void                    PrintShort(void);
//--- Конструктор
                           CChartObjCollection();

//--- Возвращает список объектов-чартов по (1) символу, (2) таймфрейму
   CArrayObj              *GetChartsList(const string symbol)              { return this.GetList(CHART_PROP_SYMBOL,symbol,EQUAL);      }
   CArrayObj              *GetChartsList(const ENUM_TIMEFRAMES timeframe)  { return this.GetList(CHART_PROP_TIMEFRAME,timeframe,EQUAL);}
//--- Возвращает указатель на объект-чарт (1) по идентификатору, (2) по индексу в списке
   CChartObj              *GetChart(const long id);
   CChartObj              *GetChart(const int index)                       { return this.m_list.At(index);                             }
//--- Возвращает (1) последний добавленный чарт, (2) последний удалённый чарт
   CChartObj              *GetLastAddedChart(void)                         { return this.m_list.At(this.m_list.Total()-1);             }
   CChartObj              *GetLastDeletedChart(void)                       { return this.m_list_del.At(this.m_list_del.Total()-1);     }
   
//--- Возвращает (1) последнее добавленное окно на чарт по идентификатору чарта, (2) последнее удалённое окно чарта
   CChartWnd              *GetLastAddedChartWindow(const long chart_id); 
   CChartWnd              *GetLastDeletedChartWindow(void)                 { return this.m_list_wnd_del.At(this.m_list_wnd_del.Total()-1);}
   
//--- Возвращает (1) последний добавленный в указанное окно указанного графика, (2) последний удалённый из окна, (3) изменённый индикатор
   CWndInd                *GetLastAddedIndicator(const long chart_id,const int win_num);
   CWndInd                *GetLastDeletedIndicator(void)                   { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);   }
   CWndInd                *GetLastChangedIndicator(void)                   { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);}
//--- Возвращает индикатор по индексу из указанного окна указанного графика
   CWndInd                *GetIndicator(const long chart_id,const int win_num,const int ind_index);

//--- Возвращает идентификатор графика с программой
   long                    GetMainChartID(void)                      const { return CBaseObj::GetMainChartID();                        }
//--- Создаёт список-коллекцию объектов-чартов
   bool                    CreateCollection(void);
//--- Обновляет (1) список-коллекцию объектов-чартов, (2) указанный объект-чарт
   void                    Refresh(void);
   void                    Refresh(const long chart_id);

//--- (1) Открывает новый график с указанным символом и периодом, (2) закрывает указанный график
   bool                    Open(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool                    Close(const long chart_id);
   
//--- Создаёт и отправляет событие чарта на график управляющей программы
   void                    SendEvent(ENUM_CHART_OBJ_EVENT event);
  
  };
//+------------------------------------------------------------------+

Рассмотрим реализацию новых методов и доработку существующих.

В конструкторе класса очищаем новые списки и устанавливаем им флаги сортированных списков:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CChartObjCollection::CChartObjCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list_ind_del.Clear();
   this.m_list_ind_del.Sort();
   this.m_list_ind_param.Clear();
   this.m_list_ind_param.Sort();
   this.m_list_wnd_del.Clear();
   this.m_list_wnd_del.Sort();
   this.m_list_del.Clear();
   this.m_list_del.Sort();
   this.m_list.Type(COLLECTION_CHARTS_ID);
   this.m_charts_total_prev=this.ChartsTotal();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Обновляет список-коллекцию объектов-чартов                       |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(void)
  {
   //--- В цикле по количеству объектов-чартов в списке
   for(int i=0;i<this.m_list.Total();i++)
     {
      //--- получаем очередной объект-чарт и
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      //--- обновляем его
      chart.Refresh();
     }
   //--- Получаем количество открытых графиков в терминале и
   int charts_total=this.ChartsTotal();
   //--- рассчитываем разницу между количеством открытых графиков в терминале
   //--- и объектов-чартов в списке-коллекции
   int change=charts_total-this.m_list.Total();
   //--- Если нет изменений - уходим
   if(change==0)
      return;
   //--- Если добавлен график в терминале
   if(change>0)
     {
      //--- Находим недостающий объект-чарт, создаём и добавляем его в список-коллекцию
      this.FindAndCreateMissingChartObj();
      //--- Получаем текущий график и возвращаемся к нему т.к.
      //--- добавление нового графика переключает фокус на него
      CChartObj *chart=this.GetChart(GetMainChartID());
      if(chart!=NULL)
         chart.SetBringToTopON(true);
      for(int i=0;i<change;i++)
        {
         chart=m_list.At(m_list.Total()-(1+i));
         if(chart==NULL)
            continue;
         this.SendEvent(CHART_OBJ_EVENT_CHART_OPEN);
        }
     }
   //--- Если удалён график в терминале
   else if(change<0)
     {
      //--- Находим лишний объект-чарт в списке-коллекции и удаляем его из списка
      this.FindAndDeleteExcessChartObj();
      for(int i=0;i<-change;i++)
        {
         CChartObj *chart=this.m_list_del.At(this.m_list_del.Total()-(1+i));
         if(chart==NULL)
            continue;
         this.SendEvent(CHART_OBJ_EVENT_CHART_CLOSE);
        }
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт новый объект-чарт и добавляет его в список               |
//+------------------------------------------------------------------+
bool CChartObjCollection::CreateNewChartObj(const long chart_id,const string source)
  {
   ::ResetLastError();
   CChartObj *chart_obj=new CChartObj(chart_id,this.GetListDeletedWindows(),this.GetListDeletedIndicators(),this.GetListChangedIndicators());
   if(chart_obj==NULL)
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,true);
      return false;
     }
   this.m_list.Sort(SORT_BY_CHART_ID);
   if(!this.m_list.InsertSort(chart_obj))
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,true);
      delete chart_obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает последнее добавленное окно на чарт по идентификатору  |
//+------------------------------------------------------------------+
CChartWnd* CChartObjCollection::GetLastAddedChartWindow(const long chart_id)
  {
   CChartObj *chart=this.GetChart(chart_id);
   if(chart==NULL)
      return NULL;
   CArrayObj *list=chart.GetList();
   return(list!=NULL ? list.At(list.Total()-1) : NULL);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает последний добавленный индикатор                       |
//| в указанное окно указанного графика                              |
//+------------------------------------------------------------------+
CWndInd* CChartObjCollection::GetLastAddedIndicator(const long chart_id,const int win_num)
  {
   CChartObj *chart=this.GetChart(chart_id);
   return(chart!=NULL ? chart.GetLastAddedIndicator(win_num) : NULL);
  }
//+------------------------------------------------------------------+

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

Метод, возвращающий индикатор по индексу из указанного окна указанного графика:

//+------------------------------------------------------------------+
//| Возвращает индикатор по индексу                                  |
//| из указанного окна указанного графика                            |
//+------------------------------------------------------------------+
CWndInd* CChartObjCollection::GetIndicator(const long chart_id,const int win_num,const int ind_index)
  {
   CChartObj *chart=this.GetChart(chart_id);
   return(chart!=NULL ? chart.GetIndicator(win_num,ind_index) : NULL);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//|Находит и удаляет из списка объект-чарт, отсутствующий в терминале|
//+------------------------------------------------------------------+
void CChartObjCollection::FindAndDeleteExcessChartObj(void)
  {
   for(int i=this.m_list.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      if(!this.IsPresentChart(chart.ID()))
        {
         chart=this.m_list.Detach(i);
         if(chart!=NULL)
           {
            if(!this.m_list_del.Add(chart))
               this.m_list.Delete(i);
           }
        }
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт и отправляет событие графика                             |
//| на график управляющей программы                                  |
//+------------------------------------------------------------------+
void CChartObjCollection::SendEvent(ENUM_CHART_OBJ_EVENT event)
  {
   //--- Если добавлен график
   if(event==CHART_OBJ_EVENT_CHART_OPEN)
     {
      //--- Получаем последний добавленный в список объект-чарт
      CChartObj *chart=this.GetLastAddedChart();
      if(chart==NULL)
         return;
      //--- Отправляем событие CHART_OBJ_EVENT_CHART_OPEN на график управляющей программы
      //--- в lparam передаём идентификатор графика,
      //--- в dparam передаём таймфрейм графика,
      //--- в sparam передаём символ графика
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
     }
   //--- Если удалён график
   else if(event==CHART_OBJ_EVENT_CHART_CLOSE)
     {
      //--- Получаем последний объект-чарт, добавленный в список удалённых графиков
      CChartObj *chart=this.GetLastDeletedChart();
      if(chart==NULL)
         return;
      //--- Отправляем событие CHART_OBJ_EVENT_CHART_CLOSE на график управляющей программы
      //--- в lparam передаём идентификатор графика,
      //--- в dparam передаём таймфрейм графика,
      //--- в sparam передаём символ графика
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
     }
  }
//+------------------------------------------------------------------+

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


Отслеживаем события чартов

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

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

В файле \MQL5\Include\DoEasy\Engine.mqh класса главного объекта библиотеки впишем методы доступа к новым методам коллекции объектов-чартов:

//--- Возвращает список объектов-чартов по (1) символу, (2) таймфрейму
   CArrayObj           *GetListCharts(const string symbol)                             { return this.m_charts.GetChartsList(symbol);         }
   CArrayObj           *GetListCharts(const ENUM_TIMEFRAMES timeframe)                 { return this.m_charts.GetChartsList(timeframe);      }
//--- Возвращает список удалённых (1) объектов-чартов, (2) окон чартов, (3) индикаторов в окне чарта, (4) изменённых индикаторов в окне чарта
   CArrayObj           *GetListChartsClosed(void)                                      { return this.m_charts.GetListDeletedCharts();        }
   CArrayObj           *GetListChartWindowsDeleted(void)                               { return this.m_charts.GetListDeletedWindows();       }
   CArrayObj           *GetListChartWindowsIndicatorsDeleted(void)                     { return this.m_charts.GetListDeletedIndicators();    }
   CArrayObj           *GetListChartWindowsIndicatorsChanged(void)                     { return this.m_charts.GetListChangedIndicators();    }

//--- Возвращает (1) указанный объект-чарт, (2) объект-чарт с программой
   CChartObj           *ChartGetChartObj(const long chart_id)                          { return this.m_charts.GetChart(chart_id);            }
   CChartObj           *ChartGetMainChart(void)                                        { return this.m_charts.GetChart(this.m_charts.GetMainChartID());}
//--- Возвращает объект-чарт последнего (1) открытого (2) закрытого графика
   CChartObj           *ChartGetLastOpenedChart(void)                                  { return this.m_charts.GetLastAddedChart();           }
   CChartObj           *ChartGetLastClosedChart(void)                                  { return this.m_charts.GetLastDeletedChart();         }

//--- Возвращает объект (1) последнего добавленного окна указанного чарта, (2) последнего удалённого окна чарта
   CChartWnd           *ChartGetLastAddedChartWindow(const long chart_id)              { return this.m_charts.GetLastAddedChartWindow(chart_id);}
   CChartWnd           *ChartGetLastDeletedChartWindow(void)                           { return this.m_charts.GetLastDeletedChartWindow();   }
   
//--- Возвращает (1) последний добавленный в указанное окно указанного графика, (2) последний удалённый из окна, (3) изменённый индикатор
   CWndInd             *ChartGetLastAddedIndicator(const long id,const int win)        { return m_charts.GetLastAddedIndicator(id,win);      }
   CWndInd             *ChartGetLastDeletedIndicator(void)                             { return this.m_charts.GetLastDeletedIndicator();     }
   CWndInd             *ChartGetLastChangedIndicator(void)                             { return this.m_charts.GetLastChangedIndicator();     }
//--- Возвращает индикатор по индексу из указанного окна указанного графика
   CWndInd             *ChartGetIndicator(const long chart_id,const int win_num,const int ind_index)
                          {
                           return m_charts.GetIndicator(chart_id,win_num,ind_index);
                          }

//--- Возвращает количество чартов в списке-коллекции
   int                  ChartsTotal(void)                                              { return this.m_charts.DataTotal();                   }

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

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

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

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

//--- Обработка событий таймсерий
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- Событие "Новый бар"
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
        }
     }
     
//--- Обработка событий чартов
   else if(idx>CHART_OBJ_EVENT_NO_EVENT && idx<CHART_OBJ_EVENTS_NEXT_CODE)
     {
      //--- Событие "Открытие нового чарта"
      if(idx==CHART_OBJ_EVENT_CHART_OPEN)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
         CChartObj *chart=engine.ChartGetLastOpenedChart();
         if(chart!=NULL)
           {
            string symbol=sparam;
            long chart_id=lparam;
            ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
            string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id;
            Print(DFUN,CMessage::Text(MSG_CHART_COLLECTION_CHART_OPENED),": ",header);
           }
        }
      //--- Событие "Закрытие чарта"
      if(idx==CHART_OBJ_EVENT_CHART_CLOSE)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
         CChartObj *chart=engine.ChartGetLastClosedChart();
         if(chart!=NULL)
           {
            string symbol=sparam;
            long   chart_id=lparam;
            ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
            string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id;
            Print(DFUN,CMessage::Text(MSG_CHART_COLLECTION_CHART_CLOSED),": ",header);
           }
        }
      //--- Событие "Добавление нового окна на чарт"
      if(idx==CHART_OBJ_EVENT_CHART_WND_ADD)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name="";
         string symbol=sparam;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id+": ";
         
         CChartObj *chart=engine.ChartGetLastOpenedChart();
         if(chart!=NULL)
           {
            timeframe=chart.Timeframe();
            CChartWnd *wnd=engine.ChartGetLastAddedChartWindow(chart.ID());
            if(wnd!=NULL)
              {
               CWndInd *ind=wnd.GetLastAddedIndicator();
               if(ind!=NULL)
                  ind_name=ind.Name();
              }
           }
         Print(DFUN,header,CMessage::Text(MSG_CHART_OBJ_WINDOW_ADDED)," ",(string)win_num," ",ind_name);
        }
      //--- Событие "Удаление окна с чарта"
      if(idx==CHART_OBJ_EVENT_CHART_WND_DEL)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
         CChartWnd *wnd=engine.ChartGetLastDeletedChartWindow();
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string symbol=sparam;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id+": ";
         Print(DFUN,header,CMessage::Text(MSG_CHART_OBJ_WINDOW_REMOVED)," ",(string)win_num);
        }
      //--- Событие "Добавление нового индикатора в окно чарта"
      if(idx==CHART_OBJ_EVENT_CHART_WND_IND_ADD)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name=sparam;
         string symbol=NULL;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=NULL;
         
         CWndInd *ind=engine.ChartGetLastAddedIndicator(chart_id,win_num);
         if(ind!=NULL)
           {
            CChartObj *chart=engine.ChartGetChartObj(chart_id);
            if(chart!=NULL)
              {
               symbol=chart.Symbol();
               timeframe=chart.Timeframe();
               CChartWnd *wnd=chart.GetWindowByNum(win_num);
               if(wnd!=NULL)
                  header=wnd.Header();
              }
           }
         Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_ADDED)," ",ind_name);
        }
      //--- Событие "Удаление индикатора из окна чарта"
      if(idx==CHART_OBJ_EVENT_CHART_WND_IND_DEL)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name=sparam;
         string symbol=NULL;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=NULL;
         
         CWndInd *ind=engine.ChartGetLastDeletedIndicator();
         if(ind!=NULL)
           {
            CChartObj *chart=engine.ChartGetChartObj(chart_id);
            if(chart!=NULL)
              {
               symbol=chart.Symbol();
               timeframe=chart.Timeframe();
               CChartWnd *wnd=chart.GetWindowByNum(win_num);
               if(wnd!=NULL)
                  header=wnd.Header();
              }
           }
         Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_REMOVED)," ",ind_name);
        }
      //--- Событие "Изменение параметров индикатора в окне чарта"
      if(idx==CHART_OBJ_EVENT_CHART_WND_IND_CHANGE)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name=sparam;
         string symbol=NULL;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=NULL;
         
         CWndInd *ind=NULL;
         CWndInd *ind_changed=engine.ChartGetLastChangedIndicator();
         if(ind_changed!=NULL)
           {
            ind=engine.ChartGetIndicator(chart_id,win_num,ind_changed.Index());
            if(ind!=NULL)
              {
               CChartObj *chart=engine.ChartGetChartObj(chart_id);
               if(chart!=NULL)
                 {
                  symbol=chart.Symbol();
                  timeframe=chart.Timeframe();
                  CChartWnd *wnd=chart.GetWindowByNum(win_num);
                  if(wnd!=NULL)
                     header=wnd.Header();
                 }
              }
           }
         Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_CHANGED)," ",ind_name," >>> ",ind.Name());
        }
     }
     
//--- Обработка торговых событий

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

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

Откроем любой новый график символа — в журнале получим сообщение из обработчика OnDoEasyEvent():

OnDoEasyEvent: Open chart: AUDNZD H4, ID 131733844391938634

Добавим на открытый график новое окно любого индикатора-осциллятора — в журнале получим сообщение из обработчика OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H1, ID 131733844391938634: Added subwindow 1 Momentum(14)

Добавим на открытый график любой индикатор, рисуемый в главном окне — в журнале получим сообщение из обработчика OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Added indicator AMA(14,2,30)

Изменим параметры осциллятора — в журнале получим сообщение из обработчика OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Chart subwindow 1: Changed indicator Momentum(14) >>> Momentum(20)

Изменим параметры индикатора в главном окне — в журнале получим сообщение из обработчика OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Changed indicator AMA(14,2,30) >>> AMA(20,2,30)

Удалим окно осциллятора — в журнале получим два сообщения из обработчика OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634: Removed indicator Momentum(20)
OnDoEasyEvent: AUDNZD H1, ID 131733844391938634: Removed subwindow 1

Удалим индикатор из главного окна — в журнале получим сообщение из обработчика OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Removed indicator AMA(20,2,30)

Закроем ранее открытое окно графика — в журнале получим сообщение из обработчика OnDoEasyEvent():

OnDoEasyEvent: Closed chart: AUDNZD H4, ID 131733844391938634

Как видим, все события обрабатываются верно и отсылаются в управляющую программу.

Что дальше

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

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

К содержанию

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

Прочие классы в библиотеке DoEasy (Часть 67): Класс объекта-чарта
Прочие классы в библиотеке DoEasy (Часть 68): Класс объекта-окна графика и классы объектов-индикаторов в окне графика
Прочие классы в библиотеке DoEasy (Часть 69): Класс-коллекция объектов-чартов
Прочие классы в библиотеке DoEasy (Часть 70): Расширение функционала и автообновление коллекции объектов-чартов

Прикрепленные файлы |
MQL5.zip (4005.93 KB)
Советы профессионального программиста (Часть II): Организация хранения и обмена параметров между экспертом, скриптами и внешними программами Советы профессионального программиста (Часть II): Организация хранения и обмена параметров между экспертом, скриптами и внешними программами
Советы профессионального программиста о методах, приемах и вспомогательных инструментах, облегчающих программирование. Речь пойдет о параметрах, которые можно восстанавливать после перезапуска (закрытия) терминала. Все примеры — реально работающие куски кода из моего проекта Cayman.
Прочие классы в библиотеке DoEasy (Часть 70): Расширение функционала и автообновление коллекции объектов-чартов Прочие классы в библиотеке DoEasy (Часть 70): Расширение функционала и автообновление коллекции объектов-чартов
В статье расширим функционал объектов-чартов, организуем навигацию по графикам, создание скриншотов, сохранение и применение шаблонов к графикам. Также сделаем автоматическое обновление коллекции объектов-чартов, их окон и индикаторов в них.
Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции
В статье завершим работу над классами объектов-чартов и их коллекцией. Сделаем автоматическое отслеживание изменения свойств чартов и их окон, а также сохранение новых параметров в свойства объекта. Такая доработка позволит в будущем сделать событийный функционал для всей коллекции чартов.
Свопы (Часть I) : Локирование и синтетические позиции Свопы (Часть I) : Локирование и синтетические позиции
В данной статье я постараюсь расширить классическую концепцию своповых методов торговли, а также расскажу, почему я пришел к выводу, что данная концепция, на мой взгляд, заслуживает особого внимания и абсолютно рекомендована к ознакомлению и изучению.