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

Для графических объектов, находящихся на графике, терминал генерирует несколько специализированных событий. Большинство из них относится к объектам любых типов. Событие окончания редактирования текста в поле ввода — 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 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);
   }
};

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

const string ObjNamePrefix = "EventShow-";
const string ButtonName = ObjNamePrefix + "Button";
const string EditBoxName = ObjNamePrefix + "EditBox";
const string VLineName = ObjNamePrefix + "VLine";
   
bool objectCreateobjectDelete;
   
void OnInit()
{
   // запоминаем исходные настройки, чтобы восстановить в OnDeinit
   objectCreate = ChartGetInteger(0CHART_EVENT_OBJECT_CREATE);
   objectDelete = ChartGetInteger(0CHART_EVENT_OBJECT_DELETE);
   
   // устанавливаем новые свойства
   ChartSetInteger(0CHART_EVENT_OBJECT_CREATEtrue);
   ChartSetInteger(0CHART_EVENT_OBJECT_DELETEtrue);
   
   ObjectBuilder button(ButtonNameOBJ_BUTTON);
   button.set(OBJPROP_XDISTANCE100).set(OBJPROP_YDISTANCE100)
   .set(OBJPROP_XSIZE200).set(OBJPROP_TEXT"Click Me");
   
   ObjectBuilder line(VLineNameOBJ_VLINE);
   line.set(OBJPROP_TIMEiTime(NULL00))
   .set(OBJPROP_SELECTABLEtrue).set(OBJPROP_SELECTEDtrue)
   .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 &lparamconst double &dparamconst string &sparam)
{
   ENUM_CHART_EVENT evt = (ENUM_CHART_EVENT)id;
   PrintFormat("%s %lld %f '%s'"EnumToString(evt), lparamdparamsparam);
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == ButtonName)
   {
      if(ObjectGetInteger(0ButtonNameOBJPROP_STATE))
      {
         ObjectBuilder edit(EditBoxNameOBJ_EDIT);
         edit.set(OBJPROP_XDISTANCE100).set(OBJPROP_YDISTANCE150)
         .set(OBJPROP_BGCOLORclrWhite)
         .set(OBJPROP_XSIZE200).set(OBJPROP_TEXT"Edit Me");
      }
      else
      {
         ObjectDelete(0EditBoxName);
      }
      
      ChartRedraw();
   }
   else if(id == CHARTEVENT_OBJECT_ENDEDIT && sparam == EditBoxName)
   {
      Print(ObjectGetString(0EditBoxNameOBJPROP_TEXT));
   }
   else if(id == CHARTEVENT_OBJECT_DRAG && sparam == VLineName)
   {
      Print(TimeToString((datetime)ObjectGetInteger(0VLineNameOBJPROP_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).