Приоритет объектов (Z-порядок)

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

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

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

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

В скрипте ObjectZorder.mq5 мы создадим 12 объектов типа OBJ_RECTANGLE_LABEL, разместив их по кругу, как на циферблате. Порядок добавления объектов соответствует часам: от 1 до 12. Для наглядности все прямоугольники получат случайный цвет (про свойство OBJPROP_BGCOLOR см. следующий раздел), а также случайный приоритет. Двигая мышью над объектами, пользователь сможет по всплывающей подсказке определить, к какому объекту она относится.

Для удобства настройки свойств объектов определим специальный класс ObjectBuilder, производный от ObjectSelector.

#include "ObjectPrefix.mqh"
#include <MQL5Book/ObjectMonitor.mqh>
   
class ObjectBuilderpublic ObjectSelector
{
protected:
   const ENUM_OBJECT type;
   const int window;
public:
   ObjectBuilder(const string _idconst ENUM_OBJECT _type,
      const long _chart = 0const int _win = 0):
      ObjectSelector(_id_chart), type(_type), window(_win)
   {
      ObjectCreate(hostidtypewindow00);
   }
   
   // изменение имени и графика запрещено
   virtual void name(const string _idoverride = delete;
   virtual void chart(const long _chartoverride = delete;
};

Поля с идентификаторами объекта (id) и графика (host) уже имеются в класса ObjectSelector. В производном мы добавляем тип объекта (ENUM_OBJECT type) и номер окна (int window).

Особенностью конструктора является то, что в нем вызывается ObjectCreate.

Настройка и чтение свойств полностью наследуется в виде группы get- и set-методов из ObjectSelector.

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

void OnStart()
{
   const int t = ChartWindowOnDropped();
   int h = (int)ChartGetInteger(0CHART_HEIGHT_IN_PIXELSt);
   int w = (int)ChartGetInteger(0CHART_WIDTH_IN_PIXELS);
   int x = w / 2;
   int y = h / 2;
   ...

Поскольку тип объектов OBJ_RECTANGLE_LABEL поддерживает явное указание размеров в пикселях, рассчитаем ширину dx и высоту dy каждого прямоугольника как четверть окна. Их мы используем для задания свойств OBJPROP_XSIZE и OBJPROP_YSIZE, рассматриваемых в разделе Определение ширины и высоты объектов.

   const int dx = w / 4;
   const int dy = h / 4;
   ...

Далее в цикле создаем 12 объектов. Переменные px и py содержать смещение очередного "знака" на "циферблате" относительно центра (x,y). Приоритет z выбирается случайно. В название объекта и его подсказку (OBJPROP_TOOLTIP) включается строка вида "XX - YYY", XX — номер "часа" (позиция на циферблате от 1 до 12), YYY — приоритет.

   for(int i = 0i < 12; ++i)
   {
      const int px = (int)(MathSin((i + 1) * 30 * M_PI / 180) * dx) - dx / 2;
      const int py = -(int)(MathCos((i + 1) * 30 * M_PI / 180) * dy) - dy / 2;
      
      const int z = rand();
      const string text = StringFormat("%02d - %d"i + 1z);
   
      ObjectBuilder *builder =
         new ObjectBuilder(ObjNamePrefix + textOBJ_RECTANGLE_LABEL);
      builder.set(OBJPROP_XDISTANCEx + px).set(OBJPROP_YDISTANCEy + py)
      .set(OBJPROP_XSIZEdx).set(OBJPROP_YSIZEdy)
      .set(OBJPROP_TOOLTIPtext)
      .set(OBJPROP_ZORDERz)
      .set(OBJPROP_BGCOLOR, (rand() << 8) | rand());
      delete builder;
   }

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

Поскольку MQL-объект больше не нужен после создания и настройки графического объекта, тут же удаляем builder.

В результате выполнения скрипта на графике появятся примерно такие объекты.

Наложение объектов и всплывающие подсказки по приоритетам Z-порядка

Наложение объектов и всплывающие подсказки по приоритетам Z-порядка

Цвета и приоритеты будут отличаться при каждом запуске, но визуальное наложение прямоугольников всегда будет именно таким, в порядке создания от 1 (внизу) до 12 (на самом верху — здесь имеется в виду наложение объектов, а не тот факт, что 12 расположено вверху циферблата).

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

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

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

Для удаления созданных объектов можно воспользоваться скриптом ObjectCleanup1.mq5.