English Русский 中文 Deutsch 日本語 Português
preview
El componente View para tablas en el paradigma MQL5 MVC: Elemento gráfico base

El componente View para tablas en el paradigma MQL5 MVC: Elemento gráfico base

MetaTrader 5Ejemplos |
20 0
Artyom Trishkin
Artyom Trishkin

Contenido



Introducción

En la programación moderna, el paradigma MVC (Modelo-Vista-Controlador) es uno de los enfoques más populares para desarrollar aplicaciones complejas. Permite dividir la lógica de la aplicación en tres componentes independientes: Modelo (modelo de datos), Vista (visualización) y Controlador. Este enfoque simplifica el desarrollo, las pruebas y el mantenimiento del código, haciéndolo más estructurado y legible.

En el marco de esta serie de artículos, analizamos el proceso de creación de tablas en el paradigma MVC en MQL5. En los dos primeros artículos, implementamos modelos de datos (Modelo) y la arquitectura base de tablas. Ahora es el momento de pasar al componente View, responsable de la visualización de datos y, en parte, de la interacción con el usuario.

En este artículo, implementaremos un objeto base para dibujar en el lienzo, que será la base para construir todos los componentes visuales de las tablas y cualquier otro control. El objeto incluirá:

  • gestión del color en varios estados (estado estándar, enfoque, toque, bloqueo);
  • soporte para transparencia y cambio de tamaño dinámico;
  • función de recorte de objetos a lo largo de los bordes del contenedor;
  • gestión de la visibilidad y bloqueo de objetos;
  • dividiendo los gráficos en dos capas: fondo y primer plano.

Aquí no consideraremos la integración con el componente Modelo ya creado. Además, con el componente Controller aún no ha sido creado, pero diseñaremos las clases en desarrollo teniendo en cuenta la integración futura. Esto facilitará aún más la vinculación de elementos visuales con datos y lógica de control, garantizando una interacción completa dentro del marco del paradigma MVC. Como resultado, obtenemos una herramienta flexible para crear tablas y otros elementos gráficos para nuestros proyectos.

Dado que la implementación de la arquitectura del componente View en MQL5 consume bastante tiempo e incluye muchas clases auxiliares y herencias, convengamos en un resumen bastante breve. Defina una clase, proporcione una breve descripción de la misma y luego, nuevamente brevemente, considere su implementación. Hoy tenemos cinco clases de este tipo:

  1. una clase base para todos los objetos gráficos,
  2. una clase para la gestión del color,
  3. una clase para gestionar los colores de varios estados de un elemento gráfico,
  4. clase de control de área rectangular,
  5. una clase base para dibujar elementos gráficos en lienzo.

Al final, todas estas clases son necesarias para que la clase base dibuje elementos gráficos. Todas las demás clases que se crearán al implementar varios controles, en particular, el control Tabla, heredarán de él.

Las primeras cuatro clases de esta lista son clases auxiliares para la implementación conveniente de la funcionalidad de la clase base para dibujar elementos gráficos (5), de la cual heredaremos para crear todos los controles y sus componentes.


Clases auxiliares

Si aún no está disponible, en el directorio de terminal \MQL5\Scripts\ cree una nueva carpeta Tables\, y en esa carpeta, la carpeta Controls. Almacenará archivos que se crearán dentro de los marcos de los artículos sobre la creación del componente View para tablas.

En esta nueva carpeta, cree un nuevo archivo de inclusión Base.mqh. Hoy implementaremos los códigos de clase de objeto base para crear controles. Implementación preliminar de sustituciones de macros, enumeraciones y funciones:

//+------------------------------------------------------------------+
//|                                                         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;
  }
//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+

En artículos anteriores implementamos la función que devuelve el tipo de objeto como una cadena string. La función que devuelve el tipo de control como una descripción de cadena es absolutamente idéntica a la implementada anteriormente. La cadena de enumeración simplemente se divide en subcadenas utilizando el separador "_" y las subcadenas resultantes se utilizan para crear la cadena string final.

Mientras estas dos funciones idénticas estén en archivos diferentes, déjalas así. A continuación, al combinar todos los archivos en un solo proyecto, reelaboraremos ambas funciones en una sola, que devolverá el nombre no de la enumeración, sino de la cadena pasada. En este caso, el mismo algoritmo devolverá el nombre del objeto, elemento, etc. de la misma manera. Es importante que las constantes de todas las enumeraciones contengan la misma estructura del nombre de la constante: OBJECT_TYPE_XXX_YYY, ELEMENT_TYPE_XXX_YYY, ANYOTHER_TYPE_XXX_YYY_ZZZ ... En este caso, se devolverá lo que está escrito en XXX_YYY (XXX_YYY_ZZZ, etc.), y lo que está marcado aquí en amarillo se eliminará.

En todos los objetos de elementos gráficos y en las clases auxiliares, cada uno de ellos contiene las mismas variables y métodos para acceder a ellos: el identificador y el nombre del objeto. Y mediante estas propiedades se pueden buscar y ordenar elementos en las listas. Es razonable colocar estas variables y sus métodos de acceso en una clase separada, de la que heredarán todos los demás elementos.

Esta será la clase base de los objetos de elementos gráficos:

//+------------------------------------------------------------------+
//| Базовый класс графических элементов                              |
//+------------------------------------------------------------------+
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);
     }
  }

Breve descripción:

Una clase base para todos los objetos gráficos:

  • contiene propiedades comunes como identificador (m_id) y nombre (m_name),
  • proporciona métodos básicos para comparar objetos (Compare) y recuperar el tipo de objeto (Type),
  • se utiliza como clase padre para todas las demás clases, lo que garantiza la uniformidad de la jerarquía.

La clase proporciona un conjunto mínimo de propiedades inherentes a cada uno de los objetos de elementos gráficos. Heredar de esta clase nos ahorrará tener que declarar estas variables en cada nueva clase e implementar métodos de acceso a ellas para estas variables: estas variables y métodos estarán disponibles en las clases heredadas. En consecuencia, el método Compare proporcionará para cada uno de los objetos heredados del base, una función para buscar y ordenar por estas dos propiedades.



Clases de gestión del color

Al crear el componente «Controlador», es necesario realizar un diseño visual de la interacción del usuario con los elementos gráficos. Una de las formas de mostrar la actividad de un objeto es cambiar su color al pasar el cursor por encima del área del elemento gráfico, su reacción a los clics del ratón o el bloqueo del software.

Cada objeto tiene tres elementos constituyentes que pueden cambiar de color durante varios eventos de interacción del usuario: el color del fondo, el borde y el texto. Cada uno de estos tres elementos puede contener su propio conjunto de colores para diferentes estados. Para controlar cómodamente el color de un elemento, así como para controlar los colores de todos los elementos que cambian de color, implemente dos clases auxiliares.

Clase de color

//+------------------------------------------------------------------+
//| Класс цвета                                                      |
//+------------------------------------------------------------------+
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;
  }

Breve descripción de la clase:

Clase de gestión del color:

  • almacena el color como un valor de tipo color (m_color),
  • proporciona métodos para establecer y recuperar un color (SetColor, Get),
  • implementa métodos para guardar y cargar colores hacia/desde un archivo (Guardar, Cargar),
  • se puede utilizar para representar cualquier color en elementos gráficos.

La clase de color del elemento de objeto gráfico:

//+------------------------------------------------------------------+
//| Класс цветов элемента графического объекта                       |
//+------------------------------------------------------------------+
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;
  }

Breve descripción de la clase:

Una clase para gestionar los colores de varios estados de un elemento gráfico:

  • almacena colores para cuatro estados: normal (m_default), foco (m_focused), presionado (m_pressed) y bloque (m_blocked),
  • admite el color actual (m_current), que se puede configurar en uno de los estados,
  • proporciona métodos para inicializar los colores de todos los estados (InitColors) y cambiar el color actual (SetCurrentAs),
  • incluye un método para obtener un nuevo color a partir del color base, indicando los desplazamientos de los componentes de color (NewColor),
  • implementa métodos para guardar y cargar todos los colores hacia/desde un archivo (Guardar, Cargar),
  • Es útil para crear elementos interactivos como botones, filas de tablas o celdas.



Clase de control de área rectangular

