- Типы объектов и особенности указания их координат
- Объекты с привязкой ко времени и цене
- Объекты с привязкой к экранным координатам
- Создание объектов
- Удаление объектов
- Поиск объектов
- Обзор функций доступа к свойствам объектов
- Основные свойства объектов
- Координаты времени и цены
- Угол окна привязки и экранные координаты
- Определение точки привязки на объекте
- Управление состоянием объекта
- Приоритет объектов (Z-порядок)
- Настройка отображения объекта: цвет, стиль и рамка
- Настройки шрифта
- Поворот текста на произвольный угол
- Определение ширины и высоты объектов
- Видимость объектов в разрезе таймфреймов
- Назначение кода символа для метки
- Свойства лучей для объектов c прямыми линиями
- Управление нажатым состоянием объекта
- Настройка изображений в объектах-картинках
- Кадрирование (вывод части) изображения
- Свойства поля ввода: выравнивание и "только чтение"
- Ширина канала стандартного отклонения
- Настройка уровней в объектах с их поддержкой
- Дополнительные свойства Ганна, Фибоначчи и Эллиота
- Объект-график
- Перемещение объектов
- Получение времени или цены в заданных точках линий
Обзор функций доступа к свойствам объектов по типам значений
Объекты обладают свойствами различных типов, которые можно читать и устанавливаться с помощью нескольких ObjectGet- и ObjectSet-функций. Как мы знаем, данный принцип уже применялся для графиков (см. раздел Обзор функций для работы с полным набором свойств графиков).
Все такие функции принимают в качестве первых трех параметров идентификатор графика, название объекта и идентификатор свойства, который должен быть элементом одного из перечислений ENUM_OBJECT_PROPERTY_INTEGER, ENUM_OBJECT_PROPERTY_DOUBLE или ENUM_OBJECT_PROPERTY_STRING. Конкретные свойства мы изучим постепенно в следующих разделах. Их полные сводные таблицы можно найти в документации по MQL5, на странице Свойства объектов.
Следует отметить, что идентификаторы свойств во всех трех перечислениях не пересекаются, что позволяет объединять в единый унифицированный код их совместную обработку. Мы воспользуется этим в примерах.
Некоторые свойства доступны только на чтение и будут помечаться "r/o" (read-only).
Также как и в случае API для графиков, функции чтения свойств имеют краткую и полную форму: краткая — непосредственно возвращает затребованное значение, а полная — логический признак успеха (true) или ошибки (false), а само значение помещается в последний параметр, передаваемый по ссылке. Отсутствие ошибки при вызове краткой формы следует проверять с помощью встроенной переменной _LastError.
При обращении к некоторым свойствам необходимо указывать дополнительный параметр (modifier), который служит для указания номера значения или уровня, если свойство является многозначным. Например, если у объекта несколько точек привязки, то модификатор позволяет выбрать конкретную из них.
Ниже приведены прототипы функций для чтения и записи целочисленных свойств. Обратите внимание, что тип значений в них — это long, что позволяет хранить свойства не только типов int или long, но также bool, color, datetime и различных перечислений (см. далее).
bool ObjectSetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, long value)
bool ObjectSetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, int modifier, long value)
long ObjectGetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, int modifier = 0)
bool ObjectGetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, int modifier, long &value)
Аналогично описаны функции для вещественных свойств.
bool ObjectSetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, double value)
bool ObjectSetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, int modifier, double value)
double ObjectGetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, int modifier = 0)
bool ObjectGetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, int modifier, double &value)
Наконец, четверка таких же функций существует и для строк.
bool ObjectSetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, const string value)
bool ObjectSetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, int modifier, const string value)
string ObjectGetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, int modifier = 0)
bool ObjectGetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, int modifier, string &value)
В целях повышения быстродействия все функции установки свойств объекта (ObjectSetInteger, ObjectSetDouble, ObjectSetString) являются асинхронными и фактически только отправляют графику команды на изменение объекта. При их успешном выполнении команды попадают в общую очередь событий графика, о чем сигнализирует возвращаемый результат true. При возникновении ошибки функции вернут false, а код ошибки необходимо проверять в переменной _LastError.
Изменение свойств объектов производится отложено, в процессе обработки очереди событий графика, то есть, с некоторой задержкой. Для принудительного обновления внешнего вида и свойств объектов на графике, в особенности после изменения сразу множества объектов, используйте функцию ChartRedraw.
Функции получения свойств графика (ObjectGetInteger, ObjectGetDouble, ObjectGetString) являются синхронными, то есть вызывающий код дожидается результата их выполнения. При этом выполняются все команды в очереди графика, чтобы получить актуальное значение свойств.
Вернемся к примеру скрипта по удалению объектов, точнее, к его новой версии ObjectCleanup2.mq5. Напомним, что в функции CustomDeleteAllObjects мы хотели реализовать возможность отбирать объекты на основе их свойств. Допустим, что таковыми свойствами должны быть цвет и точка привязки. Для их получения следует использовать функцию ObjectGetInteger и пару элементов перечисления ENUM_OBJECT_PROPERTY_INTEGER: OBJPROP_COLOR и OBJPROP_ANCHOR. Мы подробно рассмотрим их позднее.
С учетом этой информации код дополнился бы следующими проверками (здесь для простоты цвет и точка привязки заданы константами clrRed и ANCHOR_TOP — на самом деле мы предусмотрим для них входные переменные):
int CustomDeleteAllObjects(const long chart, const string prefix,
|
Обратим внимание на строки с ObjectGetInteger.
Их запись длинна и содержит некоторую тавтологию, потому что конкретные свойства привязаны к ObjectGet-функциям известных типов. Кроме того, по мере увеличения количества условий могут показаться избыточными повторения идентификатора графика и имени объекта.
Чтобы упростить запись обратимся к технологии, которую мы опробовали в файле ChartModeMonitor.mqh в разделе Режимы отображения графика. Её суть в том, чтобы описать класс-посредник с перегрузками методов для чтения и записи свойств всех типов. Назовем новый заголовочный файл ObjectMonitor.mqh.
Класс ObjectProxy почти полностью повторяет для объектов структуру класса ChartModeMonitorInterface для графиков. Основное отличие заключается в наличии виртуальных методов для установки и получения идентификатора графика и имени объекта.
class ObjectProxy
|
Реализуем эти методы в классе-наследнике (позже дополним иерархию классов монитором свойств объектов подобно монитору свойств графика).
class ObjectSelector: public ObjectProxy
|
Мы разделили абстрактный интерфейс ObjectProxy и его минимальную реализацию в ObjectSelector, потому что позднее вам может потребоваться реализовать, например, массив посредников для множества однотипных объектов. Тогда достаточно в новом классе "мультиселектора" хранить массив имен или их общий префикс и обеспечивать возврат одного из них из метода name за счет вызова перегруженного оператора []: multiSelector[i].get(OBJPROP_XYZ).
Теперь вернемся к скрипту ObjectCleanup2.mq5 и опишем две входных переменных для задания цвета и точки привязки, как дополнительные условия для отбора объектов, подлежащих удалению.
// ObjectCleanup2.mq5
|
Передадим эти значения в функцию CustomDeleteAllObjects, а новые проверки условий в цикле по объектам можно сформулировать более компактно благодаря классу-посреднику.
#include <MQL5Book/ObjectMonitor.mqh>
|
Важно отметить, что имя объекта (и неявный идентификатор текущего графика 0) мы указываем лишь раз — при создании объекта ObjectSelector. Далее все свойства запрашиваются методом get с единственным параметром, описывающим требуемое свойство, а соответствующую ObjectGet-функцию компилятор выберет сам.
Дополнительная проверка на код ошибки 4203 (OBJECT_WRONG_PROPERTY) позволяет отсеять объекты, которые не имеют запрашиваемого свойства, такого как OBJPROP_ANCHOR, например. Таким способом, в частности, можно сделать выборку, в которую попадут все типы стрелок (без необходимости, по отдельности запрашивать разные типы OBJ_ARROW_XYZ), но будут исключены из обработки линии и "события".
Это легко проверить, запустив на графике сначала скрипт ObjectSimpleShowcase.mq5 (он создаст 14 объектов разных типов), а затем ObjectCleanup2.mq5. Если включить режим UseCustomDeleteAll, на графике останется 5 неудаленных объектов: OBJ_VLINE, OBJ_HLINE, OBJ_ARROW_BUY, OBJ_ARROW_SELL и OBJ_EVENT. Первые два и последний не имеют свойства OBJPROP_ANCHOR, а стрелки покупки и продажи не проходят по цвету (предполагается, что цвет всех остальных созданных объектов по умолчанию — красный).
Однако ObjectSelector задуман не только ради вышеприведенного простого применения. На самом деле он является основой для создания монитора свойств отдельного объекта, аналогичного тому, что был реализован для графиков. Поэтому заголовочный файл ObjectMonitor.mqh содержит нечто более интересное.
class ObjectMonitorInterface: public ObjectSelector
|
Данный набор методов должен напомнить вам ChartModeMonitorInterface из ChartModeMonitor.mqh. Единственное новшество — это метод applyChanges: он предназначен для копирования свойств одного объекта в другой.
Отталкиваясь от ObjectMonitorInterface описана базовая реализация монитора свойств для пары шаблонных типов: типа значений свойств (одного из long, double, string) и типа перечислений (одного из ENUM_OBJECT_PROPERTY_-что-то там).
template<typename T,typename E>
|
Конструктор ObjectMonitorBase имеет два параметра: имя объекта и массив флагов с идентификаторами свойств, подлежащих наблюдению в указанном объекте. Большая часть этого кода почти 1 в 1 повторяет ChartModeMonitor. В частности, как и ранее, массив флагов передается во вспомогательный метод detect, основное назначение которого выявить те целочисленные константы, которые являются элементами перечисления E, и отсеять все остальные. Единственное дополнение, которое следует пояснить, — получение свойства с количеством уровней в объекте посредством ObjectGetInteger(0, id, OBJPROP_LEVELS). Это нужно, чтобы поддержать перебор свойств со множественными значениями из-за наличия уровней (например, Фибоначчи). У объектов без уровней мы получим количество 0, и такое свойство будет привычным, скалярным.
public:
|
Разумеется, метод detect несколько отличается от того, что мы видели в ChartModeMonitor. Напомним, что для начала в нем идет фрагмент с проверкой константы v на принадлежность перечислению E с помощью вызова функции EnumToString: если такого элемента в перечислении нет, будет взведен код ошибки. Если же элемент есть, мы складываем значение соответствующего свойства в массив data.
// ChartModeMonitor.mqh
|
В мониторе объектов мы вынуждены усложнить данную схему, поскольку некоторые свойства являются многозначными из-за параметра modifier в ObjectGet- и ObjectSet-функциях.
Поэтому мы вводим статический массив modifiables с перечнем тех свойств, которые поддерживают модификаторы (каждое свойство будет подробно рассмотрено в дальнейшем). Суть в том, что для подобных многозначных свойств необходимо их считывать и сохранять в массив data не единожды, а несколько раз.
// ObjectMonitor.mqh
|
Здесь, разумеется, тоже используется трюк с EnumToString, чтобы проверить существование свойства с идентификатором v. В случае успеха мы проверяем его наличие в списке modifiables, и устанавливаем соответствующий флаг modifiable в true или false.
bool result = false;
|
По умолчанию любое свойство считается однозначным и потому необходимое количество чтений через ObjectGet-функцию или записей через ObjectSet-функцию равно 1 (переменная k ниже).
int k = 1;
|
Если объект поддерживает уровни, мы ограничиваем потенциальное количество чтений/записей количеством уровней levels (как мы помним, оно получено в вызывающем коде из свойства OBJPROP_LEVELS).
Для свойства OBJPROP_BMPFILE, как мы скоро узнаем, допустимо только два состояния: включено (кнопка нажата, флаг проставлен) или выключено (кнопка отжата, флаг сброшен), поэтому k = 2.
Наконец, координаты объекта — OBJPROP_TIME и OBJPROP_PRICE — хороши тем, что генерируют ошибку при попытке прочитать/записать несуществующую точку привязки. Поэтому мы назначаем k некое заведомо большое значение MOD_MAX, и затем сможем прервать цикл чтения точек при ненулевом значении _LastError.
// читаем значение свойства - одно или много
|
Поскольку у одного свойства может быть несколько значений, которые читаются в цикле вплоть до k, мы уже не можем просто написать data.put((E)v, get((E)v)). Нам необходимо неким образом совместить идентификатор свойства v и номер его модификации i. К счастью, количество свойств ограничено и в целочисленной константе (тип int) занято не более двух младших байтов. Поэтому мы можем с помощью побитовых операторов поместить i в верхний байт. Для этой цели разработан макрос MOD_COMBINE.
#define MOD_COMBINE(V,I) (V | (I << 24)) |
Разумеется, для извлечения идентификатора свойства и номера модификации предусмотрены обратные макросы.
#define MOD_GET_NAME(V) (V & 0xFFFFFF)
|
Вот, например, как они используются в методе snapshot.
virtual int snapshot() override
|
В принципе, данный метод повторяет всю логику одноименного метода в ChartModeMonitor.mqh, однако для чтения свойств везде приходится предварительно выделять из хранимого ключа имя свойства с помощью MOD_GET_NAME и номер с помощью MOD_GET_INDEX.
Аналогичное усложнение приходится делать и в методе restore.
virtual void restore() override
|
Самым интересным нововведением ObjectMonitorBase является работа с изменениями.
MapArray<E,T> * const getChanges()
|
Передав в метод applyChanges монитор состояния другого объекта, мы можем перенять из него все последние изменения.
Для поддержки свойств всех трех основных типов (long, double, string) нам требуется реализовать класс ObjectMonitor (аналог ChartModeMonitor из ChartModeMonitor.mqh).
class ObjectMonitor: public ObjectMonitorInterface
|
Здесь также сохранена прежняя структура кода, а добавились только методы для поддержки изменений и имени (у графиков, как мы помним, имен нет).
...
|
На основе созданного монитора объектов легко реализовать несколько приемов, поддержка которых отсутствует в терминале. В частности, это создание копий объектов и групповое редактирование объектов.
Скрипт ObjectCopy
Копирование выделенных объектов демонстрирует скрипт ObjectCopy.mq5. В начале его функции OnStart мы заполняем массив flags последовательными целыми числами, которые являются кандидатами на элементы ENUM_OBJECT_PROPERTY_-перечислений разных типов. Нумерация элементов перечислений имеет ярко выраженную группировку по назначению, причем между группами встречаются большие пропуски (видимо, запас под будущие элементы), поэтому массив формируется достаточно большой: 2048 элементов.
#include <MQL5Book/ObjectMonitor.mqh>
|
Далее мы собираем в массив имена объектов, которые в данный момент выделены на графике — для этого используется свойство OBJPROP_SELECTED.
string selected[];
|
Наконец, в основном рабочем цикле по выбранным элементам мы считываем свойства каждого объекта, формируем название его копии и создаем под ним объект с таким же набором атрибутов.
for(int i = 0; i < ArraySize(selected); ++i)
|
Здесь важно отметить, что свойство OBJPROP_TYPE является одним из немногих, доступных только на чтение, и потому объект необходимо сразу создавать требуемого типа.
Вспомогательная функция GetFreeName пытается добавить к имени объекта строку "/Copy №x", где x — номер копии. Таким образом, запуская скрипт несколько раз, можно создавать 2-ую, 3-юю, и так далее копии.
string GetFreeName(const string name)
|
В принципе, терминал запоминает последние настройки конкретного типа объектов, и если их создавать один за другим, это эквивалентно копированию. Однако, настройки обычно меняются в процессе работы с разными графиками, и если по прошествии времени возникает необходимость продублировать какой-то "старый" объект, то настройки для него, как правило, приходится делать полностью. Особенно накладно это для типов объектов с большим количеством свойств, например, инструментов Фибоначчи. В таких случаях и пригодится данный скрипт.
Некоторые картинки из этой главы, на которых размещены однотипные объекты, были созданы с помощью данного скрипта.
Индикатор ObjectGroupEdit
Вторым примером использования ObjectMonitor выступит индикатор ObjectGroupEdit.mq5, который позволяет редактировать свойства сразу у группы выделенных объектов.
Представим себе, что мы выделили на графике несколько объектов (не обязательного одного и того же типа), у которых необходимо единообразно поменять то или иное свойство. Далее мы открываем диалог свойств любого из этих объектов, настраиваем его, и по нажатию OK изменения применяются ко всем выделенным объектам. Именно так работает наша следующая MQL-программа.
Индикатор как тип программы потребовался нам потому, что в нем задействованы события графика. Этому аспекту программирования на MQL5 будет посвящена целая глава, но с некоторыми основами мы познакомимся прямо сейчас.
Поскольку индикатор не имеет диаграмм, директивы #property содержат нули, а функция OnCalculate — практически пуста.
#property indicator_chart_window
|
Для автоматической генерации полного набора всех свойств объектом мы опять воспользуемся массивом размером 2048 элементов с последовательными целочисленными значениями. Также предусмотрим массив для имен выделенных элементов и массив объектов-мониторов класса ObjectMonitor.
int consts[2048];
|
В обработчике OnInit инициализируем массив чисел и запускаем таймер.
void OnInit()
|
В обработчике таймера сохраняем в массив имена выделенных объектов. Если список выделения поменялся, требуется перенастроить объекты-мониторы, для чего вызывается вспомогательная функция TrackSelectedObjects.
void OnTimer()
|
Сама функция TrackSelectedObjects довольно проста: удаляем прежние мониторы и создаем новые. При желании можно сделать её более интеллектуальной — с сохранением неизменившейся части выделения.
void TrackSelectedObjects()
|
Напомним, что при создании объекта-монитора он сразу снимает "слепок" всех свойств соответствующего графического объекта.
Теперь мы, наконец, добрались до той части, где в дело вступают события. Как уже было сказано в обзоре функций событий, за события на графике отвечает обработчик OnChartEvent. Нас в данном примере интересует конкретное событие CHARTEVENT_OBJECT_CHANGE: оно происходит, когда пользователь меняет какие-либо атрибуты в диалоге свойств объекта. Название измененного объекта передается в параметре sparam.
Если это имя совпадает с одним из объектов под наблюдением, мы просим монитор сделать новый слепок его свойств, то есть вызываем objects[i].snapshot().
void OnChartEvent(const int id,
|
Если изменения подтверждены (а иное вряд ли возможно), их количество в переменной changes будет больше 0. Тогда запускается цикл по всем выделенным объектам, и к каждому из них, кроме исходного, применяются обнаруженные изменения.
Поскольку мы потенциально можем изменять много объектов, вызываем запрос перерисовки графика с помощью ChartRedraw.
В обработчике OnDeinit не забываем удалить все мониторы.
void OnDeinit(const int)
|
Вот и все: новый инструмент готов.
Данный индикатор позволил настроить общий внешний вид нескольких групп объектов-надписей в разделе Определение точки привязки на объекте.
Кстати говоря, по похожему принципу с помощью ObjectMonitor можно сделать еще один популярный инструмент, отсутствующий в терминале: для отмены правок свойств объектов — ведь для нас готов метод restore.