English 中文 Español Deutsch 日本語 Português
Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента

Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента

MetaTrader 5Примеры | 20 мая 2021, 14:59
3 409 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

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

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

Иерархия же одного объекта всегда будет такой:

  1. Базовый объект всех графических элементов библиотеки на основе класса CObject. В этом объекте будет объявлен объект класса CCanvas, и будут содержаться все общие для графических элементов параметры — такие как ширина, высота, координаты на графике, правая и нижняя границы объекта, и т.д.,
  2. Объект-форма графического элемента — этот объект будет представлять собой основу (холст) любого графического объекта — на нём будут размещаться все другие элементы составного объекта, и при помощи его параметров можно будет задавать параметры всему графическому объекту. Здесь же будет объявлен объект класса, предоставляющего методы работы с состоянием мышки — координатами курсора и нажатыми кнопками.

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

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


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

В файл \MQL5\Include\DoEasy\Defines.mqh добавим новый подраздел параметров канваса и впишем макроподстановку с частотой его обновления:

//--- Параметры серий снимков стакана цен
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // Требуемое количество дней для снимков стакана цен в сериях по умолчанию
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Максимальное количество хранимых снимков стакана цен одного символа
//--- Параметры канваса
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Частота обновления канваса
//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Данные для работы с мышью                                        |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список возможных состояний кнопок мыши и клавиш Shift и Ctrl     |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_BUTT_KEY_STATE
  {
   MOUSE_BUTT_KEY_STATE_NONE              = 0,        // Ничто не нажато
//--- Кнопки мыши
   MOUSE_BUTT_KEY_STATE_LEFT              = 1,        // Нажата левая кнопка мыши
   MOUSE_BUTT_KEY_STATE_RIGHT             = 2,        // Нажата правая кнопка мыши
   MOUSE_BUTT_KEY_STATE_MIDDLE            = 16,       // Нажата средняя кнопка мыши
   MOUSE_BUTT_KEY_STATE_WHELL             = 128,      // Прокручивание колёсика мыши
   MOUSE_BUTT_KEY_STATE_X1                = 32,       // Нажата первая дополнительная кнопка мыши
   MOUSE_BUTT_KEY_STATE_X2                = 64,       // Нажата вторая дополнительная кнопка мыши
   MOUSE_BUTT_KEY_STATE_LEFT_RIGHT        = 3,        // Нажаты левая и правая кнопка мыши
//--- Клавиши клавиатуры
   MOUSE_BUTT_KEY_STATE_SHIFT             = 4,        // Удерживается клавиша Shift
   MOUSE_BUTT_KEY_STATE_CTRL              = 8,        // Удерживается клавиша Ctrl
   MOUSE_BUTT_KEY_STATE_CTRL_CHIFT        = 12,       // Удерживаются клавиши Ctrl и Shift
//--- Сочетания левой кнопки мыши
   MOUSE_BUTT_KEY_STATE_LEFT_WHELL        = 129,      // Нажата левая кнопка мыши и прокручивается колёсико
   MOUSE_BUTT_KEY_STATE_LEFT_SHIFT        = 5,        // Нажата левая кнопка мыши и удерживается клавиша Shift
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL         = 9,        // Нажата левая кнопка мыши и удерживается клавиша Ctrl
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL_CHIFT   = 13,       // Нажата левая кнопка мыши и удерживаются клавиши Ctrl и Shift
//--- Сочетания правой кнопки мыши
   MOUSE_BUTT_KEY_STATE_RIGHT_WHELL       = 130,      // Нажата правая кнопка мыши и прокручивается колёсико
   MOUSE_BUTT_KEY_STATE_RIGHT_SHIFT       = 6,        // Нажата правая кнопка мыши и удерживается клавиша Shift
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL        = 10,       // Нажата правая кнопка мыши и удерживается клавиша Ctrl
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL_CHIFT  = 14,       // Нажата правая кнопка мыши и удерживаются клавиши Ctrl и Shift
//--- Сочетания средней кнопки мыши
   MOUSE_BUTT_KEY_STATE_MIDDLE_WHEEL      = 144,      // Нажата средняя кнопка мыши и прокручивается колёсико
   MOUSE_BUTT_KEY_STATE_MIDDLE_SHIFT      = 20,       // Нажата средняя кнопка мыши и удерживается клавиша Shift
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL       = 24,       // Нажата средняя кнопка мыши и удерживается клавиша Ctrl
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL_CHIFT = 28,       // Нажата средняя кнопка мыши и удерживаются клавиши Ctrl и Shift
  };
//+------------------------------------------------------------------+
//| Список возможных состояний мышки относительно формы              |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Неопределённое состояние
//--- За пределами формы
   MOUSE_FORM_STATE_OUTSIDE_NOT_PRESSED,              // Курсор за пределами формы, кнопки мыши не нажаты
   MOUSE_FORM_STATE_OUTSIDE_PRESSED,                  // Курсор за пределами формы, нажата кнопка мыши (любая)
   MOUSE_FORM_STATE_OUTSIDE_WHEEL,                    // Курсор за пределами формы, прокручивается колёсико мыши
//--- В пределах формы
   MOUSE_FORM_STATE_INSIDE_NOT_PRESSED,               // Курсор в пределах формы, кнопки мыши не нажаты
   MOUSE_FORM_STATE_INSIDE_PRESSED,                   // Курсор в пределах формы, нажата кнопка мыши (любая)
   MOUSE_FORM_STATE_INSIDE_WHEEL,                     // Курсор в пределах формы, прокручивается колёсико мыши
//--- В пределах области заголовка окна
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // Курсор в пределах активной области, кнопки мыши не нажаты
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // Курсор в пределах активной области, нажата кнопка мыши (любая)
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // Курсор в пределах активной области, прокручивается колёсико мыши
//--- В пределах области прокрутки окна
   MOUSE_FORM_STATE_INSIDE_SCROLL_NOT_PRESSED,        // Курсор в пределах области прокрутки окна, кнопки мыши не нажаты
   MOUSE_FORM_STATE_INSIDE_SCROLL_PRESSED,            // Курсор в пределах области прокрутки окна, нажата кнопка мыши (любая)
   MOUSE_FORM_STATE_INSIDE_SCROLL_WHEEL,              // Курсор в пределах области прокрутки окна, прокручивается колёсико мыши
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Данные для работы с графическими элементами                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_FORM,                           // Простая форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
  };
//+------------------------------------------------------------------+

Список типов графических элементов мы добавили в качестве "бронирования места" для последующих классов на основе создаваемых сегодня — эти списки будем пополнять и использовать в следующих статьях.

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

В таблице указаны биты и соответствующие им состояния кнопок мыши и клавиш Shift и Ctrl:

Бит
 Описание Значение
0
 Состояние левой клавиши мыши
 1
1
 Состояние правой клавиши мыши
 2
2
 Состояние клавиши SHIFT
 4
3
 Состояние клавиши CTRL
 8
4
 Состояние средней клавиши мыши
 16
5
 Состояние первой дополнительной клавиши мыши
 32
6
 Состояние второй дополнительной клавиши мыши
 64

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

  • Если нажата только левая кнопка, то значение переменной будет равно 1
  • Если нажата только правая кнопка, то значение переменной будет равно 2
  • Если нажата левая и правая кнопки, то значение переменной будет равно 1 + 2 = 3
  • Если нажата только левая кнопка и удерживается клавиша Shift, то значение переменной будет равно 1 + 4 = 5

Именно по этой причине значения в перечислении ENUM_MOUSE_BUTT_KEY_STATE у нас заданы точно в соответствии с показанным расчётом значения  переменной при установленных флагах, описанных константами этого перечисления.

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

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

 Бит  Байт Состояние
Значение
0
0
 левая кнопка мыши
1
1
0
 правая кнопка мыши
2
2
0
 клавиша SHIFT
4
3
0
 клавиша CTRL
8
4
0
 средняя кнопка мыши
16
5
0
 первая дополнительная кнопка мыши
32
6
0
 вторая дополнительная кнопка мыши
64
7
0
 прокрутка колёсика
128
8 (0)
1
 курсор внутри формы
256
9 (1)
1
 курсор внутри активной зоны формы
512
10 (2)
1
 курсор в области управления окном (свернуть/развернуть/закрыть и т.д.)
1024
11 (3)
1
 курсор в области прокрутки окна
2048
12 (4)
1
 курсор на левой грани формы
4096
13 (5)
1
 курсор на нижней грани формы
8192
14 (6)
1
 курсор на правой грани формы
16384
15 (7)
1
 курсор на верхней грани формы
32768

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

Совсем немного доработаем объект класса паузы в файле \MQL5\Include\DoEasy\Services\Pause.mqh.
Его метод SetTimeBegin(), помимо установки нового времени отсчёта паузы, ещё записывает время, переданное в метод, в переменную m_time_begin.
Это требуется лишь для вывода информации в журнал и не нужно в случае, если мы просто где-то внутри метода хотим отсчитать некую паузу. Не так уж и затратно передать любое время в метод (да хоть ноль), но я решил всё же сделать перегрузку метода — без указания времени:

//--- Устанавливает новое (1) время начала отсчёта, (2) паузу в миллисекундах
   void              SetTimeBegin(const ulong time)         { this.m_time_begin=time; this.SetTimeBegin();              }
   void              SetTimeBegin(void)                     { this.m_start=this.TickCount();                            }
   void              SetWaitingMSC(const ulong pause)       { this.m_wait_msc=pause;                                    }

Теперь мы можем создать класс объекта-состояний мышки.


Класс состояний мышки

В папке сервисных функций и классов \MQL5\Include\DoEasy\Services\ создадим новый класс CMouseState в файле MouseState.mqh.

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

//+------------------------------------------------------------------+
//|                                                   MouseState.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 "DELib.mqh"
//+------------------------------------------------------------------+
//| Класс состояний мышки                                            |
//+------------------------------------------------------------------+
class CMouseState
  {
private:
   int               m_coord_x;                             // Координата X
   int               m_coord_y;                             // Координата Y
   int               m_delta_wheel;                         // Значение прокрутки колёсика мыши
   int               m_window_num;                          // Номер подокна
   long              m_chart_id;                            // Идентификатор графика
   ushort            m_state_flags;                         // Флаги состояний
   
//--- Устанавливает состояние кнопок мыши и клавиш Shift и Ctrl
   void              SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags);
//--- Устанавливает флаги состояний кнопок мыши и клавиш
   void              SetButtKeyFlags(const short flags);

//--- Расположение данных в ushort-значении состояния кнопок
   //-----------------------------------------------------------------
   //   bit    |    byte   |            state            |    dec    |
   //-----------------------------------------------------------------
   //    0     |     0     | левая кнопка мыши           |     1     |
   //-----------------------------------------------------------------
   //    1     |     0     | правая кнопка мыши          |     2     |
   //-----------------------------------------------------------------
   //    2     |     0     | клавиша SHIFT               |     4     |
   //-----------------------------------------------------------------
   //    3     |     0     | клавиша CTRL                |     8     |
   //-----------------------------------------------------------------
   //    4     |     0     | средняя кнопка мыши         |    16     |
   //-----------------------------------------------------------------
   //    5     |     0     | 1 доп. кнопка мыши          |    32     |
   //-----------------------------------------------------------------
   //    6     |     0     | 2 доп. кнопка мыши          |    64     |
   //-----------------------------------------------------------------
   //    7     |     0     | прокрутка колёсика          |    128    |
   //-----------------------------------------------------------------
   //-----------------------------------------------------------------
   //    0     |     1     | курсор внутри формы         |    256    |
   //-----------------------------------------------------------------
   //    1     |     1     | курсор внутри активной зоны |    512    |
   //-----------------------------------------------------------------
   //    2     |     1     | курсор в области управления |   1024    |
   //-----------------------------------------------------------------
   //    3     |     1     | курсор в области прокрутки  |   2048    |
   //-----------------------------------------------------------------
   //    4     |     1     | курсор на левой грани       |   4096    |
   //-----------------------------------------------------------------
   //    5     |     1     | курсор на нижней грани      |   8192    |
   //-----------------------------------------------------------------
   //    6     |     1     | курсор на правой грани      |   16384   |
   //-----------------------------------------------------------------
   //    7     |     1     | курсор на верхней грани     |   32768   |
   //-----------------------------------------------------------------
      
public:

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

public:
//--- Сбрасывает состояние всех кнопок и клавиш
   void              ResetAll(void);
//--- Устанавливает (1) номер подокна, (2) идентификатор графика
   void              SetWindowNum(const int wnd_num)           { this.m_window_num=wnd_num;        }
   void              SetChartID(const long id)                 { this.m_chart_id=id;               }
//--- Возвращает переменную с флагами состояния мыши
   ushort            GetMouseFlags(void)                       { return this.m_state_flags;        }
//--- Возвращает (1-2) кординаты курсора, (3) значение прокрутки колёсика, (4) состояние кнопок мыши и клавиш Shift и Ctrl
   int               CoordX(void)                        const { return this.m_coord_x;            }
   int               CoordY(void)                        const { return this.m_coord_y;            }
   int               DeltaWheel(void)                    const { return this.m_delta_wheel;        }
   ENUM_MOUSE_BUTT_KEY_STATE ButtKeyState(const int id,const long lparam,const double dparam,const string flags);

//--- Возвращает флаг нажатой (1) левой, (2) правой, (3) средней, (4) первой, (5) второй дополнительной кнопки мыши
   bool              IsPressedButtonLeft(void)           const { return this.m_state_flags==1;     }
   bool              IsPressedButtonRight(void)          const { return this.m_state_flags==2;     }
   bool              IsPressedButtonMiddle(void)         const { return this.m_state_flags==16;    }
   bool              IsPressedButtonX1(void)             const { return this.m_state_flags==32;    }
   bool              IsPressedButtonX2(void)             const { return this.m_state_flags==64;    }
//--- Возвращает флаг нажатой клавиши (1) Shift, (2) Ctrl, (3) Shift+Ctrl, флаг вращения колёсика мыши
   bool              IsPressedKeyShift(void)             const { return this.m_state_flags==4;     }
   bool              IsPressedKeyCtrl(void)              const { return this.m_state_flags==8;     }
   bool              IsPressedKeyCtrlShift(void)         const { return this.m_state_flags==12;    }
   bool              IsWheel(void)                       const { return this.m_state_flags==128;   }

//--- Возвращает флаг состояния левой кнопки мыши и (1) колеса, (2) клавиши Shift, (3) клавиши Ctrl, (4) клавиш Ctrl+Shift
   bool              IsPressedButtonLeftWheel(void)      const { return this.m_state_flags==129;   }
   bool              IsPressedButtonLeftShift(void)      const { return this.m_state_flags==5;     }
   bool              IsPressedButtonLeftCtrl(void)       const { return this.m_state_flags==9;     }
   bool              IsPressedButtonLeftCtrlShift(void)  const { return this.m_state_flags==13;    }
//--- Возвращает флаг состояния правой кнопки мыши и (1) колеса, (2) клавиши Shift, (3) клавиши Ctrl, (4) клавиш Ctrl+Shift
   bool              IsPressedButtonRightWheel(void)     const { return this.m_state_flags==130;   }
   bool              IsPressedButtonRightShift(void)     const { return this.m_state_flags==6;     }
   bool              IsPressedButtonRightCtrl(void)      const { return this.m_state_flags==10;    }
   bool              IsPressedButtonRightCtrlShift(void) const { return this.m_state_flags==14;    }
//--- Возвращает флаг состояния средней кнопки мыши и (1) колеса, (2) клавиши Shift, (3) клавиши Ctrl, (4) клавиш Ctrl+Shift
   bool              IsPressedButtonMiddleWheel(void)    const { return this.m_state_flags==144;   }
   bool              IsPressedButtonMiddleShift(void)    const { return this.m_state_flags==20;    }
   bool              IsPressedButtonMiddleCtrl(void)     const { return this.m_state_flags==24;    }
   bool              IsPressedButtonMiddleCtrlShift(void)const { return this.m_state_flags==28;    }

//--- Конструктор/Деструктор
                     CMouseState();
                    ~CMouseState();
  };
//+------------------------------------------------------------------+

Здесь реализованы методы, возвращающие значения переменных класса, и некоторые методы, возвращающие предопределённые состояния кнопок мышки и клавиш Ctrl и Shift.


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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CMouseState::CMouseState() : m_delta_wheel(0),m_coord_x(0),m_coord_y(0),m_window_num(0)
  {
   this.ResetAll();
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CMouseState::~CMouseState()
  {
  }
//+------------------------------------------------------------------+
//| Сбрасывает состояние всех кнопок и клавиш                        |
//+------------------------------------------------------------------+
void CMouseState::ResetAll(void)
  {
   this.m_delta_wheel = 0;
   this.m_state_flags = 0;
  }
//+------------------------------------------------------------------+

Метод, устанавливающий состояние кнопок мыши и клавиш Shift и Ctrl:

//+------------------------------------------------------------------+
//| Устанавливает состояние кнопок мыши и клавиш Shift и Ctrl        |
//+------------------------------------------------------------------+
void CMouseState::SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags)
  {
   //--- Сбрасываем значения всех битов состояния мышки
   this.ResetAll();
   //--- Если щелчок по графику или объекту - это однозначно щелчок левой кнопкой
   if(id==CHARTEVENT_CLICK || id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Запишем координаты графика, в которых был щелчок и установим бит 0
      this.m_coord_x=(int)lparam;
      this.m_coord_y=(int)dparam;
      this.m_state_flags |=(0x0001);
     }
   //--- иначе
   else
     {
      //--- если это прокрутка колёсика мышки
      if(id==CHARTEVENT_MOUSE_WHEEL)
        {
         //--- получим координаты курсора и суммарную величину прокрутки (минимум +120 или -120)
         this.m_coord_x=(int)(short)lparam;
         this.m_coord_y=(int)(short)(lparam>>16);
         this.m_delta_wheel=(int)dparam;
         //--- Вызовем метод установки флагов состояний кнопок мышки и клавиш Shift и Ctrl
         this.SetButtKeyFlags((short)(lparam>>32));
         //--- и установим бит 8
         this.m_state_flags &=0xFF7F;
         this.m_state_flags |=(0x0001<<7);
        }
      //--- Если это перемещение курсора - запишем его координаты и
      //--- вызовем метод установки флагов состояний кнопок мышки и клавиш Shift и Ctrl
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         this.m_coord_x=(int)lparam;
         this.m_coord_y=(int)dparam;
         this.SetButtKeyFlags(flags);
        }
     }
  }
//+------------------------------------------------------------------+

Здесь мы проверяем, какое событие графика обрабатываем.
Сначала обнуляем все биты в переменной, хранящей битовые флаги состояния мышки.
Затем, при щелчке мышью по графику или объекту устанавливаем бит 0 переменной, хранящей битовые флаги.
При событии прокрутки колёсика мышки целочисленный параметр lparam содержит в себе данные о координатах курсора, величины прокрутки и битовые флаги состояния кнопок и клавиш Ctrl и Shift. Вычленяем все данные из переменной lparam и записываем их в переменные, хранящие координаты курсора, и в собственную переменную с битовыми флагами так, чтобы соблюдался порядок следования битов, описанный в приватной секции класса. Затем устанавливаем бит 8, сигнализирующий о факте прокрутки колеса мышки.
При перемещении курсора по графику записываем координаты курсора в переменные и вызываем метод установки битовых флагов о состоянии кнопок мышки и клавиш Ctrl и Shift.

Метод, устанавливающий флаги состояний кнопок мыши и клавиш:

//+------------------------------------------------------------------+
//| Устанавливает флаги состояний кнопок мыши и клавиш               |
//+------------------------------------------------------------------+
void CMouseState::SetButtKeyFlags(const short flags)
  {
//--- Состояние левой клавиши мыши
   if((flags & 0x0001)!=0) this.m_state_flags |=(0x0001<<0);
//--- Состояние правой клавиши мыши
   if((flags & 0x0002)!=0) this.m_state_flags |=(0x0001<<1);
//--- Состояние клавиши SHIFT
   if((flags & 0x0004)!=0) this.m_state_flags |=(0x0001<<2);
//--- Состояние клавиши CTRL
   if((flags & 0x0008)!=0) this.m_state_flags |=(0x0001<<3);
//--- Состояние средней клавиши мыши
   if((flags & 0x0010)!=0) this.m_state_flags |=(0x0001<<4);
//--- Состояние первой дополнительной клавиши мыши
   if((flags & 0x0020)!=0) this.m_state_flags |=(0x0001<<5);
//--- Состояние второй дополнительной клавиши мыши
   if((flags & 0x0040)!=0) this.m_state_flags |=(0x0001<<6);
  }
//+------------------------------------------------------------------+

Здесь всё просто: в метод передаётся переменная с флагами состояния мышки. Поочерёдно накладываем на неё битовую маску с установленным проверяемым битом. Получаемое после наложения битовой маски значение при помощи побитового "И" будет истинно, только если оба проверяемых бита установлены (1). Если переменная с наложенной на неё маской не равна нулю (проверяемый бит установлен), то в переменную для хранения битовых флагов вписываем соответствующий бит.

Метод, возвращающий состояние кнопок мыши и клавиш Shift и Ctrl:

//+------------------------------------------------------------------+
//| Возвращает состояние кнопок мыши и клавиш Shift и Ctrl           |
//+------------------------------------------------------------------+
ENUM_MOUSE_BUTT_KEY_STATE CMouseState::ButtKeyState(const int id,const long lparam,const double dparam,const string flags)
  {
   this.SetButtonKeyState(id,lparam,dparam,(ushort)flags);
   return (ENUM_MOUSE_BUTT_KEY_STATE)this.m_state_flags;
  }
//+------------------------------------------------------------------+

Здесь мы сначала вызываем метод, проверяющий и устанавливающий все флаги состояния мышки и клавиш Ctrl и Shift, а затем возвращаем значение переменной m_state_flags как перечисления ENUM_MOUSE_BUTT_KEY_STATE. В этом перечислении значения всех констант соответствуют величине, получаемой совокупностью установленных битов переменной. Соответственно, мы сразу же возвращаем одно из значений перечисления, которое далее будем обрабатывать в классах, где требуется получить состояние мышки, её кнопок и клавиш Ctrl и Shift. Данный метод вызывается из обработчика OnChartEvent().


Класс базового объекта всех графических элементов библиотеки

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

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

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

Класс будет совсем простым: приватные переменные, защищённые методы для установки и публичные методы для возврата их значений.
Класс будет являться наследником базового класса стандартной библиотеки CObject.

В каталоге библиотеки \MQL5\Include\DoEasy\Objects\ создадим новую папку Graph\, а в ней новый файл GBaseObj.mqh класса CGBaseObj:

//+------------------------------------------------------------------+
//|                                                     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"
//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
private:
   int               m_type;                                // Тип объекта
   string            m_name_obj;                            // Имя объекта
   long              m_chart_id;                            // Идентификатор графика
   int               m_wnd_num;                             // Номер подокна графика
   int               m_coord_x;                             // Координата X холста
   int               m_coord_y;                             // Координата Y холста
   int               m_width;                               // Ширина
   int               m_height;                              // Высота
   bool              m_movable;                             // Флаг перемещаемости объекта
   bool              m_selectable;                          // Флаг выбираемости объекта

protected:
//--- Установка значений переменным класса
   void              SetNameObj(const string name)             { this.m_name_obj=name;                            }
   void              SetChartID(const long chart_id)           { this.m_chart_id=chart_id;                        }
   void              SetWindowNum(const int wnd_num)           { this.m_wnd_num=wnd_num;                          }
   void              SetCoordX(const int coord_x)              { this.m_coord_x=coord_x;                          }
   void              SetCoordY(const int coord_y)              { this.m_coord_y=coord_y;                          }
   void              SetWidth(const int width)                 { this.m_width=width;                              }
   void              SetHeight(const int height)               { this.m_height=height;                            }
   void              SetMovable(const bool flag)               { this.m_movable=flag;                             }
   void              SetSelectable(const bool flag)            { this.m_selectable=flag;                          }
   
public:
//--- Возврат значений переменных класса
   string            NameObj(void)                       const { return this.m_name_obj;                          }
   long              ChartID(void)                       const { return this.m_chart_id;                          }
   int               WindowNum(void)                     const { return this.m_wnd_num;                           }
   int               CoordX(void)                        const { return this.m_coord_x;                           }
   int               CoordY(void)                        const { return this.m_coord_y;                           }
   int               Width(void)                         const { return this.m_width;                             }
   int               Height(void)                        const { return this.m_height;                            }
   int               RightEdge(void)                     const { return this.m_coord_x+this.m_width;              }
   int               BottomEdge(void)                    const { return this.m_coord_y+this.m_height;             }
   bool              Movable(void)                       const { return this.m_movable;                           }
   bool              Selectable(void)                    const { return this.m_selectable;                        }
   
//--- Виртуальный метод, возвращающий тип объекта
   virtual int       Type(void)                          const { return this.m_type;                              }

//--- Конструктор/деструктор
                     CGBaseObj();
                    ~CGBaseObj();
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_chart_id(::ChartID()),
                         m_type(WRONG_VALUE),
                         m_wnd_num(0),
                         m_coord_x(0),
                         m_coord_y(0),
                         m_width(0),
                         m_height(0),
                         m_movable(false),
                         m_selectable(false)
  {
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CGBaseObj::~CGBaseObj()
  {
  }
//+------------------------------------------------------------------+

В классе базового объекта CObject реализован виртуальный метод Type(), возвращающий тип объекта (для идентификации объектов по их типу). Оригинальный метод всегда возвращает ноль:

   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(0);      }

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


Класс объекта-формы графических элементов

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

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

В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\ создадим новый файл Form.mqh класса CForm.
Класс должен быть наследником базового объекта всех графических объектов библиотеки. Соответственно, к нему должны быть подключены файлы класса базового графического объекта и класса объекта-свойств мышки:

//+------------------------------------------------------------------+
//|                                                         Form.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 <Canvas\Canvas.mqh>
#include "GBaseObj.mqh"
#include "..\..\Services\MouseState.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
  }

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

//+------------------------------------------------------------------+
//| Класс базового объекта графических объектов библиотеки           |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
protected:
   CCanvas              m_canvas;                              // Объект класса CCanvas
   CPause               m_pause;                               // Объект класса "Пауза"
   CMouseState          m_mouse;                               // Объект класса "Состояния мышки"
   ENUM_MOUSE_FORM_STATE m_mouse_state;                        // Состояние мыши относительно формы
   ushort               m_mouse_state_flags;                   // Флаги состояния мышки
   
   int                  m_act_area_left;                       // Левая граница активной области (смещение от левой границы внутрь)
   int                  m_act_area_right;                      // Правая граница активной области (смещение от правой границы внутрь)
   int                  m_act_area_top;                        // Верхняя граница активной области (смещение от верхней границы внутрь)
   int                  m_act_area_bottom;                     // Нижняя граница активной области (смещение от нижней границы внутрь)
   uchar                m_opacity;                             // Непрозрачность
   int                  m_shift_y;                             // Смещение координаты Y для подокна
   
private:

В приватной секции класса объявим вспомогательные методы для работы класса:

private:
//--- Устанавливает и возвращает флаги состояний кнопок мыши и клавиш Shift и Ctrl
   ENUM_MOUSE_BUTT_KEY_STATE MouseButtonKeyState(const int id,const long lparam,const double dparam,const string sparam)
                       {
                        return this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
                       }
//--- Возвращает положение курсора относительно (1) формы, (2) активной зоны
   bool              CursorInsideForm(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);

public:

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

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

public:
//--- Создаёт форму
   bool              CreateForm(const long chart_id,
                                const int wnd_num,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color colour,
                                const uchar opacity,
                                const bool movable=true,
                                const bool selectable=true);
                                
//--- Возвращает указатель на объект-канвас
   CCanvas          *CanvasObj(void)                           { return &this.m_canvas;                           }
//--- Устанавливает (1) частоту обновления формы, флаг (2) перемещаемости, (3) выбираемости для взаимодействия
   void              SetFrequency(const ulong value)           { this.m_pause.SetWaitingMSC(value);               }
   void              SetMovable(const bool flag)               { CGBaseObj::SetMovable(flag);                     }
   void              SetSelectable(const bool flag)            { CGBaseObj::SetSelectable(flag);                  }
//--- Обновляет координаты формы (сдвигает форму)
   bool              Move(const int x,const int y,const bool redraw=false);
   
//--- Возвращает состояние мышки относительно формы
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
//--- Возвращает флаг нажатой (1) левой, (2) правой, (3) средней, (4) первой, (5) второй дополнительной кнопки мыши
   bool              IsPressedButtonLeftOnly(void)             { return this.m_mouse.IsPressedButtonLeft();       }
   bool              IsPressedButtonRightOnly(void)            { return this.m_mouse.IsPressedButtonRight();      }
   bool              IsPressedButtonMiddleOnly(void)           { return this.m_mouse.IsPressedButtonMiddle();     }
   bool              IsPressedButtonX1Only(void)               { return this.m_mouse.IsPressedButtonX1();         }
   bool              IsPressedButtonX2Only(void)               { return this.m_mouse.IsPressedButtonX2();         }
//--- Возвращает флаг нажатой клавиши (1) Shift, (2) Ctrl
   bool              IsPressedKeyShiftOnly(void)               { return this.m_mouse.IsPressedKeyShift();         }
   bool              IsPressedKeyCtrlOnly(void)                { return this.m_mouse.IsPressedKeyCtrl();          }
   
//--- Устанавливает смещение (1) левого, (2) верхнего, (3) правого, (4) нижнего края активной зоны относительно формы,
//--- (5) все смещения краёв активной зоны относительно формы, (6) непрозрачность формы
   void              SetActiveAreaLeftShift(const int value)   { this.m_act_area_left=fabs(value);                }
   void              SetActiveAreaRightShift(const int value)  { this.m_act_area_right=fabs(value);               }
   void              SetActiveAreaTopShift(const int value)    { this.m_act_area_top=fabs(value);                 }
   void              SetActiveAreaBottomShift(const int value) { this.m_act_area_bottom=fabs(value);              }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetOpacity(const uchar value)             { this.m_opacity=value;                            }
//--- Возвращает координату (1) левого, (2) правого, (3) верхнего, (4) нижнего края активной зоны формы
   int               ActiveAreaLeft(void)                const { return this.CoordX()+this.m_act_area_left;       }
   int               ActiveAreaRight(void)               const { return this.RightEdge()-this.m_act_area_right;   }
   int               ActiveAreaTop(void)                 const { return this.CoordY()+this.m_act_area_top;        }
   int               ActiveAreaBottom(void)              const { return this.BottomEdge()-this.m_act_area_bottom; }
//--- Возвращает (1) непрозрачность формы, координату (2) правого, (3) нижнего края формы
   uchar             Opacity(void)                       const { return this.m_opacity;                           }
   int               RightEdge(void)                     const { return CGBaseObj::RightEdge();                   }
   int               BottomEdge(void)                    const { return CGBaseObj::BottomEdge();                  }

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

//--- Конструкторы/Деструктор
                     CForm(const long chart_id,
                           const int wnd_num,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h,
                           const color colour,
                           const uchar opacity,
                           const bool movable=true,
                           const bool selectable=true);
                     CForm(){;}
                    ~CForm();
  };
//+------------------------------------------------------------------+

Рассмотрим подробнее методы класса.

