Графика в библиотеке DoEasy (Часть 90): События стандартных графических объектов. Базовый функционал

7 декабря 2021, 08:43
Artyom Trishkin
0
609

Содержание


Концепция

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

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

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

Базовые события графических объектов определим такими:

  • Создание нового графического объекта,
  • Изменение свойств графического объекта,
  • Переименование графического объекта,
  • Удаление графического объекта,
  • Удаление графического объекта вместе с окном графика.

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

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

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


Доработка классов библиотеки

Для отслеживания факта выделения графического объекта нам необходимо добавить обработку события щелчка по графическому объекту. Двойной щелчок по невыделенному графическому объекту выделяет его для редактирования, а двойной щелчок по выделенному объекту снимает с него выделение. Для обработки такого события объекта на текущем графике нам достаточно добавить в обработчик событий новый идентификатор события, а вот для обработки такого события на других графиках, нам нужно доработать индикатор контроля событий, который у нас автоматически устанавливается на каждое вновь открываемое окно графика.

Откроем файл этого индикатора \MQL5\Indicators\DoEasy\EventControl.mq5 и добавим новый идентификатор события, которое будет отсылаться на график управляющей программы:

//+------------------------------------------------------------------+
//|                                                 EventControl.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0
//--- input parameters
input long     InpChartSRC = 0;
input long     InpChartDST = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator shortname
   IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(string)InpChartSRC+"_To#"+(string)InpChartDST);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return rates_total;
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK)
     {
      EventChartCustom(InpChartDST,(ushort)id,InpChartSRC,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+


В файл \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений:

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Не удалось получить список вновь добавленных объектов
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Не удалось изъять графический объект из списка
   
   MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,           // Создан индикатор контроля и отправки событий
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,    // Не удалось создать индикатор контроля и отправки событий
   MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR,            // Индикатор контроля и отправки событий успешно добавлен на график
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR,       // Не удалось добавить индикатор контроля и отправки событий на график
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Закрыто окон графиков:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // С ними удалено объектов:
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_OBJ,               // Не удалось создать объект-событие для графического объекта
   MSG_GRAPH_OBJ_FAILED_ADD_EVN_OBJ,                  // Не удалось добавить объект-событие в список
   
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE,                // Создан новый графический объект
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE,                // Изменено свойство графического объекта
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME,                // Графический объект переименован
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE,                // Графический объект удалён
   MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART,             // Графический объект удалён вместе с графиком
   
  };
//+------------------------------------------------------------------+

и тексты сообщений, соответствующие вновь добавленным индексам:

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   
   {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
   {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
   {"Индикатор контроля и отправки событий успешно добавлен на график ","The indicator for monitoring and sending events has been successfully added to the chart"},
   {"Не удалось добавить индикатор контроля и отправки событий на график ","Failed to add the indicator of monitoring and sending events to the chart "},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   {"Не удалось создать объект-событие для графического объекта","Failed to create event object for graphic object"},
   {"Не удалось добавить объект-событие в список","Failed to add event object to list"},
   
   {"Создан новый графический объект","New graphic object created"},
   {"Изменено свойство графического объекта","Changed graphic object property"},
   {"Графический объект переименован","Graphic object renamed"},
   {"Графический объект удалён","Graphic object deleted"},
   {"Графический объект удалён вместе с графиком","The graphic object has been removed along with the chart"},
   
  };
//+---------------------------------------------------------------------+


Внесём исправления в файле \MQL5\Include\DoEasy\Defines.mqh.

Из списка возможных событий графических объектов удалим событие перемещения объекта так как это изменение его свойств, которое и так уже есть в в этом перечислении событий графического объекта:

//+------------------------------------------------------------------+
//| Список возможных событий графических объектов                    |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// Нет события
   GRAPH_OBJ_EVENT_CREATE,                            // Событие "Создание нового графического объекта"
   GRAPH_OBJ_EVENT_CHANGE,                            // Событие "Изменение свойств графического объекта"
   GRAPH_OBJ_EVENT_MOVE,                              // Событие "Перемещение графического объекта"
   GRAPH_OBJ_EVENT_RENAME,                            // Событие "Переименование графического объекта"
   GRAPH_OBJ_EVENT_DELETE,                            // Событие "Удаление графического объекта"
  };

И добавим к этому списку новую константу для события удаления графического объекта вместе с графиком:

//+------------------------------------------------------------------+
//| Данные для работы с графическими элементами                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список возможных событий графических объектов                    |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// Нет события
   GRAPH_OBJ_EVENT_CREATE,                            // Событие "Создание нового графического объекта"
   GRAPH_OBJ_EVENT_CHANGE,                            // Событие "Изменение свойств графического объекта"
   GRAPH_OBJ_EVENT_RENAME,                            // Событие "Переименование графического объекта"
   GRAPH_OBJ_EVENT_DELETE,                            // Событие "Удаление графического объекта"
   GRAPH_OBJ_EVENT_DEL_CHART,                         // Событие "Удаление графического объекта вместе с окном графика"
  };
#define GRAPH_OBJ_EVENTS_NEXT_CODE  (GRAPH_OBJ_EVENT_DEL_CHART+1) // Код следующего события после последнего кода события графических объектов
//+------------------------------------------------------------------+

Код следующего события после кодов событий графических объектов теперь будет рассчитываться от последнего добавленного в перечисление значения.


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

Алгоритм такой: сначала считываются параметры от графического объекта при помощи стандартных ObjectGetXXX-функций (Integer, Double и String), затем, при успешном получении данных, эти параметры записываются сначала в свойство объекта класса базового графического объекта CGBaseObj, а затем — в свойства его наследника. Для стандартных графических объектов это класс CGStdGraphObj.

И тут у нас возникает некая коллизия. У нас есть методы для установки параметров графическому объекту. И эти методы должны установить переданное в них значение свойства как самому графическому объекту, так и в соответствующее свойство объекта класса — при успешной установке параметра графическому объекту при помощи функций ObjectSetXXX (Integer, Double и String). Но иногда нам нужно просто установить в свойство класса уже известное значение параметра графического объекта. И для этого нам нет необходимости ни считывать ещё раз это значение, ни присваивать его графическому объекту, а просто назначить его значение переменной класса. Методы же Set базового графического объекта библиотеки у нас сначала устанавливают значение в параметры графического объекта, а уже затем — прописывают их в переменные класса. Чтобы избежать лишней работы по установке уже известного свойства графического объекта опять в него же, и просто прописать его в переменную класса, мы в такие методы добавим bool-переменную only_prop, которая будет указывать на необходимость установки значения либо только в значения переменных, либо и в параметры графического объекта, и в свойства объекта класса. Если эта входная переменная будет иметь значение true, то параметры будут записываться только в переменные класса, в противном случае — сначала значение будет установлено в графический объект, а уже затем — в переменные класса.

В классе базового объекта всех графических объектов библиотеки \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh добавим в такие методы эту переменную и поменяем логику методов:

//--- Устанавливает флаг "Объект на заднем плане"
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_BACK,flag)) || only_prop)
                          {
                           this.m_back=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }

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

Все методы этого класса, доработанные таким образом:

//--- Устанавливает флаг "Объект на заднем плане"
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_BACK,flag)) || only_prop)
                          {
                           this.m_back=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Устанавливает флаг "Выделенность объекта"
   bool              SetFlagSelected(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTED,flag)) || only_prop)
                          {
                           this.m_selected=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Устанавливает флаг "Выделенность объекта"
   bool              SetFlagSelectable(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
                          {
                           this.m_selectable=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Устанавливает флаг "Запрет на показ имени графического объекта в списке объектов терминала"
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
                          {
                           this.m_hidden=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Устанавливает Приоритет графического объекта на получение события нажатия мышки на графике 
   bool              SetZorder(const long value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                          {
                           this.m_zorder=value;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Устанавливает видимость/невидимость объекта на всех таймфреймах
   bool              SetVisible(const bool flag,const bool only_prop)   
                       {
                        long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
                          {
                           this.m_visible=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Устанавливает флаги видимости на таймфреймах, записанных как флаги
   bool              SetVisibleOnTimeframes(const int flags,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,flags)) || only_prop)
                          {
                           this.m_timeframes_visible=flags;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Добавляет флаг видимости на указанном таймфрейме
   bool              SetVisibleOnTimeframe(const ENUM_TIMEFRAMES timeframe,const bool only_prop)
                       {
                        int flags=this.m_timeframes_visible;
                        switch(timeframe)
                          {
                           case PERIOD_M1    : flags |= OBJ_PERIOD_M1;  break;
                           case PERIOD_M2    : flags |= OBJ_PERIOD_M2;  break;
                           case PERIOD_M3    : flags |= OBJ_PERIOD_M3;  break;
                           case PERIOD_M4    : flags |= OBJ_PERIOD_M4;  break;
                           case PERIOD_M5    : flags |= OBJ_PERIOD_M5;  break;
                           case PERIOD_M6    : flags |= OBJ_PERIOD_M6;  break;
                           case PERIOD_M10   : flags |= OBJ_PERIOD_M10; break;
                           case PERIOD_M12   : flags |= OBJ_PERIOD_M12; break;
                           case PERIOD_M15   : flags |= OBJ_PERIOD_M15; break;
                           case PERIOD_M20   : flags |= OBJ_PERIOD_M20; break;
                           case PERIOD_M30   : flags |= OBJ_PERIOD_M30; break;
                           case PERIOD_H1    : flags |= OBJ_PERIOD_H1;  break;
                           case PERIOD_H2    : flags |= OBJ_PERIOD_H2;  break;
                           case PERIOD_H3    : flags |= OBJ_PERIOD_H3;  break;
                           case PERIOD_H4    : flags |= OBJ_PERIOD_H4;  break;
                           case PERIOD_H6    : flags |= OBJ_PERIOD_H6;  break;
                           case PERIOD_H8    : flags |= OBJ_PERIOD_H8;  break;
                           case PERIOD_H12   : flags |= OBJ_PERIOD_H12; break;
                           case PERIOD_D1    : flags |= OBJ_PERIOD_D1;  break;
                           case PERIOD_W1    : flags |= OBJ_PERIOD_W1;  break;
                           case PERIOD_MN1   : flags |= OBJ_PERIOD_MN1; break;
                           default           : return true;
                          }
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,flags)) || only_prop)
                          {
                           this.m_timeframes_visible=flags;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }

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

В файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh подправим вызов доработанных методов:

//--- Возвращает (1) идентификатор элемента, (2) номер элемента в списке, (3) флаг наличия тени у формы, (4) цвет фона чарта
   int               ID(void)                            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID);                   }
   int               Number(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                  }
   bool              IsShadow(void)                      const { return this.m_shadow;                                                 }
   color             ChartColorBackground(void)          const { return this.m_chart_color_bg;                                         }
//--- Устанавливает объект выше всех
   void              BringToTop(void)                          { CGBaseObj::SetVisible(false,false); CGBaseObj::SetVisible(true,false);}
//--- (1) Показывает, (2) скрывает элемент
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true,false);                                    }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false,false);                                   }
   
//+------------------------------------------------------------------+
//| Методы получения растровых данных                                |
//+------------------------------------------------------------------+

В файле \MQL5\Include\DoEasy\Objects\Graph\Form.mqh так же подправим два метода:

//+------------------------------------------------------------------+
//| Создаёт объект тени                                              |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- Если флаг тени выключен, или объект тени уже существует - уходим
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;

//--- Рассчитываем координаты объекта тени в соответствии с отступом сверху и слева
   int x=this.CoordX()-OUTER_AREA_SIZE;
   int y=this.CoordY()-OUTER_AREA_SIZE;
//--- Рассчитываем ширину и высоту в соответствии с отступом сверху, снизу, слева и справа
   int w=this.Width()+OUTER_AREA_SIZE*2;
   int h=this.Height()+OUTER_AREA_SIZE*2;
//--- Создаём новый объект тени и записываем указатель на него в переменную
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
//--- Устанавливаем свойства созданному объекту-тени
   this.m_shadow_obj.SetID(this.ID());
   this.m_shadow_obj.SetNumber(-1);
   this.m_shadow_obj.SetOpacityShadow(opacity);
   this.m_shadow_obj.SetColorShadow(colour);
   this.m_shadow_obj.SetMovable(true);
   this.m_shadow_obj.SetActive(false);
   this.m_shadow_obj.SetVisible(false,false);
//--- Объект-форму перемещаем на передний план
   this.BringToTop();
  }
//+------------------------------------------------------------------+
//| Рисует тень                                                      |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4)
  {
//--- Если флаг тени выключен - уходим
   if(!this.m_shadow)
      return;
//--- Если объект тени не создан - создадим его
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- Если объект тени существует, рисуем на нём тень,
//--- устанавливаем флаг видимости объекта-тени и
//--- перемещаем объект-форму на передний план
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.DrawShadow(shift_x,shift_y,blur);
      this.m_shadow_obj.SetVisible(true,false);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+


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

Рассмотрим на примере метода для установки номера подокна:

//--- Номер подокна графика
   int               SubWindow(void)               const { return (int)this.GetProperty(GRAPH_OBJ_PROP_WND_NUM,0);                        }
   bool              SetSubWindow(void)
                       {
                        if(!CGBaseObj::SetSubwindow(CGBaseObj::ChartID(),CGBaseObj::Name()))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());
                        return true;
                       }

Так как номер подокна ищется по идентификатору графика и имени объекта, то в метод базового класса для установки номера подокна просто передаются идентификатор графика, уже записанный в базовом объекте, и имя графического объекта. Если в базовом объекте установить значение не удалось — возвращаем false, иначе — устанавливаем в свойство объекта значение, только что записанное в базовый объект, и возвращаем true.

И на примере метода для установки видимости объекта на всех таймфреймах:

//--- Видимость объекта на всех таймфреймах
   bool              Visible(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0);                    }
   bool              SetFlagVisible(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetVisible(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,flag);
                        return true;
                       }

Здесь в метод передаётся новый параметр, ранее добавленный нами для указания в какие именно значения будем записывать — только в свойство объекта, или в параметры графического объекта и в свойства класса. Далее устанавливаем эти свойства в базовый объект и, если не получилось — возвращаем false.
Иначе — записываем значение в свойство объекта класса и возвращаем true.

Все остальные методы доработаны идентично вышерассмотренным:

//--- Объект на заднем плане
   bool              Back(void)                    const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);                          }
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagBack(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag);
                        return true;
                       }
//--- Приоритет графического объекта на получение события нажатия мышки на графике
   long              Zorder(void)                  const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0);                              }
   bool              SetZorder(const long value,const bool only_prop)
                       {
                        if(!CGBaseObj::SetZorder(value,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value);
                        return true;
                       }
//--- Запрет на показ имени графического объекта в списке объектов терминала
   bool              Hidden(void)                  const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);                        }
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagHidden(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag);
                        return true;
                       }
//--- Выделенность объекта
   bool              Selected(void)                const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);                      }
   bool              SetFlagSelected(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagSelected(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_SELECTED,0,flag);
                        return true;
                       }
//--- Доступность объекта
   bool              Selectable(void)              const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);                    }
   bool              SetFlagSelectable(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagSelectable(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,0,flag);
                        return true;
                       }
//--- Координата времени
   datetime          Time(const int modifier)      const { return (datetime)this.GetProperty(GRAPH_OBJ_PROP_TIME,modifier);               }
   bool              SetTime(const datetime time,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TIME,modifier,time))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TIME,modifier,time);
                        return true;
                       }
//--- Цвет
   color             Color(void)                   const { return (color)this.GetProperty(GRAPH_OBJ_PROP_COLOR,0);                        }
   bool              SetColor(const color colour)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_COLOR,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,colour);
                        return true;
                       }
//--- Стиль
   ENUM_LINE_STYLE   Style(void)                   const { return (ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_STYLE,0);              }
   bool              SetStyle(const ENUM_LINE_STYLE style)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_STYLE,style))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,style);
                        return true;
                       }
//--- Толщина линии
   int               Width(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_WIDTH,0);                          }
   bool              SetWidth(const int width)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_WIDTH,width))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_WIDTH,0,width);
                        return true;
                       }
//--- Заливка объекта цветом
   bool              Fill(void)                    const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_FILL,0);                          }
   bool              SetFlagFill(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FILL,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_FILL,0,flag);
                        return true;
                       }
//--- Возможность редактирования текста в объекте Edit
   bool              ReadOnly(void)                const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_READONLY,0);                      }
   bool              SetFlagReadOnly(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_READONLY,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_READONLY,0,flag);
                        return true;
                       }
//--- Количество уровней
   int               Levels(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_LEVELS,0);                         }
   bool              SetLevels(const int levels)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELS,levels))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELS,0,levels);
                        return true;
                       }
//--- Цвет линии-уровня
   color             LevelColor(const int modifier)   const { return (color)this.GetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,modifier);         }
   bool              SetLevelColor(const color colour,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELCOLOR,modifier,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,modifier,colour);
                        return true;
                       }
//--- Стиль линии-уровня
   ENUM_LINE_STYLE   LevelStyle(const int modifier)const { return (ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,modifier);  }
   bool              SetLevelStyle(const ENUM_LINE_STYLE style,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELSTYLE,modifier,style))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,modifier,style);
                        return true;
                       }
///--- Толщина линии-уровня
   int               LevelWidth(const int modifier)const { return (int)this.GetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,modifier);              }
   bool              SetLevelWidth(const int width,const int modifier)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELWIDTH,modifier,width))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,modifier,width);
                        return true;
                       }
//--- Горизонтальное выравнивание текста в объекте "Поле ввода" (OBJ_EDIT)
   ENUM_ALIGN_MODE   Align(void)                   const { return (ENUM_ALIGN_MODE)this.GetProperty(GRAPH_OBJ_PROP_ALIGN,0);              }
   bool              SetAlign(const ENUM_ALIGN_MODE align)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ALIGN,align))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ALIGN,0,align);
                        return true;
                       }
//--- Размер шрифта
   int               FontSize(void)                const { return (int)this.GetProperty(GRAPH_OBJ_PROP_FONTSIZE,0);                       }
   bool              SetFontSize(const int size)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FONTSIZE,size))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,0,size);
                        return true;
                       }
//--- Луч продолжается влево
   bool              RayLeft(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0);                      }
   bool              SetFlagRayLeft(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY_LEFT,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0,flag);
                        return true;
                       }
//--- Луч продолжается вправо
   bool              RayRight(void)                const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0);                     }
   bool              SetFlagRayRight(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY_RIGHT,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0,flag);
                        return true;
                       }
//--- Вертикальная линия продолжается на все окна графика
   bool              Ray(void)                     const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_RAY,0);                           }
   bool              SetFlagRay(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_RAY,0,flag);
                        return true;
                       }
//--- Отображение полного эллипса для объекта "Дуги Фибоначчи"
   bool              Ellipse(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_ELLIPSE,0);                       }
   bool              SetFlagEllipse(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ELLIPSE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,0,flag);
                        return true;
                       }
//--- Код стрелки для объекта "Стрелка"
   uchar             ArrowCode(void)               const { return (uchar)this.GetProperty(GRAPH_OBJ_PROP_ARROWCODE,0);                    }
   bool              SetArrowCode(const uchar code)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ARROWCODE,code))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,0,code);
                        return true;
                       }
//--- Положение точки привязки графического объекта
   int               Anchor(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0);                         }
   bool              SetAnchor(const int anchor)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ANCHOR,anchor))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,anchor);
                        return true;
                       }
//--- Дистанция в пикселях по оси X от угла привязки
   int               XDistance(void)               const { return (int)this.GetProperty(GRAPH_OBJ_PROP_XDISTANCE,0);                      }
   bool              SetXDistance(const int distance)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XDISTANCE,distance))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,0,distance);
                        return true;
                       }
//--- Дистанция в пикселях по оси Y от угла привязки
   int               YDistance(void)               const { return (int)this.GetProperty(GRAPH_OBJ_PROP_YDISTANCE,0);                      }
   bool              SetYDistance(const int distance)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YDISTANCE,distance))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,0,distance);
                        return true;
                       }
//--- Тренд объекта Ганна
   ENUM_GANN_DIRECTION Direction(void)             const { return (ENUM_GANN_DIRECTION)this.GetProperty(GRAPH_OBJ_PROP_DIRECTION,0);      }
   bool              SetDirection(const ENUM_GANN_DIRECTION direction)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DIRECTION,direction))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,0,direction);
                        return true;
                       }
//--- Уровень волновой разметки Эллиотта
   ENUM_ELLIOT_WAVE_DEGREE Degree(void)            const { return (ENUM_ELLIOT_WAVE_DEGREE)this.GetProperty(GRAPH_OBJ_PROP_DEGREE,0);     }
   bool              SetDegree(const ENUM_ELLIOT_WAVE_DEGREE degree)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DEGREE,degree))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DEGREE,0,degree);
                        return true;
                       }
//--- Отображение линий для волновой разметки Эллиотта
   bool              DrawLines(void)               const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_DRAWLINES,0);                     }
   bool              SetFlagDrawLines(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DRAWLINES,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,0,flag);
                        return true;
                       }
//--- Состояние кнопки (Нажата/Отжата)
   bool              State(void)                   const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_STATE,0);                         }
   bool              SetFlagState(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_STATE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_STATE,0,flag);
                        return true;
                       }
//--- Идентификатор объекта "График" (OBJ_CHART)
   long              ChartObjChartID(void)         const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0);                  }
   bool              SetChartObjChartID(const long chart_id)
                       {
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0,chart_id);
                        return true;
                       }
//--- Период для объекта "График"
   ENUM_TIMEFRAMES   ChartObjPeriod(void)          const { return (ENUM_TIMEFRAMES)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0);   }
   bool              SetChartObjPeriod(const ENUM_TIMEFRAMES timeframe)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PERIOD,timeframe))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0,timeframe);
                        return true;
                       }
//--- Признак отображения шкалы времени для объекта "График"
   bool              ChartObjDateScale(void)       const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0);          }
   bool              SetFlagChartObjDateScale(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DATE_SCALE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0,flag);
                        return true;
                       }
//--- Признак отображения ценовой шкалы для объекта "График"
   bool              ChartObjPriceScale(void)      const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0);         }
   bool              SetFlagPriceScale(const bool flag)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PRICE_SCALE,flag))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0,flag);
                        return true;
                       }
//--- Масштаб для объекта "График"
   int               ChartObjChartScale(void)      const { return (int)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0);          }
   bool              SetChartObjChartScale(const int scale)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_CHART_SCALE,scale))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0,scale);
                        return true;
                       }
//--- Ширина объекта по оси X в пикселях
   int               XSize(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_XSIZE,0);                          }
   bool              SetXSize(const int size)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XSIZE,size))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_XSIZE,0,size);
                        return true;
                       }
//--- Высота объекта по оси Y в пикселях
   int               YSize(void)                   const { return (int)this.GetProperty(GRAPH_OBJ_PROP_YSIZE,0);                          }
   bool              SetYSize(const int size)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YSIZE,size))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_YSIZE,0,size);
                        return true;
                       }
//--- X-координата левого верхнего угла прямоугольной области видимости
   int               XOffset(void)                 const { return (int)this.GetProperty(GRAPH_OBJ_PROP_XOFFSET,0);                        }
   bool              SetXOffset(const int offset)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XOFFSET,offset))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,0,offset);
                        return true;
                       }
//--- Y-координата левого верхнего угла прямоугольной области видимости
   int               YOffset(void)                 const { return (int)this.GetProperty(GRAPH_OBJ_PROP_YOFFSET,0);                        }
   bool              SetYOffset(const int offset)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YOFFSET,offset))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,0,offset);
                        return true;
                       }
//--- Цвет фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   color             BGColor(void)                 const { return (color)this.GetProperty(GRAPH_OBJ_PROP_BGCOLOR,0);                      }
   bool              SetBGColor(const color colour)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BGCOLOR,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,0,colour);
                        return true;
                       }
//--- Угол графика для привязки графического объекта
   ENUM_BASE_CORNER  Corner(void)                  const { return (ENUM_BASE_CORNER)this.GetProperty(GRAPH_OBJ_PROP_CORNER,0);            }
   bool              SetCorner(const ENUM_BASE_CORNER corner)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_CORNER,corner))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CORNER,0,corner);
                        return true;
                       }
//--- Тип рамки для объекта "Прямоугольная рамка"
   ENUM_BORDER_TYPE  BorderType(void)              const { return (ENUM_BORDER_TYPE)this.GetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0);       }
   bool              SetBorderType(const ENUM_BORDER_TYPE type)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BORDER_TYPE,type))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0,type);
                        return true;
                       }
//--- Цвет рамки для объекта OBJ_EDIT и OBJ_BUTTON
   color             BorderColor(void)             const { return (color)this.GetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0);                 }
   bool              SetBorderColor(const color colour)
                       {
                        if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BORDER_COLOR,colour))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0,colour);
                        return true;
                       }

//--- Координата цены
   double            Price(const int modifier)     const { return this.GetProperty(GRAPH_OBJ_PROP_PRICE,modifier);                        }
   bool              SetPrice(const double price,const int modifier)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PRICE,modifier,price))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_PRICE,modifier,price);
                        return true;
                       }
//--- Значение уровня
   double            LevelValue(const int modifier)const { return this.GetProperty(GRAPH_OBJ_PROP_LEVELVALUE,modifier);                   }
   bool              SetLevelValue(const double value,const int modifier)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELVALUE,modifier,value))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELVALUE,modifier,value);
                        return true;
                       }
//--- Масштаб
   double            Scale(void)                   const { return this.GetProperty(GRAPH_OBJ_PROP_SCALE,0);                               }
   bool              SetScale(const double scale)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SCALE,scale))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_SCALE,0,scale);
                        return true;
                       }
//--- Угол
   double            Angle(void)                   const { return this.GetProperty(GRAPH_OBJ_PROP_ANGLE,0);                               }
   bool              SetAngle(const double angle)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ANGLE,angle))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ANGLE,0,angle);
                        return true;
                       }
//--- Отклонение для канала стандартного отклонения
   double            Deviation(void)               const { return this.GetProperty(GRAPH_OBJ_PROP_DEVIATION,0);                           }
   bool              SetDeviation(const double deviation)
                       {
                        if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DEVIATION,deviation))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_DEVIATION,0,deviation);
                        return true;
                       }

//--- Имя объекта
   string            Name(void)                    const { return this.GetProperty(GRAPH_OBJ_PROP_NAME,0);                                }
   bool              SetName(const string name)
                       {
                        if(CGBaseObj::Name()==name)
                           return true;
                        if(CGBaseObj::Name()=="")
                          {
                           CGBaseObj::SetName(name);
                           this.SetProperty(GRAPH_OBJ_PROP_NAME,0,name);
                           return true;
                          }
                        else
                          {
                           if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_NAME,name))
                              return false;
                           CGBaseObj::SetName(name);
                           this.SetProperty(GRAPH_OBJ_PROP_NAME,0,name);
                           return true;
                          }
                       }
//--- Описание объекта (текст, содержащийся в объекте)
   string            Text(void)                    const { return this.GetProperty(GRAPH_OBJ_PROP_TEXT,0);                                }
   bool              SetText(const string text)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TEXT,text))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TEXT,0,text);
                        return true;
                       }
//--- Текст всплывающей подсказки
   string            Tooltip(void)                 const { return this.GetProperty(GRAPH_OBJ_PROP_TOOLTIP,0);                             }
   bool              SetTooltip(const string tooltip)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TOOLTIP,tooltip))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TOOLTIP,0,tooltip);
                        return true;
                       }
//--- Описание уровня
   string            LevelText(const int modifier) const { return this.GetProperty(GRAPH_OBJ_PROP_LEVELTEXT,modifier);                    }
   bool              SetLevelText(const string text,const int modifier)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELTEXT,modifier,text))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_LEVELTEXT,modifier,text);
                        return true;
                       }
//--- Шрифт
   string            Font(void)                    const { return this.GetProperty(GRAPH_OBJ_PROP_FONT,0);                                }
   bool              SetFont(const string font)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FONT,font))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_FONT,0,font);
                        return true;
                       }
//--- Имя BMP-файла для объекта "Графическая метка"
   string            BMPFile(const int modifier)   const { return this.GetProperty(GRAPH_OBJ_PROP_BMPFILE,modifier);                      }
   bool              SetBMPFile(const string bmp_file,const int modifier)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BMPFILE,bmp_file))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BMPFILE,modifier,bmp_file);
                        return true;
                       }
//--- Символ для объекта "График" 
   string            ChartObjSymbol(void)          const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0);                    }
   bool              SetChartObjSymbol(const string symbol)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0,symbol);
                        return true;
                       }

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

//+------------------------------------------------------------------+
//| Получает и сохраняет целочисленные свойства                      |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Свойства, присущие всем графическим объектам, имеющиеся у графического объекта
   CGBaseObj::SetVisibleOnTimeframes((int)::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES),true);   // Записываем в базовый объект Видимость объекта на таймфреймах
   CGBaseObj::SetFlagBack(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK),true);                         // Записываем в базовый объект флаг Объект на заднем плане
   CGBaseObj::SetZorder(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER),true);                         // Записываем в базовый объект Приоритет графического объекта на получение события нажатия мышки на графике
   CGBaseObj::SetFlagHidden(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN),true);                     // Записываем в базовый объект Запрет на показ имени графического объекта в списке объектов терминала
   CGBaseObj::SetFlagSelectable(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE),true);             // Записываем в базовый объект Доступность объекта
   CGBaseObj::SetFlagSelected(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED),true);                 // Записываем в базовый объект Выделенность объекта
   
   this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,CGBaseObj::VisibleOnTimeframes());                                   // Видимость объекта на таймфреймах
   this.SetProperty(GRAPH_OBJ_PROP_BACK,0,CGBaseObj::IsBack());                                                      // Объект на заднем плане
   this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,CGBaseObj::Zorder());                                                    // Приоритет графического объекта на получение события нажатия мышки на графике
   this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,CGBaseObj::IsHidden());                                                  // Запрет на показ имени графического объекта в списке объектов терминала
   this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,0,CGBaseObj::IsSelectable());                                          // Доступность объекта
   this.SetProperty(GRAPH_OBJ_PROP_SELECTED,0,CGBaseObj::IsSelected());                                              // Выделенность объекта
   
   this.SetProperty(GRAPH_OBJ_PROP_CREATETIME,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME));  // Время создания объекта
   for(int i=0;i<this.m_pivots;i++)                                                                                  // Координаты времени точек
      this.SetTimePivot(i);
   this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR));            // Цвет
   this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE));            // Стиль
   this.SetProperty(GRAPH_OBJ_PROP_WIDTH,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH));            // Толщина линии
   //--- Свойства, принадлежащие разным графическим объектам
   this.SetProperty(GRAPH_OBJ_PROP_FILL,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL));              // Заливка объекта цветом
   this.SetProperty(GRAPH_OBJ_PROP_READONLY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY));      // Возможность редактирования текста в объекте Edit
   this.SetProperty(GRAPH_OBJ_PROP_LEVELS,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS));          // Количество уровней
   
   if(this.GetProperty(GRAPH_OBJ_PROP_LEVELS,0)!=this.GetPropertyPrev(GRAPH_OBJ_PROP_LEVELS,0))                      // Проверка изменения количества уровней
     {
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,this.Levels());
     }
   for(int i=0;i<this.Levels();i++)                                                                                  // Данные уровней
     {
      this.SetLevelColor(i);
      this.SetLevelStyle(i);
      this.SetLevelWidth(i);
     }
   this.SetProperty(GRAPH_OBJ_PROP_ALIGN,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN));            // Горизонтальное выравнивание текста в объекте "Поле ввода" (OBJ_EDIT)
   this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE));      // Размер шрифта
   this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT));      // Луч продолжается влево
   this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT));    // Луч продолжается вправо
   this.SetProperty(GRAPH_OBJ_PROP_RAY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY));                // Вертикальная линия продолжается на все окна графика
   this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE));        // Отображение полного эллипса для объекта "Дуги Фибоначчи"
   this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE));    // Код стрелки для объекта "Стрелка"
   this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR));          // Положение точки привязки графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE));    // Дистанция в пикселях по оси X от угла привязки
   this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE));    // Дистанция в пикселях по оси Y от угла привязки
   this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION));    // Тренд объекта Ганна
   this.SetProperty(GRAPH_OBJ_PROP_DEGREE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE));          // Уровень волновой разметки Эллиотта
   this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES));    // Отображение линий для волновой разметки Эллиотта
   this.SetProperty(GRAPH_OBJ_PROP_STATE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE));            // Состояние кнопки (Нажата/Отжата)
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID));// Идентификатор объекта "График" (OBJ_CHART).
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD));    // Период для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE));  // Признак отображения шкалы времени для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE));// Признак отображения ценовой шкалы для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE));// Масштаб для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_XSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE));            // Ширина объекта по оси X в пикселях.
   this.SetProperty(GRAPH_OBJ_PROP_YSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE));            // Высота объекта по оси Y в пикселях.
   this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET));        // X-координата левого верхнего угла прямоугольной области видимости.
   this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET));        // Y-координата левого верхнего угла прямоугольной области видимости.
   this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR));        // Цвет фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.SetProperty(GRAPH_OBJ_PROP_CORNER,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER));          // Угол графика для привязки графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE));// Тип рамки для объекта "Прямоугольная рамка"
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR));// Цвет рамки для объекта OBJ_EDIT и OBJ_BUTTON
  }
//+------------------------------------------------------------------+

Таким образом мы заполняем все свойства, которые дублируются в базовом классе и в его наследнике. Иначе, при получении свойств объекта, мы используем методы базового объекта, а в них не было записано данных графического объекта.

Некоторые доработки оставим на рассмотрение при создании функционала для отслеживания событий стандартных графических объектов.


События стандартных графических объектов

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

  • Идентификатор события,
  • Идентификатор графика, на котором расположен графический объект, в котором произошло событие,
  • Имя объекта, в котором произошло событие.

Вообще, у пользовательского события, отправляемого на указанный график функцией EventChartCustom(), есть пять параметров:

//+------------------------------------------------------------------+
bool  EventChartCustom(
   long    chart_id,            // идентификатор графика-получателя события
   ushort  custom_event_id,     // идентификатор события
   long    lparam,              // параметр типа long
   double  dparam,              // параметр типа double
   string  sparam               // строковый параметр события
   );
//+------------------------------------------------------------------+

Из них нам нужны для заполнения custom_event_id — сюда будем указывать идентификатор события графического объекта, lparam — сюда будем указывать идентификатор графика, на котором расположен графический объект, в котором произошло событие, и sparam — сюда будем указывать имя графического объекта. И у нас остаётся ещё один свободный параметр — dparam, в который будем указывать значение константы изменённого свойства при его изменении, или количество удалённых графических объектов, которые были удалены совместно с закрытым окном графика.

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

Сегодня мы сделаем лишь создание событий и отправку их в обработчик событий коллекции графических объектов. Разложение этих событий на их составляющие мы сделаем в следующей статье.

В файле базового графического объекта \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, в самом его начале, пропишем новый класс базового события графических объектов библиотеки:

//+------------------------------------------------------------------+
//|                                                     GBaseObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\DELib.mqh"
#include <Graphics\Graphic.mqh>
//+------------------------------------------------------------------+
//| Класс события графического объекта                               |
//+------------------------------------------------------------------+
class CGBaseEvent : public CObject
  {
private:
   ushort            m_id;
   long              m_lparam;
   double            m_dparam;
   string            m_sparam;
public:
   void              ID(ushort id)                          { this.m_id=id;         }
   ushort            ID(void)                         const { return this.m_id;     }
   void              LParam(const long value)               { this.m_lparam=value;  }
   long              Lparam(void)                     const { return this.m_lparam; }
   void              DParam(const double value)             { this.m_dparam=value;  }
   double            Dparam(void)                     const { return this.m_dparam; }
   void              SParam(const string value)             { this.m_sparam=value;  }
   string            Sparam(void)                     const { return this.m_sparam; }
   bool              Send(const long chart_id)
                       {
                        ::ResetLastError();
                        return ::EventChartCustom(chart_id,m_id,m_lparam,m_dparam,m_sparam);
                       }
                     CGBaseEvent (const ushort event_id,const long lparam,const double dparam,const string sparam) : 
                        m_id(event_id),m_lparam(lparam),m_dparam(dparam),m_sparam(sparam){}
                    ~CGBaseEvent (void){}
  };
//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject

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

В защищённой секции класса базового графического объекта объявим список для хранения событий объекта и напишем методы для работы со списком и событиями графического объекта:

//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
protected:
   CArrayObj         m_list_events;                      // Список событий объекта
   ENUM_OBJECT       m_type_graph_obj;                   // Тип графического объекта
   ENUM_GRAPH_ELEMENT_TYPE m_type_element;               // Тип графического элемента
   ENUM_GRAPH_OBJ_BELONG m_belong;                       // Принадлежность программе
   ENUM_GRAPH_OBJ_GROUP m_group;                         // Группа графического объекта
   string            m_name_prefix;                      // Префикс имени объекта
   string            m_name;                             // Имя объекта
   long              m_chart_id;                         // Идентификатор графика объекта
   long              m_object_id;                        // Идентификатор объекта
   long              m_zorder;                           // Приоритет графического объекта на получение события нажатия мышки
   int               m_subwindow;                        // Номер подокна
   int               m_shift_y;                          // Смещение координаты Y для подокна
   int               m_type;                             // Тип объекта
   int               m_timeframes_visible;               // Видимость объекта на таймфреймах (набор флагов)
   int               m_digits;                           // Количество знаков после запятой в котировке символа чарта
   bool              m_visible;                          // Видимость объекта
   bool              m_back;                             // Флаг "Объект на заднем плане"
   bool              m_selected;                         // Флаг "Выделенность объекта"
   bool              m_selectable;                       // Флаг "Доступность объекта"
   bool              m_hidden;                           // Флаг "Запрет на показ имени графического объекта в списке объектов терминала"
   datetime          m_create_time;                      // Время создания объекта
   
//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void)                      { return true; }
   virtual void      StructToObject(void){;}

//--- Возвращает список событий объекта
   CArrayObj        *GetListEvents(void)                       { return &this.m_list_events;          }
//--- Создаёт новое событие объекта
   CGBaseEvent      *CreateNewEvent(const ushort event_id,const long lparam,const double dparam,const string sparam)
                       {
                        CGBaseEvent *event=new CGBaseEvent(event_id,lparam,dparam,sparam);
                        return event;
                       }
//--- Создаёт новое событие объекта и добавляет его в список событий
   bool              CreateAndAddNewEvent(const ushort event_id,const long lparam,const double dparam,const string sparam)
                       {
                        return this.AddEvent(new CGBaseEvent(event_id,lparam,dparam,sparam));
                       }
//--- Добавляет объект события в список событий
   bool              AddEvent(CGBaseEvent *event)              { return this.m_list_events.Add(event);}
//--- Очищает список событий
   void              ClearEventsList(void)                     { this.m_list_events.Clear();          }
//--- Возвращает количество событий в списке
   int               EventsTotal(void)                         { return this.m_list_events.Total();   }

public:

Все написанные методы достаточно просты и, надеюсь, не вызывают никаких вопросов. В любом случае, все вопросы можно задать в обсуждении статьи.

В конструкторе класса очистим список событий объекта и установим ему флаг сортированного списка:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(0),m_visible(false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_"),m_belong(GRAPH_OBJ_BELONG_PROGRAM)
  {
   this.m_list_events.Clear();                  // Очистка списка событий
   this.m_list_events.Sort();                   // Флаг сортированного списка
   this.m_type=OBJECT_DE_TYPE_GBASE;            // Тип объекта
   this.m_type_graph_obj=WRONG_VALUE;           // Тип графического объекта
   this.m_type_element=WRONG_VALUE;             // Тип графического элемента
   this.m_belong=WRONG_VALUE;                   // Принадлежность программе/терминалу
   this.m_name_prefix="";                       // Префикс имени объекта
   this.m_name="";                              // Имя объекта
   this.m_chart_id=0;                           // Идентификатор графика объекта
   this.m_object_id=0;                          // Идентификатор объекта
   this.m_zorder=0;                             // Приоритет графического объекта на получение события нажатия мышки
   this.m_subwindow=0;                          // Номер подокна
   this.m_shift_y=0;                            // Смещение координаты Y для подокна
   this.m_timeframes_visible=OBJ_ALL_PERIODS;   // Видимость объекта на таймфреймах (набор флагов)
   this.m_visible=true;                         // Видимость объекта
   this.m_back=false;                           // Флаг "Объект на заднем плане"
   this.m_selected=false;                       // Флаг "Выделенность объекта"
   this.m_selectable=false;                     // Флаг "Доступность объекта"
   this.m_hidden=true;                          // Флаг "Запрет на показ имени графического объекта в списке объектов терминала"
   this.m_create_time=0;                        // Время создания объекта
   
  }
//+------------------------------------------------------------------+


В классе стандартного графического объекта \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh в методе, проверяющем изменение свойств объекта, очистим список событий, пропишем создание и добавление в список объектов-событий и их отправку в обработчик событий коллекции графических объектов из созданного списка:

//+------------------------------------------------------------------+
//| Проверяет изменения свойств объекта                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   CGBaseObj::ClearEventsList();
   bool changed=false;
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME)
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

Здесь мы в цикле по всем свойствам объекта — целочисленным, вещественным и строковым, проверяем наличие изменений каждого очередного свойства объекта и, если есть изменение, то создаём объект-событие и добавляем его в список событий объекта. Затем, если есть хоть одно изменение, то в цикле по созданному списку получаем каждый очередной объект-событие и отправляем его на график управляющей программы. Далее эти события попадут в обработчик OnChartEvent() класса-коллекции графических элементов, и в нём будут обработаны.

Откроем файл класса-коллекции графических элементов \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh. Немного доработаем метод класса управления графическими объектами, проверяющий объекты на чарте:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Проверяет объекты на чарте                 |
//+------------------------------------------------------------------+
void CChartObjectsControl::Refresh(void)
  {
//--- Графические объекты на чарте
   this.m_total_objects=::ObjectsTotal(this.ChartID());
   this.m_delta_graph_obj=this.m_total_objects-this.m_last_objects;

   //--- Если добавлен объект на график
   if(this.m_delta_graph_obj>0)
     {
      //--- находим последний добавленный графический объект, выбираем его и записываем его имя
      string name=this.LastAddedGraphObjName();
      //--- Обрабатываем только объекты, созданные не программно
      if(name!="" && ::StringFind(name,m_name_program)==WRONG_VALUE)
        {
         //--- Создаём объект класса графического объекта, соответствующий типу добавленного графического объекта
         ENUM_OBJECT type=(ENUM_OBJECT)::ObjectGetInteger(this.ChartID(),name,OBJPROP_TYPE);
         ENUM_OBJECT_DE_TYPE obj_type=ENUM_OBJECT_DE_TYPE(type+OBJECT_DE_TYPE_GSTD_OBJ+1);
         CGStdGraphObj *obj=this.CreateNewGraphObj(type,name);
         if(obj==NULL)
           {
            CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ);
            return;
           }
         //--- Устанавливаем объекту его принадлежность и добавляем созданный объект в список новых объектов
         obj.SetBelong(GRAPH_OBJ_BELONG_NO_PROGRAM); 
         if(this.m_list_new_graph_obj.Search(obj)==WRONG_VALUE)
           {
            //--- Если объект добавить в список не удалось - сообщаем об этом, удаляем объект и уходим
            if(!this.m_list_new_graph_obj.Add(obj))
              {
               CMessage::ToLog(DFUN_ERR_LINE,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
               delete obj;
               return;
              }
            //--- Отправляем событие на график управляющей программы
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_CREATE,this.ChartID(),0,obj.Name());
           }
        }
     }

//--- сохранение индекса последнего добавленного графического объекта и разницы по сравнению с прошлой проверкой
   this.m_last_objects=this.m_total_objects;
   this.m_is_graph_obj_event=(bool)this.m_delta_graph_obj;
  }
//+------------------------------------------------------------------+

Здесь мы добавили вывод сообщений об ошибках в журнал и отправку события создания объекта на график управляющей программы.

В публичной секции класса-коллекции графических элементов добавим метод, возвращающий список объектов управления графиками:

//--- Возвращает количество новых графических объектов, (3) флаг произошедшего изменения в списке графических объектов
   int               NewObjects(void)   const                                                            { return this.m_delta_graph_obj;       }
   bool              IsEvent(void) const                                                                 { return this.m_is_graph_obj_event;    }
//--- Возвращает графический объект по имени и идентификатору графика
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
//--- Возвращает список объектов управления графиками
   CArrayObj        *GetListChartsControl(void)                                                          { return &this.m_list_charts_control;  }
//--- Конструктор
                     CGraphElementsCollection();


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

//--- Обработчик событий
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

private:
//--- Создаёт новый графический объект, возвращает указатель на объект управления чартами
   CChartObjectsControl *CreateNewStdGraphObjectAndGetCtrlObj(const long chart_id,
                                                              const string name,
                                                              int subwindow,
                                                              const ENUM_OBJECT type_object,
                                                              const datetime time1,
                                                              const double price1,
                                                              const datetime time2=0,
                                                              const double price2=0,
                                                              const datetime time3=0,
                                                              const double price3=0,
                                                              const datetime time4=0,
                                                              const double price4=0,
                                                              const datetime time5=0,
                                                              const double price5=0)
                       {
                        //--- Если объект с идентификатором графика и именем уже есть в коллекции - сообщаем об этом и возвращаем NULL
                        if(this.IsPresentGraphObjInList(chart_id,name))
                          {
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS)," ChartID ",(string)chart_id,", ",name);
                           return NULL;
                          }
                        //--- Если не удалось создать новый стандартный графический объект - сообщаем об этом и возвращаем NULL
                        if(!this.CreateNewStdGraphObject(chart_id,name,type_object,subwindow,time1,0))
                          {
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),StdGraphObjectTypeDescription(type_object));
                           CMessage::ToLog(::GetLastError(),true);
                           return NULL;
                          }
                        //--- Если не удалось получить объект управления графиком - сообщаем об этом
                        CChartObjectsControl *ctrl=this.GetChartObjectCtrlObj(chart_id);
                        if(ctrl==NULL)
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ),(string)chart_id);
                        //--- Возвращаем указатель на объект управления графиком, либо NULL при ошибке его получения
                        return ctrl;
                       }
//--- Добавляет созданный объект в список
   bool              AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj)
                       {
                        bool res=true;
                        if(!this.m_list_all_graph_obj.Add(obj))
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           ::ObjectDelete(chart_id,name);
                           delete obj;
                           res=false;
                          }
                        ::ChartRedraw(chart_id);
                        return res;
                       }
public:
//--- Создаёт графический объект "Вертикальная линия"

И далее, во всех методах для создания стандартных графических объектов будем возвращать результат работы этого метода:

public:
//--- Создаёт графический объект "Вертикальная линия"
   bool              CreateLineVertical(const long chart_id,const string name,const int subwindow,const datetime time)
                       {
                        //--- Задаём имя и тип создаваемого объекта
                        string nm=this.m_name_program+"_"+name;
                        ENUM_OBJECT type_object=OBJ_VLINE;
                        //--- Создаём новый графический объект и получаем указатель на объект управления графиком
                        CChartObjectsControl *ctrl=this.CreateNewStdGraphObjectAndGetCtrlObj(chart_id,nm,subwindow,type_object,time,0);
                        if(ctrl==NULL)
                           return false;
                        //--- Создаём новый объект класса, соответствующий созданному графическому объекту
                        CGStdVLineObj *obj=ctrl.CreateNewGraphObj(type_object,nm);
                        if(obj==NULL)
                          {
                           ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ),StdGraphObjectTypeDescription(type_object));
                           return false;
                          }
                        //--- Устанавливаем объекту необходимые минимальные параметры
                        obj.SetBelong(GRAPH_OBJ_BELONG_PROGRAM);
                        obj.SetFlagSelectable(true,false);
                        obj.SetFlagSelected(true,false);
                        obj.SetObjectID(this.GetFreeGraphObjID(true));
                        obj.PropertiesCopyToPrevData();
                        //--- Возвращаем результат добавления объекта в список
                        return this.AddCreatedObjToList(DFUN,chart_id,nm,obj);
                       }

Здесь же пропишем флаги, необходимые для правильной работы изменённого сегодня метода для установки значений свойств графического объекта.

Все остальные методы для создания стандартных графических объектов изменены точно таким же образом, и здесь мы их рассматривать не будем — с ними можно ознакомиться в прикреплённых к статье файлах.

В методе, создающем новый объект управления графическими объектами указанного чарта и добавляющем его в список, добавим вывод сообщений в журнал при добавлении индикатора контроля событий на график:

//+------------------------------------------------------------------+
//| Создаёт новый объект управления графическими объектами           |
//| указанного чарта и добавляет его в список                        |
//+------------------------------------------------------------------+
CChartObjectsControl *CGraphElementsCollection::CreateChartObjectCtrlObj(const long chart_id)
  {
//--- Создаём новый объект управления объектами графика по идентификатору
   CChartObjectsControl *obj=new CChartObjectsControl(chart_id);
//--- Если объект не создан - сообщаем об ошибке и возвращаем NULL
   if(obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ),(string)chart_id);
      return NULL;
     }
