Прочие классы в библиотеке DoEasy (Часть 71): События коллекции объектов-чартов
Содержание
Концепция
В прошлой статье мы сделали автоматическое обновление некоторых свойств объектов-чартов и относящихся к ним объектов — открытие нового/закрытие существующего графика символа (объекта-чарта), добавление нового или удаление существующего окна индикатора из объекта-чарта и добавление нового/удаление или изменение существующего индикатора в окне чарта.
Сегодня создадим в библиотеке событийный функционал для объектов-чартов, объектов-окон чартов и объектов-индикаторов в окне чарта, который будет отправлять на график управляющей программы пользовательские события при регистрации вышеперечисленных событий с объектами.
Сразу стоит оговориться, что работа с чартами у нас реализована для ручного управления ими, т. е. когда пользователь сам вручную или при помощи элементов управления своей программы производит изменения в графиках в своём терминале — шаг за шагом, единовременно производит операции с одним объектом. Программное изменение сразу нескольких объектов за один тик таймера может привести к неправильному определению происходящих событий — скорее всего будет фиксироваться только последнее событие в лучшем случае, в худшем — будет неправильное определение объектов, над которыми были произведены операции их изменения. Мы, конечно же, будем писать код с учётом такой вероятности, и там, где возможно нетрудозатратно сделать код, учитывающий возможность пакетного изменения параметров объектов, будем его делать именно таким. Но прорабатывать и тестировать программную одновременную работу со множеством элементов графиков мы пока не будем — если будут такие запросы от пользователей библиотеки, то мы вернёмся к доработке создаваемого сегодня функционала.
Доработка классов библиотеки
Добавим новые сообщения в библиотеку. В файле \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): Расширение функционала и автообновление коллекции объектов-чартов
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования