preview
Компонент View для таблиц в парадигме MVC на MQL5: Базовый графический элемент

Компонент View для таблиц в парадигме MVC на MQL5: Базовый графический элемент

MetaTrader 5Примеры |
442 0
Artyom Trishkin
Artyom Trishkin

Содержание



Введение

В современном программировании парадигма MVC (Model-View-Controller) является одним из популярных подходов к разработке сложных приложений. Она позволяет разделить логику приложения на три независимых компонента: Model (модель данных), View (представление) и Controller (контроллер). Такой подход упрощает разработку, тестирование и поддержку кода, делая его более структурированным и читаемым.

В рамках данной серии статей, мы рассматриваем процесс создания таблиц в парадигме MVC на языке MQL5. В первых двух статьях мы разработали модели данных (Model) и базовую архитектуру таблиц. Теперь пришло время перейти к компоненту View, который отвечает за визуализацию данных и, частично, за взаимодействие с пользователем.

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

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

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

Так как реализация архитектуры компонента View в MQL5 достаточно трудоёмкая, включающая в себя множество вспомогательных классов и наследований, то условимся о достаточно кратком изложении. Определим класс, представим его краткое описание и далее, опять же кратко, рассмотрим его реализацию. Таких классов сегодня будет пять:

  1. базовый класс для всех графических объектов,
  2. класс для управления цветом,
  3. класс для управления цветами различных состояний графического элемента,
  4. класс для управления прямоугольной областью,
  5. базовый класс для рисования графических элементов на холсте.

Все эти классы, в итоге, необходимы для работы базового класса для рисования графических элементов. От него будут наследоваться все остальные классы, которые будут создаваться при реализации различных элементов управления, в частности — элемента управления Table Control.

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


Вспомогательные классы

Если ещё нет, создадим в каталоге терминала \MQL5\Scripts\ новую папку Tables\, а в ней — папку Controls. В ней будем хранить файлы, создаваемые в рамках статей по созданию компонента View для таблиц.

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

//+------------------------------------------------------------------+
//|                                                         Base.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"

//+------------------------------------------------------------------+
//| Включаемые библиотеки                                            |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>              // Класс СБ CCanvas
#include <Arrays\List.mqh>                // Класс СБ CList

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  clrNULL              0x00FFFFFF  // Прозрачный цвет для CCanvas
#define  MARKER_START_DATA    -1          // Маркер начала данных в файле

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE                    // Перечисление типов графических элементов
  {
   ELEMENT_TYPE_BASE = 0x10000,           // Базовый объект графических элементов
   ELEMENT_TYPE_COLOR,                    // Объект цвета
   ELEMENT_TYPE_COLORS_ELEMENT,           // Объект цветов элемента графического объекта
   ELEMENT_TYPE_RECTANGLE_AREA,           // Прямоугольная область элемента
   ELEMENT_TYPE_CANVAS_BASE,              // Базовый объект холста графических элементов
  };

enum ENUM_COLOR_STATE                     // Перечисление цветов состояний элемента
  {
   COLOR_STATE_DEFAULT,                   // Цвет обычного состояния
   COLOR_STATE_FOCUSED,                   // Цвет при наведении курсора на элемент
   COLOR_STATE_PRESSED,                   // Цвет при нажатии на элемент
   COLOR_STATE_BLOCKED,                   // Цвет заблокированного элемента
  };
//+------------------------------------------------------------------+ 
//| Функции                                                          |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Возвращает тип элемента как строку                              |
//+------------------------------------------------------------------+
string ElementDescription(const ENUM_ELEMENT_TYPE type)
  {
   string array[];
   int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array);
   string result="";
   for(int i=2;i<total;i++)
     {
      array[i]+=" ";
      array[i].Lower();
      array[i].SetChar(0,ushort(array[i].GetChar(0)-0x20));
      result+=array[i];
     }
   result.TrimLeft();
   result.TrimRight();
   return result;
  }
//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+

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

Пока две эти идентичные функции находятся в разных файлах, пусть они будут. Далее, при объединении всех файлов в единый проект, мы переработаем обе функции в одну, которая будет возвращать наименование не из перечисления, а из переданной строки. В этом случае, один и тот же алгоритм будет одинаково возвращать название объекта, элемента и т.д. Важно, чтобы в константах всех перечислений была записана одинаковая структура названия константы: OBJECT_TYPE_XXX_YYY, ELEMENT_TYPE_XXX_YYY, ANYOTHER_TYPE_XXX_YYY_ZZZ ... В этом случае будет возвращаться то, что записано в XXX_YYY (XXX_YYY_ZZZ и т.п.), а отмеченное здесь жёлтым, отсекаться.

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

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

//+------------------------------------------------------------------+
//| Базовый класс графических элементов                              |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   int               m_id;                                     // Идентифткатор
   ushort            m_name[];                                 // Наименование
   
public:
//--- Устанавливает (1) наименование, (2) идентификатор
   void              SetName(const string name)                { ::StringToShortArray(name,this.m_name);    }
   void              SetID(const int id)                       { this.m_id=id;                              }
//--- Возвращает (1) наименование, (2) идентификатор
   string            Name(void)                          const { return ::ShortArrayToString(this.m_name);  }
   int               ID(void)                            const { return this.m_id;                          }

//--- Виртуальные методы (1) сравнения, (2) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BASE); }
   
//--- Конструкторы/деструктор
                     CBaseObj (void) : m_id(-1) {}
                    ~CBaseObj (void) {}
  };
//+------------------------------------------------------------------+
//| CBaseObj::Сравнение двух объектов                                |
//+------------------------------------------------------------------+
int CBaseObj::Compare(const CObject *node,const int mode=0) const
  {
   const CBaseObj *obj=node;
   switch(mode)
     {
      case 0   :  return(this.Name()>obj.Name() ? 1 : this.Name()<obj.Name() ? -1 : 0);
      default  :  return(this.ID()>obj.ID()     ? 1 : this.ID()<obj.ID()     ? -1 : 0);
     }
  }

Краткое описание:

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

  • содержит общие свойства, такие как идентификатор (m_id) и имя (m_name),
  • предоставляет базовые методы для сравнения объектов (Compare) и получения типа объекта (Type),
  • используется как родительский класс для всех остальных классов, обеспечивая единообразие иерархии.

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



Классы управления цветом

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

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

Класс цвета

//+------------------------------------------------------------------+
//| Класс цвета                                                      |
//+------------------------------------------------------------------+
class CColor : public CBaseObj
  {
protected:
   color             m_color;                                  // Цвет
   
public:
//--- Устанавливает цвет
   bool              SetColor(const color clr)
                       {
                        if(this.m_color==clr)
                           return false;
                        this.m_color=clr;
                        return true;
                       }
//--- Возвращает цвет
   color             Get(void)                           const { return this.m_color;              }

//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLOR);       }
   
//--- Конструкторы/деструктор
                     CColor(void) : m_color(clrNULL)                          { this.SetName("");  }
                     CColor(const color clr) : m_color(clr)                   { this.SetName("");  }
                     CColor(const color clr,const string name) : m_color(clr) { this.SetName(name);}
                    ~CColor(void) {}
  };
//+------------------------------------------------------------------+
//| CColor::Возвращает описание объекта                              |
//+------------------------------------------------------------------+
string CColor::Description(void)
  {
   string color_name=(this.Get()!=clrNULL ? ::ColorToString(this.Get(),true) : "clrNULL (0x00FFFFFF)");
   return(this.Name()+(this.Name()!="" ? " " : "")+"Color: "+color_name);
  }
//+------------------------------------------------------------------+
//| CColor::Выводит в журнал описание объекта                        |
//+------------------------------------------------------------------+
void CColor::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColor::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CColor::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

//--- Сохраняем цвет
   if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем идентификатор
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем наименование
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CColor::Загрузка из файла                                        |
//+------------------------------------------------------------------+
bool CColor::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

//--- Загружаем цвет
   this.m_color=(color)::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем идентификатор
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем наименование
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- Всё успешно
   return true;
  }

Краткое описание класса:

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

  • хранит цвет в виде значения типа color (m_color),
  • предоставляет методы для установки и получения цвета (SetColor, Get),
  • реализует методы сохранения и загрузки цвета в/из файла (Save, Load),
  • может быть использован для представления любого цвета в графических элементах.

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

//+------------------------------------------------------------------+
//| Класс цветов элемента графического объекта                       |
//+------------------------------------------------------------------+
class CColorElement : public CBaseObj
  {
protected:
   CColor            m_current;                                // Текущий цвет. Может быть одним из нижеследующих
   CColor            m_default;                                // Цвет обычного состояния
   CColor            m_focused;                                // Цвет при наведении курсора
   CColor            m_pressed;                                // Цвет при нажатии
   CColor            m_blocked;                                // Цвет заблокированного элемента
   
//--- Преобразует RGB в color
   color             RGBToColor(const double r,const double g,const double b) const;
//--- Записывает в переменные значения компонентов RGB
   void              ColorToRGB(const color clr,double &r,double &g,double &b);
//--- Возвращает составляющую цвета (1) Red, (2) Green, (3) Blue
   double            GetR(const color clr)                     { return clr&0xFF;                           }
   double            GetG(const color clr)                     { return(clr>>8)&0xFF;                       }
   double            GetB(const color clr)                     { return(clr>>16)&0xFF;                      }
   
public:
//--- Возвращает новый цвет
   color             NewColor(color base_color, int shift_red, int shift_green, int shift_blue);

//--- Инициализация цветов различных состояний
   bool              InitDefault(const color clr)              { return this.m_default.SetColor(clr);       }
   bool              InitFocused(const color clr)              { return this.m_focused.SetColor(clr);       }
   bool              InitPressed(const color clr)              { return this.m_pressed.SetColor(clr);       }
   bool              InitBlocked(const color clr)              { return this.m_blocked.SetColor(clr);       }
   
//--- Установка цветов для всех состояний
   void              InitColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked);
   void              InitColors(const color clr);
    
//--- Возврат цветов различных состояний
   color             GetCurrent(void)                    const { return this.m_current.Get();               }
   color             GetDefault(void)                    const { return this.m_default.Get();               }
   color             GetFocused(void)                    const { return this.m_focused.Get();               }
   color             GetPressed(void)                    const { return this.m_pressed.Get();               }
   color             GetBlocked(void)                    const { return this.m_blocked.Get();               }
   
//--- Устанавливает один из списка цветов как текущий
   bool              SetCurrentAs(const ENUM_COLOR_STATE color_state);

//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLORS_ELEMENT);       }
   
//--- Конструкторы/деструктор
                     CColorElement(void);
                     CColorElement(const color clr);
                     CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked);
                    ~CColorElement(void) {}
  };
//+------------------------------------------------------------------+
//| CColorControl::Конструктор с установкой прозрачных цветов объекта|
//+------------------------------------------------------------------+
CColorElement::CColorElement(void)
  {
   this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Конструктор с указанием цветов объекта            |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Конструктор с указанием цвета объекта             |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr)
  {
   this.InitColors(clr);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Устанавливает цвета для всех состояний            |
//+------------------------------------------------------------------+
void CColorElement::InitColors(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitDefault(clr_default);
   this.InitFocused(clr_focused);
   this.InitPressed(clr_pressed);
   this.InitBlocked(clr_blocked);   
  }
//+------------------------------------------------------------------+
//| CColorControl::Устанавливает цвета для всех состояний по текущему|
//+------------------------------------------------------------------+
void CColorElement::InitColors(const color clr)
  {
   this.InitDefault(clr);
   this.InitFocused(this.NewColor(clr,-3,-3,-3));
   this.InitPressed(this.NewColor(clr,-6,-6,-6));
   this.InitBlocked(clrSilver);   
  }
//+-------------------------------------------------------------------+
//|CColorControl::Устанавливает один цвет из списка цветов как текущий|
//+-------------------------------------------------------------------+
bool CColorElement::SetCurrentAs(const ENUM_COLOR_STATE color_state)
  {
   switch(color_state)
     {
      case COLOR_STATE_DEFAULT   :  return this.m_current.SetColor(this.m_default.Get());
      case COLOR_STATE_FOCUSED   :  return this.m_current.SetColor(this.m_focused.Get());
      case COLOR_STATE_PRESSED   :  return this.m_current.SetColor(this.m_pressed.Get());
      case COLOR_STATE_BLOCKED   :  return this.m_current.SetColor(this.m_blocked.Get());
      default                    :  return false;
     }
  }
//+------------------------------------------------------------------+
//| CColorControl::Преобразует RGB в color                           |
//+------------------------------------------------------------------+
color CColorElement::RGBToColor(const double r,const double g,const double b) const
  {
   int int_r=(int)::round(r);
   int int_g=(int)::round(g);
   int int_b=(int)::round(b);
   int clr=0;
   clr=int_b;
   clr<<=8;
   clr|=int_g;
   clr<<=8;
   clr|=int_r;

   return (color)clr;
  }
//+------------------------------------------------------------------+
//| CColorControl::Получение значений компонентов RGB                |
//+------------------------------------------------------------------+
void CColorElement::ColorToRGB(const color clr,double &r,double &g,double &b)
  {
   r=this.GetR(clr);
   g=this.GetG(clr);
   b=this.GetB(clr);
  }
//+------------------------------------------------------------------+
//| CColorControl::Возвращает цвет с новой цветовой составляющей     |
//+------------------------------------------------------------------+
color CColorElement::NewColor(color base_color, int shift_red, int shift_green, int shift_blue)
  {
   double clrR=0, clrG=0, clrB=0;
   this.ColorToRGB(base_color,clrR,clrG,clrB);
   double clrRx=(clrR+shift_red  < 0 ? 0 : clrR+shift_red  > 255 ? 255 : clrR+shift_red);
   double clrGx=(clrG+shift_green< 0 ? 0 : clrG+shift_green> 255 ? 255 : clrG+shift_green);
   double clrBx=(clrB+shift_blue < 0 ? 0 : clrB+shift_blue > 255 ? 255 : clrB+shift_blue);
   return this.RGBToColor(clrRx,clrGx,clrBx);
  }
//+------------------------------------------------------------------+
//| CColorElement::Возвращает описание объекта                       |
//+------------------------------------------------------------------+
string CColorElement::Description(void)
  {
   string res=::StringFormat("%s Colors. %s",this.Name(),this.m_current.Description());
   res+="\n  1: "+this.m_default.Description();
   res+="\n  2: "+this.m_focused.Description();
   res+="\n  3: "+this.m_pressed.Description();
   res+="\n  4: "+this.m_blocked.Description();
   return res;
  }
//+------------------------------------------------------------------+
//| CColorElement::Выводит в журнал описание объекта                 |
//+------------------------------------------------------------------+
void CColorElement::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColorElement::Сохранение в файл                                 |
//+------------------------------------------------------------------+
bool CColorElement::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Сохраняем идентификатор
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем наименование цветов элемента
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Сохраняем текущий цвет
   if(!this.m_current.Save(file_handle))
      return false;
//--- Сохраняем цвет обычного состояния
   if(!this.m_default.Save(file_handle))
      return false;
//--- Сохраняем цвет при наведении курсора
   if(!this.m_focused.Save(file_handle))
      return false;
//--- Сохраняем цвет при нажатии
   if(!this.m_pressed.Save(file_handle))
      return false;
//--- Сохраняем цвет заблокированного элемента
   if(!this.m_blocked.Save(file_handle))
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CColorElement::Загрузка из файла                                 |
//+------------------------------------------------------------------+
bool CColorElement::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Загружаем идентификатор
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем наименование цветов элемента
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Загружаем текущий цвет
   if(!this.m_current.Load(file_handle))
      return false;
//--- Загружаем цвет обычного состояния
   if(!this.m_default.Load(file_handle))
      return false;
//--- Загружаем цвет при наведении курсора
   if(!this.m_focused.Load(file_handle))
      return false;
//--- Загружаем цвет при нажатии
   if(!this.m_pressed.Load(file_handle))
      return false;
//--- Загружаем цвет заблокированного элемента
   if(!this.m_blocked.Load(file_handle))
      return false;
   
//--- Всё успешно
   return true;
  }

Краткое описание класса:

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

  • хранит цвета для четырех состояний: обычное (m_default), фокус (m_focused), нажатие (m_pressed) и блокировка (m_blocked),
  • поддерживает текущий цвет (m_current), который может быть установлен в одно из состояний,
  • предоставляет методы для инициализации цветов всех состояний (InitColors) и переключения текущего цвета (SetCurrentAs),
  • включает метод для получения нового цвета из базового, с указанием смещений цветовых составляющих (NewColor),
  • реализует методы сохранения и загрузки всех цветов в/из файла (Save, Load),
  • полезен для создания интерактивных элементов, таких как кнопки, строки или ячейки таблиц.



Класс управления прямоугольной областью

В терминале в папке \MQL5\Include\Controls\ представлена интересная структура CRect в файле Rect.mqh. Она предоставляет набор методов для контроля за окном прямоугольной формы, которое может обрамлять на графическом элементе заданный контур. Такими контурами можно виртуально выделить несколько областей одного элемента и отслеживать их координаты и границы. Это позволит на выделенных областях отследить координаты курсора мышки и организовать интерактивное взаимодействие мышкой с областью графического элемента.

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

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

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

//+------------------------------------------------------------------+
//| Класс прямоугольной области                                      |
//+------------------------------------------------------------------+
class CBound : public CBaseObj
  {
protected:
   CRect             m_bound;                                  // Структура прямоугольной области

public:
//--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника
   void              ResizeW(const int size)                   { this.m_bound.Width(size);                        }
   void              ResizeH(const int size)                   { this.m_bound.Height(size);                       }
   void              Resize(const int w,const int h)           { this.m_bound.Width(w); this.m_bound.Height(h);   }
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника
   void              SetX(const int x)                         { this.m_bound.left=x;                             }
   void              SetY(const int y)                         { this.m_bound.top=y;                              }
   void              SetXY(const int x,const int y)            { this.m_bound.LeftTop(x,y);                       }
   
//--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения
   void              Move(const int x,const int y)             { this.m_bound.Move(x,y);                          }
   void              Shift(const int dx,const int dy)          { this.m_bound.Shift(dx,dy);                       }
   
//--- Возвращает координаты, размеры и границы объекта
   int               X(void)                             const { return this.m_bound.left;                        }
   int               Y(void)                             const { return this.m_bound.top;                         }
   int               Width(void)                         const { return this.m_bound.Width();                     }
   int               Height(void)                        const { return this.m_bound.Height();                    }
   int               Right(void)                         const { return this.m_bound.right-1;                     }
   int               Bottom(void)                        const { return this.m_bound.bottom-1;                    }
   
//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_RECTANGLE_AREA);             }
   
//--- Конструкторы/деструктор
                     CBound(void) { ::ZeroMemory(this.m_bound); }
                     CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); }
                    ~CBound(void) { ::ZeroMemory(this.m_bound); }
  };
//+------------------------------------------------------------------+
//| CBound::Возвращает описание объекта                              |
//+------------------------------------------------------------------+
string CBound::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   return ::StringFormat("%s%s: x %d, y %d, w %d, h %d",
                         ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,
                         this.X(),this.Y(),this.Width(),this.Height());
  }