En la terminal, se presenta una estructura CRect interesante en el archivo Rect.mqh en la carpeta \MQL5\Include\Controls\. Proporciona un conjunto de métodos para controlar una ventana rectangular que puede enmarcar un contorno específico en un elemento gráfico. Utilizando estos contornos, puedes resaltar virtualmente varias áreas de un solo elemento y rastrear sus coordenadas y límites. Esto le permitirá rastrear las coordenadas del cursor del mouse en las áreas resaltadas y organizar la interacción del mouse con el área del elemento gráfico.

El ejemplo más simple serían los bordes de todo el elemento gráfico. Otro ejemplo de un área definida puede ser, por ejemplo, el área para la barra de estado, las barras de desplazamiento o el encabezado de la tabla.

Utilizando la estructura presentada, podemos crear un objeto especial que permita establecer un área rectangular en un elemento gráfico. Y una lista de dichos objetos le permitirá almacenar múltiples áreas monitoreadas en un solo elemento gráfico, donde cada una está designada para sus propios propósitos y a las que se accede por el nombre o identificador del área.

Para almacenar estas estructuras en listas de objetos, debemos crear un objeto heredado de CObject. En él se declara esta estructura:

//+------------------------------------------------------------------+
//| Класс прямоугольной области                                      |
//+------------------------------------------------------------------+
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;
  }

Breve descripción de la clase:

Clase de control de área rectangular:

  • almacena los límites de un área como una estructura CRect (m_bound),
  • proporciona métodos para cambiar el tamaño (Resize, ResizeW, ResizeH), establecer coordenadas (SetX, SetY, SetXY) y desplazar el área (Move, Shift),
  • permite recuperar coordenadas y dimensiones del área (X, Y, Ancho, Alto, Derecha, Abajo),
  • implementa métodos para guardar y cargar el área hacia/desde un archivo (Guardar, Cargar),
  • se utiliza para definir límites de elementos gráficos o áreas rectangulares dentro de ellos.

Usando las clases auxiliares descritas anteriormente, podemos comenzar a crear una clase base para todos los elementos gráficos.



Clase base de dibujo

La clase será bastante voluminosa, así que antes de empezar a crearla, y para una mejor comprensión, lee su breve descripción:

Una clase base para manipular elementos gráficos en el lienzo:

  • contiene dos lienzos: para el fondo (m_background) y el primer plano (m_foreground),
  • almacena los límites de los elementos (m_bound), así como información sobre el contenedor (m_container),
  • admite la gestión del color a través de objetos CColorElement para el fondo, el primer plano y el borde.
  • implementa métodos para administrar la visibilidad (Ocultar, Mostrar), bloquear (Bloquear, Desbloquear) y recortar a lo largo de los límites del contenedor (ObjectTrim),
  • admite cambio de tamaño dinámico y cambio de coordenadas (ObjectResize, ObjectSetX, ObjectSetY),
  • proporciona métodos para dibujar (Draw), actualizar (Update) y borrar (Clear) un elemento gráfico,
  • implementa métodos para guardar y cargar un objeto hacia/desde un archivo (Guardar, Cargar),
  • es la base para crear elementos gráficos complejos, como celdas de tabla, filas y encabezados.

La clase base del lienzo de elementos gráficos

//+------------------------------------------------------------------+
//| Базовый класс холста графических элементов                       |
//+------------------------------------------------------------------+
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);
  };

Una clase es una lista de propiedades de un elemento gráfico, una lista de objetos de las clases discutidas anteriormente y una lista de métodos para acceder a variables y métodos de clases auxiliares.

Como resultado, obtenemos un objeto bastante flexible que nos permite controlar las propiedades y la apariencia de un elemento gráfico. Este es un objeto que proporciona a todos sus descendientes una funcionalidad básica implementada en el objeto y extensible en clases heredadas.

Consideremos los métodos de clase.

Constructor paramétrico

//+------------------------------------------------------------------+
//| 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");
     }
  }

Las propiedades iniciales del objeto que se está creando se pasan al constructor, se crean los recursos gráficos y los objetos para dibujar el fondo y el primer plano, se establecen los valores de las coordenadas y los nombres de los objetos gráficos, y se establecen los parámetros de fuente para mostrar textos en primer plano.

En el destructor de clase, el objeto se destruye:

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

Un método que crea objetos gráficos para el fondo y el primer plano.

//+------------------------------------------------------------------+
//| 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;
  }

Mediante los métodos de creación de objetos gráficos OBJ_BITMAP_LABEL de la clase CCanvas, se crean objetos para dibujar el fondo y el primer plano, y se establecen las coordenadas y dimensiones de todo el objeto. Cabe señalar que el nombre del programa se inserta en las propiedades de los objetos gráficos OBJPROP_TEXT creados. Esto permite identificar qué objetos gráficos pertenecen a un programa particular sin especificar su nombre en los nombres de los objetos gráficos.

Un método que establece el puntero al objeto contenedor principal

//+------------------------------------------------------------------+
//| 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();
  }

Cada elemento gráfico puede ser parte de su elemento padre. Por ejemplo, los botones, las listas desplegables y cualquier otro control se pueden ubicar en el panel. Para que los objetos subordinados se recorten a lo largo de los límites de su elemento padre, se les debe pasar un puntero a este elemento padre.

Cualquier elemento padre tiene métodos que devuelven sus coordenadas y límites. Es a lo largo de estos límites que se recortan los elementos secundarios si por algún motivo exceden los límites del contenedor. Este motivo puede ser, por ejemplo, el desplazamiento por el contenido de una tabla grande.

Un método que recorta un objeto gráfico a lo largo del contorno del contenedor.

//+------------------------------------------------------------------+
//| 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);
     }
  }

Este es un método virtual, lo que significa que puede redefinirse en clases heredadas. La lógica del método se explica en detalle en los comentarios del código. Cualquier elemento gráfico, con alguna de sus transformaciones (movimiento, redimensionamiento, etc.), siempre está controlado para ir más allá de su contenedor. Si un objeto no tiene un contenedor, no se recorta.

Un método que establece la coordenada X de un objeto gráfico:

//+------------------------------------------------------------------+
//| 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;
  }

Solo cuando las coordenadas se establecen correctamente en dos objetos gráficos (el lienzo de fondo y el lienzo de primer plano), el método devuelve true..

Un método que establece la coordenada Y de un objeto gráfico:

//+------------------------------------------------------------------+
//| 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;
  }

El método es idéntico al comentado anteriormente.

Un método que cambia el ancho de un objeto gráfico:

//+------------------------------------------------------------------+
//| 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);
  }

Solo se acepta para su manejo el ancho con un valor mayor a cero. El método devuelve true solo si se ha modificado correctamente el ancho del lienzo de fondo y del lienzo de primer plano.

Un método que cambia la altura de un objeto gráfico:

//+------------------------------------------------------------------+
//| 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);
  }

El método es idéntico al comentado anteriormente.

Un método que cambia el tamaño de un objeto gráfico:

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

Aquí, los métodos para cambiar el ancho y la altura se llaman alternativamente. Devuelve true solo si tanto el ancho como el alto se han modificado correctamente.

Un método que establece nuevas coordenadas X e Y para un objeto:

//+------------------------------------------------------------------+
//| 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;
  }

En primer lugar, los objetos gráficos del fondo y del primer plano se desplazan a las coordenadas especificadas. Si se establecen correctamente nuevas coordenadas para los objetos gráficos, las mismas coordenadas se establecen para el objeto mismo. A continuación, se comprueba que el objeto no haya sobrepasado los límites del contenedor y se devuelve true.

Método que establece una nueva coordenada X para un objeto:

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

Un método auxiliar que establece solo la coordenada horizontal. La coordenada vertical permanece actual.

Un método que establece una nueva coordenada Y para un objeto:

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

Un método auxiliar que establece solo la coordenada vertical. La coordenada horizontal permanece actual.

Un método que desplaza un objeto a lo largo de los ejes X e Y mediante un desplazamiento especificado:

//+------------------------------------------------------------------+
//| 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;
  }

A diferencia del método Move, que establece las coordenadas de pantalla de un objeto, el método Shift especifica un desplazamiento local por la cantidad de píxeles relativos a las coordenadas de pantalla del objeto. En primer lugar, se desplazan los objetos gráficos del fondo y del primer plano. Y luego los mismos desplazamientos se establecen en el objeto mismo. A continuación, se comprueba si el objeto sobrepasa los límites del contenedor y se devuelve true.

