События с графическими объектами

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

События щелчка мышью на объекте (CHARTEVENT_OBJECT_CLICK), буксировки мышью (CHARTEVENT_OBJECT_DRAG) и изменения свойств объека (CHARTEVENT_OBJECT_CHANGE) активны всегда, в то время как события создания объекта CHARTEVENT_OBJECT_CREATE и удаления объекта CHARTEVENT_OBJECT_DELETE требуют явного включения путем установки свойств графика: CHART_EVENT_OBJECT_CREATE и CHART_EVENT_OBJECT_DELETE.

При переименовании объекта вручную (из диалога свойств) терминал генерирует последовательность событий CHARTEVENT_OBJECT_DELETE, CHARTEVENT_OBJECT_CREATE, CHARTEVENT_OBJECT_CHANGE. При программном переименовании объекта эти события не генерируются.

Все события в объектах несут имя связанного объекта в параметре sparam функции OnChartEvent.

Кроме того, для CHARTEVENT_OBJECT_CLICK передаются координаты щелчка: X в параметре lparam и Y в параметре dparam. Координаты являются общими для всего графика, включая подокна.

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

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

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

Проверить события в объектах поможет новая версия индикатора EventAllObjects.mq5. Мы в ней создадим и настроим с помощью уже известного класса ObjectSelector несколько объектов, а затем перехватим в обработчике OnChartEvent их характерные события.

#include <MQL5Book/ObjectMonitor.mqh>
   
class ObjectBuilder: public ObjectSelector
{
protected:
   const ENUM_OBJECT type;
   const int window;
public:
   ObjectBuilder(const string _id, const ENUM_OBJECT _type,
      const long _chart = 0, const int _win = 0):
      ObjectSelector(_id, _chart), type(_type), window(_win)
   {
      ObjectCreate(host, id, type, window, 0, 0);
   }
};

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

const string ObjNamePrefix = "EventShow-";
const string ButtonName = ObjNamePrefix + "Button";
const string EditBoxName = ObjNamePrefix + "EditBox";
const string VLineName = ObjNamePrefix + "VLine";
   
bool objectCreate, objectDelete;
   
void OnInit()
{
   // запоминаем исходные настройки, чтобы восстановить в OnDeinit
   objectCreate = ChartGetInteger(0, CHART_EVENT_OBJECT_CREATE);
   objectDelete = ChartGetInteger(0, CHART_EVENT_OBJECT_DELETE);
   
   // устанавливаем новые свойства
   ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
   ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
   
   ObjectBuilder button(ButtonName, OBJ_BUTTON);
   button.set(OBJPROP_XDISTANCE, 100).set(OBJPROP_YDISTANCE, 100)
   .set(OBJPROP_XSIZE, 200).set(OBJPROP_TEXT, "Click Me");
   
   ObjectBuilder line(VLineName, OBJ_VLINE);
   line.set(OBJPROP_TIME, iTime(NULL, 0, 0))
   .set(OBJPROP_SELECTABLE, true).set(OBJPROP_SELECTED, true)
   .set(OBJPROP_TEXT, "Drag Me").set(OBJPROP_TOOLTIP, "Drag Me");
   
   ChartRedraw();
}

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

В функции OnChartEvent обеспечим дополнительную реакцию на требуемые события: по завершении буксировки выведем в журнал новую позицию линии, а по завершении редактирования текста в поле ввода — его содержимое.

void OnChartEvent(const int id,
   const long &lparam, const double &dparam, const string &sparam)
{
   ENUM_CHART_EVENT evt = (ENUM_CHART_EVENT)id;
   PrintFormat("%s %lld %f '%s'", EnumToString(evt), lparam, dparam, sparam);
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == ButtonName)
   {
      if(ObjectGetInteger(0, ButtonName, OBJPROP_STATE))
      {
         ObjectBuilder edit(EditBoxName, OBJ_EDIT);
         edit.set(OBJPROP_XDISTANCE, 100).set(OBJPROP_YDISTANCE, 150)
         .set(OBJPROP_BGCOLOR, clrWhite)
         .set(OBJPROP_XSIZE, 200).set(OBJPROP_TEXT, "Edit Me");
      }
      else
      {
         ObjectDelete(0, EditBoxName);
      }
      
      ChartRedraw();
   }
   else if(id == CHARTEVENT_OBJECT_ENDEDIT && sparam == EditBoxName)
   {
      Print(ObjectGetString(0, EditBoxName, OBJPROP_TEXT));
   }
   else if(id == CHARTEVENT_OBJECT_DRAG && sparam == VLineName)
   {
      Print(TimeToString((datetime)ObjectGetInteger(0, VLineName, OBJPROP_TIME)));
   }
}

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

Ниже приведено изображение графика в процессе работы индикатора.

Объекты, контролируемые обработчиком событий OnChartEvent
Объекты, контролируемые обработчиком событий OnChartEvent

Сразу после запуска индикатора в журнале появляются такие строки:

CHARTEVENT_OBJECT_CREATE 0 0.000000 'EventShow-Button'
CHARTEVENT_OBJECT_CREATE 0 0.000000 'EventShow-VLine'
CHARTEVENT_CHART_CHANGE 0 0.000000 ''

Если затем перетащить линию мышью, увидим примерно следующее:

CHARTEVENT_OBJECT_DRAG 0 0.000000 'EventShow-VLine'
2022.01.05 10:00

Далее можно нажать кнопку и отредактировать текст в только что созданном поле ввода (по завершении редактирования нажмите Enter или щелкните мышью за пределами поля ввода). Это приведет к появлению таких записей в журнале (координаты и текст сообщения могут отличаться — здесь был введен текст "new message"):

CHARTEVENT_OBJECT_CLICK 181 113.000000 'EventShow-Button'
CHARTEVENT_CLICK 181 113.000000 ''
CHARTEVENT_OBJECT_CREATE 0 0.000000 'EventShow-EditBox'
CHARTEVENT_OBJECT_CLICK 152 160.000000 'EventShow-EditBox'
CHARTEVENT_CLICK 152 160.000000 ''
CHARTEVENT_OBJECT_ENDEDIT 0 0.000000 'EventShow-EditBox'
new message

Если после этого отжать кнопку, поле ввода удалится.

CHARTEVENT_OBJECT_CLICK 162 109.000000 'EventShow-Button'
CHARTEVENT_CLICK 162 109.000000 ''
CHARTEVENT_OBJECT_DELETE 0 0.000000 'EventShow-EditBox'

Стоит отметить, что кнопка работает по умолчанию как двухпозиционный переключатель, то есть "залипает" попеременно в нажатом или отжатом состоянии в результате клика мышью. Для обычной кнопки такое поведение излишне: чтобы просто отслеживать нажатия кнопки, следует при обработке события возвращать её в отжатое состояние вызовом ObjectSetInteger(0, ButtonName, OBJPROP_STATE, false).