//+------------------------------------------------------------------+
//| CBound::Выводит в журнал описание объекта                        |
//+------------------------------------------------------------------+
void CBound::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CBound::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CBound::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Сохраняем идентификатор
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем наименование
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Сохраняем структуру области
   if(::FileWriteStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CBound::Загрузка из файла                                        |
//+------------------------------------------------------------------+
bool CBound::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Загружаем идентификатор
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем наименование
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Загружаем структуру области
   if(::FileReadStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- Всё успешно
   return true;
  }

Краткое описание класса:

Класс для управления прямоугольной областью:

  • хранит границы области в виде структуры CRect (m_bound),
  • предоставляет методы для изменения размеров (Resize, ResizeW, ResizeH), установки координат (SetX, SetY, SetXY) и смещения области (Move, Shift),
  • позволяет получить координаты и размеры области (X, Y, Width, Height, Right, Bottom),
  • реализует методы сохранения и загрузки области в/из файла (Save, Load),
  • используется для определения границ графических элементов или прямоугольных областей внутри них.

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



Базовый класс рисования

Класс будет достаточно объёмным, поэтому перед тем, как приступить к его созданию, и для большего понимания, ознакомимся с его кратким описанием:

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

  • содержит два канваса: для фона (m_background) и переднего плана (m_foreground),
  • хранит границы элемента (m_bound), а также информацию о контейнере (m_container),
  • поддерживает управление цветами через объекты CColorElement для фона, переднего плана и рамки,
  • реализует методы для управления видимостью (Hide, Show), блокировкой (Block, Unblock), и обрезкой по границам контейнера (ObjectTrim),
  • поддерживает динамическое изменение размеров и координат (ObjectResize, ObjectSetX, ObjectSetY),
  • предоставляет методы для рисования (Draw), обновления (Update) и очистки (Clear) графического элемента,
  • реализует методы сохранения и загрузки объекта в/из файла (Save, Load),
  • является основой для создания сложных графических элементов, таких как ячейки таблиц, строки и заголовки.

Базовый класс холста графических элементов

//+------------------------------------------------------------------+
//| Базовый класс холста графических элементов                       |
//+------------------------------------------------------------------+
class CCanvasBase : public CBaseObj
  {
protected:
   CCanvas           m_background;                             // Канвас для рисования фона
   CCanvas           m_foreground;                             // Канвас для рисования переднего плана
   CBound            m_bound;                                  // Границы объекта
   CCanvasBase      *m_container;                              // Родительский объект-контейнер
   CColorElement     m_color_background;                       // Объект управления цветом фона
   CColorElement     m_color_foreground;                       // Объект управления цветом переднего плана
   CColorElement     m_color_border;                           // Объект управления цветом рамки
   long              m_chart_id;                               // Идентификатор графика
   int               m_wnd;                                    // Номер подокна графика
   int               m_wnd_y;                                  // Смещение координаты Y курсора в подокне
   int               m_obj_x;                                  // Координата X графического объекта
   int               m_obj_y;                                  // Координата Y графического объекта
   uchar             m_alpha;                                  // Прозрачность
   uint              m_border_width;                           // Ширина рамки
   string            m_program_name;                           // Имя программы
   bool              m_hidden;                                 // Флаг скрытого объекта
   bool              m_blocked;                                // Флаг заблокированного элемента
   bool              m_focused;                                // Флаг элемента в фокусе
   
private:
//--- Возврат смещения начальных координат рисования на холсте относительно канваса и координат объекта
   int               CanvasOffsetX(void)                 const { return(this.ObjectX()-this.X());                                                  }
   int               CanvasOffsetY(void)                 const { return(this.ObjectY()-this.Y());                                                  }
//--- Возвращает скорректированную координату точки на холсте с учётом смещения холста относительно объекта
   int               AdjX(const int x)                   const { return(x-this.CanvasOffsetX());                                                   }
   int               AdjY(const int y)                   const { return(y-this.CanvasOffsetY());                                                   }
   
protected:
//--- Возвращает скорректированный идентификатор графика
   long              CorrectChartID(const long chart_id) const { return(chart_id!=0 ? chart_id : ::ChartID());                                     }

//--- Получение границ родительского объекта-контейнера
   int               ContainerLimitLeft(void)            const { return(this.m_container==NULL ? this.X()      :  this.m_container.LimitLeft());   }
   int               ContainerLimitRight(void)           const { return(this.m_container==NULL ? this.Right()  :  this.m_container.LimitRight());  }
   int               ContainerLimitTop(void)             const { return(this.m_container==NULL ? this.Y()      :  this.m_container.LimitTop());    }
   int               ContainerLimitBottom(void)          const { return(this.m_container==NULL ? this.Bottom() :  this.m_container.LimitBottom()); }
   
//--- Возврат координат, границ и размеров графического объекта
   int               ObjectX(void)                       const { return this.m_obj_x;                                                              }
   int               ObjectY(void)                       const { return this.m_obj_y;                                                              }
   int               ObjectWidth(void)                   const { return this.m_background.Width();                                                 }
   int               ObjectHeight(void)                  const { return this.m_background.Height();                                                }
   int               ObjectRight(void)                   const { return this.ObjectX()+this.ObjectWidth()-1;                                       }
   int               ObjectBottom(void)                  const { return this.ObjectY()+this.ObjectHeight()-1;                                      }
   
//--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника
   void              BoundResizeW(const int size)              { this.m_bound.ResizeW(size);                                                       }
   void              BoundResizeH(const int size)              { this.m_bound.ResizeH(size);                                                       }
   void              BoundResize(const int w,const int h)      { this.m_bound.Resize(w,h);                                                         }
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника
   void              BoundSetX(const int x)                    { this.m_bound.SetX(x);                                                             }
   void              BoundSetY(const int y)                    { this.m_bound.SetY(y);                                                             }
   void              BoundSetXY(const int x,const int y)       { this.m_bound.SetXY(x,y);                                                          }
   
//--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения
   void              BoundMove(const int x,const int y)        { this.m_bound.Move(x,y);                                                           }
   void              BoundShift(const int dx,const int dy)     { this.m_bound.Shift(dx,dy);                                                        }
   
//--- Изменяет (1) ширину, (2) высоту, (3) размер графического объекта
   bool              ObjectResizeW(const int size);
   bool              ObjectResizeH(const int size);
   bool              ObjectResize(const int w,const int h);
   
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты графического объекта
   bool              ObjectSetX(const int x);
   bool              ObjectSetY(const int y);
   bool              ObjectSetXY(const int x,const int y)      { return(this.ObjectSetX(x) && this.ObjectSetY(y));                                 }
   
//--- (1) Устанавливает, (2) смещает графический объект на указанные координаты/размер смещения
   bool              ObjectMove(const int x,const int y)       { return this.ObjectSetXY(x,y);                                                     }
   bool              ObjectShift(const int dx,const int dy)    { return this.ObjectSetXY(this.ObjectX()+dx,this.ObjectY()+dy);                     }
   
//--- Ограничивает графический объект по размерам контейнера
   virtual void      ObjectTrim(void);
   
public:
//--- Возвращает указатель на канвас (1) фона, (2) переднего плана
   CCanvas          *GetBackground(void)                       { return &this.m_background;                                                        }
   CCanvas          *GetForeground(void)                       { return &this.m_foreground;                                                        }
   
//--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки
   CColorElement    *GetBackColorControl(void)                 { return &this.m_color_background;                                                  }
   CColorElement    *GetForeColorControl(void)                 { return &this.m_color_foreground;                                                  }
   CColorElement    *GetBorderColorControl(void)               { return &this.m_color_border;                                                      }
   
//--- Возврат цвета (1) фона, (2) переднего плана, (3) рамки
   color             BackColor(void)                     const { return this.m_color_background.GetCurrent();                                      }
   color             ForeColor(void)                     const { return this.m_color_foreground.GetCurrent();                                      }
   color             BorderColor(void)                   const { return this.m_color_border.GetCurrent();                                          }
   
//--- Установка цветов фона для всех состояний
   void              InitBackColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_background.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBackColors(const color clr)           { this.m_color_background.InitColors(clr);                                          }

//--- Установка цветов переднего плана для всех состояний
   void              InitForeColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_foreground.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitForeColors(const color clr)           { this.m_color_foreground.InitColors(clr);                                          }

//--- Установка цветов рамки для всех состояний
   void              InitBorderColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_border.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBorderColors(const color clr)         { this.m_color_border.InitColors(clr);                                              }

//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки начальными значениями
   void              InitBackColorDefault(const color clr)     { this.m_color_background.InitDefault(clr);                                         }
   void              InitForeColorDefault(const color clr)     { this.m_color_foreground.InitDefault(clr);                                         }
   void              InitBorderColorDefault(const color clr)   { this.m_color_border.InitDefault(clr);                                             }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при наведении курсора начальными значениями
   void              InitBackColorFocused(const color clr)     { this.m_color_background.InitFocused(clr);                                         }
   void              InitForeColorFocused(const color clr)     { this.m_color_foreground.InitFocused(clr);                                         }
   void              InitBorderColorFocused(const color clr)   { this.m_color_border.InitFocused(clr);                                             }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при щелчке по объекту начальными значениями
   void              InitBackColorPressed(const color clr)     { this.m_color_background.InitPressed(clr);                                         }
   void              InitForeColorPressed(const color clr)     { this.m_color_foreground.InitPressed(clr);                                         }
   void              InitBorderColorPressed(const color clr)   { this.m_color_border.InitPressed(clr);                                             }
   
//--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки для заблокированного объекта начальными значениями
   void              InitBackColorBlocked(const color clr)     { this.m_color_background.InitBlocked(clr);                                         }
   void              InitForeColorBlocked(const color clr)     { this.m_color_foreground.InitBlocked(clr);                                         }
   void              InitBorderColorBlocked(const color clr)   { this.m_color_border.InitBlocked(clr);                                             }
   
//--- Установка текущего цвета фона в различные состояния
   bool              BackColorToDefault(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              BackColorToFocused(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              BackColorToPressed(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              BackColorToBlocked(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Установка текущего цвета переднего плана в различные состояния
   bool              ForeColorToDefault(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              ForeColorToFocused(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              ForeColorToPressed(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              ForeColorToBlocked(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Установка текущего цвета рамки в различные состояния
   bool              BorderColorToDefault(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT);                     }
   bool              BorderColorToFocused(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED);                     }
   bool              BorderColorToPressed(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED);                     }
   bool              BorderColorToBlocked(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED);                     }
   
//--- Установка текущих цветов элемента в различные состояния
   bool              ColorsToDefault(void);
   bool              ColorsToFocused(void);
   bool              ColorsToPressed(void);
   bool              ColorsToBlocked(void);
   
//--- Устанавливает указатель на родительский объект-контейнер
   void              SetContainerObj(CCanvasBase *obj);
   
//--- Создаёт OBJ_BITMAP_LABEL
   bool              Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);

//--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного элемента, (4) имя графического объекта
   bool              IsBelongsToThis(void)   const { return(::ObjectGetString(this.m_chart_id,this.NameBG(),OBJPROP_TEXT)==this.m_program_name);   }
   bool              IsHidden(void)          const { return this.m_hidden;                                                                         }
   bool              IsBlocked(void)         const { return this.m_blocked;                                                                        }
   bool              IsFocused(void)         const { return this.m_focused;                                                                        }
   string            NameBG(void)    const { return this.m_background.ChartObjectName();                                                           }
   string            NameFG(void)    const { return this.m_foreground.ChartObjectName();                                                           }
   
//--- (1) Возвращает, (2) устанавливает прозрачность
   uchar             Alpha(void)                         const { return this.m_alpha;                                                              }
   void              SetAlpha(const uchar value)               { this.m_alpha=value;                                                               }
   
//--- (1) Возвращает, (2) устанавливает ширину рамки
    uint             BorderWidth(void)                   const { return this.m_border_width;                                                       } 
    void             SetBorderWidth(const uint width)          { this.m_border_width=width;                                                        }
                      
//--- Возвращает координаты, размеры и границы объекта
   int               X(void)                             const { return this.m_bound.X();                                                          }
   int               Y(void)                             const { return this.m_bound.Y();                                                          }
   int               Width(void)                         const { return this.m_bound.Width();                                                      }
   int               Height(void)                        const { return this.m_bound.Height();                                                     }
   int               Right(void)                         const { return this.m_bound.Right();                                                      }
   int               Bottom(void)                        const { return this.m_bound.Bottom();                                                     }
   
//--- Устанавливает объекту новую координату (1) X, (2) Y, (3) XY
   bool              MoveX(const int x);
   bool              MoveY(const int y);
   bool              Move(const int x,const int y);
   
//--- Смещает объект по оси (1) X, (2) Y, (3) XY на указанное смещение
   bool              ShiftX(const int dx);
   bool              ShiftY(const int dy);
   bool              Shift(const int dx,const int dy);

//--- Возврат границ объекта с учётом рамки
   int               LimitLeft(void)                     const { return this.X()+(int)this.m_border_width;                                         }
   int               LimitRight(void)                    const { return this.Right()-(int)this.m_border_width;                                     }
   int               LimitTop(void)                      const { return this.Y()+(int)this.m_border_width;                                         }
   int               LimitBottom(void)                   const { return this.Bottom()-(int)this.m_border_width;                                    }

//--- (1) Скрывает (2) отображает объект на всех периодах графика,
//--- (3) помещает объект на передний план, (4) блокирует, (5) разблокирует элемент,
//--- (6) заливает объект указанным цветом с установленной прозрачностью
   virtual void      Hide(const bool chart_redraw);
   virtual void      Show(const bool chart_redraw);
   virtual void      BringToTop(const bool chart_redraw);
   virtual void      Block(const bool chart_redraw);
   virtual void      Unblock(const bool chart_redraw);
   void              Fill(const color clr,const bool chart_redraw);
   
//--- (1) Заливает объект прозрачным цветом, (2) обновляет объект для отображения изменений,
//--- (3) рисует внешний вид, (4) уничтожает объект
   virtual void      Clear(const bool chart_redraw);
   virtual void      Update(const bool chart_redraw);
   virtual void      Draw(const bool chart_redraw);
   virtual void      Destroy(void);
   
//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_CANVAS_BASE); }
   
//--- Конструкторы/деструктор
                     CCanvasBase(void) :
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0),
                        m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_wnd_y(0) { }
                     CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
                    ~CCanvasBase(void);
  };

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

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

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

Параметрический конструктор

//+------------------------------------------------------------------+
//| CCanvasBase::Конструктор                                         |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h) :
   m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0)
  {
//--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y
//--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   this.m_chart_id=this.CorrectChartID(chart_id);
   this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);
//--- Если графический ресурс и графический объект созданы
   if(this.Create(this.m_chart_id,this.m_wnd,name,x,y,w,h))
     {
      //--- Очищаем канвасы фона и переднего плана и устанавливаем начальные значения координат,
      //--- наименования графических объектов и свойства текста, рисуемого на переднем плане
      this.Clear(false);
      this.m_obj_x=x;
      this.m_obj_y=y;
      this.m_color_background.SetName("Background");
      this.m_color_foreground.SetName("Foreground");
      this.m_color_border.SetName("Border");
      this.m_foreground.FontSet("Calibri",12);
      this.m_bound.SetName("Perimeter");
     }
  }

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

В деструкторе класса объект уничтожается:

//+------------------------------------------------------------------+
//| CCanvasBase::Деструктор                                          |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
   this.Destroy();
  }