Un método que desplaza un objeto a lo largo del eje X mediante un desplazamiento especificado:

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

Un método auxiliar que mueve el objeto únicamente horizontalmente. La coordenada vertical permanece actual.

Un método que desplaza un objeto a lo largo del eje Y mediante un desplazamiento especificado:

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

Un método auxiliar que mueve el objeto únicamente verticalmente. La coordenada horizontal permanece actual.

Un método que oculta un objeto en todos los períodos del gráfico:

//+------------------------------------------------------------------+
//| 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);
  }

Para ocultar un objeto gráfico en el gráfico, establezca el valor OBJ_NO_PERIODS para su propiedad OBJPROP_TIMEFRAMES. Si la propiedad se ha configurado correctamente para el objeto de fondo y el objeto de primer plano (en cola para eventos de gráfico), configure el indicador de objeto oculto y, si se especifica, vuelva a dibujar el gráfico.

Un método que muestra un objeto en todos los períodos del gráfico.

//+------------------------------------------------------------------+
//| 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);
  }

Para mostrar un objeto gráfico en el gráfico, establezca el valor OBJ_ALL_PERIODS para su propiedad OBJPROP_TIMEFRAMES. Si la propiedad se ha configurado correctamente para el objeto de fondo y el objeto de primer plano (en cola para eventos de gráfico), elimine la marca de objeto oculto y, si se especificó, vuelva a dibujar el gráfico.

Un método que pone un objeto en primer plano:

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

Para colocar un objeto gráfico en el gráfico por encima de todos los demás, es necesario ocultar secuencialmente el objeto y mostrarlo inmediatamente, que es lo que hace este método.

Métodos para la gestión del color de un objeto en sus distintos estados:

//+------------------------------------------------------------------+
//| 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;
  }

El elemento gráfico consta de tres partes, cuyos colores se establecen por separado:

  • color de fondo, 
  • color del texto,
  • color del borde.

Estos tres elementos pueden cambiar su color dependiendo del estado del elemento. Estos estados pueden ser:

  • Un estado normal del elemento gráfico,
  • Al pasar el cursor sobre el elemento (foco),
  • Al hacer clic en el elemento (clic),
  • El elemento está bloqueado.

El color de cada elemento individual (fondo, texto, borde) se puede configurar y ajustar por separado. Pero normalmente estos tres componentes cambian sus colores sincrónicamente, dependiendo del estado del elemento al interactuar con el usuario.

Los métodos analizados anteriormente le permiten establecer simultáneamente los colores de un objeto para sus distintos estados, para tres elementos.

Un método que bloquea el elemento:

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

Cuando un elemento está bloqueado, se establecen los colores del estado bloqueado, el objeto se vuelve a dibujar para mostrar nuevos colores y se establece el indicador de bloqueo.

Un método que desbloquea el elemento:

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

Cuando se desbloquea un elemento, se establecen los colores del estado normal, el objeto se redibuja para mostrar nuevos colores y se elimina la bandera de bloqueo.

Un método que rellena un objeto con el color especificado:

//+------------------------------------------------------------------+
//| 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);
  }

A veces es necesario rellenar completamente todo el fondo de un objeto con algún color. Este método rellena el fondo del objeto con un color sin afectar el primer plano. La transparencia se utiliza para rellenar, lo cual está preestablecido en la variable de clase m_alpha. Además, el lienzo se actualiza para corregir los cambios con el indicador de rediseño del gráfico.

Si se levanta la bandera, los cambios se mostrarán inmediatamente después de que se actualice el lienzo. Si se restablece la bandera de actualización del gráfico, la apariencia del objeto se actualizará con una nueva marca o con cualquier próxima llamada del comando de actualización del gráfico. Esto es necesario si se repintan varios objetos al mismo tiempo. La bandera de redibujo debe levantarse solo para el último objeto que se va a repintar.

Esta lógica se aplica generalmente a todos los casos de cambio de objetos gráficos: ya sea un único elemento que se actualiza inmediatamente después de cambiarse o un procesamiento por lotes de múltiples elementos, donde volver a dibujar el gráfico es necesario solo después de cambiar el último elemento gráfico.