//--- Если объект не удалось добавить в список - сообщаем об ошибке, удаляем объект и возвращаем NULL
   if(!this.m_list_charts_control.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      delete obj;
      return NULL;
     }
   if(obj.ChartID()!=CBaseObj::GetMainChartID() && obj.CreateEventControlInd(CBaseObj::GetMainChartID()))
      if(!obj.AddEventControlInd())
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR);
         CMessage::ToLog(DFUN,::GetLastError(),true);
        }
      else
         ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR),obj.Symbol()," #",obj.ChartID());
//--- Возвращаем указатель на созданный и добавленный в список объект
   return obj;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Обновляет список всех графических объектов                       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
   this.RefreshForExtraObjects();
//--- Объявим переменные для поиска графиков
   long chart_id=0;
   int i=0;
//--- В цикле по всем открытым графикам терминала (не более 100)
   while(i<CHARTS_MAX)
     {
      //--- Получим идентификатор графика
      chart_id=::ChartNext(chart_id);
      if(chart_id<0)
         break;
      //--- Получаем указатель на объект управления графическими объектами
      //--- и обновляем список графических объектов по идентификатору чарта
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      //--- Если указатель получить не удалось - идём к следующему графику
      if(obj_ctrl==NULL)
         continue;
      //--- Если есть событие изменения количества объектов на графике
      if(obj_ctrl.IsEvent())
        {
         //--- Если добавлен графический объект на график
         if(obj_ctrl.Delta()>0)
           {
            //--- Получаем список добавленных графических объектов и перемещаем их в список-коллекцию
            //--- (если объект поместить в коллекцию не удалось - идём к следующему объекту)
            if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- Если удалён графический объект
         else if(obj_ctrl.Delta()<0)
           {
            // Найдём лишний объект в списке
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);
            if(obj!=NULL)
              {
               //--- Отправляем событие на график управляющей программы
               ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,obj.ChartID(),0,obj.Name());
               //--- Удалим объект класса удалённого графического объекта из списка-коллекции
               if(!this.DeleteGraphObjFromList(obj))
                  CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
              }
           }
        }
      //--- Увеличиваем индекс цикла
      i++;
     }
  }
//+------------------------------------------------------------------+

В событии, в его строковом параметре, передаём имя удалённого объекта. Вполне возможно, что далее сделаем сохранение удалённых объектов в отдельный список — чтобы при получении события об удалении можно было посмотреть какой объект был удалён, и какие свойства он имел, ну и восстановить его при необходимости программно.

В методе, обрабатывающем удаление окна графика, тоже вместо простого сообщения в журнал теперь будем отправлять событие на график управляющей программы:

//+------------------------------------------------------------------+
//| Обрабатывает удаление окна графика                               |
//+------------------------------------------------------------------+
void CGraphElementsCollection::RefreshForExtraObjects(void)
  {
   for(int i=this.m_list_charts_control.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObjectsControl *obj_ctrl=this.m_list_charts_control.At(i);
      if(obj_ctrl==NULL)
         continue;
      if(!this.IsPresentChartWindow(obj_ctrl.ChartID()))
        {
         long chart_id=obj_ctrl.ChartID();
         string chart_symb=obj_ctrl.Symbol();
         int total_ctrl=this.m_list_charts_control.Total();
         this.DeleteGraphObjCtrlObjFromList(obj_ctrl);
         int total_obj=this.m_list_all_graph_obj.Total();
         this.DeleteGraphObjectsFromList(chart_id);
         int del_ctrl=total_ctrl-this.m_list_charts_control.Total();
         int del_obj=total_obj-this.m_list_all_graph_obj.Total();
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DEL_CHART,chart_id,del_obj,chart_symb);
        }
     }
  }
//+------------------------------------------------------------------+

Здесь мы передаём в параметрах события идентификатор закрытого графика, количество графических объектов, удалённых вместе с ним, и символ закрытого графика.

В обработчике событий класса коллекции графических элементов добавим обработку базовых событий графических объектов:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Рассчитаем идентификатор графика
      //--- Если id события соответствует событию с текущего графика, то идентификатор графика получаем от ChartID
      //--- Если id события соответствует пользовательскому событию, то идентификатор графика получаем из lparam
      //--- Иначе - идентификатору графика присваиваем -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Получим объект из списка-коллекции по его имени, записанном в sparam,
      //--- свойства которого изменены, или который был перемещён
      obj=this.GetStdGraphObject(sparam,chart_id);

      //--- Если объект не удалось получить по имени - он отсутствует в списке,
      //--- а значит его имя было изменено
      if(obj==NULL)
        {
         //--- Найдём в списке объект, которого нет на графике
         obj=this.FindMissingObj(chart_id);
         //--- Если и тут не удалось найти объект - уходим
         if(obj==NULL)
            return;
         //--- Получим имя переименованного  графического объекта на графике, которого нет в списке-коллекции
         string name_new=this.FindExtraObj(chart_id);
         //--- Отправим событие со старым именем объекта на график управляющей программы и
         //--- установим новое имя объекту в списке-коллекции, которому не соответствует ни один графический объект на графике
         ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),0,obj.Name());
         obj.SetName(name_new);
        }
      //--- Обновим свойства полученного объекта
      //--- и проверим их изменение
      obj.PropertiesRefresh();
      obj.PropertiesCheckChanged();
     }
//--- Обработка событий стандартных графических объектов
   if(idx>GRAPH_OBJ_EVENT_NO_EVENT && idx<GRAPH_OBJ_EVENTS_NEXT_CODE)
     {
      //--- В зависимости от типа события выводим сообщение о нём в журнал
      switch(idx)
        {
         case GRAPH_OBJ_EVENT_CREATE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_CHANGE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_RENAME   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME));
           obj=this.GetStdGraphObject(sparam,lparam);
           if(obj!=NULL)
              obj.PrintShort();
           break;
         case GRAPH_OBJ_EVENT_DELETE   :
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE));
           break;
         case GRAPH_OBJ_EVENT_DEL_CHART:
           ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART),": ChartID: ",lparam,", ChartSymbol: ",sparam);
           break;
         default:
           break;
        }
     }
  }
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду. Здесь мы добавили реакцию на событие щелчка по графику, и если был двойной щелчок, то у объекта изменится его состояние выделенности, что приведёт к появлению события об изменении параметров объекта. Таким образом, мы сможем обрабатывать события выделения и снятия выделения с объекта.
В конечном итоге в этот же метод приходят уже обработанные события графического объекта, для которых были созданы базовые события. Эти базовые события обрабатываются в новом блоке кода. Пока здесь у нас лишь вывод сообщений в журнал. В последующих статьях создадим полноценную обработку каждого из этих событий так, чтобы программа могла знать и быстро получить доступ как к самому изменённому объекту, так и к его свойствам.


Для упрощения работы с графическими объектами, создадим в главном классе библиотеки CEngine в \MQL5\Include\DoEasy\Engine.mqh методы для работы с графическими объектами.

Чтобы при использовании классов стандартных графических объектов определить какие графики у нас открыты, мы можем посмотреть список объектов управления графиками. В этом списке можно взять из объектов идентификаторы всех открытых графиков, тем самым получив возможность к ним обращаться. Чтобы в программе упростить получение идентификаторов, создадим метод, который будет заполнять переданный в него long-массив значениями идентификаторов открытых графиков, для которых созданы объекты управления:

//--- Запускает новый отсчёт паузы
   void                 Pause(const ulong pause_msc,const datetime time_start=0)
                          {
                           this.PauseSetWaitingMSC(pause_msc);
                           this.PauseSetTimeBegin(time_start*1000);
                           while(!this.PauseIsCompleted() && !::IsStopped()){}
                          }

//--- Возвращает коллекцию графических объектов
   CGraphElementsCollection *GetGraphicObjCollection(void)              { return &this.m_graph_objects; }
   
//--- Заполняет массив идентификаторами графиков, открытых в терминале
   void              GraphGetArrayChartsID(long &array_charts_id[])
                       {
                        CArrayObj *list=this.m_graph_objects.GetListChartsControl();
                        if(list==NULL)
                           return;
                        ::ArrayResize(array_charts_id,list.Total());
                        ::ArrayInitialize(array_charts_id,WRONG_VALUE);
                        for(int i=0;i<list.Total();i++)
                          {
                           CChartObjectsControl *obj=list.At(i);
                           if(obj==NULL)
                              continue;
                           array_charts_id[i]=obj.ChartID();
                          }
                       }

//--- Создаёт графический объект "Вертикальная линия"

Здесь мы сразу устанавливаем размер переданного в метод массива равным размеру списка объектов управления графиками и инициализируем массив значениями -1. Это нужно в случае ошибки получения объекта управления графиками в цикле, чтобы в ячейку массива, соответствующую индексу объекта управления графиками, который получить не удалось, было вписано значение -1, что будет служить сигналом ошибки получения этого объекта.
Можно конечно увеличивать размер массива в цикле только в случае успешного получения объекта, но вызов ArrayResize() внутри цикла не способствует быстродействию. Поэтому массив сразу увеличиваем под размер списка, и в случае ошибки соответствующая ячейка массива будет содержать -1.
Далее в цикле по полученному списку получаем очередной объект управления графиками и записываем в соответствующую индексу цикла ячейку массива идентификатор графика, содержащийся в объекте управления графиками.

Для упрощённого доступа к методам создания стандартных графических объектов напишем методы для их создания — для каждого объекта по два метода. Первый метод будет вызывать одноимённый метод класса коллекции графических элементов для создания стандартного графического объекта,
а второйбудет вызывать первый метод с указанием идентификатора текущего чарта:

//--- Создаёт графический объект "Вертикальная линия"
   bool              CreateLineVertical(const long chart_id,const string name,const int subwindow,const datetime time)
                       { return this.m_graph_objects.CreateLineVertical(chart_id,name,subwindow,time); }
   bool              CreateLineVertical(const string name,const int subwindow,const datetime time)
                       { return this.CreateLineVertical(::ChartID(),name,subwindow,time); }

//--- Создаёт графический объект "Горизонтальная линия"
   bool              CreateLineHorizontal(const long chart_id,const string name,const int subwindow,const double price)
                       { return this.m_graph_objects.CreateLineHorizontal(chart_id,name,subwindow,price); }
   bool              CreateLineHorizontal(const string name,const int subwindow,const double price)
                       { return this.CreateLineHorizontal(::ChartID(),name,subwindow,price); }
 
//--- Создаёт графический объект "Трендовая линия"
   bool              CreateLineTrend(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateLineTrend(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateLineTrend(const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateLineTrend(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Трендовая линия по углу"
   bool              CreateLineTrendByAngle(const long chart_id,const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const double angle)
                       { return this.m_graph_objects.CreateLineTrendByAngle(chart_id,name,subwindow,time1,price1,time2,price2,angle); }
   bool              CreateLineTrendByAngle(const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const double angle)
                       { return this.CreateLineTrendByAngle(::ChartID(),name,subwindow,time1,price1,time2,price2,angle); }

//--- Создаёт графический объект "Циклические линии"
   bool              CreateLineCycle(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateLineCycle(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateLineCycle(const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateLineCycle(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Линия со стрелкой"
   bool              CreateLineArrowed(const long chart_id,const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateLineArrowed(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateLineArrowed(const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateLineArrowed(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Равноудаленный канал"
   bool              CreateChannel(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateChannel(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateChannel(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const datetime time3,const double price3)
                       { return this.CreateChannel(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Создаёт графический объект "Канал стандартного отклонения"
   bool              CreateChannelStdDeviation(const long chart_id,const string name,const int subwindow,
                                               const datetime time1,const double price1,const datetime time2,const double price2,
                                               const double deviation=1.5)
                       { return this.m_graph_objects.CreateChannelStdDeviation(chart_id,name,subwindow,time1,price1,time2,price2,deviation); }
   bool              CreateChannelStdDeviation(const string name,const int subwindow,
                                               const datetime time1,const double price1,const datetime time2,const double price2,
                                               const double deviation=1.5)
                       { return this.CreateChannelStdDeviation(::ChartID(),name,subwindow,time1,price1,time2,price2,deviation); }

//--- Создаёт графический объект "Канал на линейной регрессии"
   bool              CreateChannelRegression(const long chart_id,const string name,const int subwindow,
                                             const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateChannelRegression(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateChannelRegression(const string name,const int subwindow,
                                             const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateChannelRegression(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Вилы Эндрюса"
   bool              CreatePitchforkAndrews(const long chart_id,const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const datetime time3,const double price3)
                       { return this.m_graph_objects.CreatePitchforkAndrews(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreatePitchforkAndrews(const string name,const int subwindow,
                                            const datetime time1,const double price1,const datetime time2,const double price2,
                                            const datetime time3,const double price3)
                       { return this.CreatePitchforkAndrews(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Создаёт графический объект "Линия Ганна"
   bool              CreateGannLine(const long chart_id,const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const double angle)
                       { return this.m_graph_objects.CreateGannLine(chart_id,name,subwindow,time1,price1,time2,price2,angle); }
   bool              CreateGannLine(const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const double angle)
                       { return this.CreateGannLine(::ChartID(),name,subwindow,time1,price1,time2,price2,angle); }

//--- Создаёт графический объект "Веер Ганна"
   bool              CreateGannFan(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.m_graph_objects.CreateGannFan(chart_id,name,subwindow,time1,price1,time2,price2,direction,scale); }
   bool              CreateGannFan(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.CreateGannFan(::ChartID(),name,subwindow,time1,price1,time2,price2,direction,scale); }

//--- Создаёт графический объект "Сетка Ганна"
   bool              CreateGannGrid(const long chart_id,const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,
                                    const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.m_graph_objects.CreateGannGrid(chart_id,name,subwindow,time1,price1,time2,direction,scale); }
   bool              CreateGannGrid(const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,
                                    const ENUM_GANN_DIRECTION direction,const double scale)
                       { return this.CreateGannGrid(::ChartID(),name,subwindow,time1,price1,time2,direction,scale); }

//--- Создаёт графический объект "Уровни Фибоначчи"
   bool              CreateFiboLevels(const long chart_id,const string name,const int subwindow,
                                      const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateFiboLevels(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateFiboLevels(const string name,const int subwindow,
                                      const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateFiboLevels(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Временные зоны Фибоначчи"
   bool              CreateFiboTimeZones(const long chart_id,const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateFiboTimeZones(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateFiboTimeZones(const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateFiboTimeZones(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Веер Фибоначчи"
   bool              CreateFiboFan(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateFiboFan(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateFiboFan(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateFiboFan(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Дуги Фибоначчи"
   bool              CreateFiboArc(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const double scale,const bool ellipse)
                       { return this.m_graph_objects.CreateFiboArc(chart_id,name,subwindow,time1,price1,time2,price2,scale,ellipse); }
   bool              CreateFiboArc(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,
                                   const double scale,const bool ellipse)
                       { return this.CreateFiboArc(::ChartID(),name,subwindow,time1,price1,time2,price2,scale,ellipse); }

//--- Создаёт графический объект "Канал Фибоначчи"
   bool              CreateFiboChannel(const long chart_id,const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateFiboChannel(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateFiboChannel(const string name,const int subwindow,
                                       const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateFiboChannel(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Создаёт графический объект "Расширение Фибоначчи"
   bool              CreateFiboExpansion(const long chart_id,const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateFiboExpansion(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateFiboExpansion(const string name,const int subwindow,
                                         const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateFiboExpansion(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Создаёт графический объект "5-волновка Эллиота"
   bool              CreateElliothWave5(const long chart_id,const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,const double price2,
                                        const datetime time3,const double price3,const datetime time4,const double price4,
                                        const datetime time5,const double price5,const ENUM_ELLIOT_WAVE_DEGREE degree,
                                        const bool draw_lines)
                       { return this.m_graph_objects.CreateElliothWave5(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3,time4,price4,time5,price5,degree,draw_lines); }
   bool              CreateElliothWave5(const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,const double price2,
                                        const datetime time3,const double price3,const datetime time4,const double price4,
                                        const datetime time5,const double price5,const ENUM_ELLIOT_WAVE_DEGREE degree,
                                        const bool draw_lines)
                       { return this.CreateElliothWave5(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3,time4,price4,time5,price5,degree,draw_lines); }

//--- Создаёт графический объект "3-волновка Эллиота"
   bool              CreateElliothWave3(const long chart_id,const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,
                                        const double price2,const datetime time3,const double price3,
                                        const ENUM_ELLIOT_WAVE_DEGREE degree,const bool draw_lines)
                       { return this.m_graph_objects.CreateElliothWave3(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3,degree,draw_lines); }
   bool              CreateElliothWave3(const string name,const int subwindow,
                                        const datetime time1,const double price1,const datetime time2,
                                        const double price2,const datetime time3,const double price3,
                                        const ENUM_ELLIOT_WAVE_DEGREE degree,const bool draw_lines)
                       { return this.CreateElliothWave3(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3,degree,draw_lines); }

//--- Создаёт графический объект "Прямоугольник"
   bool              CreateRectangle(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.m_graph_objects.CreateRectangle(chart_id,name,subwindow,time1,price1,time2,price2); }
   bool              CreateRectangle(const string name,const int subwindow,
                                     const datetime time1,const double price1,const datetime time2,const double price2)
                       { return this.CreateRectangle(::ChartID(),name,subwindow,time1,price1,time2,price2); }

//--- Создаёт графический объект "Треугольник"
   bool              CreateTriangle(const long chart_id,const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateTriangle(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateTriangle(const string name,const int subwindow,
                                    const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateTriangle(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Создаёт графический объект "Эллипс"
   bool              CreateEllipse(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.m_graph_objects.CreateEllipse(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
   bool              CreateEllipse(const string name,const int subwindow,
                                   const datetime time1,const double price1,const datetime time2,const double price2,const datetime time3,const double price3)
                       { return this.CreateEllipse(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }

//--- Создаёт графический объект "Знак "Хорошо"
   bool              CreateThumbUp(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateThumbUp(chart_id,name,subwindow,time,price); }
   bool              CreateThumbUp(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateThumbUp(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Знак "Плохо"
   bool              CreateThumbDown(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateThumbDown(chart_id,name,subwindow,time,price); }
   bool              CreateThumbDown(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateThumbDown(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Знак "Стрелка вверх"
   bool              CreateArrowUp(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateArrowUp(chart_id,name,subwindow,time,price); }
   bool              CreateArrowUp(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateArrowUp(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Знак "Стрелка вниз"
   bool              CreateArrowDown(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateArrowDown(chart_id,name,subwindow,time,price); }
   bool              CreateArrowDown(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateArrowDown(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Знак "Стоп"
   bool              CreateSignalStop(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalStop(chart_id,name,subwindow,time,price); }
   bool              CreateSignalStop(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalStop(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Знак "Птичка"
   bool              CreateSignalCheck(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalCheck(chart_id,name,subwindow,time,price); }
   bool              CreateSignalCheck(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalCheck(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Левая ценовая метка"
   bool              CreatePriceLabelLeft(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreatePriceLabelLeft(chart_id,name,subwindow,time,price); }
   bool              CreatePriceLabelLeft(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreatePriceLabelLeft(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Правая ценовая метка"
   bool              CreatePriceLabelRight(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreatePriceLabelRight(chart_id,name,subwindow,time,price); }
   bool              CreatePriceLabelRight(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreatePriceLabelRight(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Знак "Buy"
   bool              CreateSignalBuy(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalBuy(chart_id,name,subwindow,time,price); }
   bool              CreateSignalBuy(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalBuy(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Знак "Sell"
   bool              CreateSignalSell(const long chart_id,const string name,const int subwindow,const datetime time,const double price)
                       { return this.m_graph_objects.CreateSignalSell(chart_id,name,subwindow,time,price); }
   bool              CreateSignalSell(const string name,const int subwindow,const datetime time,const double price)
                       { return this.CreateSignalSell(::ChartID(),name,subwindow,time,price); }

//--- Создаёт графический объект "Стрелка"
   bool              CreateArrow(const long chart_id,const string name,const int subwindow,const datetime time,const double price,
                                 const uchar arrow_code,const ENUM_ARROW_ANCHOR anchor)
                       { return this.m_graph_objects.CreateArrow(chart_id,name,subwindow,time,price,arrow_code,anchor); }
   bool              CreateArrow(const string name,const int subwindow,const datetime time,const double price,
                                 const uchar arrow_code,const ENUM_ARROW_ANCHOR anchor)
                       { return this.CreateArrow(::ChartID(),name,subwindow,time,price,arrow_code,anchor); }

//--- Создаёт графический объект "Текст"
   bool              CreateText(const long chart_id,const string name,const int subwindow,const datetime time,const double price,
                                const string text,const int size,const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.m_graph_objects.CreateText(chart_id,name,subwindow,time,price,text,size,anchor_point,angle); }
   bool              CreateText(const string name,const int subwindow,const datetime time,const double price,
                                const string text,const int size,const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.CreateText(::ChartID(),name,subwindow,time,price,text,size,anchor_point,angle); }

//--- Создаёт графический объект "Текстовая метка"
   bool              CreateTextLabel(const long chart_id,const string name,const int subwindow,const int x,const int y,
                                     const string text,const int size,const ENUM_BASE_CORNER corner,
                                     const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.m_graph_objects.CreateTextLabel(chart_id,name,subwindow,x,y,text,size,corner,anchor_point,angle); }
   bool              CreateTextLabel(const string name,const int subwindow,const int x,const int y,
                                     const string text,const int size,const ENUM_BASE_CORNER corner,
                                     const ENUM_ANCHOR_POINT anchor_point,const double angle)
                       { return this.CreateTextLabel(::ChartID(),name,subwindow,x,y,text,size,corner,anchor_point,angle); }

//--- Создаёт графический объект "Кнопка"
   bool              CreateButton(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                  const ENUM_BASE_CORNER corner,const int font_size,const bool button_state)
                       { return this.m_graph_objects.CreateButton(chart_id,name,subwindow,x,y,w,h,corner,font_size,button_state); }
   bool              CreateButton(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                  const ENUM_BASE_CORNER corner,const int font_size,const bool button_state)
                       { return this.CreateButton(::ChartID(),name,subwindow,x,y,w,h,corner,font_size,button_state); }

//--- Создаёт графический объект "График"
   bool              CreateChart(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                 const ENUM_BASE_CORNER corner,const int scale,const string symbol,const ENUM_TIMEFRAMES timeframe)
                       { return this.m_graph_objects.CreateChart(chart_id,name,subwindow,x,y,w,h,corner,scale,symbol,timeframe); }
   bool              CreateChart(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                 const ENUM_BASE_CORNER corner,const int scale,const string symbol,const ENUM_TIMEFRAMES timeframe)
                       { return this.CreateChart(::ChartID(),name,subwindow,x,y,w,h,corner,scale,symbol,timeframe); }

//--- Создаёт графический объект "Рисунок"
   bool              CreateBitmap(const long chart_id,const string name,const int subwindow,const datetime time,const double price,
                                  const string image1,const string image2,const ENUM_ANCHOR_POINT anchor)
                       { return this.m_graph_objects.CreateBitmap(chart_id,name,subwindow,time,price,image1,image2,anchor); }
   bool              CreateBitmap(const string name,const int subwindow,const datetime time,const double price,
                                  const string image1,const string image2,const ENUM_ANCHOR_POINT anchor)
                       { return this.CreateBitmap(::ChartID(),name,subwindow,time,price,image1,image2,anchor); }

//--- Создаёт графический объект "Графическая метка"
   bool              CreateBitmapLabel(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                       const string image1,const string image2,const ENUM_BASE_CORNER corner,const ENUM_ANCHOR_POINT anchor,
                                       const bool state)
                       { return this.m_graph_objects.CreateBitmapLabel(chart_id,name,subwindow,x,y,w,h,image1,image2,corner,anchor,state); }
   bool              CreateBitmapLabel(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                       const string image1,const string image2,const ENUM_BASE_CORNER corner,const ENUM_ANCHOR_POINT anchor,
                                       const bool state)
                       { return this.CreateBitmapLabel(::ChartID(),name,subwindow,x,y,w,h,image1,image2,corner,anchor,state); }

//--- Создаёт графический объект "Поле ввода"
   bool              CreateEditField(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                     const int font_size,const ENUM_BASE_CORNER corner,const ENUM_ALIGN_MODE align,const bool readonly)
                       { return this.m_graph_objects.CreateEditField(chart_id,name,subwindow,x,y,w,h,font_size,corner,align,readonly); }
   bool              CreateEditField(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                     const int font_size,const ENUM_BASE_CORNER corner,const ENUM_ALIGN_MODE align,const bool readonly)
                       { return this.CreateEditField(::ChartID(),name,subwindow,x,y,w,h,font_size,corner,align,readonly); }

//--- Создаёт графический объект "Событие экономического календаря"
   bool              CreateCalendarEvent(const long chart_id,const string name,const int subwindow,const datetime time)
                       { return this.m_graph_objects.CreateCalendarEvent(chart_id,name,subwindow,time); }
   bool              CreateCalendarEvent(const string name,const int subwindow,const datetime time)
                       { return this.CreateCalendarEvent(::ChartID(),name,subwindow,time); }

//--- Создаёт графический объект "Прямоугольная метка"
   bool              CreateRectangleLabel(const long chart_id,const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                          const ENUM_BASE_CORNER corner,const ENUM_BORDER_TYPE border)
                       { return this.m_graph_objects.CreateRectangleLabel(chart_id,name,subwindow,x,y,w,h,corner,border); }
   bool              CreateRectangleLabel(const string name,const int subwindow,const int x,const int y,const int w,const int h,
                                          const ENUM_BASE_CORNER corner,const ENUM_BORDER_TYPE border)
                       { return this.CreateRectangleLabel(::ChartID(),name,subwindow,x,y,w,h,corner,border); }
   
//--- Конструктор/Деструктор
                        CEngine();
                       ~CEngine();

На сегодня это все доработки, необходимые для создания базового функционала для отслеживания событий графических объектов.


Тестирование

Для тестирования возьмём советник из прошлой статьи и
сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part90\ под новым именем TestDoEasyPart90.mq5.

Как будем тестировать? В прошлой статье мы при щелчке по графику с зажатой клавишей Ctrl создавали вертикальную линию. Теперь же мы будем тоже создавать её, но на всех открытых графиках в терминале.

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

   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         long array[];
         engine.GraphGetArrayChartsID(array);
         for(int i=0;i<ArraySize(array);i++)
            engine.CreateLineVertical(array[i],"LineVertical",0,time);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
   
  }
//+------------------------------------------------------------------+

Скомпилируем советник и запустим его на графике, предварительно открыв ещё один график, и расположив их горизонтально. При щелчке по графику с советником будут созданы вертикальные линии — по одной на каждом из графиков. Теперь поизменяем их свойства и посмотрим, как в журнале выводятся сообщения о полученных событиях:


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


Что дальше

В следующей статье продолжим работу над событиями графических объектов и создадим обработку каждого полученного события.

Ниже прикреплены все файлы текущей версии библиотеки, файлы тестового советника и индикатора контроля событий графиков для MQL5. Их можно скачать и протестировать всё самостоятельно. При возникновении вопросов, замечаний и пожеланий вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

Графика в библиотеке DoEasy (Часть 86): Коллекция графических объектов - контролируем модификацию свойств
Графика в библиотеке DoEasy (Часть 87): Коллекция графических объектов - контроль модификации свойств объектов на всех открытых графиках
Графика в библиотеке DoEasy (Часть 88): Коллекция графических объектов - двумерный динамический массив для хранения динамически изменяемых свойств объектов
Графика в библиотеке DoEasy (Часть 89): Программное создание стандартных графических объектов. Базовый функционал

Прикрепленные файлы |
MQL5.zip (4237 KB)
Улучшение распознавания свечных паттернов на примере Доджи Улучшение распознавания свечных паттернов на примере Доджи
Как находить свечные паттерны чаще, чем обычно. За простотой свечных паттернов скрывается и серьезный недостаток, который как раз можно устранить, используя существенно выросшие возможности современных средств автоматизации трейдинга.
Универсальная регрессионная модель для прогнозирования рыночной цены (Часть 2): Функции природных, техногенных и общественных переходных процессов Универсальная регрессионная модель для прогнозирования рыночной цены (Часть 2): Функции природных, техногенных и общественных переходных процессов
Настоящая статья является логическим продолжением предыдущей и написана для освещения выявленных фактов подтверждения ее выводов в течении последующих десяти лет после ее выхода, по части выявленных трех функций динамических переходных процессов, описывающих закономерности изменения рыночной цены.
Работаем со временем (Часть 1): Основные принципы Работаем со временем (Часть 1): Основные принципы
Рассмотренные в статье функции и код помогут лучше понять принципы обработки времени, смещение времени брокера и перехода на летнее или зимнее время. Точная работа со временем — очень важный аспект трейдинга. Лондонская или нью-йоркская биржа уже открылась или еще нет? Когда начинается и заканчивается торговая сессия на форексе?
Графика в библиотеке DoEasy (Часть 89): Программное создание стандартных графических объектов. Базовый функционал Графика в библиотеке DoEasy (Часть 89): Программное создание стандартных графических объектов. Базовый функционал
Наша библиотека теперь умеет отслеживать появление на графике клиентского терминала стандартных графических объектов, их удаление и модификацию некоторых их параметров. Но для полного "комплекта" нам, конечно же, не хватает возможности создавать стандартные графические объекты из своих программ.