Метод, создающий графические объекты фона и переднего плана

//+------------------------------------------------------------------+
//| CCanvasBase::Создаёт графические объекты фона и переднего плана  |
//+------------------------------------------------------------------+
bool CCanvasBase::Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
//--- Получаем скорректированный идентификатор графика
   long id=this.CorrectChartID(chart_id);
//--- Создаём имя графического объекта для фона и создаём канвас
   string obj_name=name+"_BG";
   if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- Создаём имя графического объекта для переднего плана и создаём канвас
   obj_name=name+"_FG";
   if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);
   
//--- Устанавливаем размеры прямоугольной области и возвращаем true
   this.m_bound.SetXY(x,y);
   this.m_bound.Resize(w,h);
   return true;
  }

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

Метод, устанавливающий указатель на родительский объект-контейнер

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает указатель                             |
//| на родительский объект-контейнер                                 |
//+------------------------------------------------------------------+
void CCanvasBase::SetContainerObj(CCanvasBase *obj)
  {
//--- Устанавливаем переданный указатель объекту
   this.m_container=obj;
//--- Если указатель пустой - уходим
   if(this.m_container==NULL)
      return;
//--- Если передан невалидный указатель - обнуляем его в объекте и уходим
   if(::CheckPointer(this.m_container)==POINTER_INVALID)
     {
      this.m_container=NULL;
      return;
     }
//--- Обрезаем объект по границам назначенного ему контейнера
   this.ObjectTrim();
  }

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

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

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

//+------------------------------------------------------------------+
//| CCanvasBase::Подрезает графический объект по контуру контейнера  |
//+------------------------------------------------------------------+
void CCanvasBase::ObjectTrim()
  {
//--- Получаем границы контейнера
   int container_left   = this.ContainerLimitLeft();
   int container_right  = this.ContainerLimitRight();
   int container_top    = this.ContainerLimitTop();
   int container_bottom = this.ContainerLimitBottom();
   
//--- Получаем текущие границы объекта
   int object_left   = this.X();
   int object_right  = this.Right();
   int object_top    = this.Y();
   int object_bottom = this.Bottom();

//--- Проверяем, полностью ли объект выходит за пределы контейнера и скрываем его, если да
   if(object_right <= container_left || object_left >= container_right ||
      object_bottom <= container_top || object_top >= container_bottom)
     {
      this.Hide(true);
      this.ObjectResize(this.Width(),this.Height());
      return;
     }

//--- Проверяем выход объекта по горизонтали и вертикали за пределы контейнера
   bool modified_horizontal=false;     // Флаг изменений по горизонтали
   bool modified_vertical  =false;     // Флаг изменений по вертикали
   
//--- Обрезка по горизонтали
   int new_left = object_left;
   int new_width = this.Width();
//--- Если объект выходит за левую границу контейнера
   if(object_left<=container_left)
     {
      int crop_left=container_left-object_left;
      new_left=container_left;
      new_width-=crop_left;
      modified_horizontal=true;
     }
//--- Если объект выходит за правую границу контейнера
   if(object_right>=container_right)
     {
      int crop_right=object_right-container_right;
      new_width-=crop_right;
      modified_horizontal=true;
     }
//--- Если были изменения по горизонтали
   if(modified_horizontal)
     {
      this.ObjectSetX(new_left);
      this.ObjectResizeW(new_width);
     }

//--- Обрезка по вертикали
   int new_top=object_top;
   int new_height=this.Height();
//--- Если объект выходит за верхнюю границу контейнера
   if(object_top<=container_top)
     {
      int crop_top=container_top-object_top;
      new_top=container_top;
      new_height-=crop_top;
      modified_vertical=true;
     }
//--- Если объект выходит за нижнюю границу контейнера
   if(object_bottom>=container_bottom)
     {
      int crop_bottom=object_bottom-container_bottom;
      new_height-=crop_bottom;
      modified_vertical=true;
     }
//--- Если были изменения по вертикали
   if(modified_vertical)
     {
      this.ObjectSetY(new_top);
      this.ObjectResizeH(new_height);
     }

//--- После рассчётов, объект может быть скрыт, но теперь находится в области контейнера - отображаем его
   this.Show(false);

//--- Если объект был изменен, перерисовываем его
   if(modified_horizontal || modified_vertical)
     {
      this.Update(false);
      this.Draw(false);
     }
  }

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