В параметрическом конструкторе создаём объект-форму с переданными в конструктор параметрами:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int wnd_num,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h,
             const color colour,
             const uchar opacity,
             const bool movable=true,
             const bool selectable=true) : m_act_area_bottom(0),
                                           m_act_area_left(0),
                                           m_act_area_right(0),
                                           m_act_area_top(0),
                                           m_mouse_state(0),
                                           m_mouse_state_flags(0)
                                          
  {
   if(this.CreateForm(chart_id,wnd_num,name,x,y,w,h,colour,opacity,movable,selectable))
     {
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      this.SetWindowNum(wnd_num);
      this.m_pause.SetWaitingMSC(PAUSE_FOR_CANV_UPDATE);
      this.m_pause.SetTimeBegin();
      this.m_mouse.SetChartID(chart_id);
      this.m_mouse.SetWindowNum(wnd_num);
      this.m_mouse.ResetAll();
      this.m_mouse_state_flags=0;
      CGBaseObj::SetMovable(movable);
      CGBaseObj::SetSelectable(selectable);
      this.SetOpacity(opacity);
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   ::ObjectsDeleteAll(this.ChartID(),this.NameObj());
  }
//+------------------------------------------------------------------+


Метод, создающий графический объект-форму:

//+------------------------------------------------------------------+
//| Создаёт графический объект-форму                                 |
//+------------------------------------------------------------------+
bool CForm::CreateForm(const long chart_id,
                       const int wnd_num,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h,
                       const color colour,
                       const uchar opacity,
                       const bool movable=true,
                       const bool selectable=true)
  {
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.SetChartID(chart_id);
      this.SetWindowNum(wnd_num);
      this.SetNameObj(name);
      this.SetCoordX(x);
      this.SetCoordY(y);
      this.SetWidth(w);
      this.SetHeight(h);
      this.SetActiveAreaLeftShift(1);
      this.SetActiveAreaRightShift(1);
      this.SetActiveAreaTopShift(1);
      this.SetActiveAreaBottomShift(1);
      this.SetOpacity(opacity);
      this.SetMovable(movable);
      this.SetSelectable(selectable);
      this.m_canvas.Erase(::ColorToARGB(colour,this.Opacity()));
      this.m_canvas.Update();
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

При помощи метода CreateBitmapLabel() класса CCanvas создаём графический ресурс с использованием идентификатора графика и номера подокна (вторая форма вызова метода). Если графический ресурс создан успешно, то устанавливаем объекту-форме все переданные в метод параметры, затем заполняем форму цветом и устанавливаем непрозрачность при помощи метода Erase() и отображаем изменения на экран при помощи метода Update().

Хочу пояснить насчёт термина "непрозрачность" или плотность цвета. Класс CCanvas позволяет задавать своим объектам прозрачность. При этом значение 0 — это полностью прозрачный цвет, значение 255 — это полностью непрозрачный. Получается как бы всё наоборот. Поэтому я решил использовать термин "opacity" (непрозрачность), так как значения 0 — 255 соответствуют как раз повышению плотности цвета от нуля (полностью прозрачный) до 255 (полностью непрозрачный).

Обработчик событий класса CForm:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CForm::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Получаем состояние кнопок мыши и клавиш Shift и Ctrl и состояние мышки относительно формы
   ENUM_MOUSE_BUTT_KEY_STATE mouse_state=this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
   this.m_mouse_state=this.MouseFormState(id,lparam,dparam-this.m_shift_y,sparam);
//--- Инициализируем разницу между координатами X и Y формы и курсора
   static int diff_x=0;
   static int diff_y=0;
//--- Если событие изменения графика - пересчитаем смещение по Y для подокна
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      this.m_shift_y=(int)::ChartGetInteger(this.ChartID(),CHART_WINDOW_YDISTANCE,this.WindowNum());
     }
//--- Если курсор внутри формы - запретим скроллинг графика, контекстное меню и инструмент "Перекрестие"
   if((this.m_mouse_state_flags & 0x0100)!=0)
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,false);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,false);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,false);
     }
//--- Иначе, если курсор за пределами формы - разрешим скроллинг графика, контекстное меню и инструмент "Перекрестие"
   else
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,true);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,true);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,true);
     }
//--- Если событие перемещения мышки и курсор находится в активной области формы
   if(id==CHARTEVENT_MOUSE_MOVE && m_mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED)
     {
      //--- Если зажата только левая кнопка мышки и перемещение формы осуществлено -
      //--- установим новые параметры смещения формы относительно курсора
      if(IsPressedButtonLeftOnly() && this.Move(this.m_mouse.CoordX()-diff_x,this.m_mouse.CoordY()-diff_y))
        {
         diff_x=this.m_mouse.CoordX()-this.CoordX();
         diff_y=this.m_mouse.CoordY()-this.CoordY();
        }
     }
//--- В любых иных случаях - устанавливаем параметры смещения формы относительно курсора
   else
     {
      diff_x=this.m_mouse.CoordX()-this.CoordX();
      diff_y=this.m_mouse.CoordY()-this.CoordY();
     }
//--- Тестовый вывод на график состояний мышки
   Comment(EnumToString(mouse_state),"\n",EnumToString(this.m_mouse_state));
  }
//+------------------------------------------------------------------+

В листинге кода вся его логика пояснена в комментариях. Метод должен вызываться из стандартного обработчика OnChartEvent() программы и имеет точно такие же параметры.

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

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

//+------------------------------------------------------------------+
//| Возвращает положение курсора относительно формы                  |
//+------------------------------------------------------------------+
bool CForm::CursorInsideForm(const int x,const int y)
  {
   return(x>=this.CoordX() && x<this.RightEdge() && y>=this.CoordY() && y<=this.BottomEdge());
  }
//+------------------------------------------------------------------+

В метод передаются координаты X и Y курсора.

Если

  • (координата X курсора больше или равна координате X формы и координата X курсора меньше или равна координате правой границы формы) и
  • (координата Y курсора больше или равна координате Y формы и координата Y курсора меньше или равна координате нижней границы формы)

... то возвращается true — курсор находится внутри объекта-формы.

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

//+------------------------------------------------------------------+
//| Возвращает положение курсора относительно активной зоны формы    |
//+------------------------------------------------------------------+
bool CForm::CursorInsideActiveArea(const int x,const int y)
  {
   return(x>=this.ActiveAreaLeft() && x<this.ActiveAreaRight() && y>=this.ActiveAreaTop() && y<=this.ActiveAreaBottom());
  }
//+------------------------------------------------------------------+

В метод передаются координаты X и Y курсора.

Если

  • (координата X курсора больше или равна координате X активной зоны формы и координата X курсора меньше или равна координате правой границы активной зоны формы ) и
  • (координата Y курсора больше или равна координате Y активной зоны формы и координата Y курсора меньше или равна координате нижней границы активной зоны формы )

... то возвращается true — курсор находится внутри активной зоны объекта-формы.

Метод, возвращающий состояние мышки относительно формы:

//+------------------------------------------------------------------+
//| Возвращает состояние мышки относительно формы                    |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Получаем состояние мышки относительно формы и состояние кнопок мыши и клавиш Shift и Ctrl
   ENUM_MOUSE_FORM_STATE form_state=MOUSE_FORM_STATE_NONE;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.MouseButtonKeyState(id,lparam,dparam,sparam);