Un método que rellena un objeto con el color transparente.

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

El lienzo de fondo y el lienzo de primer plano se rellenan con un color transparente, y ambos objetos se actualizan al señalar el indicador de redibujado del gráfico.

Un método que actualiza un objeto para mostrar los cambios:

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

El lienzo de fondo se actualiza sin volver a dibujar el gráfico y, al actualizar el lienzo de primer plano, se utiliza el valor especificado del indicador de volver a dibujar el gráfico. Esto permite actualizar ambos objetos CCanvas a la vez, mientras se controla el redibujo del gráfico para múltiples objetos especificando el indicador de redibujo para el método.

Un método que dibuja la apariencia:

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

Este es un método virtual. Su implementación debe realizarse en clases heredadas. Este método no hace nada aquí ya que no es necesario realizar ninguna representación en el gráfico para el objeto base: es solo un objeto base sobre el cual se implementan los controles de base.

Un método que destruye el objeto:

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

Ambos lienzos se destruyen utilizando los métodos Destroy de la clase CCanvas.

Un método que devuelve la descripción de un objeto:

//+------------------------------------------------------------------+
//| 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);
  }

Devuelve la descripción del objeto con la descripción, identificador, nombres de los objetos gráficos de fondo y primer plano establecidos para él, y especificando las coordenadas y dimensiones del objeto en el siguiente formato:

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

Un método que imprime la descripción del objeto en el registro:

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

Imprime la descripción del objeto que devuelve el método Description en el registro.

Los métodos para guardar un elemento gráfico en un archivo y cargarlo desde un archivo aún no se han implementado, solo se ha creado un espacio en blanco para ellos:

//+------------------------------------------------------------------+
//| 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;
  }

Dado que esta es la primera versión del objeto base y es probable que se desarrolle más, aún no se han implementado los métodos de trabajo con archivos. Al refinar esta clase, agregar nuevas propiedades y optimizar las existentes, tendrá que realizar cambios en los métodos de trabajo con archivos al mismo tiempo. Para no realizar trabajo innecesario, deje la implementación de los métodos Guardar y Cargar hasta que el trabajo en el objeto base de elementos gráficos esté completamente completado.

Ahora estamos listos para la prueba.



Probando el resultado

Para probar cómo se ejecuta la clase, cree dos objetos, uno encima del otro. El primer objeto será un contenedor para el segundo. Y el segundo objeto se desplazará programáticamente dentro del elemento padre en todas las direcciones posibles. Esto nos permitirá comprender el funcionamiento correcto de los métodos para desplazar, redimensionar elementos y recortar el elemento secundario al tamaño del contenedor. Antes de completar el trabajo, establezca la bandera del elemento bloqueado en el segundo objeto para verificar cómo funciona.

Pero hay un “pero”, de todos modos. El método Draw no hace nada en el objeto base, y simplemente no veremos el funcionamiento de la clase, ya que los objetos desarrollados serán completamente transparentes.

Hagámoslo así: simplemente rellenemos el primer objeto con color y dibujemos un borde. Como no se mueve a ninguna parte ni cambia de tamaño, tampoco es necesario volver a dibujarlo. Es suficiente dibujar algo sobre el objeto una vez creado. Al mismo tiempo, para el segundo objeto, su apariencia debe actualizarse constantemente hasta que el método ObjectTrim() llame al método de redibujado del objeto. Pero en esta clase, este método no hace nada. Por lo tanto, modifique temporalmente el método Draw para que se dibuje algo en el objeto:

//+------------------------------------------------------------------+
//| 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);
  }

Aquí, para el lienzo de fondo, rellene el fondo con el color de fondo establecido y dibuje el borde con el color de borde establecido.

Para el lienzo en primer plano, límpielo y muestre dos textos, uno debajo del otro, en el color de texto establecido:

  1. con el ancho/alto del objeto y entre paréntesis con el ancho/alto de los objetos gráficos del fondo y del primer plano;
  2. con las coordenadas X/Y del objeto y entre paréntesis con las coordenadas X/Y de los objetos gráficos del fondo y del primer plano.

Después de la prueba, elimine este código del método.