Метод, устанавливающий координату X графического объекта

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает координату X графического объекта     |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetX(const int x)
  {
//--- Если передана существующая координата - возвращаем true
   if(this.ObjectX()==x)
      return true;
//--- Если не удалось установить новую координату в графические объекты фона и переднего плана - возвращаем false
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_XDISTANCE,x) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_XDISTANCE,x))
      return false;
//--- Записываем новую координату в переменную и возвращаем true
   this.m_obj_x=x;
   return true;
  }

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

Метод, устанавливающий координату Y графического объекта

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает координату Y графического объекта     |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetY(const int y)
  {
//--- Если передана существующая координата - возвращаем true
   if(this.ObjectY()==y)
      return true;
//--- Если не удалось установить новую координату в графические объекты фона и переднего плана - возвращаем false
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_YDISTANCE,y) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_YDISTANCE,y))
      return false;
//--- Записываем новую координату в переменную и возвращаем true
   this.m_obj_y=y;
   return true;
  }

Метод идентичен рассмотренному выше.

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

//+------------------------------------------------------------------+
//| CCanvasBase::Изменяет ширину графического объекта                |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeW(const int size)
  {
//--- Если передана существующая ширина - возвращаем true
   if(this.ObjectWidth()==size)
      return true;
//--- Если передан размер больше 0, возвращаем результат изменения ширины фона и переднего плана, иначе - false
   return(size>0 ? (this.m_background.Resize(size,this.ObjectHeight()) && this.m_foreground.Resize(size,this.ObjectHeight())) : false);
  }

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

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

//+------------------------------------------------------------------+
//| CCanvasBase::Изменяет высоту графического объекта                |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeH(const int size)
  {
//--- Если передана существующая высота - возвращаем true
   if(this.ObjectHeight()==size)
      return true;
//--- Если передан размер больше 0, возвращаем результат изменения высоты фона и переднего плана, иначе - false
   return(size>0 ? (this.m_background.Resize(this.ObjectWidth(),size) && this.m_foreground.Resize(this.ObjectWidth(),size)) : false);
  }

Метод идентичен рассмотренному выше.

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

//+------------------------------------------------------------------+
//| CCanvasBase::Изменяет размер графического объекта                |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResize(const int w,const int h)
  {
   if(!this.ObjectResizeW(w))
      return false;
   return this.ObjectResizeH(h);
  }

Здесь поочерёдно вызываются методы изменения ширины и высоты. Возвращает true только в случае успешного изменения как ширины, так и высоты.

Метод, устанавливающий объекту новые координаты X и Y

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает объекту новые координаты X и Y        |
//+------------------------------------------------------------------+
bool CCanvasBase::Move(const int x,const int y)
  {
   if(!this.ObjectMove(x,y))
      return false;
   this.BoundMove(x,y);
   this.ObjectTrim();
   return true;
  }

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

Метод, устанавливающий объекту новую координату X

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает объекту новую координату X            |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveX(const int x)
  {
   return this.Move(x,this.ObjectY());
  }

Вспомогательный метод, устанавливающий только горизонтальную координату. Вертикальная координата остаётся текущей.

Метод, устанавливающий объекту новую координату Y

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает объекту новую координату Y            |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveY(const int y)
  {
   return this.Move(this.ObjectX(),y);
  }

Вспомогательный метод, устанавливающий только вертикальную координату. Горизонтальная координата остаётся текущей.

Метод, смещающий объект по осям X и Y на указанное смещение

//+------------------------------------------------------------------+
//| CCanvasBase::Смещает объект по осям X и Y на указанное смещение  |
//+------------------------------------------------------------------+
bool CCanvasBase::Shift(const int dx,const int dy)
  {
   if(!this.ObjectShift(dx,dy))
      return false;
   this.BoundShift(dx,dy);
   this.ObjectTrim();
   return true;
  }

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

Метод, смещающий объект по оси X на указанное смещение

//+------------------------------------------------------------------+
//| CCanvasBase::Смещает объект по оси X на указанное смещение       |
//+------------------------------------------------------------------+
bool CCanvasBase::ShiftX(const int dx)
  {
   return this.Shift(dx,0);
  }

Вспомогательный метод, смещающий объект только по горизонтали. Вертикальная координата остаётся текущей.

Метод, смещающий объект по оси Y на указанное смещение

//+------------------------------------------------------------------+
//| CCanvasBase::Смещает объект по оси Y на указанное смещение       |
//+------------------------------------------------------------------+
bool CCanvasBase::ShiftY(const int dy)
  {
   return this.Shift(0,dy);
  }

Вспомогательный метод, смещающий объект только по вертикали. Горизонтальная координата остаётся текущей.

Метод, скрывающий объект на всех периодах графика

//+------------------------------------------------------------------+
//| CCanvasBase::Скрывает объект на всех периодах графика            |
//+------------------------------------------------------------------+
void CCanvasBase::Hide(const bool chart_redraw)
  {
//--- Если объект уже скрыт - уходим
   if(this.m_hidden)
      return;
//--- Если изменение видимости для фона и переднего плана успешно поставлено
//--- в очередь команд графика - устанавливаем флаг скрытого объекта
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS)
      ) this.m_hidden=true;
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Чтобы скрыть графический объект на графике, нужно для его свойства OBJPROP_TIMEFRAMES установить значение OBJ_NO_PERIODS. Если для объекта фона и объекта переднего плана свойство успешно установлено (поставлено в очередь событий графика), устанавливаем флаг скрытого объекта и, если указано, перерисовываем график.

Метод, отображающий объект на всех периодах графика

//+------------------------------------------------------------------+
//| CCanvasBase::Отображает объект на всех периодах графика          |
//+------------------------------------------------------------------+
void CCanvasBase::Show(const bool chart_redraw)
  {
//--- Если объект уже видимый - уходим
   if(!this.m_hidden)
      return;
//--- Если изменение видимости для фона и переднего плана успешно поставлено
//--- в очередь команд графика - сбрасываем флаг скрытого объекта
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS)
      ) this.m_hidden=false;
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Чтобы отобразить графический объект на графике, нужно для его свойства OBJPROP_TIMEFRAMES установить значение OBJ_ALL_PERIODS. Если для объекта фона и объекта переднего плана свойство успешно установлено (поставлено в очередь событий графика), снимаем флаг скрытого объекта и, если указано, перерисовываем график.

Метод, помещающий объект на передний план