//--- Получаем флаги состоянии мышки из объекта класса CMouseState и сохраняем их в переменной
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- Если курсор внутри формы
   if(this.CursorInsideForm(m_mouse.CoordX(),m_mouse.CoordY()))
     {
      //--- Устанавливаем бит 8, отвечающий за флаг "курсор внутри формы"
      this.m_mouse_state_flags |= (0x0001<<8);
      //--- Если курсор внутри активной зоны - устанавливаем бит 9 "курсор внутри активной зоны"
      if(CursorInsideActiveArea(m_mouse.CoordX(),m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- иначе - снимаем бит "курсор внутри активной зоны"
      else this.m_mouse_state_flags &=0xFDFF;
      //--- Если нажата одна из трёх кнопок мыши - проверяем расположение курсора в активной области и
      //--- возвращаем соответствующее значение нажатой кнопки (в активной зоне или в области формы)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_PRESSED);
      //--- иначе - проверяем расположение курсора в активной области и
      //--- возвращаем соответствующее значение ненажатой кнопки (в активной зоне или в области формы)
      else
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_NOT_PRESSED);
     }
   return form_state;
  }
//+------------------------------------------------------------------+

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

Метод, обновляющий координаты формы (смещающий форму на графике):

//+------------------------------------------------------------------+
//| Обновляет координаты формы                                       |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
//--- Если форма не перемещаемая - уходим
   if(!this.Movable())
      return false;
//--- Если удалось записать новые значения координат в свойства графического объекта
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,x) &&
      ::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,y))
     {
      //--- устанавливаем новые значения свойств координат X и Y
      this.SetCoordX(x);
      this.SetCoordY(y);
      //--- Если стоит флаг обновления - перерисовываем график.
      if(redraw)
         ::ChartRedraw(this.ChartID());
      //--- Возвращаем true
      return true;
     }
//--- Что-то пошло не так ...
   return false;
  }
//+------------------------------------------------------------------+

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

Метод, устанавливающий все смещения активной зоны относительно формы:

//+------------------------------------------------------------------+
//| Устанавливает все смещения активной зоны относительно формы      |
//+------------------------------------------------------------------+
void CForm::SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift)
  {
   this.SetActiveAreaLeftShift(left_shift);
   this.SetActiveAreaBottomShift(bottom_shift);
   this.SetActiveAreaRightShift(right_shift);
   this.SetActiveAreaTopShift(top_shift);
  }
//+------------------------------------------------------------------+

У нас есть методы для раздельной установки границ активной зоны. Но иногда требуется установить все границы за один вызов одного метода. Именно это и делает этот метод — устанавливает новые значения отступа границ активной зоны от краёв формы при помощи вызовов соответствующих методов.

На этом создание первой версии объекта-формы завершено. Протестируем что получилось.

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

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

Создадим в новой папке \MQL5\Experts\TestDoEasy\Part73\ новый файл советника TestDoEasyPart73.mq5.

При создании файла советника укажем, что нам необходима входная переменная InpMovable с типом bool и начальным значением true:


Далее укажем, что нам необходим дополнительный обработчик OnChartEvent():

В итоге получим такую заготовку советника:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.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"
//--- input parameters
input bool     InpMovable=true;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   
  }
//+------------------------------------------------------------------+

Подключим к файлу советника класс только что созданного объекта-формы и объявим две глобальные переменные — префикс имён объектов и объект класса CForm:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.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"
//--- includes
#include <DoEasy\Objects\Graph\Form.mqh>
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
string         prefix;
CForm          form;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

В обработчике OnInit() включим разрешение на отсылку событий перемещения курсора и прокрутки колёсика мышки, зададим значение для префикса имён объектов как (имя файла)+"_" и создадим объект-форму на графике. После его создания установим отступы в 10 пикселей для границ активной зоны:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка разрешений на отсылку событий перемещения курсора и прокрутки колёсика мышки
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Установка глобальных переменных советника
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
//--- Если форма создана - установим для неё активную область с отступом в 10 пикселей от краёв
   if(form.CreateForm(ChartID(),0,prefix+"Form_01",300,20,100,70,clrSilver,200,InpMovable))
     {
      form.SetActiveAreaShift(10,10,10,10);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Теперь осталось вызвать из обработчика OnChartEvent() советника обработчик OnChartEvent() объекта-формы:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   form.OnChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Скомпилируем советник и запустим его на графике символа:


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

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

Что дальше

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

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

К содержанию

*Завершающая статья прошлой серии:

Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции

Прикрепленные файлы |
MQL5.zip (3999.57 KB)
Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы
В данной серии статей будем искать практическое применение теории вероятностей для описания процесса торговли и ценообразования. В первой статье мы познакомимся с основами комбинаторики и теории вероятностей, и разберем первый пример применения фракталов в рамках теории вероятности.
Паттерны с примерами (Часть I): Кратная вершина Паттерны с примерами (Часть I): Кратная вершина
Статья начинает цикл рассмотрения разворотных паттернов в рамках алготрейдинга. Мы начнем мысль, исследуя первое и самое интересное семейство данных паттернов, которые берут начало из паттерна "Двойная вершина" и "Двойное дно".
Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas
Переработаем концепцию построения графических объектов из прошлой статьи и подготовим базовый класс-основу всех графических объектов библиотеки, создаваемых на базе класса CCanvas Стандартной библиотеки.
Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции
В статье завершим работу над классами объектов-чартов и их коллекцией. Сделаем автоматическое отслеживание изменения свойств чартов и их окон, а также сохранение новых параметров в свойства объекта. Такая доработка позволит в будущем сделать событийный функционал для всей коллекции чартов.