En la carpeta \MQL5\Scripts\Tables\ cree el archivo de script de prueba 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);
     }
  }
//+------------------------------------------------------------------+

El código del script está comentado en detalle. Toda su lógica es fácil de entender a partir de los comentarios proporcionados.

Compila y ejecuta el script en el gráfico:

El objeto secundario se recorta correctamente a lo largo de los bordes del área contenedora (no a lo largo de los bordes del objeto, sino con un desplazamiento del ancho del borde); al bloquear el objeto, se vuelve a dibujar con los colores del elemento bloqueado.

Los pequeños "tirones" al mover un objeto anidado en el contenedor son causados por defectos en la grabación de una imagen GIF, no por retrasos en el funcionamiento de los métodos de clase.

Después de ejecutar el script, se imprimirán en el registro dos líneas que describen los dos objetos creados:

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


Conclusión

Hoy hemos sentado las bases para la creación de cualquier elemento gráfico donde cada clase realiza una tarea claramente definida, lo que hace que la arquitectura sea modular y fácilmente extensible.

Las clases implementadas establecen una base sólida para crear elementos gráficos complejos e integrarlos en los componentes del modelo y del controlador en el paradigma MVC.

En el próximo artículo comenzaremos a crear todos los elementos necesarios para construir y gestionar tablas. Dado que en el lenguaje MQL el modelo de eventos se integra en los objetos creados mediante eventos de gráfico, el manejo de eventos se organizará en todos los controles posteriores para implementar la conexión entre el componente View y el componente Controller.

Programas utilizados en el artículo:

#
 Nombre Tipo
Descripción
 1  Base.mqh  Biblioteca de clases  Clases para crear un objeto base de controles.
 2  TestControls.mq5  Script de prueba  Script para probar manipulaciones con la clase de objeto base.
 3  MQL5.zip  Archivo  Un archivo de los archivos presentados anteriormente para descomprimirlos en el directorio MQL5 de la terminal del cliente.

Clases dentro de la biblioteca Base.mqh:

#
Nombre
 Descripción
 1  CBaseObj  Una clase base para todos los objetos gráficos.
 2  CColor  Clase de gestión del color.
 3  CColorElement  Una clase para gestionar los colores de varios estados de un elemento gráfico.
 4  CBound  Clase de control de área rectangular.
 5  CCanvasBase  Una clase base para manipular elementos gráficos en el lienzo.
Todos los archivos creados se adjuntan al artículo para autoaprendizaje. El archivo comprimido se puede descomprimir en la carpeta del terminal y todos los archivos se ubicarán en la carpeta: MQL5\Scripts\Tables.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/17960

Archivos adjuntos |
Base.mqh (139.67 KB)
TestControls.mq5 (10.86 KB)
MQL5.zip (15.56 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Simulación de mercado (Parte 22): Iniciando el SQL (V) Simulación de mercado (Parte 22): Iniciando el SQL (V)
Antes de que tires la toalla y decidas abandonar el estudio sobre cómo usar SQL, déjame recordarte, mi querido lector, que aquí todavía estamos usando solo lo más básico de lo básico. Aún no hemos explorado algunas cosas que es posible hacer en SQL. En cuanto las exploremos, verás que SQL es mucho más práctico de lo que parece. Aunque, muy probablemente, yo termine cambiando la dirección de lo que estamos creando. Esto se debe a que el proceso de creación es dinámico. Voy a mostrar un poco más sobre cómo hacer las cosas en SQL. Esto se debe a que, de hecho, es algo que necesitas entender y conocer. Simplemente pensar que eres más capaz que toda una comunidad de programadores y desarrolladores solo te hará perder tiempo y oportunidades. Ten calma, porque esto se va a volver aún más interesante.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Redes neuronales en el trading: Pronóstico de series temporales con descomposición modal adaptativa (ACEFormer) Redes neuronales en el trading: Pronóstico de series temporales con descomposición modal adaptativa (ACEFormer)
Lo invitamos a explorar la arquitectura ACEFormer, una solución moderna que combina la efectividad de la atención probabilística con la descomposición adaptativa de series temporales. Este material resultará útil para quienes buscan un equilibrio entre el rendimiento computacional y la precisión de los pronósticos en los mercados financieros.