//+------------------------------------------------------------------+
//| CCanvasBase::Помещает объект на передний план                    |
//+------------------------------------------------------------------+
void CCanvasBase::BringToTop(const bool chart_redraw)
  {
   this.Hide(false);
   this.Show(chart_redraw);
  }

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

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

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает текущие цвета                         |
//| элемента в состояние по умолчанию                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToDefault(void)
  {
   bool res=true;
   res &=this.BackColorToDefault();
   res &=this.ForeColorToDefault();
   res &=this.BorderColorToDefault();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает текущие цвета                         |
//| элемента в состояние при наведении курсора                       |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToFocused(void)
  {
   bool res=true;
   res &=this.BackColorToFocused();
   res &=this.ForeColorToFocused();
   res &=this.BorderColorToFocused();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает текущие цвета                         |
//| элемента в состояние при нажатии курсора                         |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToPressed(void)
  {
   bool res=true;
   res &=this.BackColorToPressed();
   res &=this.ForeColorToPressed();
   res &=this.BorderColorToPressed();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает текущие цвета                         |
//| элемента в заблокированное состояние                             |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToBlocked(void)
  {
   bool res=true;
   res &=this.BackColorToBlocked();
   res &=this.ForeColorToBlocked();
   res &=this.BorderColorToBlocked();
   return res;
  }

У графического элемента есть три части, цвета для которых задаются отдельно:

  • цвет фона, 
  • цвет текста,
  • цвет рамки.

Эти три элемента могут менять свой цвет в зависимости от состояния элемента. Такими состояниями могут быть:

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

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

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

Метод, блокирующий элемент

//+------------------------------------------------------------------+
//| CCanvasBase::Блокирует элемент                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Block(const bool chart_redraw)
  {
//--- Если элемент уже заблокирован - уходим
   if(this.m_blocked)
      return;
//--- Устанавливаем текущие цвета как цвета заблокированного элемента, 
//--- перерисовываем объект и устанавливаем флаг блокировки
   this.ColorsToBlocked();
   this.Draw(chart_redraw);
   this.m_blocked=true;
  }

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

Метод, разблокирующий элемент

//+------------------------------------------------------------------+
//| CCanvasBase::Разблокирует элемент                                |
//+------------------------------------------------------------------+
void CCanvasBase::Unblock(const bool chart_redraw)
  {
//--- Если элемент уже разблокирован - уходим
   if(!this.m_blocked)
      return;
//--- Устанавливаем текущие цвета как цвета элемента в обычном состоянии, 
//--- перерисовываем объект и сбрасываем флаг блокировки
   this.ColorsToDefault();
   this.Draw(chart_redraw);
   this.m_blocked=false;
  }

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

Метод, заливающий объект указанным цветом

//+------------------------------------------------------------------+
//| CCanvasBase::Заливает объект указанным цветом                    |
//| с установленной в m_alpha прозрачностью                          |
//+------------------------------------------------------------------+
void CCanvasBase::Fill(const color clr,const bool chart_redraw)
  {
   this.m_background.Erase(::ColorToARGB(clr,this.m_alpha));
   this.m_background.Update(chart_redraw);
  }

Иногда требуется весь фон объекта полностью залить каким-либо цветом. Метод заливает фон объекта цветом, не затрагивая передний план. Для заливки используется прозрачность, предварительно установленная в переменную класса m_alpha. И далее, канвас обновляется для фиксации изменений с флагом перерисовки чарта.

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

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

Метод, заливающий объект прозрачным цветом

//+------------------------------------------------------------------+
//| CCanvasBase::Заливает объект прозрачным цветом                   |
//+------------------------------------------------------------------+
void CCanvasBase::Clear(const bool chart_redraw)
  {
   this.m_background.Erase(clrNULL);
   this.m_foreground.Erase(clrNULL);
   this.Update(chart_redraw);
  }

Канвас фона и канвас переднего плана заливаются прозрачным цветом, и оба объекта обновляются с указанием флага перерисовки графика.

Метод, обновляющий объект для отображения изменений

//+------------------------------------------------------------------+
//| CCanvasBase::Обновляет объект для отображения изменений          |
//+------------------------------------------------------------------+
void CCanvasBase::Update(const bool chart_redraw)
  {
   this.m_background.Update(false);
   this.m_foreground.Update(chart_redraw);
  }

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

Метод, рисующий внешний вид

//+------------------------------------------------------------------+
//| CCanvasBase::Рисует внешний вид                                  |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   return;
  }

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

Метод, уничтожающий объект

//+------------------------------------------------------------------+
//| CCanvasBase::Уничтожает объект                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Destroy(void)
  {
   this.m_background.Destroy();
   this.m_foreground.Destroy();
  }

Оба канваса уничтожаются при помощи методов Destroy класса CCanvas.

Метод, возвращающий описание объекта

//+------------------------------------------------------------------+
//| CCanvasBase::Возвращает описание объекта                         |
//+------------------------------------------------------------------+
string CCanvasBase::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   string area=::StringFormat("x %d, y %d, w %d, h %d",this.X(),this.Y(),this.Width(),this.Height());
   return ::StringFormat("%s%s (%s, %s): ID %d, %s",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.NameBG(),this.NameFG(),this.ID(),area);
  }

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

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 100, h 100
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 80, h 80

Метод, выводящий в журнал описание объекта

//+------------------------------------------------------------------+
//| CCanvasBase::Выводит в журнал описание объекта                   |
//+------------------------------------------------------------------+
void CCanvasBase::Print(void)
  {
   ::Print(this.Description());
  }

Распечатывает в журнале описание объекта, возвращаемое методом Description.

Методы сохранения графического элемента в файл и загрузки его из файла на данный момент ещё не реализованы — просто сделана для них заготовка:

//+------------------------------------------------------------------+
//| CCanvasBase::Сохранение в файл                                   |
//+------------------------------------------------------------------+
bool CCanvasBase::Save(const int file_handle)
  {
//--- Метод временно отключен
   return false;
   
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

/*
//--- Сохранение свойств
      
*/
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| Загрузка из файла                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::Load(const int file_handle)
  {
//--- Метод временно отключен
   return false;
   
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

/*
//--- Загрузка свойств
   
*/
//--- Всё успешно
   return true;
  }

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

У нас всё готово для тестирования.



Тестируем результат

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

Но есть одно "но". В базовом объекте метод Draw ничего не делает, и мы просто не увидим работу класса, так как построенные объекты будут полностью прозрачны.

Сделаем так: первый объект просто зальём цветом и нарисуем рамку. Так как он никуда не смещается и не меняет размеров, то и перерисовывать его не нужно — достаточно один раз, после его создания, что-то на нём нарисовать. А вот для второго объекта нам необходимо постоянно обновлять его внешний вид, так как метод ObjectTrim() вызывает метод перерисовки объекта. Но в этом классе этот метод ничего не делает. Значит, временно внесём доработку в метод Draw, чтобы на объекте что-то рисовалось:

//+------------------------------------------------------------------+
//| CCanvasBase::Рисует внешний вид                                  |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   //return;
   Fill(BackColor(),false);
   m_background.Rectangle(this.AdjX(0),this.AdjY(0),AdjX(this.Width()-1),AdjY(this.Height()-1),ColorToARGB(this.BorderColor()));
   
   m_foreground.Erase(clrNULL);
   m_foreground.TextOut(AdjX(6),AdjY(6),StringFormat("%dx%d (%dx%d)",this.Width(),this.Height(),this.ObjectWidth(),this.ObjectHeight()),ColorToARGB(this.ForeColor()));
   m_foreground.TextOut(AdjX(6),AdjY(16),StringFormat("%dx%d (%dx%d)",this.X(),this.Y(),this.ObjectX(),this.ObjectY()),ColorToARGB(this.ForeColor()));
   
   Update(chart_redraw);
  }

Здесь для канваса фона мы заполняем фон установленным цветом фона и рисуем рамку установленным цветом рамки.

Для канваса переднего плана очищаем его и выводим два текста, один под другим, установленным цветом текста:

  1. с шириной/высотой объекта и в скобках с шириной/высотой графических объектов фона и переднего плана;
  2. с координатами X/Y объекта и в скобках с координатами X/Y графических объектов фона и переднего плана.

После тестирования, этот код удалим из метода.

В папке \MQL5\Scripts\Tables\ создадим файл тестового скрипта TestControls.mq5:

//+------------------------------------------------------------------+
//|                                                 TestControls.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Включаемые библиотеки                                            |
//+------------------------------------------------------------------+
#include "Controls\Base.mqh"
  
CCanvasBase *obj1=NULL;       // Указатель на первый графический элемент
CCanvasBase *obj2=NULL;       // Указатель на второй графический элемент
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Создаём первый графический элемент
   obj1=new CCanvasBase(0,0,"TestScr1",100,40,160,160);
   obj1.SetAlpha(250);        // Прозрачность
   obj1.SetBorderWidth(6);    // Ширина рамки
//--- Заливаем цветом фон и рисуем рамку с отступом в один пиксель от установленной ширины рамки
   obj1.Fill(clrDodgerBlue,false);
   uint wd=obj1.BorderWidth();
   obj1.GetBackground().Rectangle(wd-2,wd-2,obj1.Width()-wd+1,obj1.Height()-wd+1,ColorToARGB(clrWheat));
   obj1.Update(false);
//--- Устанавливаем наименование и идентификатор элемента и выводим в журнал его описание
   obj1.SetName("Rectangle 1");
   obj1.SetID(1);
   obj1.Print();

//--- Внутри первого создаём второй элемент, устанавливаем для него прозрачность
//--- и указываем для второго элемента в качестве контейнера первый элемент
   int shift=10;
   int x=obj1.X()+shift;
   int y=obj1.Y()+shift;
   int w=obj1.Width()-shift*2;
   int h=obj1.Height()-shift*2;
   obj2=new CCanvasBase(0,0,"TestScr2",x,y,w,h);
   obj2.SetAlpha(250);
   obj2.SetContainerObj(obj1);

//--- Инициализируем цвет фона, указываем цвет для заблокированного элемента
//--- и делаем текущим цветом фона элемента цвет фона, заданный по умолчанию 
   obj2.InitBackColors(clrLime);
   obj2.InitBackColorBlocked(clrLightGray);
   obj2.BackColorToDefault();

//--- Инициализируем цвет переднего плана, указываем цвет для заблокированного элемента
//--- и делаем текущим цветом переднего плана элемента цвет текста, заданный по умолчанию 
   obj2.InitForeColors(clrBlack);
   obj2.InitForeColorBlocked(clrDimGray);
   obj2.ForeColorToDefault();

//--- Инициализируем цвет рамки, указываем цвет для заблокированного элемента
//--- и делаем текущим цветом рамки элемента цвет рамки, заданный по умолчанию 
   obj2.InitBorderColors(clrBlue);
   obj2.InitBorderColorBlocked(clrSilver);
   obj2.BorderColorToDefault();
//--- Устанавливаем наименование и идентификатор элемента,
//--- выводим в журнал его описание, и рисуем элемент
   obj2.SetName("Rectangle 2");
   obj2.SetID(2);
   obj2.Print();
   obj2.Draw(true);
   
//--- Проверим обрезку элемента по границам его контейнера
   int ms=1;         // Задержка при смещении в миллисекундах
   int total=obj1.Width()-shift; // Количество итераций цикла смещения
   
//--- Ждём секунду и смещаем внутренний объект за левый край контейнера
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftHorisontal(1,total,ms);

//--- Ждём секунду и смещаем внутренний объект за правый край контейнера
   Sleep(1000);
   ShiftHorisontal(1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);

   
//--- Ждём секунду и смещаем внутренний объект за верхний край контейнера
   Sleep(1000);
   ShiftVertical(-1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftVertical(1,total,ms);
     
//--- Ждём секунду и смещаем внутренний объект за нижний край контейнера
   Sleep(1000);
   ShiftVertical(1,total,ms);
//--- Ждём секунду и возвращаем внутренний объект на исходное место
   Sleep(1000);
   ShiftVertical(-1,total,ms);

//--- Ожидаем секунду и ставим внутреннему объекту флаг заблокированного элемента
   Sleep(1000);
   obj2.Block(true);

//--- Через три секунды, перед завершением работы, чистим за собой
   Sleep(3000);
   delete obj1;
   delete obj2;
  }
//+------------------------------------------------------------------+
//| Смещает объект по горизонтали                                    |
//+------------------------------------------------------------------+
void ShiftHorisontal(const int dx, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftX(dx))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+
//| Смещает объект по вертикали                                      |
//+------------------------------------------------------------------+
void ShiftVertical(const int dy, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftY(dy))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+

Код скрипта подробно прокомментирован. Всю его логику легко понять по представленным комментариям.

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

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

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

После запуска скрипта, в журнал будут выведены две строки с описанием двух созданных объектов:

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 160, h 160
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 140, h 140


Заключение

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

Реализованные классы закладывают прочную основу для создания сложных графических элементов и их интеграции в компоненты Model и Controller в парадигме MVC.

Со следующей статьи начнём создание всех необходимых элементов для построения таблиц и управления ими. Так как в языке MQL событийная модель интегрирована в создаваемые объекты при помощи событий чарта, то во всех последующих элементах управления будет организована обработка событий для реализации связи компоненты Viewс компонентой Controller.

Программы, используемые в статье:

#
 Имя Тип
Описание
 1  Base.mqh  Библиотека классов  Классы для создания базового объекта элементов управления
 2  TestControls.mq5  Тестовый скрипт  Скрипт для тестирования работы с классом базового объекта
 3  MQL5.zip  Архив  Архив файлов, представленных выше, для распаковки в каталог MQL5 клиентского терминала

Классы в составе библиотеки Base.mqh:

#
Наименование
 Описание
 1  CBaseObj  Базовый класс для всех графических объектов
 2  CColor  Класс для управления цветом
 3  CColorElement  Класс для управления цветами различных состояний графического элемента
 4  CBound  Класс для управления прямоугольной областью
 5  CCanvasBase  Базовый класс для работы с графическими элементами на холсте
Все созданные файлы прилагаются к статье для самостоятельного изучения. Файл архива можно распаковать в папку терминала, и все файлы будут расположены в нужной папке: MQL5\Scripts\Tables.
Прикрепленные файлы |
Base.mqh (139.67 KB)
TestControls.mq5 (10.86 KB)
MQL5.zip (15.56 KB)
Применение Conditional LSTM и индикатора VAM в автоматической торговле Применение Conditional LSTM и индикатора VAM в автоматической торговле
В настоящей статье рассматривается разработка советника (EA) для автоматической торговли, сочетающего в себе технический анализ с прогнозами с помощью глубокого обучения.
Нейросети в трейдинге: Прогнозирование временных рядов при помощи адаптивного модального разложения (Окончание) Нейросети в трейдинге: Прогнозирование временных рядов при помощи адаптивного модального разложения (Окончание)
В статье рассматривается адаптация и практическая реализация фреймворка ACEFormer средствами MQL5 в контексте алгоритмической торговли. Показаны ключевые архитектурные решения, особенности обучения и результаты тестирования модели на реальных данных.
Самооптимизирующийся советник на языках MQL5 и Python (Часть V): Глубокие марковские модели Самооптимизирующийся советник на языках MQL5 и Python (Часть V): Глубокие марковские модели
Мы применим простую цепь Маркова к индикатору RSI, чтобы наблюдать за поведением цены после того, как индикатор проходит через ключевые уровни. Мы пришли к выводу, что самые сильные сигналы на покупку и продажу по паре NZDJPY генерируются, когда RSI находится в диапазоне 11–20 и 71–80 соответственно. Мы покажем, как можно манипулировать данными, чтобы создавать оптимальные торговые стратегии, основанные непосредственно на имеющихся данных. Кроме того, мы продемонстрируем, как обучить глубокую нейронную сеть оптимальному использованию матрицы перехода.
Скрытые марковские модели в торговых системах на машинном обучении Скрытые марковские модели в торговых системах на машинном обучении
Скрытые марковские модели (СММ) представляют собой мощный класс вероятностных моделей, предназначенных для анализа последовательных данных, где наблюдаемые события зависят от некоторой последовательности ненаблюдаемых (скрытых) состояний, которые формируют марковский процесс. Основные предположения СММ включают марковское свойство для скрытых состояний, означающее, что вероятность перехода в следующее состояние зависит только от текущего состояния, и независимость наблюдений при условии знания текущего скрытого состояния.