English Русский Deutsch 日本語 Português
preview
El componente View para tablas en el paradigma MQL5 MVC: Contenedores

El componente View para tablas en el paradigma MQL5 MVC: Contenedores

MetaTrader 5Ejemplos |
218 2
Artyom Trishkin
Artyom Trishkin

Contenido


Introducción

En las interfaces de usuario modernas, a menudo es necesario mostrar de forma compacta y cómoda grandes cantidades de datos diversos. Para estos fines, se utilizan controles especiales denominados contenedores con soporte para contenido desplazable. Este enfoque permite colocar tablas y otros elementos gráficos en un espacio limitado de la ventana, lo que proporciona al usuario un acceso rápido e intuitivo a la información.

Como parte del desarrollo del control TableView en el paradigma MVC (Modelo-Vista-Controlador), ya hemos creado el componente Modelo, un modelo de tabla, y hemos comenzado a crear los componentes Vista y Controlador. En el último artículo, se crearon controles sencillos pero bastante funcionales. Los controles complejos se montarán a partir de dichos elementos. Hoy escribiremos clases de control como Panel, GroupBox y Container; los tres elementos son contenedores para colocar diversos controles en ellos.

  • El control Panel es un panel que permite colocar cualquier número de otros controles en él. Al mover el panel a nuevas coordenadas, todos los controles situados en él también se mueven junto con el panel. Por lo tanto, el panel es un contenedor para los controles que se encuentran en él. Sin embargo, este elemento no tiene barras de desplazamiento que permitan desplazarse por el contenido del contenedor si este sobrepasa los límites del panel. Dicho contenido simplemente se recorta a los límites del contenedor.
  • El control GroupBox es un conjunto de elementos organizados en un grupo. Se hereda del panel y permite agrupar elementos según un propósito común, por ejemplo, un grupo de elementos RadioButton, en el que solo se puede seleccionar un elemento de todo el grupo, y el resto de los elementos del grupo quedan deseleccionados.
  • El control Container permite adjuntar solo un control a uno mismo. Si el elemento adjunto se extiende más allá del contenedor, aparecerán barras de desplazamiento en el contenedor. Permiten desplazarse por el contenido del contenedor. Para colocar cualquier número de controles en un contenedor, es necesario colocar un panel en él y adjuntar el número requerido de controles al panel. De este modo, el contenedor se desplazará por el panel y este último cambiará su contenido tras el desplazamiento.

Por lo tanto, además de los tres controles principales especificados, tenemos que crear clases para crear barras de desplazamiento: la clase Thumb y la clase ScrollBar. Habrá dos clases de este tipo: para barras de desplazamiento verticales y horizontales.

Si observas detenidamente el funcionamiento de los botones de desplazamiento situados en los extremos de las barras de desplazamiento, verás que al mantener pulsado el botón durante un tiempo prolongado, se activa el desplazamiento automático. Es decir, el botón comienza a enviar eventos de clic automáticamente. Para este comportamiento, crearemos dos clases auxiliares más: la clase del contador de retraso y la clase de repetición automática del evento real.

La clase del contador de retrasos se puede utilizar para organizar la espera sin congelar la ejecución del programa, y la clase de repetición automática de eventos se implementará de modo que podamos especificar qué evento debe enviar. Esto permitirá utilizarlo no solo para organizar la repetición automática de clics en botones, sino también para cualquier otro algoritmo que requiera repetir eventos después de un tiempo determinado y con una frecuencia determinada.

En la etapa final, las tablas se colocarán dentro de un contenedor universal, que permitirá desplazarse mediante barras de desplazamiento. Este contenedor se convertirá en la base para crear interfaces complejas y flexibles, lo que permitirá no solo trabajar con tablas, sino también utilizarlo en otros componentes, por ejemplo, al crear un bloc de notas de varias páginas u otros elementos de usuario para la terminal de cliente MetaTrader 5.

Vale la pena destacar cómo funcionan los controles. Cada elemento de control está equipado con una funcionalidad basada en eventos (componente controlador) y reacciona adecuadamente a la interacción con el cursor del ratón.

Con ciertas acciones, el elemento con el que se está interactuando envía un evento al gráfico. Ese evento debe ser recibido y gestionado por otro control. Pero todos los elementos reciben este tipo de eventos. Es necesario determinar qué elemento está activo (sobre el que se encuentra actualmente el cursor) y procesar solo los mensajes procedentes de ese elemento. Es decir, cuando se pasa el cursor del ratón por encima de un control, este debe marcarse como activo, mientras que el resto (los elementos inactivos) no deben tenerse en cuenta.

Para organizar la selección de un elemento activo, es necesario asegurarse de que cada control tenga acceso a dicha información. Hay diferentes maneras de hacerlo. Por ejemplo, puede crear una lista en la que se registren los nombres de todos los elementos creados, buscar en ella una coincidencia con el nombre del objeto sobre el que se encuentra actualmente el cursor y trabajar con el objeto encontrado en esta lista.

Este enfoque es posible, pero complica el código y el trabajo con él. Es más fácil crear un único objeto que sea accesible globalmente en el programa y registrar en él el nombre del control activo. Los elementos restantes verán inmediatamente el nombre del elemento activo y decidirán si gestionar los mensajes entrantes o no, sin necesidad de realizar búsquedas adicionales en ninguna base de datos.

Una clase Singleton puede ser una clase pública:

Una clase Singleton es un patrón de diseño que garantiza la existencia de una única instancia de una clase determinada durante el tiempo de ejecución del programa y proporciona un punto de acceso global a dicha instancia.

El propósito de Singleton

Singleton se utiliza cuando es necesario que un objeto tenga una sola instancia y que esta instancia sea accesible desde cualquier parte del programa. Ejemplos: gestor de configuraciones, registrador, grupo de conexiones de bases de datos, gestor de recursos, etc. 

Cómo funciona Singleton

  1. Un constructor oculto: El constructor de la clase se declara privado o protegido para impedir la creación de instancias desde el exterior.
  2. Variable estática: Una variable estática se crea dentro de la clase y almacena una única instancia de la clase.
  3. Método de acceso estático: Para obtener una instancia de una clase, se utiliza un método estático (por ejemplo, Instance() o getInstance() ), que crea un objeto en el primer acceso y lo devuelve en las llamadas posteriores.

Singleton es una clase que solo se puede crear una vez, y esa única instancia es accesible globalmente. Esto resulta útil para gestionar recursos compartidos o el estado de las aplicaciones.

Implementemos dicha clase.


Clase Singleton como gestor de datos compartidos

En el último artículo, todos los códigos de la biblioteca se encontraban en \MQL5\Indicators\Tables\Controls\. Aquí nos interesan ambos archivos: Base.mqh y Control.mqh. Hoy los perfeccionaremos.

Abre el archivo Base.mqh y escribe el siguiente código en el bloque de clase:

//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс-синглтон для общих флагов и событий графических элементов  |
//+------------------------------------------------------------------+
class CCommonManager
  {
private:
   static CCommonManager *m_instance;                          // Экземпляр класса
   string            m_element_name;                           // Имя активного элемента
   
//--- Конструктор/деструктор
                     CCommonManager(void) : m_element_name("") {}
                    ~CCommonManager() {}
public:
//--- Метод для получения экземпляра Singleton
   static CCommonManager *GetInstance(void)
                       {
                        if(m_instance==NULL)
                           m_instance=new CCommonManager();
                        return m_instance;
                       }
//--- Метод для уничтожения экземпляра Singleton
   static void       DestroyInstance(void)
                       {
                        if(m_instance!=NULL)
                          {
                           delete m_instance;
                           m_instance=NULL;
                          }
                       }
//--- (1) Устанавливает, (2) возвращает имя активного текущего элемента
   void              SetElementName(const string name)         { this.m_element_name=name;   }
   string            ElementName(void)                   const { return this.m_element_name; }
  };
//--- Инициализация статической переменной экземпляра класса
CCommonManager* CCommonManager::m_instance=NULL;
//+------------------------------------------------------------------+
//| Базовый класс графических элементов                              |
//+------------------------------------------------------------------+

Un constructor de clase privado y un método estático para acceder a una instancia de la clase garantizan que solo exista una instancia en la aplicación.

  • Un método static CCommonManager* GetInstance(void) — devuelve un puntero a una única instancia de la clase, creándola la primera vez que se accede a ella.
  • Un método static void DestroyInstance(void) — destruye una instancia de la clase y libera memoria.
  • Un método void SetElementName(const string nombre) — establece el nombre del elemento gráfico activo.
  • Un método string ElementName(void) const — devuelve el nombre del elemento gráfico activo.

Ahora cada uno de los elementos gráficos puede acceder a una instancia de esta clase para leer y escribir el nombre del elemento activo. Todos los elementos comparten la misma variable Esto garantiza que cada uno de los múltiples objetos lea y escriba datos en la misma variable. 
Dado que no podemos tener más de un control activo al mismo tiempo, esta implementación del gestor de elementos activos es más que suficiente sin la funcionalidad de control de acceso (para que dos o más elementos no puedan escribir sus datos en una variable).

Más adelante, se pueden añadir otros datos a esta clase de gestor de datos, por ejemplo, indicadores de permiso para el cuadro de trabajo. En este momento, cada uno de los elementos gráficos se está creando tratando de recordar los estados de los indicadores del gráfico. Estos datos también se pueden transferir a esta clase en las variables adecuadas.


Clases para organizar la repetición automática de clics de botones

Anteriormente, hablamos sobre la creación de una función de repetición automática para enviar eventos desde los botones de la barra de desplazamiento al mantener pulsado el botón durante un tiempo prolongado. Este comportamiento es habitual en la mayoría de las aplicaciones del sistema operativo. Por lo tanto, creo que no hay razón para no hacer lo mismo aquí. Al mantener pulsado el botón, se inicia primero el contador de tiempo de pulsación del botón (normalmente, este periodo es de 350-500 ms). Además, si el botón no se ha soltado antes de que expire el tiempo de espera, se inicia el segundo contador: el contador del intervalo de envío de eventos de pulsación del botón. Y dichos eventos se envían con una frecuencia de aproximadamente 100 ms hasta que se suelta el botón.

Para implementar este comportamiento, implemente dos clases auxiliares: la clase de temporizador de milisegundos y la clase de envío automático de eventos.

Continúa escribiendo el código en el mismo archivo Base.mqh:

//+------------------------------------------------------------------+
//| Класс счётчика миллисекунд                                       |
//+------------------------------------------------------------------+
class CCounter : public CBaseObj
  {
private:
   bool              m_launched;                               // Флаг запущенного отсчёта
//--- Запускает отсчёт
   void              Run(const uint delay)
                       {
                        //--- Если отсчёт уже запущен - уходим
                        if(this.m_launched)
                           return;
                        //--- Если передано ненулевое значение задержки - устанавливаем новое значение
                        if(delay!=0)
                           this.m_delay=delay;
                        //--- Запоминаем время запуска и устанавливаем флаг, что отсчёт уже запущен
                        this.m_start=::GetTickCount64();
                        this.m_launched=true;
                       }
protected:
   ulong             m_start;                                  // Время начала отсчёта
   uint              m_delay;                                  // Задержка

public:
//--- (1) Устанавливает задержку, запускает отсчёт с (2) установленной, (3) указанной задержкой
   void              SetDelay(const uint delay)                { this.m_delay=delay;            }
   void              Start(void)                               { this.Run(0);                   }
   void              Start(const uint delay)                   { this.Run(delay);               }
//--- Возвращает флаг окончания отсчёта
   bool              IsDone(void)
                       {
                        //--- Если отсчёт не запущен - возвращаем false
                        if(!this.m_launched)
                           return false;
                        //--- Если прошло миллисекунд больше, чем время ожидания
                        if(::GetTickCount64()-this.m_start>this.m_delay)
                          {
                           //--- сбрасываем флаг запущенного отсчёта и возвращаем true
                           this.m_launched=false;
                           return true;
                          }
                        //--- Заданное время ещё не прошло
                        return false;
                       }
   
//--- Виртуальные методы (1) сохранения в файл, (2) загрузки из файла, (3) тип объекта
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COUNTER);  }
   
//--- Конструктор/деструктор
                     CCounter(void) : m_start(0), m_delay(0), m_launched(false) {}
                    ~CCounter(void) {}
  };
//+------------------------------------------------------------------+
//| CCounter::Сохранение в файл                                      |
//+------------------------------------------------------------------+
bool CCounter::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CBaseObj::Save(file_handle))
      return false;
      
//--- Сохраняем значение задержки
   if(::FileWriteInteger(file_handle,this.m_delay,INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CCounter::Загрузка из файла                                      |
//+------------------------------------------------------------------+
bool CCounter::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CBaseObj::Load(file_handle))
      return false;
      
//--- Загружаем значение задержки
   this.m_delay=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Всё успешно
   return true;
  }

La clase de temporizador de milisegundos está diseñada para realizar un seguimiento de la expiración de un intervalo de tiempo específico (retardo) en milisegundos. Se hereda de la clase base CBaseObj y se puede utilizar para implementar temporizadores, retrasos y control de tiempo para diversas operaciones en aplicaciones MQL5.

  • Un método void SetDelay(const uint delay) establece el valor de retardo (en milisegundos).
  • Un método void Start(const uint delay) inicia la cuenta atrás con un nuevo retraso.
  • Un método bool IsDone(void) devuelve true si la cuenta atrás ha finalizado, y false en caso contrario.
  • Un método virtual bool Save(const int file_handle) es un método virtual para guardar un estado en un archivo.
  • Un método virtual bool Load(const int file_handle) es un método virtual para descargar un estado desde un archivo.
  • Un método virtual int Tipo(void) const devuelve el tipo de objeto que se va a identificar en el sistema.

Basándote en esta clase, crea una clase de repetición automática de eventos:

//+------------------------------------------------------------------+
//| Класс автоповтора событий                                        |
//+------------------------------------------------------------------+
class CAutoRepeat : public CBaseObj
  {
private:
   CCounter          m_delay_counter;                          // Счетчик для задержки перед автоповтором
   CCounter          m_repeat_counter;                         // Счетчик для периодической отправки событий
   long              m_chart_id;                               // График для отправки пользовательского события
   bool              m_button_pressed;                         // Флаг, указывающий нажата ли кнопка
   bool              m_auto_repeat_started;                    // Флаг, указывающий начался ли автоповтор
   uint              m_delay_before_repeat;                    // Задержка перед началом автоповтора (мс)
   uint              m_repeat_interval;                        // Периодичность отправки событий (мс)
   ushort            m_event_id;                               // Идентификатор пользовательского события
   long              m_event_lparam;                           // long-параметр пользовательского события
   double            m_event_dparam;                           // double-параметр пользовательского события
   string            m_event_sparam;                           // string-параметр пользовательского события

//--- Отправка пользовательского события
   void              SendEvent() { ::EventChartCustom((this.m_chart_id<=0 ? ::ChartID() : this.m_chart_id), this.m_event_id, this.m_event_lparam, this.m_event_dparam, this.m_event_sparam); }
public:
//--- Тип объекта
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_AUTOREPEAT_CONTROL);   }
                       
//--- Конструкторы
                     CAutoRepeat(void) : 
                        m_button_pressed(false), m_auto_repeat_started(false), m_delay_before_repeat(350), m_repeat_interval(100),
                        m_event_id(0), m_event_lparam(0), m_event_dparam(0), m_event_sparam(""), m_chart_id(::ChartID()) {}
                     
                     CAutoRepeat(long chart_id, int delay_before_repeat=350, int repeat_interval=100, ushort event_id=0, long event_lparam=0, double event_dparam=0, string event_sparam="") :
                        m_button_pressed(false), m_auto_repeat_started(false), m_delay_before_repeat(delay_before_repeat), m_repeat_interval(repeat_interval),
                        m_event_id(event_id), m_event_lparam(event_lparam), m_event_dparam(event_dparam), m_event_sparam(event_sparam), m_chart_id(chart_id) {}

//--- Установка идентификатора графика
   void              SetChartID(const long chart_id)              { this.m_chart_id=chart_id;         }
   void              SetDelay(const uint delay)                   { this.m_delay_before_repeat=delay; }
   void              SetInterval(const uint interval)             { this.m_repeat_interval=interval;  }

//--- Установка идентификатора и параметров пользовательского события
   void              SetEvent(ushort event_id, long event_lparam, double event_dparam, string event_sparam)
                       {
                        this.m_event_id=event_id;
                        this.m_event_lparam=event_lparam;
                        this.m_event_dparam=event_dparam;
                        this.m_event_sparam=event_sparam;
                       }

//--- Возврат флагов
   bool              ButtonPressedFlag(void)                const { return this.m_button_pressed;     }
   bool              AutorepeatStartedFlag(void)            const { return this.m_auto_repeat_started;}
   uint              Delay(void)                            const { return this.m_delay_before_repeat;}
   uint              Interval(void)                         const { return this.m_repeat_interval;    }

//--- Обработка нажатия кнопки (запуск автоповтора)
   void              OnButtonPress(void)
                       {
                        if(this.m_button_pressed)
                           return;
                        this.m_button_pressed=true;
                        this.m_auto_repeat_started=false;
                        this.m_delay_counter.Start(this.m_delay_before_repeat);  // Запускаем счётчик ожидания
                       }

//--- Обработка отпускания кнопки (остановка автоповтора)
   void              OnButtonRelease(void)
                       {
                        this.m_button_pressed=false;
                        this.m_auto_repeat_started=false;
                       }

//--- Метод выполнения автоповтора (запускается в таймере)
   void              Process(void)
                       {
                        //--- Если кнопка удерживается
                        if(this.m_button_pressed)
                          {
                           //--- Проверяем, истекла ли задержка перед началом автоповтора
                           if(!this.m_auto_repeat_started && this.m_delay_counter.IsDone())
                             {
                              this.m_auto_repeat_started=true;
                              this.m_repeat_counter.Start(this.m_repeat_interval); // Запускаем счетчик автоповтора
                             }
                           //--- Если автоповтор начался, проверяем периодичность отправки событий
                           if(this.m_auto_repeat_started && this.m_repeat_counter.IsDone())
                             {
                              //--- Отправляем событие и перезапускаем счетчик
                              this.SendEvent();
                              this.m_repeat_counter.Start(this.m_repeat_interval);
                             }
                          }
                       }
  };

Esta clase te permite enviar eventos de usuario automáticamente con una frecuencia determinada mientras se mantiene pulsado el botón. Esto garantiza un comportamiento intuitivo de la interfaz (como en las barras de desplazamiento estándar del sistema operativo).

  • El método OnButtonPress() se invoca cuando se pulsa el botón; inicia el recuento del retraso antes de la repetición automática.
  • El método OnButtonRelease() se invoca cuando se suelta el botón; detiene la repetición automática.
  • El método Process() es el método principal que debe llamarse en el temporizador. Garantiza que los eventos se envíen con la frecuencia requerida si se mantiene pulsado el botón.
  • El método SetEvent(...) — establecer los parámetros de un evento personalizado.
  • Métodos SetDelay(...), setInterval(...) — configuración del retraso y del intervalo de repetición automática.

Declare un objeto de la clase de repetición automática en la clase base del elemento gráfico canvas CCanvasBase. De esta forma será posible utilizar la repetición automática de eventos en cualquier objeto de elementos gráficos. Será suficiente con configurar los parámetros de retardo e intervalo e iniciar la repetición automática en las situaciones requeridas.


Refinando las clases base

Se ha realizado mucho trabajo en la biblioteca para corregir errores y fallos. Las mejoras afectaron a casi todas las clases. No describiremos aquí cada paso del trabajo realizado. Pero los puntos clave, por supuesto, se anunciarán.

En Base.mqh declara todas las clases que vamos a implementar hoy:

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

//--- Форвард-декларация классов элементов управления
class    CCounter;                        // Класс счётчика задержки
class    CAutoRepeat;                     // Класс автоповтора событий
class    CImagePainter;                   // Класс рисования изображений
class    CLabel;                          // Класс текстовой метки
class    CButton;                         // Класс простой кнопки
class    CButtonTriggered;                // Класс двухпозиционной кнопки
class    CButtonArrowUp;                  // Класс кнопки со стрелкой вверх
class    CButtonArrowDown;                // Класс кнопки со стрелкой вниз
class    CButtonArrowLeft;                // Класс кнопки со стрелкой влево
class    CButtonArrowRight;               // Класс кнопки со стрелкой вправо
class    CCheckBox;                       // Класс элемента управления CheckBox
class    CRadioButton;                    // Класс элемента управления RadioButton
class    CScrollBarThumbH;                // Класс ползунка горизонтальной полосы прокрутки
class    CScrollBarThumbV;                // Класс ползунка вертикальной полосы прокрутки
class    CScrollBarH;                     // Класс горизонтальной полосы прокрутки
class    CScrollBarV;                     // Класс вертикальной полосы прокрутки
class    CPanel;                          // Класс элемента управления Panel
class    CGroupBox;                       // Класс элемента управления GroupBox
class    CContainer;                      // Класс элемента управления Container

Esta declaración anticipada de clases es necesaria para la compilación sin errores de los archivos Base.mqh y Controls.mqh incluidos, ya que el acceso a estas clases se realizó incluso antes de su declaración real en los archivos.

Añadir nuevos tipos a la enumeración de tipos de elementos gráficos y especificar el rango de constantes de los tipos de objetos que pueden participar en la interacción con el usuario:

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE                    // Перечисление типов графических элементов
  {
   ELEMENT_TYPE_BASE = 0x10000,           // Базовый объект графических элементов
   ELEMENT_TYPE_COLOR,                    // Объект цвета
   ELEMENT_TYPE_COLORS_ELEMENT,           // Объект цветов элемента графического объекта
   ELEMENT_TYPE_RECTANGLE_AREA,           // Прямоугольная область элемента
   ELEMENT_TYPE_IMAGE_PAINTER,            // Объект для рисования изображений
   ELEMENT_TYPE_COUNTER,                  // Объект счётчика
   ELEMENT_TYPE_AUTOREPEAT_CONTROL,       // Объект автоповтора событий
   ELEMENT_TYPE_CANVAS_BASE,              // Базовый объект холста графических элементов
   ELEMENT_TYPE_ELEMENT_BASE,             // Базовый объект графических элементов
   ELEMENT_TYPE_LABEL,                    // Текстовая метка
   ELEMENT_TYPE_BUTTON,                   // Простая кнопка
   ELEMENT_TYPE_BUTTON_TRIGGERED,         // Двухпозиционная кнопка
   ELEMENT_TYPE_BUTTON_ARROW_UP,          // Кнопка со стрелкой вверх
   ELEMENT_TYPE_BUTTON_ARROW_DOWN,        // Кнопка со стрелкой вниз
   ELEMENT_TYPE_BUTTON_ARROW_LEFT,        // Кнопка со стрелкой влево
   ELEMENT_TYPE_BUTTON_ARROW_RIGHT,       // Кнопка со стрелкой вправо
   ELEMENT_TYPE_CHECKBOX,                 // Элемент управления CheckBox
   ELEMENT_TYPE_RADIOBUTTON,              // Элемент управления RadioButton
   ELEMENT_TYPE_SCROLLBAR_THUMB_H,        // Ползунок горизонтальной полосы прокрутки
   ELEMENT_TYPE_SCROLLBAR_THUMB_V,        // Ползунок вертикальной полосы прокрутки
   ELEMENT_TYPE_SCROLLBAR_H,              // Элемент управления ScrollBarHorisontal
   ELEMENT_TYPE_SCROLLBAR_V,              // Элемент управления ScrollBarVertical
   ELEMENT_TYPE_PANEL,                    // Элемент управления Panel
   ELEMENT_TYPE_GROUPBOX,                 // Элемент управления GroupBox
   ELEMENT_TYPE_CONTAINER,                // Элемент управления Container
  };
#define  ACTIVE_ELEMENT_MIN   ELEMENT_TYPE_LABEL         // Минимальное значение списка активных элементов
#define  ACTIVE_ELEMENT_MAX   ELEMENT_TYPE_SCROLLBAR_V   // Максимальное значение списка активных элементов

Al interactuar con el cursor del ratón, cada elemento gráfico es básicamente capaz de gestionar los mensajes de eventos entrantes. Pero no todos los elementos deben hacer esto. En otras palabras, es necesario comprobar el tipo del elemento y tomar una decisión basada en ello: si este elemento procesa eventos o no. Si seguimos el camino de comprobar los tipos de objetos, en la condición obtenemos una larga lista de elementos que no se pueden manejar. Esto es un inconveniente. Es más fácil añadir otra propiedad, un indicador que señale si este elemento está activo para la interacción o es estático. Entonces solo podemos comprobar esta propiedad para decidir si el evento debe gestionarse o no. Aquí hemos especificado los valores iniciales y finales de las constantes de los tipos de elementos gráficos. Al tomar una decisión sobre el manejo de eventos, basta con comprobar si el tipo de elemento se encuentra dentro de este rango de valores. Y sobre esta base se puede tomar una decisión.

Añade una enumeración de propiedades mediante las cuales puedes ordenar objetos base y buscarlos (CBaseObj):

enum ENUM_BASE_COMPARE_BY                 // Сравниваемые свойства базовых объектов
  {
   BASE_SORT_BY_ID   =  0,                // Сравнение базовых объектов по идентификатору
   BASE_SORT_BY_NAME,                     // Сравнение базовых объектов по имени
   BASE_SORT_BY_X,                        // Сравнение базовых объектов по координате X
   BASE_SORT_BY_Y,                        // Сравнение базовых объектов по  координате Y
   BASE_SORT_BY_WIDTH,                    // Сравнение базовых объектов по ширине
   BASE_SORT_BY_HEIGHT,                   // Сравнение базовых объектов по высоте
   BASE_SORT_BY_ZORDER,                   // Сравнение по Z-order объектов
  };

Ahora todos los objetos heredados del objeto base se pueden ordenar según las propiedades especificadas en la enumeración, si un objeto tiene dichas propiedades, lo que añadirá más flexibilidad al crear nuevas clases descendientes de CBaseObj.

En la función que devuelve el tipo de elemento como una cadena , añade la salida de las letras «V» y «H» a las legibles «Vertical» y «Horizontal»:

//+------------------------------------------------------------------+
//|  Возвращает тип элемента как строку                              |
//+------------------------------------------------------------------+
string ElementDescription(const ENUM_ELEMENT_TYPE type)
  {
   string array[];
   int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array);
   if(array[array.Size()-1]=="V")
      array[array.Size()-1]="Vertical";
   if(array[array.Size()-1]=="H")
      array[array.Size()-1]="Horisontal";
      
   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;
  }

Esto hará que las descripciones de los elementos sean más legibles.

Al crear nuevos elementos y añadirlos a la lista de los adjuntos al contenedor, habrá que crear nombres de objetos. Implementa una función que devuelva abreviaturas cortas de los tipos de elementos que se pueden utilizar para crear un elemento y, a continuación, utiliza esta abreviatura en el nombre del objeto para comprender qué tipo de elemento es:

//+------------------------------------------------------------------+
//|  Возвращает короткое имя элемента по типу                        |
//+------------------------------------------------------------------+
string ElementShortName(const ENUM_ELEMENT_TYPE type)
  {
   switch(type)
     {
      case  ELEMENT_TYPE_ELEMENT_BASE     :  return "BASE";    // Базовый объект графических элементов
      case  ELEMENT_TYPE_LABEL            :  return "LBL";     // Текстовая метка
      case ELEMENT_TYPE_BUTTON            :  return "SBTN";    // Простая кнопка
      case ELEMENT_TYPE_BUTTON_TRIGGERED  :  return "TBTN";    // Двухпозиционная кнопка
      case ELEMENT_TYPE_BUTTON_ARROW_UP   :  return "BTARU";   // Кнопка со стрелкой вверх
      case ELEMENT_TYPE_BUTTON_ARROW_DOWN :  return "BTARD";   // Кнопка со стрелкой вниз
      case ELEMENT_TYPE_BUTTON_ARROW_LEFT :  return "BTARL";   // Кнопка со стрелкой влево
      case ELEMENT_TYPE_BUTTON_ARROW_RIGHT:  return "BTARR";   // Кнопка со стрелкой вправо
      case ELEMENT_TYPE_CHECKBOX          :  return "CHKB";    // Элемент управления CheckBox
      case ELEMENT_TYPE_RADIOBUTTON       :  return "RBTN";    // Элемент управления RadioButton
      case ELEMENT_TYPE_SCROLLBAR_THUMB_H :  return "THMBH";   // Ползунок горизонтальной полосы прокрутки
      case ELEMENT_TYPE_SCROLLBAR_THUMB_V :  return "THMBV";   // Ползунок вертикальной полосы прокрутки
      case ELEMENT_TYPE_SCROLLBAR_H       :  return "SCBH";    // Элемент управления ScrollBarHorisontal
      case ELEMENT_TYPE_SCROLLBAR_V       :  return "SCBV";    // Элемент управления ScrollBarVertical
      case ELEMENT_TYPE_PANEL             :  return "PNL";     // Элемент управления Panel
      case ELEMENT_TYPE_GROUPBOX          :  return "GRBX";    // Элемент управления GroupBox
      case ELEMENT_TYPE_CONTAINER         :  return "CNTR";    // Элемент управления Container
      default                             :  return "Unknown"; // Unknown
     }
  }

Al adjuntar elementos a un contenedor, sus nombres se realizarán según la jerarquía de objetos: «contenedor -- un elemento adjunto al contenedor --- un elemento adjunto a un elemento adjunto», etc.

Los delimitadores entre los nombres de los elementos en la cadena de nombre serán guiones bajos ("_"). Podemos usar el nombre completo para crear una lista de nombres para toda la jerarquía de objetos. Para ello, implemente la siguiente función:

//+------------------------------------------------------------------+
//| Возвращает массив имён иерархии элементов                        |
//+------------------------------------------------------------------+
int GetElementNames(string value, string sep, string &array[])
  {
   if(value=="" || value==NULL)
     {
      PrintFormat("%s: Error. Empty string passed");
      return 0;
     }
   ResetLastError();
   int res=StringSplit(value, StringGetCharacter(sep,0),array);
   if(res==WRONG_VALUE)
     {
      PrintFormat("%s: StringSplit() failed. Error %d",__FUNCTION__, GetLastError());
      return WRONG_VALUE;
     }
   return res;
  }

La función devuelve el número de objetos en la jerarquía y completa la matriz de nombres de todos los elementos.

En la clase de área rectangular CBound, escriba un método para comparar dos objetos:

//+------------------------------------------------------------------+
//| CBound::Сравнение двух объектов                                  |
//+------------------------------------------------------------------+
int CBound::Compare(const CObject *node,const int mode=0) const
  {
   if(node==NULL)
      return -1;
   const CBound *obj=node;
   switch(mode)
     {
      case BASE_SORT_BY_NAME  :  return(this.Name()   >obj.Name()    ? 1 : this.Name()    <obj.Name()    ? -1 : 0);
      case BASE_SORT_BY_X     :  return(this.X()      >obj.X()       ? 1 : this.X()       <obj.X()       ? -1 : 0);
      case BASE_SORT_BY_Y     :  return(this.Y()      >obj.Y()       ? 1 : this.Y()       <obj.Y()       ? -1 : 0);
      case BASE_SORT_BY_WIDTH :  return(this.Width()  >obj.Width()   ? 1 : this.Width()   <obj.Width()   ? -1 : 0);
      case BASE_SORT_BY_HEIGHT:  return(this.Height() >obj.Height()  ? 1 : this.Height()  <obj.Height()  ? -1 : 0);
      default                 :  return(this.ID()     >obj.ID()      ? 1 : this.ID()      <obj.ID()      ? -1 : 0);
     }
  }

Anteriormente, la comparación se realizaba utilizando el método de la clase padre del mismo nombre. Esto permitió comparar solo dos propiedades: el nombre y el identificador del objeto.

La mayor parte de las mejoras se refieren a la clase base del objeto canvas del elemento gráfico CCanvasBase, ya que es ella la que acumula las propiedades principales de todos los elementos gráficos.

En la sección protegida de la clase, declare nuevas variables y tres métodos para trabajar con el gestor de recursos compartidos:

//+------------------------------------------------------------------+
//| Базовый класс холста графических элементов                       |
//+------------------------------------------------------------------+
class CCanvasBase : public CBaseObj
  {
private:
   bool              m_chart_mouse_wheel_flag;                 // Флаг отправки сообщений о прокрутке колёсика мышки
   bool              m_chart_mouse_move_flag;                  // Флаг отправки сообщений о перемещениях курсора мышки
   bool              m_chart_object_create_flag;               // Флаг отправки сообщений о событии создания графического объекта
   bool              m_chart_mouse_scroll_flag;                // Флаг прокрутки графика левой кнопкой и колёсиком мышки
   bool              m_chart_context_menu_flag;                // Флаг доступа к контекстному меню по нажатию правой клавиши мышки
   bool              m_chart_crosshair_tool_flag;              // Флаг доступа к инструменту "Перекрестие" по нажатию средней клавиши мышки
   bool              m_flags_state;                            // Состояние флагов прокрутки графика колёсиком, контекстного меню и перекрестия
   
//--- Установка запретов для графика (прокрутка колёсиком, контекстное меню и перекрестие)
   void              SetFlags(const bool flag);
   
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;                           // Объект управления цветом рамки
   
   CColorElement     m_color_background_act;                   // Объект управления цветом фона активированного элемента
   CColorElement     m_color_foreground_act;                   // Объект управления цветом переднего плана активированного элемента
   CColorElement     m_color_border_act;                       // Объект управления цветом рамки активированного элемента
   
   CAutoRepeat       m_autorepeat;                             // Объект управления автоповторами событий
   
   ENUM_ELEMENT_STATE m_state;                                 // Состояние элемента (напр., кнопки (вкл/выкл))
   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_bg;                               // Прозрачность фона
   uchar             m_alpha_fg;                               // Прозрачность переднего плана
   uint              m_border_width_lt;                        // Ширина рамки слева
   uint              m_border_width_rt;                        // Ширина рамки справа
   uint              m_border_width_up;                        // Ширина рамки сверху
   uint              m_border_width_dn;                        // Ширина рамки снизу
   string            m_program_name;                           // Имя программы
   bool              m_hidden;                                 // Флаг скрытого объекта
   bool              m_blocked;                                // Флаг заблокированного элемента
   bool              m_movable;                                // Флаг перемещаемого элемента
   bool              m_focused;                                // Флаг элемента в фокусе
   bool              m_main;                                   // Флаг главного объекта
   bool              m_autorepeat_flag;                        // Флаг автоповтора отправки событий
   bool              m_scroll_flag;                            // Флаг прокрутки содержимого при помощи скроллбаров
   bool              m_trim_flag;                              // Флаг обрезки элемента по границам контейнера
   int               m_cursor_delta_x;                         // Дистанция от курсора до левого края элемента
   int               m_cursor_delta_y;                         // Дистанция от курсора до верхнего края элемента
   int               m_z_order;                                // Z-ордер графического объекта
   
//--- (1) Устанавливает, возвращает (2) имя, (3) флаг активного элемента
   void              SetActiveElementName(const string name)   { CCommonManager::GetInstance().SetElementName(name);                               }
   string            ActiveElementName(void)             const { return CCommonManager::GetInstance().ElementName();                               }
   bool              IsCurrentActiveElement(void)        const { return this.ActiveElementName()==this.NameFG();                                   }
   
//--- Возврат смещения начальных координат рисования на холсте относительно канваса и координат объекта
   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());                                                   }
   
//--- Возвращает скорректированный идентификатор графика
   long              CorrectChartID(const long chart_id) const { return(chart_id!=0 ? chart_id : ::ChartID());                                     }

public:
  • CAutoRepeat m_autorepeat: un objeto de repetición automática de eventos; cualquiera de los elementos gráficos puede tener la funcionalidad proporcionada por la clase de este objeto.
  • uint m_border_width_lt — ancho del borde izquierdo; el borde es el límite del área visible del contenedor, y la sangría del área visible desde el borde del elemento puede tener diferentes tamaños en los distintos lados.
  • uint m_border_width_rt — ancho del borde derecho.
  • uint m_border_width_up — ancho del borde en la parte superior.
  • uint m_border_width_dn — Ancho del borde en la parte inferior.
  • bool m_movable: indicador para el objeto que se va a mover; por ejemplo, un botón es un elemento no móvil, mientras que el controlador de una barra de desplazamiento es móvil, etc.
  • bool m_main: indicador del elemento principal; el elemento principal es el primero en la jerarquía de objetos vinculados, por ejemplo, un panel en el que se encuentran otros controles; normalmente se trata de un objeto de formulario.
  • bool m_autorepeat_flag: indicador para utilizar la repetición automática de eventos por parte del elemento.
  • bool m_scroll_flag: indicador para desplazar un elemento con barras de desplazamiento.
  • bool m_trim_flag: indicador para recortar un elemento en los bordes del área visible del contenedor; por ejemplo, las barras de desplazamiento están fuera del área visible del contenedor, pero no se recortan en sus bordes.
  • int m_cursor_delta_x: variable auxiliar que almacena la distancia del cursor desde el límite izquierdo del elemento.
  • int m_cursor_delta_y: variable auxiliar que almacena la distancia del cursor desde el límite superior del elemento.
  • int m_z_order — prioridad de un objeto gráfico para recibir un evento de clic del ratón en el gráfico; cuando los objetos se superponen, el evento CHARTEVENT_CLICK recuperará solo un objeto cuya prioridad sea mayor que la de los demás.
  • Método void SetActiveElementName(const string nombre): establece el nombre del elemento actualmente activo en el administrador de datos compartidos.
  • Método string ActiveElementName(void): devuelve el nombre del elemento activo actual.
  • Método bool IsCurrentActiveElement(void): devuelve un indicador que indica que este objeto está actualmente activo.

En la sección protegida de la clase, añade controladores para mover el cursor del ratón y cambiar el control:

//--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), (3) перемещения курсора (Move),
//--- (4) прокрутки колёсика (Wheel), (5) ухода из фокуса (Release), (6) создания графического объекта (Create). Переопределяются в наследниках.
   virtual void      OnFocusEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnPressEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnMoveEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnReleaseEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnCreateEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam)         { return;   }  // обработчик здесь отключен
   
//--- Обработчики пользовательских событий элемента при наведении курсора, щелчке, прокрутке колёсика в области объекта и его изменения
   virtual void      MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam)     { return;   }  // обработчик здесь отключен
   virtual void      MousePressHandler(const int id, const long lparam, const double dparam, const string sparam)    { return;   }  // обработчик здесь отключен
   virtual void      MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam)    { return;   }  // обработчик здесь отключен
   virtual void      ObjectChangeHandler(const int id, const long lparam, const double dparam, const string sparam)  { return;   }  // обработчик здесь отключен

Al mover el cursor sobre un objeto, se deben gestionar dichos eventos, y algunos controles podrán posteriormente cambiar de tamaño con el ratón. Hemos anunciado aquí los responsables de dichos eventos.

En la sección pública de la clase, añade métodos para trabajar con algunas variables de la clase:

public:
//--- Возвращает указатель на (1) контейнер, (2) объект класса автоповтора событий
   CCanvasBase      *GetContainer(void)                  const { return this.m_container;                                                          }
   CAutoRepeat      *GetAutorepeatObj(void)                    { return &this.m_autorepeat;                                                        }

...

//--- (1) Устанавливает, (2) возвращает z-ордер
   bool              ObjectSetZOrder(const int value);
   int               ObjectZOrder(void)                  const { return this.m_z_order;                                                            }
   
//--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного,
//--- (4) перемещаемого, (5) главного элемента, (6) в фокусе, (7) имя графического объекта (фон, текст)
   bool              IsBelongsToThis(const string name)  const { return(::ObjectGetString(this.m_chart_id,name,OBJPROP_TEXT)==this.m_program_name);}
   bool              IsHidden(void)                      const { return this.m_hidden;                                                             }
   bool              IsBlocked(void)                     const { return this.m_blocked;                                                            }
   bool              IsMovable(void)                     const { return this.m_movable;                                                            }
   bool              IsMain(void)                        const { return this.m_main;                                                               }
   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) устанавливает ширину рамки слева
    uint             BorderWidthLeft(void)               const { return this.m_border_width_lt;                                                    } 
    void             SetBorderWidthLeft(const uint width)      { this.m_border_width_lt=width;                                                     }
    
//--- (1) Возвращает, (2) устанавливает ширину рамки справа
    uint             BorderWidthRight(void)              const { return this.m_border_width_rt;                                                    } 
    void             SetBorderWidthRight(const uint width)     { this.m_border_width_rt=width;                                                     }
                      
//--- (1) Возвращает, (2) устанавливает ширину рамки сверху
    uint             BorderWidthTop(void)                const { return this.m_border_width_up;                                                    } 
    void             SetBorderWidthTop(const uint width)       { this.m_border_width_up=width;                                                     }
                      
//--- (1) Возвращает, (2) устанавливает ширину рамки снизу
    uint             BorderWidthBottom(void)             const { return this.m_border_width_dn;                                                    } 
    void             SetBorderWidthBottom(const uint width)    { this.m_border_width_dn=width;                                                     }
                      
//--- Устанавливает одинаковую ширину рамки со всех сторон
    void             SetBorderWidth(const uint width)
                       {
                        this.m_border_width_lt=this.m_border_width_rt=this.m_border_width_up=this.m_border_width_dn=width;
                       }
                      
//--- Устанавливает ширину рамки
    void             SetBorderWidth(const uint left,const uint right,const uint top,const uint bottom)
                       {
                        this.m_border_width_lt=left;
                        this.m_border_width_rt=right;
                        this.m_border_width_up=top;
                        this.m_border_width_dn=bottom;
                       }

...

Algunos métodos deberían ser virtuales, ya que deben funcionar de manera diferente para distintos elementos.

//--- Устанавливает объекту флаг (1) перемещаемости, (2) главного объекта
   void              SetMovable(const bool flag)               { this.m_movable=flag;                                                              }
   void              SetAsMain(void)                           { this.m_main=true;                                                                 }
   
//--- Ограничивает графический объект по размерам контейнера
   virtual bool      ObjectTrim(void);
   
//--- Изменяет размеры объекта
   virtual bool      ResizeW(const int w);
   virtual bool      ResizeH(const int h);
   virtual bool      Resize(const int w,const int h);

//--- Устанавливает объекту новую координату (1) X, (2) Y, (3) XY
   virtual bool      MoveX(const int x);
   virtual bool      MoveY(const int y);
   virtual bool      Move(const int x,const int y);
   
//--- Смещает объект по оси (1) X, (2) Y, (3) XY на указанное смещение
   virtual bool      ShiftX(const int dx);
   virtual bool      ShiftY(const int dy);
   virtual bool      Shift(const int dx,const int dy);

...

//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
   
//--- (1) Таймер, (2) обработчик события таймера
   virtual void      OnTimer()                                 { this.TimerEventHandler();         }
   virtual void      TimerEventHandler(void)                   { return;                           }

En los constructores de clase, todas las variables nuevas se inicializan con valores predeterminados:

//--- Конструкторы/деструктор
                     CCanvasBase(void) :
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0), m_alpha_bg(0), m_alpha_fg(255), 
                        m_hidden(false), m_blocked(false), m_focused(false), m_movable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false),
                        m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0),
                        m_state(0), m_wnd_y(0), m_cursor_delta_x(0), m_cursor_delta_y(0) { this.Init(); }
                     CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h);
                    ~CCanvasBase(void);
  };
//+------------------------------------------------------------------+
//| CCanvasBase::Конструктор                                         |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(const string object_name,const long chart_id,const int wnd,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_bg(0), m_alpha_fg(255),
   m_hidden(false), m_blocked(false), m_focused(false), m_movable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false),
   m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0),
   m_state(0), m_cursor_delta_x(0), m_cursor_delta_y(0)
  {
...

Añadir la implementación del método virtual Compare:

//+------------------------------------------------------------------+
//| CCanvasBase::Сравнение двух объектов                             |
//+------------------------------------------------------------------+
int CCanvasBase::Compare(const CObject *node,const int mode=0) const
  {
   if(node==NULL)
      return -1;
   const CCanvasBase *obj=node;
   switch(mode)
     {
      case BASE_SORT_BY_NAME  :  return(this.Name()         >obj.Name()          ? 1 : this.Name()          <obj.Name()          ? -1 : 0);
      case BASE_SORT_BY_X     :  return(this.X()            >obj.X()             ? 1 : this.X()             <obj.X()             ? -1 : 0);
      case BASE_SORT_BY_Y     :  return(this.Y()            >obj.Y()             ? 1 : this.Y()             <obj.Y()             ? -1 : 0);
      case BASE_SORT_BY_WIDTH :  return(this.Width()        >obj.Width()         ? 1 : this.Width()         <obj.Width()         ? -1 : 0);
      case BASE_SORT_BY_HEIGHT:  return(this.Height()       >obj.Height()        ? 1 : this.Height()        <obj.Height()        ? -1 : 0);
      case BASE_SORT_BY_ZORDER:  return(this.ObjectZOrder() >obj.ObjectZOrder()  ? 1 : this.ObjectZOrder()  <obj.ObjectZOrder()  ? -1 : 0);
      default                 :  return(this.ID()           >obj.ID()            ? 1 : this.ID()            <obj.ID()            ? -1 : 0);
     }
  }

Refine es un método que recorta un objeto gráfico a lo largo del contorno del contenedor:

//+------------------------------------------------------------------+
//| CCanvasBase::Подрезает графический объект по контуру контейнера  |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectTrim()
  {
//--- Проверяем флаг разрешения обрезки элемента и,
//--- если элемент не должен обрезаться по границам контейнера - возвращаем false
   if(!this.m_trim_flag)
      return false;
//--- Получаем границы контейнера
   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);
      if(this.ObjectResize(this.Width(),this.Height()))
         this.BoundResize(this.Width(),this.Height());
      return false;
     }
//--- Объект полностью или частично находится внутри видимой области контейнера
   else
     {
      //--- Если элемент полностью внутри контейнера
      if(object_right<=container_right && object_left>=container_left &&
         object_bottom<=container_bottom && object_top>=container_top)
        {
         //--- Если ширина или высота графического объекта не совпадает с шириной или высотой элемента,
         //--- модифицируем графический объект по размерам элемента и возвращаем true
         if(this.ObjectWidth()!=this.Width() || this.ObjectHeight()!=this.Height())
           {
            if(this.ObjectResize(this.Width(),this.Height()))
               return true;
           }
        }
      //--- Если элемент частично находится в видимой области контейнера
      else
        {
         //--- Если элемент по вертикали находится в видимой области контейнера
         if(object_bottom<=container_bottom && object_top>=container_top)
           {
            //--- Если высота графического объекта не совпадает с высотой элемента,
            //--- модифицируем графический объект по высоте элемента
            if(this.ObjectHeight()!=this.Height())
               this.ObjectResizeH(this.Height());
           }
         else
           {
            //--- Если элемент по горизонтали находится в видимой области контейнера
            if(object_right<=container_right && object_left>=container_left)
              {
               //--- Если ширина графического объекта не совпадает с шириной элемента,
               //--- модифицируем графический объект по ширине элемента
               if(this.ObjectWidth()!=this.Width())
                  this.ObjectResizeW(this.Width());
              }
           }
        }
     }
     
//--- Проверяем выход объекта по горизонтали и вертикали за пределы контейнера
   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);
      return true;
     }
   return false;
  }

En primer lugar, el método se implementó con el tipo bool para poder comprender la necesidad de volver a dibujar el gráfico después de que el método se haya ejecutado. En varios modos de prueba, se descubrió una falla en el método. Esto se manifestó en el hecho de que los elementos recortados no recuperaron sus dimensiones. Esto sucedió si el elemento iba más allá de los límites del contenedor y luego regresó nuevamente al área visible del contenedor. Un elemento se recorta cambiando las coordenadas y dimensiones de su objeto gráfico. Después de cambiar las dimensiones, nunca más las restauraron. Ahora esto está arreglado.

Un método que establece el orden z de un objeto gráfico:

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает z-ордер графического объекта          |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetZOrder(const int value)
  {
//--- Если передано уже установленное значение - возвращаем true
   if(this.ObjectZOrder()==value)
      return true;
//--- Если не удалось установить новое значение в графические объекты фона и переднего плана - возвращаем false
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_ZORDER,value) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_ZORDER,value))
      return false;
//--- Записываем новое значение z-ордер в переменную и возвращаем true
   this.m_z_order=value;
   return true;
  }

Primero, el valor de orden z pasado se establece en los objetos gráficos de fondo y primer plano, y luego en una variable. Si no se pudo establecer el valor en los objetos gráficos, entonces deje el método con el retorno en false.

Métodos para cambiar el tamaño de un elemento gráfico:

//+------------------------------------------------------------------+
//| CCanvasBase::Изменяет ширину объекта                             |
//+------------------------------------------------------------------+
bool CCanvasBase::ResizeW(const int w)
  {
   if(!this.ObjectResizeW(w))
      return false;
   this.BoundResizeW(w);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   return true;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Изменяет высоту объекта                             |
//+------------------------------------------------------------------+
bool CCanvasBase::ResizeH(const int h)
  {
   if(!this.ObjectResizeH(h))
      return false;
   this.BoundResizeH(h);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   return true;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Изменяет размеры объекта                            |
//+------------------------------------------------------------------+
bool CCanvasBase::Resize(const int w,const int h)
  {
   if(!this.ObjectResize(w,h))
      return false;
   this.BoundResize(w,h);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   return true;
  }

Si no se ha podido cambiar el tamaño físico del objeto gráfico, el método devuelve false. Una vez cambiado correctamente el tamaño del objeto gráfico, establecemos nuevos valores para el objeto de área rectangular que describe el tamaño del elemento y llamamos al método para recortar el elemento a lo largo de los límites del contenedor. Si el método ObjectTrim devuelve false, significa que no ha cambiado nada en el objeto o que este objeto no es modificable. En este caso, el objeto aún debe actualizarse y volver a dibujarse, pero sin volver a dibujar el gráfico. Por último, devuelve true.

En los métodos para mover un elemento, la coordenada no modificable debe ajustarse según su ubicación real con respecto al contenedor, que es lo que hacen los métodos AdjX y AdjY:

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

En el método de inicialización de clase inicializar el temporizador de milisegundos:

//+------------------------------------------------------------------+
//| CCanvasBase::Инициализация класса                                |
//+------------------------------------------------------------------+
void CCanvasBase::Init(void)
  {
//--- Запоминаем разрешения для мышки и инструментов графика
   this.m_chart_mouse_wheel_flag   = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL);
   this.m_chart_mouse_move_flag    = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE);
   this.m_chart_object_create_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE);
   this.m_chart_mouse_scroll_flag  = ::ChartGetInteger(this.m_chart_id, CHART_MOUSE_SCROLL);
   this.m_chart_context_menu_flag  = ::ChartGetInteger(this.m_chart_id, CHART_CONTEXT_MENU);
   this.m_chart_crosshair_tool_flag= ::ChartGetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL);
//--- Устанавливаем разрешения для мышки и графика
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, true);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, true);
   ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, true);

//--- Инициализируем цвета объекта по умолчанию
   this.InitColors();
//--- Инициализируем миллисекундный таймер
   ::EventSetMillisecondTimer(16);
  }

Ahora, refine el controlador de eventos de clase. Practique el comportamiento habitual del usuario, como en el sistema operativo. Cuando pasas el cursor por encima del elemento activo, su color debería cambiar, y cuando apartas el cursor, debería volver a su color original. Al hacer clic en un elemento, su color también cambia y el elemento queda listo para la interacción. Si se trata de un botón simple, al soltar el botón del ratón en el área del botón se generará un evento de clic. Si alejas el cursor del objeto mientras mantienes pulsado el botón, su color cambiará y, cuando sueltes el botón, no se generará un evento de clic. Si se trata de un elemento móvil, al mantener pulsado el botón y mover el cursor, el elemento seleccionado se desplazará. Y no importa si el cursor está sobre el objeto o fuera de él en el momento de mantenerlo pulsado, el elemento se moverá hasta que se suelte el botón del ratón.

Veamos qué mejoras se han realizado en el controlador de eventos de clase para esto:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик событий                                  |
//+------------------------------------------------------------------+
void CCanvasBase::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Событие изменения графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- скорректируем дистанцию между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
      this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);
     }
     
//--- Событие создания графического объекта
   if(id==CHARTEVENT_OBJECT_CREATE)
     {
      this.OnCreateEvent(id,lparam,dparam,sparam);
     }

//--- Если элемент заблокирован или скрыт - уходим
   if(this.IsBlocked() || this.IsHidden())
      return;
      
//--- Координаты курсора мышки
   int x=(int)lparam;
   int y=(int)dparam-this.m_wnd_y;  // Корректируем Y по высоте окна индикатора
     
//--- Событие перемещения курсора
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Неактивные элементы, кроме главного, не обрабатываем
      if(!this.IsMain() && (this.Type()<ACTIVE_ELEMENT_MIN || this.Type()>ACTIVE_ELEMENT_MAX))
         return;

      //--- Кнопка мышки удерживается
      if(sparam=="1")
        {
         //--- Курсор в пределах объекта
         if(this.Contains(x, y))
           {
            //--- Если это главный объект - запрещаем инструменты графика
            if(this.IsMain())
               this.SetFlags(false);
            
            //--- Если кнопка мышки была зажата на графике - обрабатывать нечего, уходим
            if(this.ActiveElementName()=="Chart")
               return;
               
            //--- Фиксируем имя активного элемента, над которым был курсор при нажатии кнопки мышки
            this.SetActiveElementName(this.ActiveElementName());
            
            //--- Если это текущий активный элемент - обрабатываем его перемещение
            if(this.IsCurrentActiveElement())
              {
               this.OnMoveEvent(id,lparam,dparam,sparam);
               
               //--- Если у элемента активен автоповтор событий - указываем, что кнопка нажата
               if(this.m_autorepeat_flag)
                  this.m_autorepeat.OnButtonPress();
              }
           }
         //--- Курсор за пределами объекта
         else
           {
            //--- Если это активный главный объект, либо кнопка мышки зажата на графике - разрешаем инструменты графика
            if(this.IsMain() && (this.ActiveElementName()==this.NameFG() || this.ActiveElementName()=="Chart"))
               this.SetFlags(true);
               
            //--- Если это текущий активный элемент
            if(this.IsCurrentActiveElement())
              {
               //--- Если элемент неперемещаемый
               if(!this.IsMovable())
                 {
                  //--- вызываем обработчик наведения курсора мышки
                  this.OnFocusEvent(id,lparam,dparam,sparam);
                  //--- Если у элемента активен автоповтор событий - указываем, что кнопка отжата
                  if(this.m_autorepeat_flag)
                     this.m_autorepeat.OnButtonRelease();
                 }
               //--- Если элемент перемещаемый - вызываем обработчик перемещения
               else
                  this.OnMoveEvent(id,lparam,dparam,sparam);
              }
           }
        }
      
      //--- Кнопка мышки не нажата
      else
        {
         //--- Курсор в пределах объекта
         if(this.Contains(x, y))
           {
            //--- Если это главный элемент - отключаем инструменты графика
            if(this.IsMain())
               this.SetFlags(false);
            
            //--- Вызываем обработчик наведения курсора и
            //--- устанавливаем элемент как текущий активный
            this.OnFocusEvent(id,lparam,dparam,sparam);
            this.SetActiveElementName(this.NameFG());
           }
         //--- Курсор за пределами объекта
         else
           {
            //--- Если это главный объект
            if(this.IsMain())
              {
               //--- Разрешаем инструменты графика и
               //--- устанавливаем график как текущий активный элемент
               this.SetFlags(true);
               this.SetActiveElementName("Chart");
              }
            //--- Вызываем обработчик увода курсора из фокуса 
            this.OnReleaseEvent(id,lparam,dparam,sparam);
           }
        }
     }
     
//--- Событие щелчка кнопкой мышки на объекте (отпускание кнопки)
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Если щелчок (отпускание кнопки мышки) был по этому объекту
      if(sparam==this.NameFG())
        {
         //--- Вызываем обработчик щелчка мышки и освобождаем текущий активный объект
         this.OnPressEvent(id, lparam, dparam, sparam);
         this.SetActiveElementName("");
               
         //--- Если у элемента активен автоповтор событий - указываем, что кнопка отжата
         if(this.m_autorepeat_flag)
            this.m_autorepeat.OnButtonRelease();
        }
     }
   
//--- Событие прокрутки колёсика мышки
   if(id==CHARTEVENT_MOUSE_WHEEL)
     {
      if(this.IsCurrentActiveElement())
         this.OnWheelEvent(id,lparam,dparam,sparam);
     }

//--- Если пришло пользовательское событие графика
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- собственные события не обрабатываем
      if(sparam==this.NameFG())
         return;

      //--- приводим пользовательское событие в соответствие со стандартными
      ENUM_CHART_EVENT chart_event=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM);
      //--- Если щелчок мышки по объекту - вызываем обработчик пользовательского события
      if(chart_event==CHARTEVENT_OBJECT_CLICK)
        {
         this.MousePressHandler(chart_event, lparam, dparam, sparam);
        }
      //--- Если перемещение курсора мышки - вызываем обработчик пользовательского события
      if(chart_event==CHARTEVENT_MOUSE_MOVE)
        {
         this.MouseMoveHandler(chart_event, lparam, dparam, sparam);
        }
      //--- Если прокрутка колёсика мышки - вызываем обработчик пользовательского события
      if(chart_event==CHARTEVENT_MOUSE_WHEEL)
        {
         this.MouseWheelHandler(chart_event, lparam, dparam, sparam);
        }
      //--- Если изменение графического элемента - вызываем обработчик пользовательского события
      if(chart_event==CHARTEVENT_OBJECT_CHANGE)
        {
         this.ObjectChangeHandler(chart_event, lparam, dparam, sparam);
        }
     }
  }

Toda la lógica del método se describe en los comentarios del código y actualmente se corresponde con la funcionalidad indicada.

Controlador de movimiento del cursor:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик перемещения курсора                      |
//+------------------------------------------------------------------+
void CCanvasBase::OnMoveEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Элемент в фокусе при щелчке по нему
   this.m_focused=true;
//--- Если цвета объекта не для режима Pressed
   if(!this.CheckColor(COLOR_STATE_PRESSED))
     {
      //--- устанавливаем цвета Pressed и перерисовываем объект
      this.ColorChange(COLOR_STATE_PRESSED);
      this.Draw(true);
     }
//--- Рассчитываем отступ курсора от верхнего левого угла элемента по осям X и Y
   if(this.m_cursor_delta_x==0)
      this.m_cursor_delta_x=(int)lparam-this.X();
   if(this.m_cursor_delta_y==0)
      this.m_cursor_delta_y=(int)::round(dparam-this.Y());
  }

Al mantener pulsado el botón del ratón sobre el elemento, establece el indicador de que el elemento se encuentra en estado pulsado. Cambia el color del elemento y calcula la distancia del cursor desde la esquina superior izquierda del elemento por dos ejes. Estas compensaciones se utilizarán al manejar el movimiento del cursor del ratón, de modo que el objeto se desplace tras el cursor, anclándose a él no con el punto de origen (esquina superior izquierda), sino con la sangría registrada en estas variables.

En los controladores de enfoque del cursor, al salir del enfoque y al hacer clic en un objeto, inicialice los valores de sangría con ceros:

//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик ухода из фокуса                          |
//+------------------------------------------------------------------+
void CCanvasBase::OnReleaseEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Элемент не в фокусе при уводе курсора
   this.m_focused=false;
//--- восстанавливаем исходные цвета, сбрасываем флаг Focused и перерисовываем объект
   if(!this.CheckColor(COLOR_STATE_DEFAULT))
     {
      this.ColorChange(COLOR_STATE_DEFAULT);
      this.Draw(true);
     }
//--- Инициализируем отступ курсора от верхнего левого угла элемента по осям X и Y
   this.m_cursor_delta_x=0;
   this.m_cursor_delta_y=0;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик наведения курсора                        |
//+------------------------------------------------------------------+
void CCanvasBase::OnFocusEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Элемент в фокусе
   this.m_focused=true;
//--- Если цвета объекта не для режима Focused
   if(!this.CheckColor(COLOR_STATE_FOCUSED))
     {
      //--- устанавливаем цвета и флаг Focused и перерисовываем объект
      this.ColorChange(COLOR_STATE_FOCUSED);
      this.Draw(true);
     }
//--- Инициализируем отступ курсора от верхнего левого угла элемента по осям X и Y
   this.m_cursor_delta_x=0;
   this.m_cursor_delta_y=0;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Обработчик нажатия на объект                        |
//+------------------------------------------------------------------+
void CCanvasBase::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Элемент в фокусе при щелчке по нему
   this.m_focused=true;
//--- Если цвета объекта не для режима Pressed
   if(!this.CheckColor(COLOR_STATE_PRESSED))
     {
      //--- устанавливаем цвета Pressed и перерисовываем объект
      this.ColorChange(COLOR_STATE_PRESSED);
      this.Draw(true);
     }
//--- Инициализируем отступ курсора от верхнего левого угла элемента по осям X и Y
   this.m_cursor_delta_x=0;
   this.m_cursor_delta_y=0;
//--- отправляем пользовательское событие на график с переданными значениями в lparam, dparam, и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameFG());
  }

Además de inicializar variables, en el controlador de clics, envíe al gráfico un evento de clic personalizado en un elemento con el nombre del objeto gráfico en primer plano.

Estos cambios (y otros menores pero obligatorios) afectaron al archivo con las clases de objetos base.

Ahora, refine el archivo Controls.mqh de las clases de elementos gráficos.

Añadir nuevas sustituciones de macros y constantes de enumeración de propiedades de elementos gráficos:

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  DEF_LABEL_W                50          // Ширина текстовой метки по умолчанию
#define  DEF_LABEL_H                16          // Высота текстовой метки по умолчанию
#define  DEF_BUTTON_W               60          // Ширина кнопки по умолчанию
#define  DEF_BUTTON_H               16          // Высота кнопки по умолчанию
#define  DEF_PANEL_W                80          // Ширина панели по умолчанию
#define  DEF_PANEL_H                80          // Высота панели по умолчанию
#define  DEF_SCROLLBAR_TH           13          // Толщина полосы прокрутки по умолчанию
#define  DEF_THUMB_MIN_SIZE         8           // Минимальная толщина ползунка полосы прокрутки
#define  DEF_AUTOREPEAT_DELAY       500         // Задержка перед запуском автоповтора
#define  DEF_AUTOREPEAT_INTERVAL    100         // Частота автоповторов

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_SORT_BY                       // Сравниваемые свойства
  {
   ELEMENT_SORT_BY_ID   =  BASE_SORT_BY_ID,     // Сравнение по идентификатору элемента
   ELEMENT_SORT_BY_NAME =  BASE_SORT_BY_NAME,   // Сравнение по наименованию элемента
   ELEMENT_SORT_BY_X    =  BASE_SORT_BY_X,      // Сравнение по координате X элемента
   ELEMENT_SORT_BY_Y    =  BASE_SORT_BY_Y,      // Сравнение по координате Y элемента
   ELEMENT_SORT_BY_WIDTH=  BASE_SORT_BY_WIDTH,  // Сравнение по ширине элемента
   ELEMENT_SORT_BY_HEIGHT= BASE_SORT_BY_HEIGHT, // Сравнение по высоте элемента
   ELEMENT_SORT_BY_ZORDER= BASE_SORT_BY_ZORDER, // Сравнение по Z-order элемента
   ELEMENT_SORT_BY_TEXT,                        // Сравнение по тексту элемента
   ELEMENT_SORT_BY_COLOR_BG,                    // Сравнение по цвету фона элемента
   ELEMENT_SORT_BY_ALPHA_BG,                    // Сравнение по прозрачности фона элемента
   ELEMENT_SORT_BY_COLOR_FG,                    // Сравнение по цвету переднего плана элемента
   ELEMENT_SORT_BY_ALPHA_FG,                    // Сравнение по прозрачности переднего плана элемента
   ELEMENT_SORT_BY_STATE,                       // Сравнение по состоянию элемента
   ELEMENT_SORT_BY_GROUP,                       // Сравнение по группе элемента
  };

Las primeras siete constantes corresponden a las constantes similares de enumeración de las propiedades del objeto base. Por lo tanto, esta enumeración continúa la lista de propiedades del objeto base.

En la clase de dibujo de imágenes CImagePainter, declare un nuevo método para dibujar los bordes de un grupo de elementos:

//--- Очищает область
   bool              Clear(const int x,const int y,const int w,const int h,const bool update=true);
//--- Рисует закрашенную стрелку (1) вверх, (2) вниз, (3) влево, (4) вправо
   bool              ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   
//--- Рисует (1) отмеченный, (2) неотмеченный CheckBox
   bool              CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   
//--- Рисует (1) отмеченный, (2) неотмеченный RadioButton
   bool              CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);

//--- Рисует рамку группы элементов
   bool              FrameGroupElements(const int x,const int y,const int w,const int h,const string text,
                                        const color clr_text,const color clr_dark,const color clr_light,
                                        const uchar alpha,const bool update=true);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта

Y fuera del cuerpo de la clase, escribe su implementación:

//+------------------------------------------------------------------+
//| Рисует рамку группы элементов                                    |
//+------------------------------------------------------------------+
bool CImagePainter::FrameGroupElements(const int x,const int y,const int w,const int h,const string text,
                                       const color clr_text,const color clr_dark,const color clr_light,
                                       const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Корректировка координаты Y
   int tw=0, th=0;
   if(text!="" && text!=NULL)
      this.m_canvas.TextSize(text,tw,th);
   int shift_v=int(th!=0 ? ::ceil(th/2) : 0);

//--- Координаты и размеры рамки
   int x1=x;                  // Левый верхний угол области рамки, X
   int y1=y+shift_v;          // Левый верхний угол области рамки, Y
   int x2=x+w-1;              // Правый нижний угол области рамки, X
   int y2=y+h-1;              // Правый нижний угол области рамки, Y
   
//--- Рисуем левую-верхнюю часть рамки
   int arrx[3], arry[3];
   arrx[0]=arrx[1]=x1;
   arrx[2]=x2-1;
   arry[0]=y2;
   arry[1]=arry[2]=y1;
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_dark, alpha));
   arrx[0]++;
   arrx[1]++;
   arry[1]++;
   arry[2]++;
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_light, alpha));
//--- Рисуем правую-нижнюю часть рамки
   arrx[0]=arrx[1]=x2-1;
   arrx[2]=x1+1;
   arry[0]=y1;
   arry[1]=arry[2]=y2-1;
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_dark, alpha));
   arrx[0]++;
   arrx[1]++;
   arry[1]++;
   arry[2]++;
   this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_light, alpha));
   
   if(tw>0)
      this.m_canvas.FillRectangle(x+5,y,x+7+tw,y+th,clrNULL);
   this.m_canvas.TextOut(x+6,y-1,text,::ColorToARGB(clr_text, alpha));
   
   if(update)
      this.m_canvas.Update(false);
   return true;
  }

El método dibuja un borde en relieve en dos colores, que se pasan al método: claro y oscuro. Si se pasa un texto vacío, simplemente se dibuja un borde alrededor del perímetro del objeto de grupo. Si el texto contiene algo, la parte superior del borde se dibuja por debajo del límite superior del grupo, a una altura igual a la mitad de la altura del texto.

Refine el método para comparar la clase de dibujo de imágenes:

//+------------------------------------------------------------------+
//| CImagePainter::Сравнение двух объектов                           |
//+------------------------------------------------------------------+
int CImagePainter::Compare(const CObject *node,const int mode=0) const
  {
   if(node==NULL)
      return -1;
   const CImagePainter *obj=node;
   switch(mode)
     {
      case ELEMENT_SORT_BY_NAME     :  return(this.Name()   >obj.Name()    ? 1 : this.Name()    <obj.Name()    ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA_FG :
      case ELEMENT_SORT_BY_ALPHA_BG :  return(this.Alpha()  >obj.Alpha()   ? 1 : this.Alpha()   <obj.Alpha()   ? -1 : 0);
      case ELEMENT_SORT_BY_X        :  return(this.X()      >obj.X()       ? 1 : this.X()       <obj.X()       ? -1 : 0);
      case ELEMENT_SORT_BY_Y        :  return(this.Y()      >obj.Y()       ? 1 : this.Y()       <obj.Y()       ? -1 : 0);
      case ELEMENT_SORT_BY_WIDTH    :  return(this.Width()  >obj.Width()   ? 1 : this.Width()   <obj.Width()   ? -1 : 0);
      case ELEMENT_SORT_BY_HEIGHT   :  return(this.Height() >obj.Height()  ? 1 : this.Height()  <obj.Height()  ? -1 : 0);
      default                       :  return(this.ID()     >obj.ID()      ? 1 : this.ID()      <obj.ID()      ? -1 : 0);
     }
  }

Ahora la clasificación se basará en todas las propiedades disponibles del objeto.


Clase de lista de objetos

En el artículo de esta serie sobre tablas «Implementación de un modelo de tabla en MQL5: Aplicación del concepto MVC (Modelo-Vista-Controlador)», ya hemos hablado de la clase de lista enlazada. Simplemente transfiere esta clase al archivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс связанного списка объектов                                 |
//+------------------------------------------------------------------+
class CListObj : public CList
  {
protected:
   ENUM_ELEMENT_TYPE m_element_type;   // Тип создаваемого объекта в CreateElement()
public:
//--- Установка типа элемента
   void              SetElementType(const ENUM_ELEMENT_TYPE type) { this.m_element_type=type;   }
   
//--- Виртуальный метод (1) загрузки списка из файла, (2) создания элемента списка
   virtual bool      Load(const int file_handle);
   virtual CObject  *CreateElement(void);
  };
//+------------------------------------------------------------------+
//| Загрузка списка из файла                                         |
//+------------------------------------------------------------------+
bool CListObj::Load(const int file_handle)
  {
//--- Переменные
   CObject *node;
   bool     result=true;
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Загрузка и проверка маркера начала списка - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Загрузка и проверка типа списка
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return(false);
//--- Чтение размера списка (количество объектов)
   uint num=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Последовательно заново создаём элементы списка с помощью вызова метода Load() объектов node
   this.Clear();
   for(uint i=0; i<num; i++)
     {
      //--- Читаем и проверяем маркер начала данных объекта - 0xFFFFFFFFFFFFFFFF
      if(::FileReadLong(file_handle)!=MARKER_START_DATA)
         return false;
      //--- Читаем тип объекта
      this.m_element_type=(ENUM_ELEMENT_TYPE)::FileReadInteger(file_handle,INT_VALUE);
      node=this.CreateElement();
      if(node==NULL)
         return false;
      this.Add(node);
      //--- Сейчас файловый указатель смещён относительно начала маркера объекта на 12 байт (8 - маркер, 4 - тип)
      //--- Поставим указатель на начало данных объекта и загрузим свойства объекта из файла методом Load() элемента node.
      if(!::FileSeek(file_handle,-12,SEEK_CUR))
         return false;
      result &=node.Load(file_handle);
     }
//--- Результат
   return result;
  }
//+------------------------------------------------------------------+
//| Метод создания элемента списка                                   |
//+------------------------------------------------------------------+
CObject *CListObj::CreateElement(void)
  {
//--- В зависимости от типа объекта в m_element_type, создаём новый объект
   switch(this.m_element_type)
     {
      case ELEMENT_TYPE_BASE              :  return new CBaseObj();           // Базовый объект графических элементов
      case ELEMENT_TYPE_COLOR             :  return new CColor();             // Объект цвета
      case ELEMENT_TYPE_COLORS_ELEMENT    :  return new CColorElement();      // Объект цветов элемента графического объекта
      case ELEMENT_TYPE_RECTANGLE_AREA    :  return new CBound();             // Прямоугольная область элемента
      case ELEMENT_TYPE_IMAGE_PAINTER     :  return new CImagePainter();      // Объект для рисования изображений
      case ELEMENT_TYPE_CANVAS_BASE       :  return new CCanvasBase();        // Базовый объект холста графических элементов
      case ELEMENT_TYPE_ELEMENT_BASE      :  return new CElementBase();       // Базовый объект графических элементов
      case ELEMENT_TYPE_LABEL             :  return new CLabel();             // Текстовая метка
      case ELEMENT_TYPE_BUTTON            :  return new CButton();            // Простая кнопка
      case ELEMENT_TYPE_BUTTON_TRIGGERED  :  return new CButtonTriggered();   // Двухпозиционная кнопка
      case ELEMENT_TYPE_BUTTON_ARROW_UP   :  return new CButtonArrowUp();     // Кнопка со стрелкой вверх
      case ELEMENT_TYPE_BUTTON_ARROW_DOWN :  return new CButtonArrowDown();   // Кнопка со стрелкой вниз
      case ELEMENT_TYPE_BUTTON_ARROW_LEFT :  return new CButtonArrowLeft();   // Кнопка со стрелкой влево
      case ELEMENT_TYPE_BUTTON_ARROW_RIGHT:  return new CButtonArrowRight();  // Кнопка со стрелкой вправо
      case ELEMENT_TYPE_CHECKBOX          :  return new CCheckBox();          // Элемент управления CheckBox
      case ELEMENT_TYPE_RADIOBUTTON       :  return new CRadioButton();       // Элемент управления RadioButton
      case ELEMENT_TYPE_PANEL             :  return new CPanel();             // Элемент управления Panel
      case ELEMENT_TYPE_GROUPBOX          :  return new CGroupBox();          // Элемент управления GroupBox
      case ELEMENT_TYPE_CONTAINER         :  return new CContainer();         // Элемент управления GroupBox
      default                             :  return NULL;
     }
  }

En los objetos de esta clase, almacenaremos elementos de la interfaz de usuario que están vinculados al elemento principal y, si es necesario, implementaremos listas de varios objetos como parte de clases de elementos gráficos. La clase también será necesaria en los métodos para cargar las propiedades de los elementos desde archivos.


Clase base de un elemento gráfico

Todos los elementos gráficos tienen propiedades inherentes a cada uno de los elementos de la lista completa. Para gestionar estas propiedades, guardarlas en un archivo y cargarlas desde un archivo, colóquelas en una clase separada, de la que heredarán todos los elementos gráficos. Esto simplificará su desarrollo posterior.

Implementar una nueva clase base de un elemento gráfico:

//+------------------------------------------------------------------+
//| Базовый класс графического элемента                              |
//+------------------------------------------------------------------+
class CElementBase : public CCanvasBase
  {
protected:
   CImagePainter     m_painter;                                // Класс рисования
   int               m_group;                                  // Группа элементов
public:
//--- Возвращает указатель на класс рисования
   CImagePainter    *Painter(void)                             { return &this.m_painter;           }
   
//--- (1) Устанавливает координаты, (2) изменяет размеры области изображения
   void              SetImageXY(const int x,const int y)       { this.m_painter.SetXY(x,y);        }
   void              SetImageSize(const int w,const int h)     { this.m_painter.SetSize(w,h);      }
//--- Устанавливает координаты и размеры области изображения
   void              SetImageBound(const int x,const int y,const int w,const int h)
                       {
                        this.SetImageXY(x,y);
                        this.SetImageSize(w,h);
                       }
//--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правую, (6) нижнюю границу области изображения
   int               ImageX(void)                        const { return this.m_painter.X();        }
   int               ImageY(void)                        const { return this.m_painter.Y();        }
   int               ImageWidth(void)                    const { return this.m_painter.Width();    }
   int               ImageHeight(void)                   const { return this.m_painter.Height();   }
   int               ImageRight(void)                    const { return this.m_painter.Right();    }
   int               ImageBottom(void)                   const { return this.m_painter.Bottom();   }

//--- (1) Устанавливает, (2) возвращает группу элементов
   virtual void      SetGroup(const int group)                 { this.m_group=group;               }
   int               Group(void)                         const { return this.m_group;              }
   
//--- Возвращает описание объекта
   virtual string    Description(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_ELEMENT_BASE);}

//--- Конструкторы/деструктор
                     CElementBase(void) {}
                     CElementBase(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CElementBase(void) {}
  };

Constructor paramétrico:

//+----------------------------------------------------------------------+
//| CElementBase::Конструктор параметрический. Строит элемент в указанном|
//| окне указанного графика с указанными текстом, координами и размерами |
//+----------------------------------------------------------------------+
CElementBase::CElementBase(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CCanvasBase(object_name,chart_id,wnd,x,y,w,h),m_group(-1)
  {
//--- Объекту рисования назначаем канвас переднего плана и
//--- обнуляем координаты и размеры, что делает его неактивным
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(0,0);
   this.m_painter.SetSize(0,0);
  }

En la lista de inicialización, los valores de los parámetros formales del constructor se pasan al constructor de la clase padre. Y luego se asigna un lienzo para dibujar imágenes. Su tamaño se restablece. Cuando sea necesario utilizar un objeto de dibujo, establezca sus coordenadas y dimensiones. El tamaño cero del área de dibujo la hace inactiva.

Método para comparar dos objetos:

//+------------------------------------------------------------------+
//| CElementBase::Сравнение двух объектов                            |
//+------------------------------------------------------------------+
int CElementBase::Compare(const CObject *node,const int mode=0) const
  {
   if(node==NULL)
      return -1;
   const CElementBase *obj=node;
   switch(mode)
     {
      case ELEMENT_SORT_BY_NAME     :  return(this.Name()         >obj.Name()          ? 1 : this.Name()          <obj.Name()          ? -1 : 0);
      case ELEMENT_SORT_BY_X        :  return(this.X()            >obj.X()             ? 1 : this.X()             <obj.X()             ? -1 : 0);
      case ELEMENT_SORT_BY_Y        :  return(this.Y()            >obj.Y()             ? 1 : this.Y()             <obj.Y()             ? -1 : 0);
      case ELEMENT_SORT_BY_WIDTH    :  return(this.Width()        >obj.Width()         ? 1 : this.Width()         <obj.Width()         ? -1 : 0);
      case ELEMENT_SORT_BY_HEIGHT   :  return(this.Height()       >obj.Height()        ? 1 : this.Height()        <obj.Height()        ? -1 : 0);
      case ELEMENT_SORT_BY_COLOR_BG :  return(this.BackColor()    >obj.BackColor()     ? 1 : this.BackColor()     <obj.BackColor()     ? -1 : 0);
      case ELEMENT_SORT_BY_COLOR_FG :  return(this.ForeColor()    >obj.ForeColor()     ? 1 : this.ForeColor()     <obj.ForeColor()     ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA_BG :  return(this.AlphaBG()      >obj.AlphaBG()       ? 1 : this.AlphaBG()       <obj.AlphaBG()       ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA_FG :  return(this.AlphaFG()      >obj.AlphaFG()       ? 1 : this.AlphaFG()       <obj.AlphaFG()       ? -1 : 0);
      case ELEMENT_SORT_BY_STATE    :  return(this.State()        >obj.State()         ? 1 : this.State()         <obj.State()         ? -1 : 0);
      case ELEMENT_SORT_BY_GROUP    :  return(this.Group()        >obj.Group()         ? 1 : this.Group()         <obj.Group()         ? -1 : 0);
      case ELEMENT_SORT_BY_ZORDER   :  return(this.ObjectZOrder() >obj.ObjectZOrder()  ? 1 : this.ObjectZOrder()  <obj.ObjectZOrder()  ? -1 : 0);
      default                       :  return(this.ID()           >obj.ID()            ? 1 : this.ID()            <obj.ID()            ? -1 : 0);
     }
  }

El método compara dos objetos por todas las propiedades disponibles.

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

//+------------------------------------------------------------------+
//| CElementBase::Возвращает описание объекта                        |
//+------------------------------------------------------------------+
string CElementBase::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, Group %d, %s",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.NameBG(),this.NameFG(),this.ID(),this.Group(),area);
  }

El método crea y devuelve una cadena a partir de algunas propiedades del objeto que son útiles para la depuración, por ejemplo:

Container "Main" (ContainerBG, ContainerFG): ID 1, Group -1, x 100, y 40, w 300, h 200

Un objeto contenedor con el nombre de usuario «Main», con los nombres de fondo del lienzo ContainerBG y primer plano ContainerFG; ID de objeto 1, grupo -1 (sin asignar), coordenadas x 100, y 40, anchura 300, altura 200.

Un método para manejar archivos:

//+------------------------------------------------------------------+
//| CElementBase::Сохранение в файл                                  |
//+------------------------------------------------------------------+
bool CElementBase::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CCanvasBase::Save(file_handle))
      return false;
  
//--- Сохраняем объект изображения
   if(!this.m_painter.Save(file_handle))
      return false;
//--- Сохраняем группу
   if(::FileWriteInteger(file_handle,this.m_group,INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CElementBase::Загрузка из файла                                  |
//+------------------------------------------------------------------+
bool CElementBase::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CCanvasBase::Load(file_handle))
      return false;
      
//--- Загружаем объект изображения
   if(!this.m_painter.Load(file_handle))
      return false;
//--- Загружаем группу
   this.m_group=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Всё успешно
   return true;
  }


Refinando controles simples

Dado que algunas propiedades se han trasladado al nuevo objeto base de los elementos gráficos, elimínelas de la clase del objeto de etiqueta de texto:

//+------------------------------------------------------------------+
//| Класс текстовой метки                                            |
//+------------------------------------------------------------------+
class CLabel : public CCanvasBase
  {
protected:
   CImagePainter     m_painter;                                // Класс рисования
   ushort            m_text[];                                 // Текст
   ushort            m_text_prev[];                            // Прошлый текст
   int               m_text_x;                                 // Координата X текста (смещение относительно  левой границы объекта)
   int               m_text_y;                                 // Координата Y текста (смещение относительно  верхней границы объекта)
   
//--- (1) Устанавливает, (2) возвращает прошлый текст
   void              SetTextPrev(const string text)            { ::StringToShortArray(text,this.m_text_prev);  }
   string            TextPrev(void)                      const { return ::ShortArrayToString(this.m_text_prev);}
      
//--- Стирает текст
   void              ClearText(void);

public:
//--- Возвращает указатель на класс рисования
   CImagePainter    *Painter(void)                             { return &this.m_painter;                       }
   
//--- (1) Устанавливает, (2) возвращает текст
   void              SetText(const string text)                { ::StringToShortArray(text,this.m_text);       }
   string            Text(void)                          const { return ::ShortArrayToString(this.m_text);     }
   
//--- Возвращает координату (1) X, (2) Y текста
   int               TextX(void)                         const { return this.m_text_x;                         }
   int               TextY(void)                         const { return this.m_text_y;                         }

//--- Устанавливает координату (1) X, (2) Y текста
   void              SetTextShiftH(const int x)                { this.m_text_x=x;                              }
   void              SetTextShiftV(const int y)                { this.m_text_y=y;                              }
   
//--- (1) Устанавливает координаты, (2) изменяет размеры области изображения
   void              SetImageXY(const int x,const int y)       { this.m_painter.SetXY(x,y);                    }
   void              SetImageSize(const int w,const int h)     { this.m_painter.SetSize(w,h);                  }
//--- Устанавливает координаты и размеры области изображения
   void              SetImageBound(const int x,const int y,const int w,const int h)
                       {
                        this.SetImageXY(x,y);
                        this.SetImageSize(w,h);
                       }
//--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правую, (6) нижнюю границу области изображения
   int               ImageX(void)                        const { return this.m_painter.X();                    }
   int               ImageY(void)                        const { return this.m_painter.Y();                    }
   int               ImageWidth(void)                    const { return this.m_painter.Width();                }
   int               ImageHeight(void)                   const { return this.m_painter.Height();               }
   int               ImageRight(void)                    const { return this.m_painter.Right();                }
   int               ImageBottom(void)                   const { return this.m_painter.Bottom();               }

//--- Выводит текст
   void              DrawText(const int dx, const int dy, const string text, const bool chart_redraw);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (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_LABEL);                   }

//--- Конструкторы/деструктор
                     CLabel(void);
                     CLabel(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CLabel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CLabel(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h);
                    ~CLabel(void) {}
  };

Y para evitar prescribir ajustes de parámetros en cada constructor y en cada objeto, colóquelos en métodos separados:

//+------------------------------------------------------------------+
//| Класс текстовой метки                                            |
//+------------------------------------------------------------------+
class CLabel : public CElementBase
  {
protected:

   ushort            m_text[];                                 // Текст
   ushort            m_text_prev[];                            // Прошлый текст
   int               m_text_x;                                 // Координата X текста (смещение относительно  левой границы объекта)
   int               m_text_y;                                 // Координата Y текста (смещение относительно  верхней границы объекта)
   
//--- (1) Устанавливает, (2) возвращает прошлый текст
   void              SetTextPrev(const string text)            { ::StringToShortArray(text,this.m_text_prev);  }
   string            TextPrev(void)                      const { return ::ShortArrayToString(this.m_text_prev);}
      
//--- Стирает текст
   void              ClearText(void);

public:
//--- (1) Устанавливает, (2) возвращает текст
   void              SetText(const string text)                { ::StringToShortArray(text,this.m_text);       }
   string            Text(void)                          const { return ::ShortArrayToString(this.m_text);     }
   
//--- Возвращает координату (1) X, (2) Y текста
   int               TextX(void)                         const { return this.m_text_x;                         }
   int               TextY(void)                         const { return this.m_text_y;                         }

//--- Устанавливает координату (1) X, (2) Y текста
   void              SetTextShiftH(const int x)                { this.ClearText(); this.m_text_x=x;            }
   void              SetTextShiftV(const int y)                { this.ClearText(); this.m_text_y=y;            }
   
//--- Выводит текст
   void              DrawText(const int dx, const int dy, const string text, const bool chart_redraw);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (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_LABEL);                   }

//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(const string text);
   virtual void      InitColors(void){}
   
//--- Конструкторы/деструктор
                     CLabel(void);
                     CLabel(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CLabel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CLabel(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CLabel(void) {}
  };

Ahora, en lugar de utilizar este tipo de constructores de todos los objetos (como se hacía antes).

//+------------------------------------------------------------------+
//| CLabel::Конструктор по умолчанию. Строит метку в главном окне    |
//| текущего графика в координатах 0,0 с размерами по умолчанию      |
//+------------------------------------------------------------------+
CLabel::CLabel(void) : CCanvasBase("Label",::ChartID(),0,0,0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(0), m_text_y(0)
  {
//--- Объекту рисования назначаем канвас переднего плана и
//--- обнуляем координаты и размеры, что делает его неактивным
   this.m_painter.CanvasAssign(this.GetForeground());
   this.m_painter.SetXY(0,0);
   this.m_painter.SetSize(0,0);
//--- Устанавливаем текущий и предыдущий текст
   this.SetText("Label");
   this.SetTextPrev("");
//--- Фон - прозрачный, передний план - нет
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
  }

Todos ellos tendrán el siguiente aspecto:

//+------------------------------------------------------------------+
//| CLabel::Конструктор по умолчанию. Строит метку в главном окне    |
//| текущего графика в координатах 0,0 с размерами по умолчанию      |
//+------------------------------------------------------------------+
CLabel::CLabel(void) : CElementBase("Label","Label",::ChartID(),0,0,0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(0), m_text_y(0)
  {
//--- Инициализация
   this.Init("Label");
  }
//+------------------------------------------------------------------+
//| CLabel::Конструктор параметрический. Строит метку в главном окне |
//| текущего графика с указанными текстом, координами и размерами    |
//+------------------------------------------------------------------+
CLabel::CLabel(const string object_name, const string text,const int x,const int y,const int w,const int h) :
   CElementBase(object_name,text,::ChartID(),0,x,y,w,h), m_text_x(0), m_text_y(0)
  {
//--- Инициализация
   this.Init(text);
  }
//+-------------------------------------------------------------------+
//| CLabel::Конструктор параметрический. Строит метку в указанном окне|
//| текущего графика с указанными текстом, координами и размерами     |
//+-------------------------------------------------------------------+
CLabel::CLabel(const string object_name, const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CElementBase(object_name,text,::ChartID(),wnd,x,y,w,h), m_text_x(0), m_text_y(0)
  {
//--- Инициализация
   this.Init(text);
  }
//+-------------------------------------------------------------------+
//| CLabel::Конструктор параметрический. Строит метку в указанном окне|
//| указанного графика с указанными текстом, координами и размерами   |
//+-------------------------------------------------------------------+
CLabel::CLabel(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CElementBase(object_name,text,chart_id,wnd,x,y,w,h), m_text_x(0), m_text_y(0)
  {
//--- Инициализация
   this.Init(text);
  }

Implementar el método de inicialización:

//+------------------------------------------------------------------+
//| CLabel::Инициализация                                            |
//+------------------------------------------------------------------+
void CLabel::Init(const string text)
  {
//--- Устанавливаем текущий и предыдущий текст
   this.SetText(text);
   this.SetTextPrev("");
//--- Фон - прозрачный, передний план - нет
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
  }

Ahora, para las clases heredadas de la clase CLabel, es posible asignar un método para establecer colores predeterminados utilizando un método virtual InitColors().

Ahora se maximiza el número de propiedades a comparar en el método de comparación. Puede comparar por todas las propiedades disponibles de un elemento gráfico + texto de etiqueta:

//+------------------------------------------------------------------+
//| CLabel::Сравнение двух объектов                                  |
//+------------------------------------------------------------------+
int CLabel::Compare(const CObject *node,const int mode=0) const
  {
   if(node==NULL)
      return -1;
   const CLabel *obj=node;
   switch(mode)
     {
      case ELEMENT_SORT_BY_NAME     :  return(this.Name()         >obj.Name()          ? 1 : this.Name()          <obj.Name()          ? -1 : 0);
      case ELEMENT_SORT_BY_TEXT     :  return(this.Text()         >obj.Text()          ? 1 : this.Text()          <obj.Text()          ? -1 : 0);
      case ELEMENT_SORT_BY_X        :  return(this.X()            >obj.X()             ? 1 : this.X()             <obj.X()             ? -1 : 0);
      case ELEMENT_SORT_BY_Y        :  return(this.Y()            >obj.Y()             ? 1 : this.Y()             <obj.Y()             ? -1 : 0);
      case ELEMENT_SORT_BY_WIDTH    :  return(this.Width()        >obj.Width()         ? 1 : this.Width()         <obj.Width()         ? -1 : 0);
      case ELEMENT_SORT_BY_HEIGHT   :  return(this.Height()       >obj.Height()        ? 1 : this.Height()        <obj.Height()        ? -1 : 0);
      case ELEMENT_SORT_BY_COLOR_BG :  return(this.BackColor()    >obj.BackColor()     ? 1 : this.BackColor()     <obj.BackColor()     ? -1 : 0);
      case ELEMENT_SORT_BY_COLOR_FG :  return(this.ForeColor()    >obj.ForeColor()     ? 1 : this.ForeColor()     <obj.ForeColor()     ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA_BG :  return(this.AlphaBG()      >obj.AlphaBG()       ? 1 : this.AlphaBG()       <obj.AlphaBG()       ? -1 : 0);
      case ELEMENT_SORT_BY_ALPHA_FG :  return(this.AlphaFG()      >obj.AlphaFG()       ? 1 : this.AlphaFG()       <obj.AlphaFG()       ? -1 : 0);
      case ELEMENT_SORT_BY_STATE    :  return(this.State()        >obj.State()         ? 1 : this.State()         <obj.State()         ? -1 : 0);
      case ELEMENT_SORT_BY_ZORDER   :  return(this.ObjectZOrder() >obj.ObjectZOrder()  ? 1 : this.ObjectZOrder()  <obj.ObjectZOrder()  ? -1 : 0);
      default                       :  return(this.ID()           >obj.ID()            ? 1 : this.ID()            <obj.ID()            ? -1 : 0);
     }
  }

En los métodos para trabajar con archivos, ahora nos referimos a un método de la clase padre que no es CCanvasBase, sino a uno nuevo: CElementBase.

//+------------------------------------------------------------------+
//| CLabel::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CLabel::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CElementBase::Save(file_handle))
      return false;
  
//--- Сохраняем текст
   if(::FileWriteArray(file_handle,this.m_text)!=sizeof(this.m_text))
      return false;
//--- Сохраняем предыдущий текст
   if(::FileWriteArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev))
      return false;
//--- Сохраняем координату X текста
   if(::FileWriteInteger(file_handle,this.m_text_x,INT_VALUE)!=INT_VALUE)
      return false;
//--- Сохраняем координату Y текста
   if(::FileWriteInteger(file_handle,this.m_text_y,INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CLabel::Загрузка из файла                                        |
//+------------------------------------------------------------------+
bool CLabel::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CElementBase::Load(file_handle))
      return false;
      
//--- Загружаем текст
   if(::FileReadArray(file_handle,this.m_text)!=sizeof(this.m_text))
      return false;
//--- Загружаем предыдущий текст
   if(::FileReadArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev))
      return false;
//--- Загружаем координату X текста
   this.m_text_x=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем координату Y текста
   this.m_text_y=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Всё успешно
   return true;
  }


En la clase simple button declare los mismos dos métodos de inicialización y un controlador de eventos de temporizador:

//+------------------------------------------------------------------+
//| Класс простой кнопки                                             |
//+------------------------------------------------------------------+
class CButton : public CLabel
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CLabel::Save(file_handle); }
   virtual bool      Load(const int file_handle)               { return CLabel::Load(file_handle); }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON);      }
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(const string text);
   virtual void      InitColors(void){}
   
//--- Обработчик события таймера
   virtual void      TimerEventHandler(void);
   
//--- Конструкторы/деструктор
                     CButton(void);
                     CButton(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CButton(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CButton (void) {}
  };

El controlador de eventos del temporizador ejecutará los contadores de la clase de evento de repetición automática. 

En los constructores de clase, al igual que en la clase de etiqueta de texto, simplemente llame al método de inicialización de clase:

//+------------------------------------------------------------------+
//| CButton::Конструктор по умолчанию. Строит кнопку в главном окне  |
//| текущего графика в координатах 0,0 с размерами по умолчанию      |
//+------------------------------------------------------------------+
CButton::CButton(void) : CLabel("Button","Button",::ChartID(),0,0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
//--- Инициализация
   this.Init("");
  }
//+-------------------------------------------------------------------+
//| CButton::Конструктор параметрический. Строит кнопку в главном окне|
//| текущего графика с указанными текстом, координами и размерами     |
//+-------------------------------------------------------------------+
CButton::CButton(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CLabel(object_name,text,::ChartID(),0,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+---------------------------------------------------------------------+
//| CButton::Конструктор параметрический. Строит кнопку в указанном окне|
//| текущего графика с указанными текстом, координами и размерами       |
//+---------------------------------------------------------------------+
CButton::CButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CLabel(object_name,text,::ChartID(),wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+---------------------------------------------------------------------+
//| CButton::Конструктор параметрический. Строит кнопку в указанном окне|
//| указанного графика с указанными текстом, координами и размерами     |
//+---------------------------------------------------------------------+
CButton::CButton(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CLabel(object_name,text,chart_id,wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }


Dado que todos los botones de diferentes tipos se heredan de esta clase de botón simple, es suficiente establecer el indicador de evento de repetición automática e inicializar un objeto de la clase de evento de repetición automática. Y luego el botón estará dotado de esta funcionalidad. Aquí, para un botón simple, el indicador de evento de repetición automática se restablece en el método de inicialización:

//+------------------------------------------------------------------+
//| CButton::Инициализация                                           |
//+------------------------------------------------------------------+
void CButton::Init(const string text)
  {
//--- Устанавливаем состояние по умолчанию
   this.SetState(ELEMENT_STATE_DEF);
//--- Фон и передний план - непрозрачные
   this.SetAlpha(255);
//--- Смещение текста от левого края кнопки по умолчанию
   this.m_text_x=2;
//--- Автоповтор нажатий отключен
   this.m_autorepeat_flag=false;
  }

El método para comparar dos objetos devuelve el resultado de llamar al método similar de la clase padre:

//+------------------------------------------------------------------+
//| CButton::Сравнение двух объектов                                 |
//+------------------------------------------------------------------+
int CButton::Compare(const CObject *node,const int mode=0) const
  {
   return CLabel::Compare(node,mode);
  }

De hecho, puedes simplemente eliminar la declaración e implementación de este método virtual de esta clase. Y funcionará exactamente de la misma manera: se llamará al método de la clase padre. Pero dejémoslo así por ahora, ya que la biblioteca aún está en desarrollo y puede ser necesario refinar este método aquí. Como resultado, al final del desarrollo, la necesidad de este método será visible aquí (y en clases posteriores de elementos simples que se perfeccionarán).

En el controlador de eventos del temporizador, el método principal de la clase de repetición automática de eventos se activa si el indicador de repetición automática de eventos está configurado para la clase:

//+------------------------------------------------------------------+
//| Обработчик события таймера                                       |
//+------------------------------------------------------------------+
void CButton::TimerEventHandler(void)
  {
   if(this.m_autorepeat_flag)
      this.m_autorepeat.Process();
  }


Cambios en la clase de botones de dos posiciones:

//+------------------------------------------------------------------+
//| Класс двухпозиционной кнопки                                     |
//+------------------------------------------------------------------+
class CButtonTriggered : public CButton
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);      }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);      }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON_TRIGGERED);  }
  
//--- Обработчик событий нажатий кнопок мышки (Press)
   virtual void      OnPressEvent(const int id, const long lparam, const double dparam, const string sparam);

//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(const string text);
   virtual void      InitColors(void);
   
//--- Конструкторы/деструктор
                     CButtonTriggered(void);
                     CButtonTriggered(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CButtonTriggered(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CButtonTriggered(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CButtonTriggered (void) {}
  };
//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор по умолчанию.                      |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(void) : CButton("Button","Button",::ChartID(),0,0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор параметрический.                   |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,::ChartID(),0,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,::ChartID(),wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonTriggered::Конструктор параметрический.                   |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonTriggered::CButtonTriggered(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,chart_id,wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonTriggered::Инициализация                                  |
//+------------------------------------------------------------------+
void CButtonTriggered::Init(const string text)
  {
//--- Инициализируем цвета по умолчанию
   this.InitColors();
  }

En general, aquí todo se reduce a un estándar común para clases de elementos simples: llamar al método Init() en el constructor de la clase y prescribir allí los pasos necesarios para inicializar la clase. Esto ya se ha hecho para todas las clases de elementos simples de la interfaz de usuario.

En las clases de botones de flecha, en sus métodos de inicialización, es necesario establecer indicadores para utilizar la repetición automática de eventos y establecer los parámetros del objeto de la clase de repetición automática de eventos.

Vea cómo se hace esto utilizando un ejemplo de la clase del botón de flecha hacia arriba:

//+------------------------------------------------------------------+
//| Класс кнопки со стрелкой вверх                                   |
//+------------------------------------------------------------------+
class CButtonArrowUp : public CButton
  {
public:
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle)               { return CButton::Save(file_handle);   }
   virtual bool      Load(const int file_handle)               { return CButton::Load(file_handle);   }
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BUTTON_ARROW_UP);}
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(const string text);
   virtual void      InitColors(void){}
   
//--- Конструкторы/деструктор
                     CButtonArrowUp(void);
                     CButtonArrowUp(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CButtonArrowUp(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CButtonArrowUp(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CButtonArrowUp (void) {}
  };
//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор по умолчанию.                        |
//| Строит кнопку в главном окне текущего графика                    |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(void) : CButton("Arrow Up Button","",::ChartID(),0,0,0,DEF_BUTTON_W,DEF_BUTTON_H)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор параметрический.                     |
//| Строит кнопку в главном окне текущего графика                    |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(const string object_name, const string text,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,::ChartID(),0,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор параметрический.                     |
//| Строит кнопку в указанном окне текущего графика                  |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,::ChartID(),wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonArrowUp::Конструктор параметрический.                     |
//| Строит кнопку в указанном окне указанного графика                |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CButtonArrowUp::CButtonArrowUp(const string object_name, const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,chart_id,wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CButtonArrowUp::Инициализация                                    |
//+------------------------------------------------------------------+
void CButtonArrowUp::Init(const string text)
  {
//--- Инициализируем цвета по умолчанию
   this.InitColors();
//--- Устанавливаем смещение и размеры области изображенеия
   this.SetImageBound(1,1,this.Height()-2,this.Height()-2);

//--- Инициализируем счётчики автоповтора
   this.m_autorepeat_flag=true;

//--- Инициализируем свойства объекта управления автоповтором событий
   this.m_autorepeat.SetChartID(this.m_chart_id);
   this.m_autorepeat.SetID(0);
   this.m_autorepeat.SetName("ButtUpAutorepeatControl");
   this.m_autorepeat.SetDelay(DEF_AUTOREPEAT_DELAY);
   this.m_autorepeat.SetInterval(DEF_AUTOREPEAT_INTERVAL);
   this.m_autorepeat.SetEvent(CHARTEVENT_OBJECT_CLICK,0,0,this.NameFG());
  }

Lo mismo se hace en las clases de los botones de flecha abajo, izquierda y derecha. 


Clases de contenedor para colocar controles

Todos los elementos creados anteriormente son controles simples. Son bastante funcionales y tienen un comportamiento personalizable para la interacción del usuario. Pero..., estos son controles sencillos. Ahora es necesario desarrollar elementos contenedores que permitan adjuntar otros componentes gráficos a sí mismos y proporcionar una gestión conjunta de un grupo de objetos vinculados. Y el primer elemento de la lista de contenedores es el "Panel". 

Clase Panel

El elemento gráfico Panel es un elemento contenedor base de la interfaz de usuario. Está diseñado para agrupar y organizar otros elementos gráficos en el concepto general de la interfaz gráfica del programa. El panel sirve como base para construir elementos complejos: en él se colocan botones, etiquetas, campos de entrada y otros controles. Utilizando el panel, puedes estructurar el espacio visual, crear bloques lógicos, grupos de configuraciones y cualquier otro elemento componente de la interfaz. El panel no solo combina visualmente elementos vinculados, sino que también controla su posición, visibilidad, bloqueo, manejo de eventos y comportamiento con estado.

La clase de panel permitirá ubicar muchos controles diferentes en ella. Todos los elementos que vayan más allá de los límites del panel serán recortados en sus bordes. Todas las manipulaciones que se realicen programáticamente con el panel también afectarán a todos los controles incluidos en el panel (ocultar, mostrar, mover, etc.).

Continúe escribiendo el código en el archivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс панели                                                     |
//+------------------------------------------------------------------+
class CPanel : public CLabel
  {
private:
   CElementBase      m_temp_elm;                // Временный объект для поиска элементов
   CBound            m_temp_bound;              // Временный объект для поиска областей
protected:
   CListObj          m_list_elm;                // Список прикреплённых элементов
   CListObj          m_list_bounds;             // Список областей
//--- Добавляет новый элемент в список
   bool              AddNewElement(CElementBase *element);

public:
//--- Возвращает указатель на список (1) прикреплённых элементов, (2) областей
   CListObj         *GetListAttachedElements(void)             { return &this.m_list_elm;                         }
   CListObj         *GetListBounds(void)                       { return &this.m_list_bounds;                      }
//--- Возвращает элемент по (1) индексу в списке, (2) идентификатору, (3) назначенному имени объекта
   CElementBase     *GetAttachedElementAt(const uint index)    { return this.m_list_elm.GetNodeAtIndex(index);    }
   CElementBase     *GetAttachedElementByID(const int id);
   CElementBase     *GetAttachedElementByName(const string name);
   
//--- Возвращает область по (1) индексу в списке, (2) идентификатору, (3) назначенному имени области
   CBound           *GetBoundAt(const uint index)              { return this.m_list_bounds.GetNodeAtIndex(index); }
   CBound           *GetBoundByID(const int id);
   CBound           *GetBoundByName(const string name);
   
//--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список
   virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h);
   virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy);

//--- Создаёт и добавляет в список новую область
   CBound           *InsertNewBound(const string name,const int dx,const int dy,const int w,const int h);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);
   
//--- Виртуальные методы (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_PANEL);                      }
  
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);
   virtual void      InitColors(void);
   
//--- Устанавливает объекту новые координаты XY
   virtual bool      Move(const int x,const int y);
//--- Смещает объект по осям XY на указанное смещение
   virtual bool      Shift(const int dx,const int dy);

//--- (1) Скрывает (2) отображает объект на всех периодах графика,
//--- (3) помещает объект на передний план, (4) блокирует, (5) разблокирует элемент,
   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);
   
//--- Выводит в журнал описание объекта
   virtual void      Print(void);
   
//--- Распечатывает список (1) присоединённых объектов, (2) областей
   void              PrintAttached(const uint tab=3);
   void              PrintBounds(void);

//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
   
//--- Обработчик события таймера
   virtual void      TimerEventHandler(void);
   
//--- Конструкторы/деструктор
                     CPanel(void);
                     CPanel(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CPanel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CPanel(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CPanel (void) { this.m_list_elm.Clear(); this.m_list_bounds.Clear(); }
  };

En los constructores, en la cadena de inicialización, todos los parámetros formales se pasan a la clase padre y, a continuación, se llama al método de inicialización del objeto:

//+------------------------------------------------------------------+
//| CPanel::Инициализация                                            |
//+------------------------------------------------------------------+
void CPanel::Init(void)
  {
//--- Инициализация цветов по умолчанию
   this.InitColors();
//--- Фон - прозрачный, передний план - нет
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
//--- Устанавливаем смещение и размеры области изображенеия
   this.SetImageBound(0,0,this.Width(),this.Height());
//--- Ширина рамки
   this.SetBorderWidth(2);
  }

Método de inicialización del color predeterminado del objeto:

//+------------------------------------------------------------------+
//| CPanel::Инициализация цветов объекта по умолчанию                |
//+------------------------------------------------------------------+
void CPanel::InitColors(void)
  {
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke);
   this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke);
   this.BackColorToDefault();
   
//--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста
   this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver);
   this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver);
   this.ForeColorToDefault();
   
//--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки
   this.InitBorderColors(clrNULL,clrNULL,clrNULL,clrNULL);
   this.InitBorderColorsAct(clrNULL,clrNULL,clrNULL,clrNULL);
   this.BorderColorToDefault();
   
//--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента
   this.InitBorderColorBlocked(clrNULL);
   this.InitForeColorBlocked(clrSilver);
  }

En el método para comparar dos objetos se devuelve el resultado de ejecutar el método similar de la clase padre:

//+------------------------------------------------------------------+
//| CPanel::Сравнение двух объектов                                  |
//+------------------------------------------------------------------+
int CPanel::Compare(const CObject *node,const int mode=0) const
  {
   return CLabel::Compare(node,mode);
  }

El método para dibujar el aspecto del panel:

//+------------------------------------------------------------------+
//| CPanel::Рисует внешний вид                                       |
//+------------------------------------------------------------------+
void CPanel::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона
   this.Fill(this.BackColor(),false);
   
//--- Очищаем область рисунка
   this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false);
//--- Задаём цвет для тёмной и светлой линий и рисуем рамку панели
   color clr_dark =(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(),-20,-20,-20));
   color clr_light=(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(),  6,  6,  6));
   this.m_painter.FrameGroupElements(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),
                                     this.m_painter.Width(),this.m_painter.Height(),this.Text(),
                                     this.ForeColor(),clr_dark,clr_light,this.AlphaFG(),true);
   
//--- Обновляем канвас фона без перерисовки графика
   this.m_background.Update(false);
   
//--- Рисуем элементы списка
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.Draw(false);
     }
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Primero se dibuja el panel y, a continuación, se enlazan todos los controles asociados a él.

Un método que añade un nuevo elemento a la lista:

//+------------------------------------------------------------------+
//| CPanel::Добавляет новый элемент в список                         |
//+------------------------------------------------------------------+
bool CPanel::AddNewElement(CElementBase *element)
  {
//--- Если передан пустой указатель - сообщаем об этом и возвращаем false
   if(element==NULL)
     {
      ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__);
      return false;
     }
//--- Устанавливаем списку флаг сортировки по идентификатору
   this.m_list_elm.Sort(ELEMENT_SORT_BY_ID);
//--- Если такого элемента нет в списке - возвращаем результат его добавления в список
   if(this.m_list_elm.Search(element)==NULL)
      return(this.m_list_elm.Add(element)>-1);
//--- Элемент с таким идентификатором уже есть в списке - возвращаем false
   return false;
  }

Se pasa al método un puntero al elemento que se va a colocar en la lista. Si el elemento con el ID asignado aún no está en la lista, devuelve el resultado de añadir el elemento a la lista. De lo contrario, devuelve false.

Un método que implementa y añade un nuevo elemento a la lista:

//+------------------------------------------------------------------+
//| CPanel::Создаёт и добавляет новый элемент в список               |
//+------------------------------------------------------------------+
CElementBase *CPanel::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h)
  {
//--- Создаём имя графического объекта
   int elm_total=this.m_list_elm.Total();
   string obj_name=this.NameFG()+"_"+ElementShortName(type)+(string)elm_total;
//--- Рассчитываем координаты
   int x=this.X()+dx;
   int y=this.Y()+dy;
//--- В зависимости от типа объекта, создаём новый объект
   CElementBase *element=NULL;
   switch(type)
     {
      case ELEMENT_TYPE_LABEL             :  element = new CLabel(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);             break;   // Текстовая метка
      case ELEMENT_TYPE_BUTTON            :  element = new CButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);            break;   // Простая кнопка
      case ELEMENT_TYPE_BUTTON_TRIGGERED  :  element = new CButtonTriggered(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Двухпозиционная кнопка
      case ELEMENT_TYPE_BUTTON_ARROW_UP   :  element = new CButtonArrowUp(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);     break;   // Кнопка со стрелкой вверх
      case ELEMENT_TYPE_BUTTON_ARROW_DOWN :  element = new CButtonArrowDown(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Кнопка со стрелкой вниз
      case ELEMENT_TYPE_BUTTON_ARROW_LEFT :  element = new CButtonArrowLeft(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Кнопка со стрелкой влево
      case ELEMENT_TYPE_BUTTON_ARROW_RIGHT:  element = new CButtonArrowRight(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);  break;   // Кнопка со стрелкой вправо
      case ELEMENT_TYPE_CHECKBOX          :  element = new CCheckBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);          break;   // Элемент управления CheckBox
      case ELEMENT_TYPE_RADIOBUTTON       :  element = new CRadioButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);       break;   // Элемент управления RadioButton
      case ELEMENT_TYPE_PANEL             :  element = new CPanel(obj_name,"",this.m_chart_id,this.m_wnd,x,y,w,h);               break;   // Элемент управления Panel
      case ELEMENT_TYPE_GROUPBOX          :  element = new CGroupBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);          break;   // Элемент управления GroupBox
      case ELEMENT_TYPE_SCROLLBAR_THUMB_H :  element = new CScrollBarThumbH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Полоса прокрутки горизонтального ScrollBar
      case ELEMENT_TYPE_SCROLLBAR_THUMB_V :  element = new CScrollBarThumbV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);   break;   // Полоса прокрутки вертикального ScrollBar
      case ELEMENT_TYPE_SCROLLBAR_H       :  element = new CScrollBarH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);        break;   // Элемент управления горизонтальный ScrollBar
      case ELEMENT_TYPE_SCROLLBAR_V       :  element = new CScrollBarV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);        break;   // Элемент управления вертикальный ScrollBar
      case ELEMENT_TYPE_CONTAINER         :  element = new CContainer(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);         break;   // Элемент управления Container
      default                             :  element = NULL;
     }
   
//--- Если новый элемент не создан - сообщаем об этом и возвращаем NULL
   if(element==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create graphic element %s",__FUNCTION__,ElementDescription(type));
      return NULL;
     }
//--- Устанавливаем идентификатор, имя, контейнер и z-order элемента
   element.SetID(elm_total);
   element.SetName(user_name);
   element.SetContainerObj(&this);
   element.ObjectSetZOrder(this.ObjectZOrder()+1);
   
//--- Если созданный элемент не добавлен в список - сообщаем об этом, удаляем созданный элемент и возвращаем NULL
   if(!this.AddNewElement(element))
     {
      ::PrintFormat("%s: Error. Failed to add %s element with ID %d to list",__FUNCTION__,ElementDescription(type),element.ID());
      delete element;
      return NULL;
     }
//--- Получаем родительский элемент, к которому привязаны дочерние
   CElementBase *elm=this.GetContainer();
//--- Если родительский элемент имеет тип "Контейнер", значит, у него есть полосы прокрутки
   if(elm!=NULL && elm.Type()==ELEMENT_TYPE_CONTAINER)
     {
      //--- Преобразуем CElementBase в CContainer
      CContainer *container_obj=elm;
      //--- Если горизонтальная полоса прокрутки видима,
      if(container_obj.ScrollBarHorIsVisible())
        {
         //--- получаем указатель на горизонтальный скроллбар и переносим его на передний план
         CScrollBarH *sbh=container_obj.GetScrollBarH();
         if(sbh!=NULL)
            sbh.BringToTop(false);
        }
      //--- Если вертикальная полоса прокрутки видима,
      if(container_obj.ScrollBarVerIsVisible())
        {
         //--- получаем указатель на вертикальный скроллбар и переносим его на передний план
         CScrollBarV *sbv=container_obj.GetScrollBarV();
         if(sbv!=NULL)
            sbv.BringToTop(false);
        }
     }
//--- Возвращаем указатель на созданный и присоединённый элемент
   return element;
  }

En los comentarios sobre el método se describe detalladamente toda su lógica. Permítanme señalar que, al crear nuevas clases de nuevos controles, aquí registraremos nuevos tipos de elementos para crearlos.

Un método que añade un elemento específico a la lista:

//+------------------------------------------------------------------+
//| CPanel::Добавляет указанный элемент в список                     |
//+------------------------------------------------------------------+
CElementBase *CPanel::InsertElement(CElementBase *element,const int dx,const int dy)
  {
//--- Если передан пустой или невалидный указатель на элемент - возвращаем NULL
   if(::CheckPointer(element)==POINTER_INVALID)
     {
      ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__);
      return NULL;
     }
//--- Если передан базовый элемент - возвращаем NULL
   if(element.Type()==ELEMENT_TYPE_BASE)
     {
      ::PrintFormat("%s: Error. The base element cannot be used",__FUNCTION__);
      return NULL;
     }
//--- Запоминаем идентификатор элемента и устанавливаем новый
   int id=element.ID();
   element.SetID(this.m_list_elm.Total());
   
//--- Добавляем элемент в список; при неудаче - сообщаем об этом, устанавливаем начальный идентификатор и возвращаем NULL
   if(!this.AddNewElement(element))
     {
      ::PrintFormat("%s: Error. Failed to add element %s to list",__FUNCTION__,ElementDescription((ENUM_ELEMENT_TYPE)element.Type()));
      element.SetID(id);
      return NULL;
     }
//--- Устанавливаем новые координаты, контейнер и z-order элемента
   int x=this.X()+dx;
   int y=this.Y()+dy;
   element.Move(x,y);
   element.SetContainerObj(&this);
   element.ObjectSetZOrder(this.ObjectZOrder()+1);
     
//--- Возвращаем указатель на присоединённый элемент
   return element;
  }

El método, a diferencia del anterior, no crea un nuevo elemento, sino que añade uno ya existente a la lista, cuyo puntero se pasa al método. Si el elemento se ha eliminado o el puntero al elemento es NULL, el método regresa. Si un elemento sigue sin poder colocarse en la lista, se le devuelve el identificador que se le había asignado antes de intentar unirse a la lista. Si se añade un elemento a la lista, se le proporcionan nuevas coordenadas especificadas en los parámetros formales del método, y se le asignan un contenedor y un valor de orden z.

Un método que devuelve un elemento por ID:

//+------------------------------------------------------------------+
//| CPanel::Возвращает элемент по идентификатору                     |
//+------------------------------------------------------------------+
CElementBase *CPanel::GetAttachedElementByID(const int id)
  {
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL && elm.ID()==id)
         return elm;
     }
   return NULL;
  }

En un bucle que recorre todos los elementos vinculados, busca un elemento con el identificador especificado. Si se encuentra, devuelve el puntero al elemento de la lista. Si no se encuentra, devuelve NULL.

Método que devuelve un elemento por el nombre del objeto asignado:

//+------------------------------------------------------------------+
//| CPanel::Возвращает элемент по назначенному имени объекта         |
//+------------------------------------------------------------------+
CElementBase *CPanel::GetAttachedElementByName(const string name)
  {
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL && elm.Name()==name)
         return elm;
     }
   return NULL;
  }

En un bucle que recorre todos los elementos vinculados, busca un elemento con el nombre de usuario especificado. Si se encuentra, devuelve el puntero al elemento de la lista. Si no se encuentra, devuelve NULL. El método ofrece una búsqueda cómoda por el nombre asignado al elemento gráfico. Es más conveniente nombrar un elemento y referirse a él por su nombre único que recordar identificadores impersonales para referirse al elemento.

Un método que implementa y añade una nueva área a la lista:

//+------------------------------------------------------------------+
//| Создаёт и добавляет в список новую область                       |
//+------------------------------------------------------------------+
CBound *CPanel::InsertNewBound(const string name,const int dx,const int dy,const int w,const int h)
  {
//--- Проверяем есть ли в списке область с указанным именем и, если да - сообщаем об этом и возвращаем NULL
   this.m_temp_bound.SetName(name);
   if(this.m_list_bounds.Search(&this.m_temp_bound)!=NULL)
     {
      ::PrintFormat("%s: Error. An area named \"%s\" is already in the list",__FUNCTION__,name);
      return NULL;
     }
//--- Создаём новый объект-область; при неудаче - сообщаем об этом и возвращаем NULL
   CBound *bound=new CBound(dx,dy,w,h);
   if(bound==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create CBound object",__FUNCTION__);
      return NULL;
     }
//--- Если новый объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем NULL
   if(this.m_list_bounds.Add(bound)==-1)
     {
      ::PrintFormat("%s: Error. Failed to add CBound object to list",__FUNCTION__);
      delete bound;
      return NULL;
     }
//--- Устанавливаем имя области и идентификатор, и возвращаем указатель на объект
   bound.SetName(name);
   bound.SetID(this.m_list_bounds.Total());
   return bound;
  }

Se pueden marcar varias áreas independientes en el panel. Se pueden controlar por separado. El programador es quien decide qué se debe colocar en cada área individual, pero las áreas separadas añaden flexibilidad a la hora de planificar y crear interfaces gráficas. Todas las áreas se almacenan en una lista, y el método anterior crea un nuevo objeto de área y lo añade a la lista, asignándole un nombre que se pasa en los parámetros formales del método y un identificador que depende del número total de áreas de la lista.

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

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

Imprima la descripción del objeto y todos los elementos asociados en el registro.

Un método que imprime una lista de objetos adjuntos:

//+------------------------------------------------------------------+
//| CPanel::Распечатывает список присоединённых объектов             |
//+------------------------------------------------------------------+
void CPanel::PrintAttached(const uint tab=3)
  {
//--- В цикле по всем привязанным элементам
   int total=this.m_list_elm.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередной элемент
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm==NULL)
         continue;
      //--- Получаем тип элемента и, если это полоса прокрутки - пропускаем его
      ENUM_ELEMENT_TYPE type=(ENUM_ELEMENT_TYPE)elm.Type();
      if(type==ELEMENT_TYPE_SCROLLBAR_H || type==ELEMENT_TYPE_SCROLLBAR_V)
         continue;
      //--- Распечатываем в журнале описание элемента
      ::PrintFormat("%*s[%d]: %s",tab,"",i,elm.Description());
      //--- Если элемент является контейнером - распечатываем в журнал его список привязанных элементов
      if(type==ELEMENT_TYPE_PANEL || type==ELEMENT_TYPE_GROUPBOX || type==ELEMENT_TYPE_CONTAINER)
        {
         CPanel *obj=elm;
         obj.PrintAttached(tab*2);
        }
     }
  }

El método registra descripciones de todos los elementos ubicados en la lista de objetos adjuntos.

Un método que imprime la lista de áreas en el registro:

//+------------------------------------------------------------------+
//| CPanel::Распечатывает список областей                            |
//+------------------------------------------------------------------+
void CPanel::PrintBounds(void)
  {
//--- В цикле по списку областей элемента
   int total=this.m_list_bounds.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередную область и распечатываем её описание в журнал
      CBound *obj=this.GetBoundAt(i);
      if(obj==NULL)
         continue;
      ::PrintFormat("  [%d]: %s",i,obj.Description());
     }
  

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

//+------------------------------------------------------------------+
//| CPanel::Устанавливает объекту новые координаты X и Y             |
//+------------------------------------------------------------------+
bool CPanel::Move(const int x,const int y)
  {
   //--- Вычисляем дистанцию, на которую сместится элемент
   int delta_x=x-this.X();
   int delta_y=y-this.Y();

   //--- Перемещаем элемент на указанные координаты
   bool res=this.ObjectMove(x,y);
   if(!res)
      return false;
   this.BoundMove(x,y);
   this.ObjectTrim();
   
//--- Перемещаем все привязанные элементы на рассчитанную дистанцию
   int total=this.m_list_elm.Total();
   for(int i=0;i<total;i++)
     {
      //--- Перемещаем привязанный элемент с учётом смещения родительского элемента
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         res &=elm.Move(elm.X()+delta_x, elm.Y()+delta_y);
     }
//--- Возвращаем результат перемещения всех привязанных элементов
   return res;
  }

En primer lugar, se calcula la distancia a la que se desplazará el elemento. Luego, el elemento se desplaza a las coordenadas especificadas. Después de esto, todos los elementos adjuntos se desplazan la distancia calculada al principio.

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

//+------------------------------------------------------------------+
//| CPanel::Смещает объект по осям X и Y на указанное смещение       |
//+------------------------------------------------------------------+
bool CPanel::Shift(const int dx,const int dy)
  {
//--- Смещаем элемент на указанную дистанцию
   bool res=this.ObjectShift(dx,dy);
   if(!res)
      return false;
   this.BoundShift(dx,dy);
   this.ObjectTrim();
   
//--- Смещаем все привязанные элементы
   int total=this.m_list_elm.Total();
   for(int i=0;i<total;i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         res &=elm.Shift(dx,dy);
     }
//--- Возвращаем результат смещения всех привязанных элементов
   return res;
  }

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

//+------------------------------------------------------------------+
//| CPanel::Скрывает объект на всех периодах графика                 |
//+------------------------------------------------------------------+
void CPanel::Hide(const bool chart_redraw)
  {
//--- Если объект уже скрыт - уходим
   if(this.m_hidden)
      return;
      
//--- Скрываем панель
   CCanvasBase::Hide(false);
//--- Скрываем прикреплённые объекты
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.Hide(false);
     }
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

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

//+------------------------------------------------------------------+
//| CPanel::Отображает объект на всех периодах графика               |
//+------------------------------------------------------------------+
void CPanel::Show(const bool chart_redraw)
  {
//--- Если объект уже видимый - уходим
   if(!this.m_hidden)
      return;
      
//--- Отображаем панель
   CCanvasBase::Show(false);
//--- Отображаем прикреплённые объекты
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.Show(false);
     }
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

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

//+------------------------------------------------------------------+
//| CPanel::Помещает объект на передний план                         |
//+------------------------------------------------------------------+
void CPanel::BringToTop(const bool chart_redraw)
  {
//--- Помещаем панель на передний план
   CCanvasBase::BringToTop(false);
//--- Помещаем на передний план прикреплённые объекты
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.BringToTop(false);
     }
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Un método que bloquea el elemento:

//+------------------------------------------------------------------+
//| CPanel::Блокирует элемент                                        |
//+------------------------------------------------------------------+
void CPanel::Block(const bool chart_redraw)
  {
//--- Если элемент уже заблокирован - уходим
   if(this.m_blocked)
      return;
      
//--- Блокируем панель
   CCanvasBase::Block(false);
//--- Блокируем прикреплённые объекты
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.Block(false);
     }
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Un método que desbloquea el elemento:

//+------------------------------------------------------------------+
//| CPanel::Разблокирует элемент                                     |
//+------------------------------------------------------------------+
void CPanel::Unblock(const bool chart_redraw)
  {
//--- Если элемент уже разблокирован - уходим
   if(!this.m_blocked)
      return;
      
//--- Разблокируем панель
   CCanvasBase::Unblock(false);
//--- Разблокируем прикреплённые объекты
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.Unblock(false);
     }
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Un método para manejar archivos:

//+------------------------------------------------------------------+
//| CPanel::Сохранение в файл                                        |
//+------------------------------------------------------------------+
bool CPanel::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CElementBase::Save(file_handle))
      return false;
  
//--- Сохраняем список прикреплённых элементов
   if(!this.m_list_elm.Save(file_handle))
      return false;
//--- Сохраняем список областей
   if(!this.m_list_bounds.Save(file_handle))
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CPanel::Загрузка из файла                                        |
//+------------------------------------------------------------------+
bool CPanel::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CElementBase::Load(file_handle))
      return false;
      
//--- Загружаем список прикреплённых элементов
   if(!this.m_list_elm.Load(file_handle))
      return false;
//--- Загружаем список областей
   if(!this.m_list_bounds.Load(file_handle))
      return false;
   
//--- Всё успешно
   return true;
  }

Controlador de eventos:

//+------------------------------------------------------------------+
//| CPanel::Обработчик событий                                       |
//+------------------------------------------------------------------+
void CPanel::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Вызываем обработчик событий родительского класса
   CCanvasBase::OnChartEvent(id,lparam,dparam,sparam);
//--- В цикле по всем привязанным элементам
   int total=this.m_list_elm.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередной элемент и вызываем его обработчик событий
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.OnChartEvent(id,lparam,dparam,sparam);
     }
  }

Primero, se llama al controlador de eventos del panel, luego se llaman los controladores de los elementos adjuntos en un bucle a través de la lista de elementos adjuntos.

Controlador de eventos del temporizador:

//+------------------------------------------------------------------+
//| CPanel::Обработчик события таймера                               |
//+------------------------------------------------------------------+
void CPanel::TimerEventHandler(void)
  {
//--- В цикле по всем привязанным элементам
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      //--- получаем очередной элемент и вызываем его обработчик событий таймера
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.TimerEventHandler();
     }
  }

Primero, se llama al controlador del temporizador del panel. A continuación, se invocan los controladores de los elementos adjuntos en un bucle que recorre la lista de elementos adjuntos. Si se implementa un controlador virtual para cualquier elemento, este gestionará el evento del temporizador. Por el momento, dichos controladores están implementados para los botones.

El siguiente control será un Grupo de Objetos (GroupBox). Permite crear "cuadros de grupo", comunes en las interfaces de programa: son bloques visuales con un encabezado que contiene controles relacionados (por ejemplo, botones de opción, casillas de verificación, botones, campos de entrada, etc.). Este enfoque ayuda a estructurar la interfaz, aumentar su legibilidad y facilidad de uso. La clase se heredará de la clase de objeto Panel, lo que le permitirá obtener todas las características del panel de la clase padre: agregar/eliminar elementos, administrar su posición, manejo de eventos, guardar/cargar estado, etc.


Clase GroupBox

Continúe escribiendo el código en el archivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс группы объектов                                            |
//+------------------------------------------------------------------+
class CGroupBox : public CPanel
  {
public:
//--- Тип объекта
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_GROUPBOX); }
  
//--- Инициализация объекта класса
   void              Init(void);
   
//--- Устанавливает группу элементов
   virtual void      SetGroup(const int group);
   
//--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список
   virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h);
   virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy);

//--- Конструкторы/деструктор
                     CGroupBox(void);
                     CGroupBox(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CGroupBox(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CGroupBox(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CGroupBox(void) {}
  };

Aquí se ha agregado un nuevo método para configurar un grupo y se redefinirán los métodos para crear y agregar elementos a la lista.

En los constructores de clase, en la lista de inicialización, todos los valores pasados en parámetros formales se pasarán al constructor de la clase padre. Luego se llama al método de inicialización:

//+------------------------------------------------------------------+
//| CGroupBox::Конструктор по умолчанию.                             |
//| Строит элемент в главном окне текущего графика                   |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CGroupBox::CGroupBox(void) : CPanel("GroupBox","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CGroupBox::Конструктор параметрический.                          |
//| Строит элемент в главном окне текущего графика                   |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CGroupBox::CGroupBox(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,::ChartID(),0,x,y,w,h)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CGroupBox::Конструктор параметрический.                          |
//| Строит элемент в указанном окне текущего графика                 |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CGroupBox::CGroupBox(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,::ChartID(),wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CGroupBox::Конструктор параметрический.                          |
//| Строит элемент в указанном окне указанного графика               |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CGroupBox::CGroupBox(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,chart_id,wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init();
  }

Un elemento se inicializa llamando al método de inicialización de la clase padre:

//+------------------------------------------------------------------+
//| CGroupBox::Инициализация                                         |
//+------------------------------------------------------------------+
void CGroupBox::Init(void)
  {
//--- Инициализация при помощи родительского класса
   CPanel::Init();
  }

Un método que establece un grupo de elementos:

//+------------------------------------------------------------------+
//| CGroupBox::Устанавливает группу элементов                        |
//+------------------------------------------------------------------+
void CGroupBox::SetGroup(const int group)
  {
//--- Устанавливаем группу этому элементу методом родительского класса
   CElementBase::SetGroup(group);
//--- В цикле по списку привязанных элементов 
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      //--- получаем очередной элемент и назначаем ему группу
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.SetGroup(group);
     }
  }

Después de establecer un grupo para un elemento, se establece un grupo para cada control subordinado en un bucle a través de la lista de objetos adjuntos.

Un método que crea y agrega un nuevo elemento a la lista:

//+------------------------------------------------------------------+
//| CGroupBox::Создаёт и добавляет новый элемент в список            |
//+------------------------------------------------------------------+
CElementBase *CGroupBox::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h)
  {
//--- Создаём и добавляем в список элементов новый элемент
   CElementBase *element=CPanel::InsertNewElement(type,text,user_name,dx,dy,w,h);
   if(element==NULL)
      return NULL;
//--- Устанавливаем созданному элементу группу, равную группе этого объекта
   element.SetGroup(this.Group());
   return element;
  }

Primero, se crea un nuevo elemento y se agrega a la lista de objetos vinculados utilizando el método de la clase principal. Luego, al elemento recién creado se le asigna un grupo de este objeto.

Un método que añade un elemento específico a la lista:

//+------------------------------------------------------------------+
//| CGroupBox::Добавляет указанный элемент в список                  |
//+------------------------------------------------------------------+
CElementBase *CGroupBox::InsertElement(CElementBase *element,const int dx,const int dy)
  {
//--- Добавляем в список элементов новый элемент
   if(CPanel::InsertElement(element,dx,dy)==NULL)
      return NULL;
//--- Устанавливаем добавленному элементу группу, равную группе этого объекта
   element.SetGroup(this.Group());
   return element;
  }

Aquí, similar al método anterior, pero el puntero pasado al método a un elemento creado previamente se agrega a la lista.

Todos los elementos agregados a CGroupBox obtienen automáticamente el mismo ID de grupo asignado al panel. Esto le permite implementar lógica cuando, por ejemplo, solo un elemento de un grupo puede estar activo (relevante para botones de opción) o cuando se requiere un control masivo del estado de un grupo de elementos. CGroupBox muestra un borde con un encabezado, que separa su área del resto de la interfaz. El método SetGroup permite asignar un nuevo identificador a todo el grupo, configurándolo automáticamente para todos los elementos vinculados.

El siguiente es la clase Container, que se utiliza para crear áreas de interfaz donde el contenido puede extenderse más allá del área visible. En tales casos, se le da al usuario la opción de desplazarse por el contenido utilizando una barra de desplazamiento horizontal y/o vertical. Esto es especialmente importante para implementar listas desplazables, tablas, formularios grandes, gráficos y otros elementos que puedan exceder el tamaño del contenedor.

La clase contenedora se heredará de la clase panel. Pero para crearlo, primero es necesario crear elementos auxiliares: clases de barras de desplazamiento verticales y horizontales. 

Una barra de desplazamiento es un elemento de la interfaz de usuario diseñado para desplazarse por el contenido que no cabe en el área visible de la ventana (contenedor). Las barras de desplazamiento permiten al usuario navegar horizontal y verticalmente, controlando la visualización de grandes listas, tablas, formularios y otros elementos. En las interfaces gráficas, las barras de desplazamiento proporcionan una navegación sencilla y hacen que trabajar con una gran cantidad de información sea fácil y familiar para el usuario.

Clases para crear barras de desplazamiento

La clase Scrollbar está compuesta de elementos que incluyen:

  • Dos botones de flecha (izquierda/derecha o arriba/abajo) para desplazarse paso a paso.
  • Un cursor que se puede arrastrar para desplazarse rápidamente.
  • Una pista: el área por donde se mueve el cursor.

El control deslizante se creará a partir del elemento gráfico Button, la barra se creará a partir del elemento Panel y habrá botones de flecha ya preparados. Se adjuntarán botones de desplazamiento y un control deslizante al panel (pista).

  • Al hacer clic en los botones de flecha, el cursor se moverá a un paso fijo y el contenido del contenedor se desplazará a un valor calculado proporcional al desplazamiento del control deslizante con respecto a la pista.
  • Al arrastrar el cursor con el mouse o desplazarse con la rueda, su posición cambiará y el contenido del contenedor se desplazará proporcionalmente.
  • La barra de desplazamiento calculará el tamaño de la pista y la miniatura dependiendo del tamaño del contenido y del área visible del contenedor.


Clases de control deslizante

Continúe escribiendo el código en el archivo Controls.mqh.

Clase de miniatura de barra de desplazamiento horizontal.

//+------------------------------------------------------------------+
//| Класс ползунка горизонтальной полосы прокрутки                   |
//+------------------------------------------------------------------+
class CScrollBarThumbH : public CButton
  {
protected:
   bool              m_chart_redraw;                           // Флаг обновления графика
public:
//--- (1) Устанавливает, (2) возвращает флаг обновления графика
   void              SetChartRedrawFlag(const bool flag)       { this.m_chart_redraw=flag;               }
   bool              ChartRedrawFlag(void)               const { return this.m_chart_redraw;             }
   
//--- Виртуальные методы (1) сохранения в файл, (2) загрузки из файла, (3) тип объекта
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_SCROLLBAR_THUMB_H); }
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(const string text);
   
//--- Обработчики событий (1) перемещения курсора, (2) прокрутки колёсика
   virtual void      OnMoveEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam);
   
//--- Конструкторы/деструктор
                     CScrollBarThumbH(void);
                     CScrollBarThumbH(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CScrollBarThumbH (void) {}
  };

La clase se hereda de un botón simple. Respectivamente, hereda los controladores de eventos. Un botón simple no maneja el movimiento de la rueda del mouse ni los eventos de desplazamiento. Por lo tanto, estos métodos virtuales se implementarán aquí. Y aquí se ha añadido la bandera de actualización automática del gráfico. ¿Cuál es el propósito de esto? Si se usa esta clase por separado del contenedor (por ejemplo, para controles con cursores), entonces cuando la posición del cursor cambia, el gráfico debe volver a dibujarse para mostrar inmediatamente los cambios. Para ello, esta bandera se establece en true. Como parte del contenedor, las barras de desplazamiento y el rediseño de gráficos están controlados por el contenedor. En este caso, esta bandera debe restablecerse aquí (este es su valor predeterminado).

En los constructores de clase, en la cadena de inicialización al constructor de la clase padre se establecen los valores pasados en los parámetros del constructor formal y luego se llama al método de inicialización de la clase:

//+------------------------------------------------------------------+
//| CScrollBarThumbH::Конструктор по умолчанию.                      |
//| Строит элемент в главном окне текущего графика                   |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CScrollBarThumbH::CScrollBarThumbH(void) : CButton("SBThumb","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_SCROLLBAR_TH)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbH::Конструктор параметрический.                   |
//| Строит элемент в указанном окне указанного графика               |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CScrollBarThumbH::CScrollBarThumbH(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,chart_id,wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }

Método de inicialización de clase:

//+------------------------------------------------------------------+
//| CScrollBarThumbH::Инициализация                                  |
//+------------------------------------------------------------------+
void CScrollBarThumbH::Init(const string text)
  {
//--- Инициализация родительского класса
   CButton::Init("");
//--- Устанавливаем флаги перемещаемости и обновления графика
   this.SetMovable(true);
   this.SetChartRedrawFlag(false);
  }

Este elemento, al estar unido a otro elemento, puede desplazarse. Por lo tanto, la bandera de la reubicación está puesta para ello. La bandera de redibujado del gráfico se restablece de manera predeterminada. Se puede instalar en cualquier momento si es necesario.

Controlador de movimiento del cursor:

//+------------------------------------------------------------------+
//| CScrollBarThumbH::Обработчик перемещения курсора                 |
//+------------------------------------------------------------------+
void CScrollBarThumbH::OnMoveEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Обработчик перемещения курсора базового объекта
   CCanvasBase::OnMoveEvent(id,lparam,dparam,sparam);
//--- Получаем указатель на базовый объект (элемент управления "горизонтальная полоса прокрутки")
   CCanvasBase *base_obj=this.GetContainer();
//--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим
   if(!this.IsMovable() || base_obj==NULL)
      return;
   
//--- Получаем ширину базового объекта и рассчитываем границы пространства для ползунка
   int base_w=base_obj.Width();
   int base_left=base_obj.X()+base_obj.Height();
   int base_right=base_obj.Right()-base_obj.Height()+1;
   
//--- Из координат курсора и размеров ползунка рассчитываем ограничения для перемещения
   int x=(int)lparam-this.m_cursor_delta_x;
   if(x<base_left)
      x=base_left;
   if(x+this.Width()>base_right)
      x=base_right-this.Width();
//--- Сдвигаем ползунок на рассчитанную координату X
   if(!this.MoveX(x))
      return;
      
//--- Рассчитываем позицию ползунка
   int thumb_pos=this.X()-base_left;
   
//--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_MOVE, thumb_pos, dparam, this.NameFG());
//--- Перерисовываем график
   if(this.m_chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Aquí, las dimensiones de la pista dentro de la cual se puede mover el cursor se calculan en función del tamaño del objeto base. A continuación, el cursor se mueve después del cursor, pero sujeto a limitaciones de seguimiento. Después de esto, se calcula el desplazamiento del cursor con respecto al borde izquierdo de la pista y este valor se envía al gráfico en el evento de usuario, donde se indica que este es el evento de movimiento del mouse y el nombre del objeto del cursor. Estos valores son necesarios para la barra de desplazamiento, a la que pertenece el cursor, para realizar trabajos posteriores de desplazamiento del contenido del contenedor, al que pertenece la barra de desplazamiento, respectivamente.

Controlador de desplazamiento de la rueda:

//+------------------------------------------------------------------+
//| CScrollBarThumbH::Обработчик прокрутки колёсика                  |
//+------------------------------------------------------------------+
void CScrollBarThumbH::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Получаем указатель на базовый объект (элемент управления "горизонтальная полоса прогрутки")
   CCanvasBase *base_obj=this.GetContainer();
//--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим
   if(!this.IsMovable() || base_obj==NULL)
      return;
   
//--- Получаем ширину базового объекта и рассчитываем границы пространства для ползунка
   int base_w=base_obj.Width();
   int base_left=base_obj.X()+base_obj.Height();
   int base_right=base_obj.Right()-base_obj.Height()+1;
   
//--- Задаём направление смещения в зависимости от направления вращения колёсика мышки
   int dx=(dparam<0 ? 2 : dparam>0 ? -2 : 0);
   if(dx==0)
      dx=(int)lparam;

//--- Если при смещении ползунок выйдет за левый край своей области - устанавливаем его на левый край
   if(dx<0 && this.X()+dx<=base_left)
      this.MoveX(base_left);
//--- иначе, если при смещении ползунок выйдет за правый край своей области - позиционируем его по правому краю
   else if(dx>0 && this.Right()+dx>=base_right)
      this.MoveX(base_right-this.Width());
//--- Иначе, если ползунок в пределах своей области - смещаем его на величину смещения
   else if(this.ShiftX(dx))
      this.OnFocusEvent(id,lparam,dparam,sparam);
      
//--- Рассчитываем позицию ползунка
   int thumb_pos=this.X()-base_left;
   
//--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_WHEEL, thumb_pos, dparam, this.NameFG());
//--- Перерисовываем график
   if(this.m_chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

La lógica es aproximadamente la misma que la del método anterior. Pero se envía el evento de desplazamiento de la rueda.

Métodos de manipulación de archivos:

//+------------------------------------------------------------------+
//| CScrollBarThumbH::Сохранение в файл                              |
//+------------------------------------------------------------------+
bool CScrollBarThumbH::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CButton::Save(file_handle))
      return false;
  
//--- Сохраняем флаг обновления графика
   if(::FileWriteInteger(file_handle,this.m_chart_redraw,INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbH::Загрузка из файла                              |
//+------------------------------------------------------------------+
bool CScrollBarThumbH::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CButton::Load(file_handle))
      return false;
      
//--- Загружаем флаг обновления графика
   this.m_chart_redraw=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Всё успешно
   return true;
  }


Barra de desplazamiento vertical Clase Thumb:

//+------------------------------------------------------------------+
//| Класс ползунка вертикальной полосы прокрутки                     |
//+------------------------------------------------------------------+
class CScrollBarThumbV : public CButton
  {
protected:
   bool              m_chart_redraw;                           // Флаг обновления графика
public:
//--- (1) Устанавливает, (2) возвращает флаг обновления графика
   void              SetChartRedrawFlag(const bool flag)       { this.m_chart_redraw=flag;               }
   bool              ChartRedrawFlag(void)               const { return this.m_chart_redraw;             }
   
//--- Виртуальные методы (1) сохранения в файл, (2) загрузки из файла, (3) тип объекта
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_SCROLLBAR_THUMB_V); }
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(const string text);
   
//--- Обработчики событий (1) перемещения курсора, (2) прокрутки колёсика
   virtual void      OnMoveEvent(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam);
   
//--- Конструкторы/деструктор
                     CScrollBarThumbV(void);
                     CScrollBarThumbV(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CScrollBarThumbV (void) {}
  };
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Конструктор по умолчанию.                      |
//| Строит элемент в главном окне текущего графика                   |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CScrollBarThumbV::CScrollBarThumbV(void) : CButton("SBThumb","",::ChartID(),0,0,0,DEF_SCROLLBAR_TH,DEF_PANEL_W)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Конструктор параметрический.                   |
//| Строит элемент в указанном окне указанного графика               |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CScrollBarThumbV::CScrollBarThumbV(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CButton(object_name,text,chart_id,wnd,x,y,w,h)
  {
//--- Инициализация
   this.Init("");
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Инициализация                                  |
//+------------------------------------------------------------------+
void CScrollBarThumbV::Init(const string text)
  {
//--- Инициализация родительского класса
   CButton::Init("");
//--- Устанавливаем флаги перемещаемости и обновления графика
   this.SetMovable(true);
   this.SetChartRedrawFlag(false);
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Обработчик перемещения курсора                 |
//+------------------------------------------------------------------+
void CScrollBarThumbV::OnMoveEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Обработчик перемещения курсора базового объекта
   CCanvasBase::OnMoveEvent(id,lparam,dparam,sparam);
//--- Получаем указатель на базовый объект (элемент управления "вертикальная полоса прогрутки")
   CCanvasBase *base_obj=this.GetContainer();
//--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим
   if(!this.IsMovable() || base_obj==NULL)
      return;
   
//--- Получаем высоту базового объекта и рассчитываем границы пространства для ползунка
   int base_h=base_obj.Height();
   int base_top=base_obj.Y()+base_obj.Width();
   int base_bottom=base_obj.Bottom()-base_obj.Width()+1;
   
//--- Из координат курсора и размеров ползунка рассчитываем ограничения для перемещения
   int y=(int)dparam-this.m_cursor_delta_y;
   if(y<base_top)
      y=base_top;
   if(y+this.Height()>base_bottom)
      y=base_bottom-this.Height();
//--- Сдвигаем ползунок на рассчитанную координату Y
   if(!this.MoveY(y))
      return;
   
//--- Рассчитываем позицию ползунка
   int thumb_pos=this.Y()-base_top;
   
//--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_MOVE, thumb_pos, dparam, this.NameFG());
//--- Перерисовываем график
   if(this.m_chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Обработчик прокрутки колёсика                  |
//+------------------------------------------------------------------+
void CScrollBarThumbV::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Получаем указатель на базовый объект (элемент управления "вертикальная полоса прогрутки")
   CCanvasBase *base_obj=this.GetContainer();
//--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим
   if(!this.IsMovable() || base_obj==NULL)
      return;
   
//--- Получаем высоту базового объекта и рассчитываем границы пространства для ползунка
   int base_h=base_obj.Height();
   int base_top=base_obj.Y()+base_obj.Width();
   int base_bottom=base_obj.Bottom()-base_obj.Width()+1;
   
//--- Задаём направление смещения в зависимости от направления вращения колёсика мышки
   int dy=(dparam<0 ? 2 : dparam>0 ? -2 : 0);
   if(dy==0)
      dy=(int)lparam;

//--- Если при смещении ползунок выйдет за верхний край своей области - устанавливаем его на верхний край
   if(dy<0 && this.Y()+dy<=base_top)
      this.MoveY(base_top);
//--- иначе, если при смещении ползунок выйдет за нижний край своей области - позиционируем его по нижнему краю
   else if(dy>0 && this.Bottom()+dy>=base_bottom)
      this.MoveY(base_bottom-this.Height());
//--- Иначе, если ползунок в пределах своей области - смещаем его на величину смещения
   else if(this.ShiftY(dy))
      this.OnFocusEvent(id,lparam,dparam,sparam);
      
//--- Рассчитываем позицию ползунка
   int thumb_pos=this.Y()-base_top;
   
//--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_WHEEL, thumb_pos, dparam, this.NameFG());
//--- Перерисовываем график
   if(this.m_chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Сохранение в файл                              |
//+------------------------------------------------------------------+
bool CScrollBarThumbV::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CButton::Save(file_handle))
      return false;
  
//--- Сохраняем флаг обновления графика
   if(::FileWriteInteger(file_handle,this.m_chart_redraw,INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Загрузка из файла                              |
//+------------------------------------------------------------------+
bool CScrollBarThumbV::Load(const int file_handle)
  {
//--- Загружаем данные родительского объекта
   if(!CButton::Load(file_handle))
      return false;
      
//--- Загружаем флаг обновления графика
   this.m_chart_redraw=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Всё успешно
   return true;
  }

La diferencia con la clase anterior está únicamente en el cálculo de las restricciones para el desplazamiento del cursor, ya que aquí éste se desplaza verticalmente. El resto es idéntico a la clase del cursor de la barra de desplazamiento horizontal.

Las clases CScrollBarThumbH (pulsador horizontal) y CScrollBarThumbV (pulsador vertical) implementan elementos móviles en las interfaces de usuario. Las clases se heredan de la clase de botón, admiten el movimiento del mouse a lo largo de la pista de la barra de desplazamiento u otro elemento limitador, y también responden al desplazamiento de la rueda del mouse. Cuando se cambia la posición del cursor, las clases envían un evento con la nueva posición, lo que permite sincronizar la visualización del contenido del contenedor. Los cursores están limitados en su movimiento por los límites de la pista, pueden guardar su estado en un archivo y descargarlo desde un archivo, y controlar la necesidad de volver a dibujar el gráfico. Estas clases proporcionan una interacción de usuario intuitiva y familiar con áreas de interfaz desplazables.

En este contexto, los cursores funcionarán como parte de las clases de barra de desplazamiento horizontal y vertical.


Clase de barra de desplazamiento horizontal

Continúe escribiendo el código en el archivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс горизонтальной полосы прокрутки                            |
//+------------------------------------------------------------------+
class CScrollBarH : public CPanel
  {
protected:
   CButtonArrowLeft *m_butt_left;                              // Кнопка со стрелкой влево
   CButtonArrowRight*m_butt_right;                             // Кнопка со стрелкой вправо
   CScrollBarThumbH *m_thumb;                                  // Ползунок скроллбара
   
public:
//--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок
   CButtonArrowLeft *GetButtonLeft(void)                       { return this.m_butt_left;                                              }
   CButtonArrowRight*GetButtonRight(void)                      { return this.m_butt_right;                                             }
   CScrollBarThumbH *GetThumb(void)                            { return this.m_thumb;                                                  }

//--- (1) Устанавливает, (2) возвращает флаг обновления графика
   void              SetChartRedrawFlag(const bool flag)       { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag);         }
   bool              ChartRedrawFlag(void)               const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false);  }

//--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка
   int               TrackLength(void)    const;
   int               TrackBegin(void)     const;
   int               ThumbPosition(void)  const;
   
//--- Изменяет размер ползунка
   bool              SetThumbSize(const uint size)       const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeW(size) : false);      }

//--- Изменяет ширину объекта
   virtual bool      ResizeW(const int size);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);
   
//--- Тип объекта
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_SCROLLBAR_H);                                     }
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);
   virtual void      InitColors(void);
   
//--- Обработчик прокрутки колёсика (Wheel)
   virtual void      OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam);

//--- Конструкторы/деструктор
                     CScrollBarH(void);
                     CScrollBarH(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CScrollBarH(void) {}
  };

Los métodos declarados muestran que la clase tiene punteros a botones de flecha izquierda/derecha y un objeto de cursor. Todos estos objetos se implementan en el método de inicialización de la clase. La clase implementa métodos que devuelven punteros a estos objetos. También se implementan métodos para establecer la bandera para actualizar el gráfico con la barra de desplazamiento, y para obtener el estado de esta bandera desde el objeto de la barra de desplazamiento.

En los constructores de clase, en la cadena de inicialización, los valores de los parámetros formales se pasan al constructor de la clase padre. Y luego se llama al método de inicialización de la clase:

//+------------------------------------------------------------------+
//| CScrollBarH::Конструктор по умолчанию.                           |
//| Строит элемент в главном окне текущего графика                   |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CScrollBarH::CScrollBarH(void) : CPanel("ScrollBarH","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H),m_butt_left(NULL),m_butt_right(NULL),m_thumb(NULL)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CScrollBarH::Конструктор параметрический.                        |
//| Строит элемент в указанном окне указанного графика               |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CScrollBarH::CScrollBarH(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,chart_id,wnd,x,y,w,h),m_butt_left(NULL),m_butt_right(NULL),m_thumb(NULL)
  {
//--- Инициализация
   this.Init();
  }

Método de inicialización de clase:

//+------------------------------------------------------------------+
//| CScrollBarH::Инициализация                                       |
//+------------------------------------------------------------------+
void CScrollBarH::Init(void)
  {
//--- Инициализация родительского класса
   CPanel::Init();
//--- Фон - непрозрачный
   this.SetAlphaBG(255);
//--- Ширина рамки и текст
   this.SetBorderWidth(0);
   this.SetText("");
//--- Элемент не обрезается по границам контейнера
   this.m_trim_flag=false;
   
//--- Создаём кнопки прокрутки
   int w=this.Height();
   int h=this.Height();
   this.m_butt_left = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_LEFT, "","ButtL",0,0,w,h);
   this.m_butt_right= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_RIGHT,"","ButtR",this.Width()-w,0,w,h);
   if(this.m_butt_left==NULL || this.m_butt_right==NULL)
     {
      ::PrintFormat("%s: Init failed",__FUNCTION__);
      return;
     }
//--- Настраиваем цвета и вид кнопки со стрелкой влево
   this.m_butt_left.SetImageBound(1,1,w-2,h-4);
   this.m_butt_left.InitBackColors(this.m_butt_left.BackColorFocused());
   this.m_butt_left.ColorsToDefault();
   this.m_butt_left.InitBorderColors(this.BorderColor(),this.m_butt_left.BackColorFocused(),this.m_butt_left.BackColorPressed(),this.m_butt_left.BackColorBlocked());
   this.m_butt_left.ColorsToDefault();
   
//--- Настраиваем цвета и вид кнопки со стрелкой вправо
   this.m_butt_right.SetImageBound(1,1,w-2,h-4);
   this.m_butt_right.InitBackColors(this.m_butt_right.BackColorFocused());
   this.m_butt_right.ColorsToDefault();
   this.m_butt_right.InitBorderColors(this.BorderColor(),this.m_butt_right.BackColorFocused(),this.m_butt_right.BackColorPressed(),this.m_butt_right.BackColorBlocked());
   this.m_butt_right.ColorsToDefault();
   
//--- Создаём ползунок
   int tsz=this.Width()-w*2;
   this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_H,"","ThumbH",w,1,tsz-w*4,h-2);
   if(this.m_thumb==NULL)
     {
      ::PrintFormat("%s: Init failed",__FUNCTION__);
      return;
     }
//--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости
   this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused());
   this.m_thumb.ColorsToDefault();
   this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked());
   this.m_thumb.ColorsToDefault();
   this.m_thumb.SetMovable(true);
//--- запрещаем самостоятельную перерисовку графика
   this.m_thumb.SetChartRedrawFlag(false);
  }

Las barras de desplazamiento se encuentran en la parte inferior y derecha del contenedor, fuera de su campo de visión, donde se encuentra el contenido del contenedor. Todos los objetos adjuntos al contenedor siempre se recortan a lo largo de los límites del área visible del contenedor. Las barras de desplazamiento se encuentran fuera del área visible del contenedor, lo que significa que se recortan y se vuelven invisibles. Para evitar este comportamiento, todos los objetos tienen una bandera que indica la necesidad de recortar a lo largo de los límites del contenedor. Esta bandera está establecida de forma predeterminada. Aquí establecemos este indicador en false, es decir, el objeto no se recortará a lo largo de los límites del contenedor, pero su visibilidad estará controlada por la clase del contenedor.

En el método de inicialización, se crean y configuran todos los controles: botones de flecha y un cursor. Se restablece la bandera para controlar el rediseño del gráfico con el objeto cursor: la clase contenedora controlará el rediseño.

Método de inicialización del color predeterminado del objeto:

//+------------------------------------------------------------------+
//| CScrollBarH::Инициализация цветов объекта по умолчанию           |
//+------------------------------------------------------------------+
void CScrollBarH::InitColors(void)
  {
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke);
   this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke);
   this.BackColorToDefault();
   
//--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста
   this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver);
   this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver);
   this.ForeColorToDefault();
   
//--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки
   this.InitBorderColors(clrLightGray,clrLightGray,clrLightGray,clrSilver);
   this.InitBorderColorsAct(clrLightGray,clrLightGray,clrLightGray,clrSilver);
   this.BorderColorToDefault();
   
//--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента
   this.InitBorderColorBlocked(clrSilver);
   this.InitForeColorBlocked(clrSilver);
  }

Los colores de los diferentes estados se han establecido para que sean iguales, de modo que no haya cambios en el color del elemento al interactuar con el ratón.

Un método que dibuja la apariencia:

//+------------------------------------------------------------------+
//| CScrollBarH::Рисует внешний вид                                  |
//+------------------------------------------------------------------+
void CScrollBarH::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Обновляем канвас фона без перерисовки графика
   this.m_background.Update(false);
   
//--- Рисуем элементы списка без перерисовки графика
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.Draw(false);
     }
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Un método que devuelve la longitud de la pista:

//+------------------------------------------------------------------+
//| CScrollBarH::Возвращает длину трека                              |
//+------------------------------------------------------------------+
int CScrollBarH::TrackLength(void) const
  {
   if(this.m_butt_left==NULL || this.m_butt_right==NULL)
      return 0;
   return(this.m_butt_right.X()-this.m_butt_left.Right());
  }

Devuelve la distancia en píxeles entre la coordenada X del botón derecho y el borde derecho del botón izquierdo.

Un método que devuelve el comienzo de la pista:

//+------------------------------------------------------------------+
//| CScrollBarH::Возвращает начало трека                             |
//+------------------------------------------------------------------+
int CScrollBarH::TrackBegin(void) const
  {
   return(this.m_butt_left!=NULL ? this.m_butt_left.Width() : 0);
  }

Devuelve el desplazamiento por el ancho del botón sobre el borde izquierdo del elemento.

Un método que devuelve la posición del cursor:

//+------------------------------------------------------------------+
//| CScrollBarH::Возвращает позицию ползунка                         |
//+------------------------------------------------------------------+
int CScrollBarH::ThumbPosition(void) const
  {
   return(this.m_thumb!=NULL ? this.m_thumb.X()-this.TrackBegin()-this.X() : 0);
  }

Devuelve el desplazamiento del cursor sobre el inicio de la pista.

Un método que cambia el ancho del objeto:

//+------------------------------------------------------------------+
//| CScrollBarH::Изменяет ширину объекта                             |
//+------------------------------------------------------------------+
bool CScrollBarH::ResizeW(const int size)
  {
//--- Получаем указатели на левую и правую кнопки
   if(this.m_butt_left==NULL || this.m_butt_right==NULL)
      return false;
//--- Изменяем ширину объекта
   if(!CCanvasBase::ResizeW(size))
      return false;
//--- Смещаем кнопки на новое расположение относительно левой и правой границ изменившего размер элемента
   if(!this.m_butt_left.MoveX(this.X()))
      return false;
   return(this.m_butt_right.MoveX(this.Right()-this.m_butt_right.Width()+1));
  }

La barra de desplazamiento horizontal solo puede cambiar su tamaño en anchura. Después de cambiar el tamaño del elemento, los botones deben moverse a su nueva ubicación para que queden situados en los bordes del elemento.

Controlador de desplazamiento de la rueda:

//+------------------------------------------------------------------+
//| CScrollBarH::Обработчик прокрутки колёсика                       |
//+------------------------------------------------------------------+
void CScrollBarH::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Вызываем обработчик прокрутки для ползунка
   if(this.m_thumb!=NULL)
      this.m_thumb.OnWheelEvent(id,this.ThumbPosition(),dparam,this.NameFG());
      
//--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id,CHARTEVENT_MOUSE_WHEEL,this.ThumbPosition(),dparam,this.NameFG());
  }

Para que la barra de desplazamiento se desplace cuando la rueda se desplaza cuando el cursor está entre los botones y el cursor, el método delega el manejo de eventos al objeto del cursor y envía el evento de desplazamiento al gráfico.

Todos los demás controladores se llaman cuando el cursor está por encima de los elementos de la barra de desplazamiento (por encima de los botones y por encima del cursor). Estos objetos ya contienen controladores de eventos.


Clase de barra de desplazamiento vertical

La clase de barra de desplazamiento vertical es idéntica a la clase de barra de desplazamiento horizontal anterior. La única diferencia está en el cálculo de la longitud y el comienzo de la pista, la posición del cursor y el cambio de tamaño: aquí el tamaño cambia solo verticalmente. Consideremos toda la clase:

//+------------------------------------------------------------------+
//| Класс вертикальной полосы прокрутки                              |
//+------------------------------------------------------------------+
class CScrollBarV : public CPanel
  {
protected:
   CButtonArrowUp   *m_butt_up;                                // Кнопка со стрелкой вверх
   CButtonArrowDown *m_butt_down;                              // Кнопка со стрелкой вниз
   CScrollBarThumbV *m_thumb;                                  // Ползунок скроллбара

public:
//--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок
   CButtonArrowUp   *GetButtonUp(void)                         { return this.m_butt_up;      }
   CButtonArrowDown *GetButtonDown(void)                       { return this.m_butt_down;    }
   CScrollBarThumbV *GetThumb(void)                            { return this.m_thumb;        }

//--- (1) Устанавливает, (2) возвращает флаг обновления графика
   void              SetChartRedrawFlag(const bool flag)       { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag);         }
   bool              ChartRedrawFlag(void)               const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false);  }

//--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка
   int               TrackLength(void)    const;
   int               TrackBegin(void)     const;
   int               ThumbPosition(void)  const;
   
//--- Изменяет размер ползунка
   bool              SetThumbSize(const uint size)       const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeH(size) : false);      }
   
//--- Изменяет высоту объекта
   virtual bool      ResizeH(const int size);
   
//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);
   
//--- Тип объекта
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_SCROLLBAR_V);                                     }
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(void);
   virtual void      InitColors(void);
   
//--- Обработчик прокрутки колёсика (Wheel)
   virtual void      OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam);
   
//--- Конструкторы/деструктор
                     CScrollBarV(void);
                     CScrollBarV(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CScrollBarV(void) {}
  };
//+------------------------------------------------------------------+
//| CScrollBarV::Конструктор по умолчанию.                           |
//| Строит элемент в главном окне текущего графика                   |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CScrollBarV::CScrollBarV(void) : CPanel("ScrollBarV","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H),m_butt_up(NULL),m_butt_down(NULL),m_thumb(NULL)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Конструктор параметрический.                        |
//| Строит элемент в указанном окне указанного графика               |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CScrollBarV::CScrollBarV(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,chart_id,wnd,x,y,w,h),m_butt_up(NULL),m_butt_down(NULL),m_thumb(NULL)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Инициализация                                       |
//+------------------------------------------------------------------+
void CScrollBarV::Init(void)
  {
//--- Инициализация родительского класса
   CPanel::Init();
//--- Фон - непрозрачный
   this.SetAlphaBG(255);
//--- Ширина рамки и текст
   this.SetBorderWidth(0);
   this.SetText("");
//--- Элемент не обрезается по границам контейнера
   this.m_trim_flag=false;
   
//--- Создаём кнопки прокрутки
   int w=this.Width();
   int h=this.Width();
   this.m_butt_up = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_UP, "","ButtU",0,0,w,h);
   this.m_butt_down= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_DOWN,"","ButtD",0,this.Height()-w,w,h);
   if(this.m_butt_up==NULL || this.m_butt_down==NULL)
     {
      ::PrintFormat("%s: Init failed",__FUNCTION__);
      return;
     }
//--- Настраиваем цвета и вид кнопки со стрелкой вверх
   this.m_butt_up.SetImageBound(1,0,w-4,h-2);
   this.m_butt_up.InitBackColors(this.m_butt_up.BackColorFocused());
   this.m_butt_up.ColorsToDefault();
   this.m_butt_up.InitBorderColors(this.BorderColor(),this.m_butt_up.BackColorFocused(),this.m_butt_up.BackColorPressed(),this.m_butt_up.BackColorBlocked());
   this.m_butt_up.ColorsToDefault();
   
//--- Настраиваем цвета и вид кнопки со стрелкой вниз
   this.m_butt_down.SetImageBound(1,0,w-4,h-2);
   this.m_butt_down.InitBackColors(this.m_butt_down.BackColorFocused());
   this.m_butt_down.ColorsToDefault();
   this.m_butt_down.InitBorderColors(this.BorderColor(),this.m_butt_down.BackColorFocused(),this.m_butt_down.BackColorPressed(),this.m_butt_down.BackColorBlocked());
   this.m_butt_down.ColorsToDefault();
   
//--- Создаём ползунок
   int tsz=this.Height()-w*2;
   this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_V,"","ThumbV",1,w,w-2,tsz/2);
   if(this.m_thumb==NULL)
     {
      ::PrintFormat("%s: Init failed",__FUNCTION__);
      return;
     }
//--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости
   this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused());
   this.m_thumb.ColorsToDefault();
   this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked());
   this.m_thumb.ColorsToDefault();
   this.m_thumb.SetMovable(true);
//--- запрещаем самостоятельную перерисовку графика
   this.m_thumb.SetChartRedrawFlag(false);
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Инициализация цветов объекта по умолчанию           |
//+------------------------------------------------------------------+
void CScrollBarV::InitColors(void)
  {
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke);
   this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke);
   this.BackColorToDefault();
   
//--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста
   this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver);
   this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver);
   this.ForeColorToDefault();
   
//--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки
   this.InitBorderColors(clrLightGray,clrLightGray,clrLightGray,clrSilver);
   this.InitBorderColorsAct(clrLightGray,clrLightGray,clrLightGray,clrSilver);
   this.BorderColorToDefault();
   
//--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента
   this.InitBorderColorBlocked(clrSilver);
   this.InitForeColorBlocked(clrSilver);
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Рисует внешний вид                                  |
//+------------------------------------------------------------------+
void CScrollBarV::Draw(const bool chart_redraw)
  {
//--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона
   this.Fill(this.BackColor(),false);
   this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG()));
   this.m_background.Update(false);
//--- Обновляем канвас фона без перерисовки графика
   this.m_background.Update(false);
   
//--- Рисуем элементы списка без перерисовки графика
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
         elm.Draw(false);
     }
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Возвращает длину трека                              |
//+------------------------------------------------------------------+
int CScrollBarV::TrackLength(void) const
  {
   if(this.m_butt_up==NULL || this.m_butt_down==NULL)
      return 0;
   return(this.m_butt_down.Y()-this.m_butt_up.Bottom());
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Возвращает начало ползунка                          |
//+------------------------------------------------------------------+
int CScrollBarV::TrackBegin(void) const
  {
   return(this.m_butt_up!=NULL ? this.m_butt_up.Height() : 0);
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Возвращает позицию ползунка                         |
//+------------------------------------------------------------------+
int CScrollBarV::ThumbPosition(void) const
  {
   return(this.m_thumb!=NULL ? this.m_thumb.Y()-this.TrackBegin()-this.Y() : 0);
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Изменяет высоту объекта                             |
//+------------------------------------------------------------------+
bool CScrollBarV::ResizeH(const int size)
  {
//--- Получаем указатели на верхнюю и нижнюю кнопки
   if(this.m_butt_up==NULL || this.m_butt_down==NULL)
      return false;
//--- Изменяем высоту объекта
   if(!CCanvasBase::ResizeH(size))
      return false;
//--- Смещаем кнопки на новое расположение относительно верхней и нижней границ изменившего размер элемента
   if(!this.m_butt_up.MoveY(this.Y()))
      return false;
   return(this.m_butt_down.MoveY(this.Bottom()-this.m_butt_down.Height()+1));
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Обработчик прокрутки колёсика                       |
//+------------------------------------------------------------------+
void CScrollBarV::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Вызываем обработчик прокрутки для ползунка
   if(this.m_thumb!=NULL)
      this.m_thumb.OnWheelEvent(id,this.ThumbPosition(),dparam,this.NameFG());
      
//--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam
   ::EventChartCustom(this.m_chart_id,CHARTEVENT_MOUSE_WHEEL,this.ThumbPosition(),dparam,this.NameFG());
  }

Entonces, tenemos todo preparado para crear el elemento gráfico Contenedor. A diferencia de un Panel y un Grupo de Elementos, solo se puede colocar un control en el contenedor, por ejemplo, un Panel. Luego, el contenedor desplazará solo el panel con barras de desplazamiento, y varios controles ubicados en el panel se moverán con él, recortando correctamente los límites del área visible del contenedor. El área visible se establece mediante cuatro valores: el ancho del borde en la parte superior, inferior, izquierda y derecha.

CContainer es un contenedor universal para interfaces de usuario, diseñado para alojar un único elemento grande con la función de desplazarse automáticamente por el contenido de forma horizontal y/o vertical. La clase implementa la lógica de apariencia y control de las barras de desplazamiento en función del tamaño de un elemento anidado sobre el área visible del contenedor.

Clase Container

Continúe escribiendo el código en el archivo Controls.mqh:

//+------------------------------------------------------------------+
//| Класс Контейнер                                                  |
//+------------------------------------------------------------------+
class CContainer : public CPanel
  {
private:
   bool              m_visible_scrollbar_h;                    // Флаг видимости горизонтальной полосы прокрутки
   bool              m_visible_scrollbar_v;                    // Флаг видимости вертикальной полосы прокрутки
//--- Возвращает тип элемента, отправившего событие
   ENUM_ELEMENT_TYPE GetEventElementType(const string name);
   
protected:
   CScrollBarH      *m_scrollbar_h;                            // Указатель на горизонтальную полосу прокрутки
   CScrollBarV      *m_scrollbar_v;                            // Указатель на вертикальную полосу прокрутки
   
//--- Проверяет размеры элемента для отображения полос прокрутки
   void              CheckElementSizes(CElementBase *element);
//--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека горизонтального скроллбара
   int               ThumbSizeHor(void);
   int               TrackLengthHor(void)                const { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.TrackLength() : 0);       }
   int               TrackEffectiveLengthHor(void)             { return(this.TrackLengthHor()-this.ThumbSizeHor());                             }
//--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека вертикального скроллбара
   int               ThumbSizeVer(void);
   int               TrackLengthVer(void)                const { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.TrackLength() : 0);       }
   int               TrackEffectiveLengthVer(void)             { return(this.TrackLengthVer()-this.ThumbSizeVer());                             }
//--- Размер видимой области содержимого по (1) горизонтали, (2) вертикали
   int               ContentVisibleHor(void)             const { return int(this.Width()-this.BorderWidthLeft()-this.BorderWidthRight());       }
   int               ContentVisibleVer(void)             const { return int(this.Height()-this.BorderWidthTop()-this.BorderWidthBottom());      }
   
//--- Полный размер содержимого по (1) горизонтали, (2) вертикали
   int               ContentSizeHor(void);
   int               ContentSizeVer(void);
   
//--- Позиция содержимого по (1) горизонтали, (2) вертикали
   int               ContentPositionHor(void);
   int               ContentPositionVer(void);
//--- Рассчитывает и возвращает величину смещения содержимого по (1) горизонтали, (2) вертикали в зависимости от положения ползунка
   int               CalculateContentOffsetHor(const uint thumb_position);
   int               CalculateContentOffsetVer(const uint thumb_position);
//--- Рассчитывает и возвращает величину смещения ползунка по (1) горизонтали, (2) вертикали в зависимости от положения контента
   int               CalculateThumbOffsetHor(const uint content_position);
   int               CalculateThumbOffsetVer(const uint content_position);
   
//--- Смещает содержимое по (1) горизонтали, (2) вертикали на указанное значение
   bool              ContentShiftHor(const int value);
   bool              ContentShiftVer(const int value);
   
public:
//--- Возврат указателей на скроллбары, кнопки и ползунки скроллбаров
   CScrollBarH      *GetScrollBarH(void)                       { return this.m_scrollbar_h;                                                     }
   CScrollBarV      *GetScrollBarV(void)                       { return this.m_scrollbar_v;                                                     }
   CButtonArrowUp   *GetScrollBarButtonUp(void)                { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetButtonUp()   : NULL);  }
   CButtonArrowDown *GetScrollBarButtonDown(void)              { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetButtonDown() : NULL);  }
   CButtonArrowLeft *GetScrollBarButtonLeft(void)              { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetButtonLeft() : NULL);  }
   CButtonArrowRight*GetScrollBarButtonRight(void)             { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetButtonRight(): NULL);  }
   CScrollBarThumbH *GetScrollBarThumbH(void)                  { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetThumb()      : NULL);  }
   CScrollBarThumbV *GetScrollBarThumbV(void)                  { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetThumb()      : NULL);  }
   
//--- Устанавливает флаг прокрутки содержимого
   void              SetScrolling(const bool flag)             { this.m_scroll_flag=flag;                                                       }

//--- Возвращает флаг видимости (1) горизонтального, (2) вертикального скроллбара
   bool              ScrollBarHorIsVisible(void)         const { return this.m_visible_scrollbar_h;                                             }
   bool              ScrollBarVerIsVisible(void)         const { return this.m_visible_scrollbar_v;                                             }

//--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список
   virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h);
   virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy);

//--- Рисует внешний вид
   virtual void      Draw(const bool chart_redraw);

//--- Тип объекта
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_CONTAINER);                                                }
   
//--- Обработчики пользовательских событий элемента при наведении курсора, щелчке и прокрутке колёсика в области объекта
   virtual void      MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      MousePressHandler(const int id, const long lparam, const double dparam, const string sparam);
   virtual void      MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam);
   
//--- Инициализация объекта класса
   void              Init(void);
   
//--- Конструкторы/деструктор
                     CContainer(void);
                     CContainer(const string object_name, const string text, const int x, const int y, const int w, const int h);
                     CContainer(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h);
                     CContainer(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CContainer (void) {}
  };

Al crear un contenedor, también se crean inmediatamente barras de desplazamiento. Inicialmente están ocultos y pueden aparecer si el tamaño del elemento anidado en el contenedor excede el ancho y/o alto del área visible del contenedor. Después de que aparecen las barras de desplazamiento, la posición del contenido del contenedor se controla automáticamente mediante ellas.

En los constructores de clase, en la lista de inicialización, los valores de los parámetros formales del constructor se pasan al constructor de la clase padre. Y luego se llama al método de inicialización de la clase:

//+------------------------------------------------------------------+
//| CContainer::Конструктор по умолчанию.                            |
//| Строит элемент в главном окне текущего графика                   |
//| в координатах 0,0 с размерами по умолчанию                       |
//+------------------------------------------------------------------+
CContainer::CContainer(void) : CPanel("Container","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CContainer::Конструктор параметрический.                         |
//| Строит элемент в главном окне текущего графика                   |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CContainer::CContainer(const string object_name,const string text,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,::ChartID(),0,x,y,w,h), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CContainer::Конструктор параметрический.                         |
//| Строит элемент в указанном окне текущего графика                 |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CContainer::CContainer(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,::ChartID(),wnd,x,y,w,h), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false)
  {
//--- Инициализация
   this.Init();
  }
//+------------------------------------------------------------------+
//| CContainer::Конструктор параметрический.                         |
//| Строит элемент в указанном окне указанного графика               |
//| с указанными текстом, координами и размерами                     |
//+------------------------------------------------------------------+
CContainer::CContainer(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) :
   CPanel(object_name,text,chart_id,wnd,x,y,w,h), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false)
  {
//--- Инициализация
   this.Init();
  }

Método de inicialización de clase:

//+------------------------------------------------------------------+
//| CContainer::Инициализация                                        |
//+------------------------------------------------------------------+
void CContainer::Init(void)
  {
//--- Инициализация родительского объекта
   CPanel::Init();
//--- Ширина рамки
   this.SetBorderWidth(0);
//--- Создаём горизонтальный скроллбар
   this.m_scrollbar_h=dynamic_cast<CScrollBarH *>(CPanel::InsertNewElement(ELEMENT_TYPE_SCROLLBAR_H,"","ScrollBarH",0,this.Height()-DEF_SCROLLBAR_TH-1,this.Width()-1,DEF_SCROLLBAR_TH));
   if(m_scrollbar_h!=NULL)
     {
      //--- Скрываем элемент и устанавливаем запрет самостоятельной перерисовки графика
      this.m_scrollbar_h.Hide(false);
      this.m_scrollbar_h.SetChartRedrawFlag(false);
     }
//--- Создаём вертикальный скроллбар
   this.m_scrollbar_v=dynamic_cast<CScrollBarV *>(CPanel::InsertNewElement(ELEMENT_TYPE_SCROLLBAR_V,"","ScrollBarV",this.Width()-DEF_SCROLLBAR_TH-1,0,DEF_SCROLLBAR_TH,this.Height()-1));
   if(m_scrollbar_v!=NULL)
     {
      //--- Скрываем элемент и устанавливаем запрет самостоятельной перерисовки графика
      this.m_scrollbar_v.Hide(false);
      this.m_scrollbar_v.SetChartRedrawFlag(false);
     }
//--- Разрешаем прокрутку содержимого
   this.m_scroll_flag=true;
  }

Primero, inicialice el objeto usando el método de inicialización de la clase padre, luego cree dos barras de desplazamiento ocultas y configure el indicador que permite el desplazamiento del contenido del contenedor.

Método de dibujo:

//+------------------------------------------------------------------+
//| CContainer::Рисует внешний вид                                   |
//+------------------------------------------------------------------+
void CContainer::Draw(const bool chart_redraw)
  {
//--- Рисуем внешний вид
   CPanel::Draw(false);
   
//--- Если прокрутка разрешена
   if(this.m_scroll_flag)
     {
      //--- Если оба скроллбара видимы
      if(this.m_visible_scrollbar_h && this.m_visible_scrollbar_v)
        {
         //--- получаем указатели на две кнопки правого нижнего угла
         CButtonArrowDown *butt_dn=this.GetScrollBarButtonDown();
         CButtonArrowRight*butt_rt=this.GetScrollBarButtonRight();
         //--- Получаем указатель на горизонтальную полосу прокрутки и берём цвет её фона
         CScrollBarH *scroll_bar=this.GetScrollBarH();
         color clr=(scroll_bar!=NULL ? scroll_bar.BackColor() : clrWhiteSmoke);
         
         //--- Определяем размеры прямоугольника в нижнем правом углу по размерам двух кнопок
         int bw=(butt_rt!=NULL ? butt_rt.Width() : DEF_SCROLLBAR_TH-3);
         int bh=(butt_dn!=NULL ? butt_dn.Height(): DEF_SCROLLBAR_TH-3);
         
         //--- Устанавливаем координаты, в которых будет нарисован закрашенный прямоугольник
         int x1=this.Width()-bw-1;
         int y1=this.Height()-bh-1;
         int x2=this.Width()-3;
         int y2=this.Height()-3;
         
         //--- Рисуем прямоугольник цветом фона скроллбара в нижнем правом углу
         this.m_foreground.FillRectangle(x1,y1,x2,y2,::ColorToARGB(clr));
         this.m_foreground.Update(false);
        }
     }

//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Primero, dibuje el panel y luego verifique los indicadores de visibilidad de la barra de desplazamiento. Si ambas barras de desplazamiento están visibles, entonces es necesario dibujar un rectángulo relleno con el color de fondo de la barra de desplazamiento en la esquina inferior derecha en la intersección de las barras de desplazamiento horizontal y vertical para crear la integridad de su visualización.

Un método que implementa y añade un nuevo elemento a la lista:

//+------------------------------------------------------------------+
//| CContainer::Создаёт и добавляет новый элемент в список           |
//+------------------------------------------------------------------+
CElementBase *CContainer::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h)
  {
//--- Проверяем, чтобы в списке было не более трёх объектов - две полосы прокрутки и добавляемый
   if(this.m_list_elm.Total()>2)
     {
      ::PrintFormat("%s: Error. You can only add one element to a container\nTo add multiple elements, use the panel",__FUNCTION__);
      return NULL;
     }
//--- Создаём и добавляем новый элемент при помощи метода родительского класса
//--- Элемент помещается в координаты 0,0 независимо от указанных в параметрах
   CElementBase *elm=CPanel::InsertNewElement(type,text,user_name,0,0,w,h);
//--- Проверяем размеры элемента для отображения полос прокрутки
   this.CheckElementSizes(elm);
//--- Возвращаем указатель на элемент
   return elm;
  }

No se puede agregar más de un elemento + 2 barras de desplazamiento a un contenedor. Todos los elementos añadidos están contenidos en una sola lista. Se agregan dos barras de desplazamiento a la lista cuando se crea el contenedor y solo hay espacio para un elemento gráfico que se puede agregar al contenedor. Es este elemento el que representará el contenido del contenedor y se desplazará con barras de desplazamiento si sus dimensiones exceden el ancho y/o alto del área visible del contenedor. Después de agregar un elemento, se verifican sus dimensiones para mostrar barras de desplazamiento si el elemento es más grande que la parte visible del contenedor.

Un método que añade un elemento específico a la lista:

//+------------------------------------------------------------------+
//| CContainer::Добавляет указанный элемент в список                 |
//+------------------------------------------------------------------+
CElementBase *CContainer::InsertElement(CElementBase *element,const int dx,const int dy)
  {
//--- Проверяем, чтобы в списке было не более трёх объектов - две полосы прокрутки и добавляемый
   if(this.m_list_elm.Total()>2)
     {
      ::PrintFormat("%s: Error. You can only add one element to a container\nTo add multiple elements, use the panel",__FUNCTION__);
      return NULL;
     }
//--- Добавляем указанный элемент при помощи метода родительского класса
//--- Элемент помещается в координаты 0,0 независимо от указанных в параметрах
   CElementBase *elm=CPanel::InsertElement(element,0,0);
//--- Проверяем размеры элемента для отображения полос прокрутки
   this.CheckElementSizes(elm);
//--- Возвращаем указатель на элемент
   return elm;
  }

El método funciona de manera similar al anterior, con la única diferencia de que se añade a la lista un elemento que ya se había creado anteriormente.

Un método que comprueba el tamaño de los elementos para mostrar barras de desplazamiento:

//+------------------------------------------------------------------+
//| CContainer::Проверяет размеры элемента                           |
//| для отображения полос прокрутки                                  |
//+------------------------------------------------------------------+
void CContainer::CheckElementSizes(CElementBase *element)
  {
//--- Если передан пустой элемент, или прокрутка запрещена - уходим
   if(element==NULL || !this.m_scroll_flag)
      return;
      
//--- Получаем тип элемента и, если это скроллбар - уходим
   ENUM_ELEMENT_TYPE type=(ENUM_ELEMENT_TYPE)element.Type();
   if(type==ELEMENT_TYPE_SCROLLBAR_H || type==ELEMENT_TYPE_SCROLLBAR_V)
      return;
      
//--- Инициализируем флаги отображения полос прокрутки
   this.m_visible_scrollbar_h=false;
   this.m_visible_scrollbar_v=false;
   
//--- Если ширина элемента больше ширины видимой области контейнера -
//--- устанавливаем флаг отображения горизонтальной полосы прокрутки
   if(element.Width()>this.ContentVisibleHor())
      this.m_visible_scrollbar_h=true;
//--- Если высота элемента больше высоты видимой области контейнера -
//--- устанавливаем флаг отображения вертикальной полосы прокрутки
   if(element.Height()>this.ContentVisibleVer())
      this.m_visible_scrollbar_v=true;

//--- Если обе полосы прокрутки должны быть отображены
   if(this.m_visible_scrollbar_h && this.m_visible_scrollbar_v)
     {
      //--- Получаем указатели на две кнопки прокрутки в нижнем правом углу
      CButtonArrowRight *br=this.m_scrollbar_h.GetButtonRight();
      CButtonArrowDown  *bd=this.m_scrollbar_v.GetButtonDown();
   
      //--- Получаем размеры кнопок прокрутки в высоту и ширину,
      //--- на которые необходимо уменьшить полосы прокрутки, и
      int v=(bd!=NULL ? bd.Height() : DEF_SCROLLBAR_TH);
      int h=(br!=NULL ? br.Width()  : DEF_SCROLLBAR_TH);
      //--- изменяем размеры обеих полос прокрутки на размер кнопок
      this.m_scrollbar_v.ResizeH(this.m_scrollbar_v.Height()-v);
      this.m_scrollbar_h.ResizeW(this.m_scrollbar_h.Width() -h);
     }
//--- Если горизонтальная полоса прокрутки должна быть показана
   if(this.m_visible_scrollbar_h)
     {
      //--- Уменьшаем размер видимого окна контейнера снизу на толщину полосы прокрутки + 1 пиксель
      this.SetBorderWidthBottom(this.m_scrollbar_h.Height()+1);
      //--- Корректируем размер ползунка под новый размер полосы прокрутки и
      //--- переносим скроллбар на передний план, делая его при этом видимым
      this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHor());
      this.m_scrollbar_h.BringToTop(false);
     }
//--- Если вертикальная полоса прокрутки должна быть показана
   if(this.m_visible_scrollbar_v)
     {
      //--- Уменьшаем размер видимого окна контейнера справа на толщину полосы прокрутки + 1 пиксель
      this.SetBorderWidthRight(this.m_scrollbar_v.Width()+1);
      //--- Корректируем размер ползунка под новый размер полосы прокрутки и
      //--- переносим скроллбар на передний план, делая его при этом видимым
      this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVer());
      this.m_scrollbar_v.BringToTop(false);
     }
//--- Если любая из полос прокрутки видима - обрезаем привязанный элемент по новым размерам видимой области 
   if(this.m_visible_scrollbar_h || this.m_visible_scrollbar_v)
     {
      CElementBase *elm=this.GetAttachedElementAt(2);
      if(elm!=NULL)
         elm.ObjectTrim();
     }
  }

La lógica del método se explica en los comentarios del código. El método solo se invoca cuando se añade al contenedor un elemento que representa su contenido.


Métodos para calcular el tamaño de los punteros de la barra de desplazamiento:

//+-------------------------------------------------------------------+
//|CContainer::Рассчитывает размер ползунка горизонтального скроллбара|
//+-------------------------------------------------------------------+
int CContainer::ThumbSizeHor(void)
  {
   CElementBase *elm=this.GetAttachedElementAt(2);
   if(elm==NULL || elm.Width()==0 || this.TrackLengthHor()==0)
      return 0;
   return int(::round(::fmax(((double)this.ContentVisibleHor() / (double)elm.Width()) * (double)this.TrackLengthHor(), DEF_THUMB_MIN_SIZE)));
  }
//+------------------------------------------------------------------+
//| CContainer::Рассчитывает размер ползунка вертикального скроллбара|
//+------------------------------------------------------------------+
int CContainer::ThumbSizeVer(void)
  {
   CElementBase *elm=this.GetAttachedElementAt(2);
   if(elm==NULL || elm.Height()==0 || this.TrackLengthVer()==0)
      return 0;
   return int(::round(::fmax(((double)this.ContentVisibleVer() / (double)elm.Height()) * (double)this.TrackLengthVer(), DEF_THUMB_MIN_SIZE)));
  }

El método calcula el tamaño del control deslizante de la barra de desplazamiento de modo que sea proporcional a la relación entre el área visible del contenedor y el tamaño completo (anchura/altura) del contenido. Cuanto mayor sea la parte visible sobre todo el contenido, mayor será el cursor. El tamaño mínimo está limitado por la constante DEF_THUMB_MIN_SIZE.

  • Si no hay contenido (elm==NULL o el ancho es 0) o la barra de desplazamiento tiene una longitud cero, el método devuelve 0.
  • De lo contrario, el método calcula:
    (tamaño visible del contenedor / tamaño total del contenido) * longitud de la barra de desplazamiento.
  • El resultado se redondea y se compara con el tamaño mínimo del cursor para que no sea demasiado pequeño.
Dado que un contenedor solo puede tener tres elementos vinculados (dos barras de desplazamiento [índices 0 y 1 en la matriz] y un elemento que representa el contenido del contenedor), recupera un elemento de la lista en el índice 2.

    Métodos que devuelven el tamaño completo del contenido del contenedor:

    //+------------------------------------------------------------------+
    //| CContainer::Полный размер содержимого по горизонтали             |
    //+------------------------------------------------------------------+
    int CContainer::ContentSizeHor(void)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       return(elm!=NULL ? elm.Width() : 0);
      }
    //+------------------------------------------------------------------+
    //| CContainer::Полный размер содержимого по вертикали               |
    //+------------------------------------------------------------------+
    int CContainer::ContentSizeVer(void)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       return(elm!=NULL ? elm.Height() : 0);
      }
    

    Los métodos devuelven el ancho/alto del contenido del contenedor. Los métodos devuelven el ancho/alto del contenido del contenedor.

    Métodos que devuelven la posición horizontal/vertical del contenido del contenedor:

    //+--------------------------------------------------------------------+
    //|CContainer::Возвращает позицию содержимого контейнера по горизонтали|
    //+--------------------------------------------------------------------+
    int CContainer::ContentPositionHor(void)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       return(elm!=NULL ? elm.X()-this.X() : 0);
      }
    //+------------------------------------------------------------------+
    //|CContainer::Возвращает позицию содержимого контейнера по вертикали|
    //+------------------------------------------------------------------+
    int CContainer::ContentPositionVer(void)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       return(elm!=NULL ? elm.Y()-this.Y() : 0);
      }
    

    Los métodos devuelven el desplazamiento del origen del contenido del contenedor sobre el origen del contenedor. La esquina superior izquierda se toma como origen.

    Métodos que calculan y devuelven el valor del desplazamiento del contenido del contenedor en función de la posición del cursor:

    //+------------------------------------------------------------------+
    //| CContainer::Рассчитывает и возвращает величину смещения          |
    //| содержимого контейнера по горизонтали по положению ползунка      |
    //+------------------------------------------------------------------+
    int CContainer::CalculateContentOffsetHor(const uint thumb_position)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       int effective_track_length=this.TrackEffectiveLengthHor();
       if(elm==NULL || effective_track_length==0)
          return 0;
       return (int)::round(((double)thumb_position / (double)effective_track_length) * ((double)elm.Width() - (double)this.ContentVisibleHor()));
      }
    //+------------------------------------------------------------------+
    //| CContainer::Рассчитывает и возвращает величину смещения          |
    //| содержимого контейнера по вертикали по положению ползунка        |
    //+------------------------------------------------------------------+
    int CContainer::CalculateContentOffsetVer(const uint thumb_position)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       int effective_track_length=this.TrackEffectiveLengthVer();
       if(elm==NULL || effective_track_length==0)
          return 0;
       return (int)::round(((double)thumb_position / (double)effective_track_length) * ((double)elm.Height() - (double)this.ContentVisibleVer()));
      }
    

    Los métodos calculan cuántos píxeles debe desplazarse el contenido del contenedor en función de la posición actual del control deslizante de la barra de desplazamiento.

    • Se determina la longitud efectiva de la barra de desplazamiento (la longitud de la barra menos el tamaño del control deslizante).
    • Si no hay contenido o la pista tiene una longitud cero, se devuelve 0.
    • El desplazamiento del contenido se calcula en proporción a la posición del cursor:
      • Barra de desplazamiento horizontal:
        (posición del cursor / longitud de la pista) * (el ancho total del contenido es el ancho del área visible)
      • Barra de desplazamiento vertical:
        (posición del cursor / longitud de la pista) * (la altura total del contenido es la altura del área visible)
    • El resultado se redondea al entero más cercano.

    Los métodos sincronizan la posición del cursor y el desplazamiento del contenido: cuando el usuario mueve el cursor, el contenido se desplaza la distancia adecuada.

    Métodos que calculan y devuelven el valor del desplazamiento del cursor en función de la posición del contenido:

    //+------------------------------------------------------------------+
    //| CContainer::Рассчитывает и возвращает величину смещения ползунка |
    //| по горизонтали в зависимости от положения контента               |
    //+------------------------------------------------------------------+
    int CContainer::CalculateThumbOffsetHor(const uint content_position)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       if(elm==NULL)
          return 0;
       int value=elm.Width()-this.ContentVisibleHor();
       if(value==0)
          return 0;
       return (int)::round(((double)content_position / (double)value) * (double)this.TrackEffectiveLengthHor());
      }
    //+------------------------------------------------------------------+
    //| CContainer::Рассчитывает и возвращает величину смещения ползунка |
    //| по вертикали в зависимости от положения контента                 |
    //+------------------------------------------------------------------+
    int CContainer::CalculateThumbOffsetVer(const uint content_position)
      {
       CElementBase *elm=this.GetAttachedElementAt(2);
       if(elm==NULL)
          return 0;
       int value=elm.Height()-this.ContentVisibleVer();
       if(value==0)
          return 0;
       return (int)::round(((double)content_position / (double)value) * (double)this.TrackEffectiveLengthVer());
      }
    

    Los métodos calculan la posición del control deslizante (horizontal o verticalmente) en función del desplazamiento actual del contenido del contenedor.

    • Se determina el desplazamiento máximo posible del contenido (el tamaño del contenido menos el tamaño del área visible).
    • Si no hay contenido o cabe completamente en el contenedor, se devuelve 0.
    • La posición del cursor se calcula proporcionalmente al desplazamiento del contenido:
      • (desplazamiento de contenido / desplazamiento máximo) * longitud de la pista de la barra de desplazamiento
    • El resultado se redondea al entero más cercano.

    Los métodos garantizan la sincronización: cuando el contenido se desplaza de forma programada o manual, la barra de desplazamiento toma automáticamente la posición adecuada en la pista.

    Métodos que cambian el contenido del contenedor para el valor especificado:

    //+-------------------------------------------------------------------+
    //|CContainer::Смещает содержимое по горизонтали на указанное значение|
    //+-------------------------------------------------------------------+
    bool CContainer::ContentShiftHor(const int value)
      {
    //--- Получаем указатель на содержимое контейнера
       CElementBase *elm=this.GetAttachedElementAt(2);
       if(elm==NULL)
          return false;
    //--- Рассчитываем величину смещения по положению ползунка
       int content_offset=this.CalculateContentOffsetHor(value);
    //--- Возвращаем результат сдвига содержимого на рассчитанную величину
       return(elm.MoveX(this.X()-content_offset));
      }
    //+------------------------------------------------------------------+
    //| CContainer::Смещает содержимое по вертикали на указанное значение|
    //+------------------------------------------------------------------+
    bool CContainer::ContentShiftVer(const int value)
      {
    //--- Получаем указатель на содержимое контейнера
       CElementBase *elm=this.GetAttachedElementAt(2);
       if(elm==NULL)
          return false;
    //--- Рассчитываем величину смещения по положению ползунка
       int content_offset=this.CalculateContentOffsetVer(value);
    //--- Возвращаем результат сдвига содержимого на рассчитанную величину
       return(elm.MoveY(this.Y()-content_offset));
      }
    

    Obtenga un puntero al contenido del contenedor, mediante la posición del cursor calcule el desplazamiento del contenido y devuelva el resultado del desplazamiento del contenido del contenedor para la cantidad resultante.

    Un método que devuelve el tipo del elemento de la barra de desplazamiento que envió el evento:

    //+------------------------------------------------------------------+
    //| Возвращает тип элемента, отправившего событие                    |
    //+------------------------------------------------------------------+
    ENUM_ELEMENT_TYPE CContainer::GetEventElementType(const string name)
      {
    //--- Получаем имена всех элементов в иерархии (при ошибке - возвращаем -1)
       string names[]={};
       int total = GetElementNames(name,"_",names);
       if(total==WRONG_VALUE)
          return WRONG_VALUE;
          
    //--- Если имя базового элемента в иерархии не совпадает с именем контейнера, то это не наше событие - уходим
       string base_name=names[0];
       if(base_name!=this.NameFG())
          return WRONG_VALUE;
          
    //--- События, пришедшие не от скроллбаров, пропускаем
       string check_name=::StringSubstr(names[1],0,4);
       if(check_name!="SCBH" && check_name!="SCBV")
          return WRONG_VALUE;
          
    //--- Получаем имя элемента, от которого пришло событие и инициализируем тип элемента
       string elm_name=names[names.Size()-1];
       ENUM_ELEMENT_TYPE type=WRONG_VALUE;
       
    //--- Проверяем и записываем тип элемента
    //--- Кнопка со стрелкой вверх
       if(::StringFind(elm_name,"BTARU")==0)
          type=ELEMENT_TYPE_BUTTON_ARROW_UP;
    //--- Кнопка со стрелкой вниз
       else if(::StringFind(elm_name,"BTARD")==0)
          type=ELEMENT_TYPE_BUTTON_ARROW_DOWN;
    //--- Кнопка со стрелкой влево
       else if(::StringFind(elm_name,"BTARL")==0)
          type=ELEMENT_TYPE_BUTTON_ARROW_LEFT;
    //--- Кнопка со стрелкой вправо
       else if(::StringFind(elm_name,"BTARR")==0)
          type=ELEMENT_TYPE_BUTTON_ARROW_RIGHT;
    //--- Ползунок горизонтальной полосы прокрутки
       else if(::StringFind(elm_name,"THMBH")==0)
          type=ELEMENT_TYPE_SCROLLBAR_THUMB_H;
    //--- Ползунок вертикальной полосы прокрутки
       else if(::StringFind(elm_name,"THMBV")==0)
          type=ELEMENT_TYPE_SCROLLBAR_THUMB_V;
    //--- Элемент управления ScrollBarHorisontal
       else if(::StringFind(elm_name,"SCBH")==0)
          type=ELEMENT_TYPE_SCROLLBAR_H;
    //--- Элемент управления ScrollBarVertical
       else if(::StringFind(elm_name,"SCBV")==0)
          type=ELEMENT_TYPE_SCROLLBAR_V;
          
    //--- Возвращаем тип элемента
       return type;
      }
    

    El método determina el tipo de elemento (por ejemplo, un botón de barra de desplazamiento, cursor, etc.) que envió el evento por el nombre de este elemento.

    1. El nombre del elemento se divide en partes mediante el carácter "_" para obtener una jerarquía de objetos anidados.

    2. Comprueba que el nombre base (el primer elemento de la jerarquía) coincida con el nombre del contenedor actual. Si no es así, el evento no se aplica a este contenedor, WRONG_VALUE.

    3. A continuación, se comprueba que el segundo elemento de la jerarquía sea una barra de desplazamiento (SCBH o SCBV). En caso contrario se ignora el evento.

    4. La última parte del nombre (el nombre del elemento en sí) determina el tipo de elemento:

      • BTARU — Botón de flecha hacia arriba
      • BTARD — Botón de flecha hacia abajo
      • BTARL — Botón de flecha izquierda
      • BTARR — Botón de flecha derecha
      • THMBH — Cursor horizontal
      • THMBV — Cursor vertical
      • SCBH — Barra de desplazamiento horizontal
      • SCBV — Barra de desplazamiento vertical

    5. Se devuelve el tipo de elemento correspondiente (ENUM_ELEMENT_TYPE). Si el tipo no está definido, se devuelve WRONG_VALUE.

    Este método permite al contenedor comprender de forma rápida y fiable qué elemento de la barra de desplazamiento ha activado el evento para procesarlo correctamente (por ejemplo, desplazarse por el contenido o mover el control deslizante).

    El controlador de un evento de elemento personalizado al mover el cursor en el área del objeto:

    //+------------------------------------------------------------------+
    //| CContainer::Обработчик пользовательского события элемента        |
    //| при перемещении курсора в области объекта                        |
    //+------------------------------------------------------------------+
    void CContainer::MouseMoveHandler(const int id,const long lparam,const double dparam,const string sparam)
      {
       bool res=false;
    //--- Получаем указатель на содержимое контейнера
       CElementBase *elm=this.GetAttachedElementAt(2);
    //--- Получаем тип элемента, от которого пришло событие
       ENUM_ELEMENT_TYPE type=this.GetEventElementType(sparam);
    //--- Если не удалось получить тип элемента или указатель на содержимое - уходим
       if(type==WRONG_VALUE || elm==NULL)
          return;
       
    //--- Если событие ползунка горизонтального скроллбара - сдвигаем содержимое по горизонтали
       if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_H)
          res=this.ContentShiftHor((int)lparam);
    
    //--- Если событие ползунка вертикального скроллбара - сдвигаем содержимое по вертикали
       if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_V)
          res=this.ContentShiftVer((int)lparam);
       
    //--- Если содержимое успешно сдвинуто - обновляем график
       if(res)
          ::ChartRedraw(this.m_chart_id);
      }
    

    Determina el tipo de evento y, si el evento proviene de la barra de desplazamiento, llama al método para desplazar el contenido del contenedor según el tipo de barra de desplazamiento: vertical u horizontal.

    El controlador de un evento de elemento personalizado al hacer clic en el área del objeto:

    //+------------------------------------------------------------------+
    //| CContainer::Обработчик пользовательского события элемента        |
    //| при щелчке в области объекта                                     |
    //+------------------------------------------------------------------+
    void CContainer::MousePressHandler(const int id,const long lparam,const double dparam,const string sparam)
      {
       bool res=false;
    //--- Получаем указатель на содержимое контейнера
       CElementBase *elm=this.GetAttachedElementAt(2);
    //--- Получаем тип элемента, от которого пришло событие
       ENUM_ELEMENT_TYPE type=this.GetEventElementType(sparam);
    //--- Если не удалось получить тип элемента или указатель на содержимое - уходим
       if(type==WRONG_VALUE || elm==NULL)
          return;
       
    //--- Если события кнопок горизонтального скроллбара,
       if(type==ELEMENT_TYPE_BUTTON_ARROW_LEFT || type==ELEMENT_TYPE_BUTTON_ARROW_RIGHT)
         {
          //--- Проверяем указатель на горизонтальный скроллбар
          if(this.m_scrollbar_h==NULL)
             return;
          //--- получаем указатель на ползунок скроллбара
          CScrollBarThumbH *obj=this.m_scrollbar_h.GetThumb();
          if(obj==NULL)
             return;
          //--- определяем направление смещения ползунка по типу нажатой кнопки
          int direction=(type==ELEMENT_TYPE_BUTTON_ARROW_LEFT ? 120 : -120);
          //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении direction
          obj.OnWheelEvent(id,0,direction,this.NameFG());
          //--- Успешно
          res=true;
         }
       
    //--- Если события кнопок вертикального скроллбара,
       if(type==ELEMENT_TYPE_BUTTON_ARROW_UP || type==ELEMENT_TYPE_BUTTON_ARROW_DOWN)
         {
          //--- Проверяем указатель на вертикальный скроллбар
          if(this.m_scrollbar_v==NULL)
             return;
          //--- получаем указатель на ползунок скроллбара
          CScrollBarThumbV *obj=this.m_scrollbar_v.GetThumb();
          if(obj==NULL)
             return;
          //--- определяем направление смещения ползунка по типу нажатой кнопки
          int direction=(type==ELEMENT_TYPE_BUTTON_ARROW_UP ? 120 : -120);
          //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении direction
          obj.OnWheelEvent(id,0,direction,this.NameFG());
          //--- Успешно
          res=true;
         }
    
    //--- Если событие щелчка по горизонтальному скроллбару (между ползунком и кнопками прокрутки),
       if(type==ELEMENT_TYPE_SCROLLBAR_H)
         {
          //--- Проверяем указатель на горизонтальный скроллбар
          if(this.m_scrollbar_h==NULL)
             return;
          //--- получаем указатель на ползунок скроллбара
          CScrollBarThumbH *thumb=this.m_scrollbar_h.GetThumb();
          if(thumb==NULL)
             return;
          //--- Направление смещения ползунка
          int direction=(lparam>=thumb.Right() ? 1 : lparam<=thumb.X() ? -1 : 0);
    
          //--- Проверяем делитель на нулевое значение
          if(this.ContentSizeHor()-this.ContentVisibleHor()==0)
             return;     
          
          //--- Рассчитываем смещение ползунка, пропорциональное смещению содержимого на один экран
          int thumb_shift=(int)::round(direction * ((double)this.ContentVisibleHor() / double(this.ContentSizeHor()-this.ContentVisibleHor())) * (double)this.TrackEffectiveLengthHor());
          //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении смещения
          thumb.OnWheelEvent(id,thumb_shift,0,this.NameFG());
          //--- Записываем результат смещения содержимого контейнера
          res=this.ContentShiftHor(thumb_shift);
         }
       
    //--- Если событие щелчка по вертикальному скроллбару (между ползунком и кнопками прокрутки),
       if(type==ELEMENT_TYPE_SCROLLBAR_V)
         {
          //--- Проверяем указатель на вертикальный скроллбар
          if(this.m_scrollbar_v==NULL)
             return;
          //--- получаем указатель на ползунок скроллбара
          CScrollBarThumbV *thumb=this.m_scrollbar_v.GetThumb();
          if(thumb==NULL)
             return;
          //--- Направление смещения ползунка
          int cursor=int(dparam-this.m_wnd_y);
          int direction=(cursor>=thumb.Bottom() ? 1 : cursor<=thumb.Y() ? -1 : 0);
    
          //--- Проверяем делитель на нулевое значение
          if(this.ContentSizeVer()-this.ContentVisibleVer()==0)
             return;     
          
          //--- Рассчитываем смещение ползунка, пропорциональное смещению содержимого на один экран
          int thumb_shift=(int)::round(direction * ((double)this.ContentVisibleVer() / double(this.ContentSizeVer()-this.ContentVisibleVer())) * (double)this.TrackEffectiveLengthVer());
          //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении смещения
          thumb.OnWheelEvent(id,thumb_shift,0,this.NameFG());
          //--- Записываем результат смещения содержимого контейнера
          res=this.ContentShiftVer(thumb_shift);
         }
       
    //--- Если всё успешно - обновляем график
       if(res)
          ::ChartRedraw(this.m_chart_id);
      }
    

    El método gestiona los clics del ratón en los elementos de la barra de desplazamiento (botones, pista, controles deslizantes).

    • Cuando se hace clic en los botones, el manejo del desplazamiento del cursor se delega al cursor de la barra de desplazamiento. Y como resultado, el cursor se desplaza y el contenido del contenedor se desplaza.
    • Al hacer clic en una pista (entre los botones del cursor y de la barra de desplazamiento), el contenido se desplaza a una pantalla. El manejo se delega al controlador de desplazamiento del cursor de la barra de desplazamiento. Como resultado, el contenido del contenedor se desplaza a una pantalla.

    Este método proporciona el comportamiento estándar de las barras de desplazamiento:

    • Al hacer clic en la flecha se realiza un desplazamiento paso a paso.
    • Al hacer clic en una pista se desplaza hasta la página.
    • Todo está sincronizado con el contenido del contenedor y la posición del cursor.
      Esto hace que trabajar con la barra de desplazamiento sea familiar y fácil de usar.

    El controlador de un evento de elemento personalizado al desplazarse con la rueda en el área del cursor de la barra de desplazamiento:

    //+------------------------------------------------------------------+
    //| CContainer::Обработчик пользовательского события элемента        |
    //| при прокрутке колёсика в области ползунка скроллбара             |
    //+------------------------------------------------------------------+
    void CContainer::MouseWheelHandler(const int id,const long lparam,const double dparam,const string sparam)
      {
       bool res=false;
    //--- Получаем указатель на содержимое контейнера
       CElementBase *elm=this.GetAttachedElementAt(2);
    //--- Получаем тип элемента, от которого пришло событие
       ENUM_ELEMENT_TYPE type=this.GetEventElementType(sparam);
    //--- Если не удалось получить указатель на содержимое, или тип элемента - уходим
       if(type==WRONG_VALUE || elm==NULL)
          return;
       
    //--- Если событие ползунка горизонтального скроллбара - сдвигаем содержимое по горизонтали
       if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_H)
          res=this.ContentShiftHor((int)lparam);
    
    //--- Если событие ползунка вертикального скроллбара - сдвигаем содержимое по вертикали
       if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_V)
          res=this.ContentShiftVer((int)lparam);
       
    //--- Если содержимое успешно сдвинуто - обновляем график
       if(res)
          ::ChartRedraw(this.m_chart_id);
      }
    

    El método maneja el evento en el que la rueda del mouse se desplaza sobre la barra de desplazamiento. Dependiendo de qué cursor haya desencadenado el evento (horizontal o vertical), el método desplaza el contenido del contenedor horizontal o verticalmente la distancia adecuada. Una vez que el contenido se ha cambiado correctamente, se actualiza el gráfico.

    Y por hoy esto es todo lo que planeamos implementar.

    Veamos qué tenemos. Crea un indicador en una ventana separada del gráfico. Implementar un elemento gráfico «Contenedor», que contendrá un «Grupo de elementos». En el grupo de elementos, cree un conjunto de cadenas a partir de los elementos «Etiqueta de texto». Haga que el elemento GroupBox sea más grande que el contenedor para mostrar las barras de desplazamiento. Los probaremos.


    Probando el resultado

    En el directorio \MQL5\Indicators\ de la subcarpeta Tables\, cree un nuevo archivo de indicador en la subventana del gráfico con el nombre iTestContainer.mq5. Conecte la biblioteca a él y declare un puntero al elemento gráfico Container:

    //+------------------------------------------------------------------+
    //|                                               iTestContainer.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"
    #property indicator_separate_window
    #property indicator_buffers 0
    #property indicator_plots   0
    
    //+------------------------------------------------------------------+
    //| Включаемые библиотеки                                            |
    //+------------------------------------------------------------------+
    #include "Controls\Controls.mqh"    // Библиотека элементов управления
    
    CContainer       *container=NULL;   // Указатель на графический элемент Container
    
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- 
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Custom deindicator initialization function                       |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
      }
    //+------------------------------------------------------------------+
    //| Custom indicator iteration function                              |
    //+------------------------------------------------------------------+
    int OnCalculate(const int rates_total,
                    const int prev_calculated,
                    const datetime &time[],
                    const double &open[],
                    const double &high[],
                    const double &low[],
                    const double &close[],
                    const long &tick_volume[],
                    const long &volume[],
                    const int &spread[])
      {
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
      }
    //+------------------------------------------------------------------+
    //| Таймер                                                           |
    //+------------------------------------------------------------------+
    void OnTimer(void)
      {
      }
    

    Cree todos los elementos en el controlador OnInit() del indicador:

    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- Ищем подокно графика
       int wnd=ChartWindowFind();
    
    //--- Создаём графический элемент "Контейнер"
       container=new CContainer("Container","",0,wnd,100,40,300,200);
       if(container==NULL)
          return INIT_FAILED;
       container.SetID(1);           // Идентификатор
       container.SetAsMain();        // На графике обязательно должен быть один главный элемент
       container.SetBorderWidth(1);  // Ширина рамки (отступ видимой области на один пиксель с каждой стороны контейнера)
       
    //--- Присоединяем к контейнеру элемент GroupBox
       CGroupBox *groupbox=container.InsertNewElement(ELEMENT_TYPE_GROUPBOX,"","Attached Groupbox",4,4,container.Width()*2+20,container.Height()*3+10);
       if(groupbox==NULL)
          return INIT_FAILED;
       groupbox.SetGroup(1);         // Номер группы
       
    //--- В цикле создаёи и присоединяем к элементу GroupBox 30 строк из элементов "Текстовая метка"
       for(int i=0;i<30;i++)
         {
          string text=StringFormat("This is test line number %d to demonstrate how scrollbars work when scrolling the contents of the container.",(i+1));
          int len=groupbox.GetForeground().TextWidth(text);
          CLabel *lbl=groupbox.InsertNewElement(ELEMENT_TYPE_LABEL,text,"TextString"+string(i+1),8,8+(20*i),len,20);
          if(lbl==NULL)
             return INIT_FAILED;
         }
       
    //--- Отрисовываем на графике все созданные элементы и распечатываем их описание в журнале
       container.Draw(true);
       container.Print();
       
    //--- Успешно
       return(INIT_SUCCEEDED);
      }
    

    En el controlador OnDeinit() del indicador, elimine el contenedor creado y el administrador de recursos compartidos de la biblioteca:

    //+------------------------------------------------------------------+
    //| Custom deindicator initialization function                       |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- Удаляем элемент Контейнер и уничтожаем менеджер общих ресурсов библиотеки
       delete container;
       CCommonManager::DestroyInstance();
      }
    

    En el controlador OnChartEvent(), llame al controlador de contenedor similar:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Вызываем обработчик OnChartEvent элемента Контейнер
       container.OnChartEvent(id,lparam,dparam,sparam);
      }
    

    En el controlador OnTimer() del indicador, llame a OnTimer del contenedor:

    //+------------------------------------------------------------------+
    //| Таймер                                                           |
    //+------------------------------------------------------------------+
    void OnTimer(void)
      {
    //--- Вызываем обработчик OnTimer элемента Контейнер
       container.OnTimer();
      }
    

    Compile el indicador y ejecútelo en el gráfico:


    El cambio a pantalla completa al hacer clic en una pista funciona, el cambio al hacer clic en los botones funciona, la repetición automática de eventos al mantener pulsados los botones funciona, el desplazamiento con la rueda funciona.

    Una vez creados todos los controles, se imprimirán en el registro las descripciones de todos los elementos creados:

    Container (ContainerBG, ContainerFG): ID 1, Group -1, x 100, y 40, w 300, h 200
       [2]: Groupbox "Attached Groupbox" (ContainerFG_GRBX2BG, ContainerFG_GRBX2FG): ID 2, Group 1, x 100, y 40, w 620, h 610
          [0]: Label "TextString1" (ContainerFG_GRBX2FG_LBL0BG, ContainerFG_GRBX2FG_LBL0FG): ID 0, Group 1, x 108, y 48, w 587, h 20
          [1]: Label "TextString2" (ContainerFG_GRBX2FG_LBL1BG, ContainerFG_GRBX2FG_LBL1FG): ID 1, Group 1, x 108, y 68, w 587, h 20
          [2]: Label "TextString3" (ContainerFG_GRBX2FG_LBL2BG, ContainerFG_GRBX2FG_LBL2FG): ID 2, Group 1, x 108, y 88, w 587, h 20
          [3]: Label "TextString4" (ContainerFG_GRBX2FG_LBL3BG, ContainerFG_GRBX2FG_LBL3FG): ID 3, Group 1, x 108, y 108, w 587, h 20
          [4]: Label "TextString5" (ContainerFG_GRBX2FG_LBL4BG, ContainerFG_GRBX2FG_LBL4FG): ID 4, Group 1, x 108, y 128, w 587, h 20
          [5]: Label "TextString6" (ContainerFG_GRBX2FG_LBL5BG, ContainerFG_GRBX2FG_LBL5FG): ID 5, Group 1, x 108, y 148, w 587, h 20
          [6]: Label "TextString7" (ContainerFG_GRBX2FG_LBL6BG, ContainerFG_GRBX2FG_LBL6FG): ID 6, Group 1, x 108, y 168, w 587, h 20
          [7]: Label "TextString8" (ContainerFG_GRBX2FG_LBL7BG, ContainerFG_GRBX2FG_LBL7FG): ID 7, Group 1, x 108, y 188, w 587, h 20
          [8]: Label "TextString9" (ContainerFG_GRBX2FG_LBL8BG, ContainerFG_GRBX2FG_LBL8FG): ID 8, Group 1, x 108, y 208, w 587, h 20
          [9]: Label "TextString10" (ContainerFG_GRBX2FG_LBL9BG, ContainerFG_GRBX2FG_LBL9FG): ID 9, Group 1, x 108, y 228, w 594, h 20
          [10]: Label "TextString11" (ContainerFG_GRBX2FG_LBL10BG, ContainerFG_GRBX2FG_LBL10FG): ID 10, Group 1, x 108, y 248, w 594, h 20
          [11]: Label "TextString12" (ContainerFG_GRBX2FG_LBL11BG, ContainerFG_GRBX2FG_LBL11FG): ID 11, Group 1, x 108, y 268, w 594, h 20
          [12]: Label "TextString13" (ContainerFG_GRBX2FG_LBL12BG, ContainerFG_GRBX2FG_LBL12FG): ID 12, Group 1, x 108, y 288, w 594, h 20
          [13]: Label "TextString14" (ContainerFG_GRBX2FG_LBL13BG, ContainerFG_GRBX2FG_LBL13FG): ID 13, Group 1, x 108, y 308, w 594, h 20
          [14]: Label "TextString15" (ContainerFG_GRBX2FG_LBL14BG, ContainerFG_GRBX2FG_LBL14FG): ID 14, Group 1, x 108, y 328, w 594, h 20
          [15]: Label "TextString16" (ContainerFG_GRBX2FG_LBL15BG, ContainerFG_GRBX2FG_LBL15FG): ID 15, Group 1, x 108, y 348, w 594, h 20
          [16]: Label "TextString17" (ContainerFG_GRBX2FG_LBL16BG, ContainerFG_GRBX2FG_LBL16FG): ID 16, Group 1, x 108, y 368, w 594, h 20
          [17]: Label "TextString18" (ContainerFG_GRBX2FG_LBL17BG, ContainerFG_GRBX2FG_LBL17FG): ID 17, Group 1, x 108, y 388, w 594, h 20
          [18]: Label "TextString19" (ContainerFG_GRBX2FG_LBL18BG, ContainerFG_GRBX2FG_LBL18FG): ID 18, Group 1, x 108, y 408, w 594, h 20
          [19]: Label "TextString20" (ContainerFG_GRBX2FG_LBL19BG, ContainerFG_GRBX2FG_LBL19FG): ID 19, Group 1, x 108, y 428, w 594, h 20
          [20]: Label "TextString21" (ContainerFG_GRBX2FG_LBL20BG, ContainerFG_GRBX2FG_LBL20FG): ID 20, Group 1, x 108, y 448, w 594, h 20
          [21]: Label "TextString22" (ContainerFG_GRBX2FG_LBL21BG, ContainerFG_GRBX2FG_LBL21FG): ID 21, Group 1, x 108, y 468, w 594, h 20
          [22]: Label "TextString23" (ContainerFG_GRBX2FG_LBL22BG, ContainerFG_GRBX2FG_LBL22FG): ID 22, Group 1, x 108, y 488, w 594, h 20
          [23]: Label "TextString24" (ContainerFG_GRBX2FG_LBL23BG, ContainerFG_GRBX2FG_LBL23FG): ID 23, Group 1, x 108, y 508, w 594, h 20
          [24]: Label "TextString25" (ContainerFG_GRBX2FG_LBL24BG, ContainerFG_GRBX2FG_LBL24FG): ID 24, Group 1, x 108, y 528, w 594, h 20
          [25]: Label "TextString26" (ContainerFG_GRBX2FG_LBL25BG, ContainerFG_GRBX2FG_LBL25FG): ID 25, Group 1, x 108, y 548, w 594, h 20
          [26]: Label "TextString27" (ContainerFG_GRBX2FG_LBL26BG, ContainerFG_GRBX2FG_LBL26FG): ID 26, Group 1, x 108, y 568, w 594, h 20
          [27]: Label "TextString28" (ContainerFG_GRBX2FG_LBL27BG, ContainerFG_GRBX2FG_LBL27FG): ID 27, Group 1, x 108, y 588, w 594, h 20
          [28]: Label "TextString29" (ContainerFG_GRBX2FG_LBL28BG, ContainerFG_GRBX2FG_LBL28FG): ID 28, Group 1, x 108, y 608, w 594, h 20
          [29]: Label "TextString30" (ContainerFG_GRBX2FG_LBL29BG, ContainerFG_GRBX2FG_LBL29FG): ID 29, Group 1, x 108, y 628, w 594, h 20
    

    La funcionalidad indicada funciona correctamente. Hay algunos defectos menores, pero se eliminarán con el desarrollo posterior del control TableView.


    Conclusión

    Hoy hemos implementado una funcionalidad bastante amplia y necesaria en la biblioteca de controles que estamos desarrollando.

    La clase CContainer es una herramienta potente y práctica para crear áreas desplazables en interfaces de usuario. Automatiza el funcionamiento de las barras de desplazamiento, facilita la gestión de contenidos de gran tamaño y proporciona una interacción intuitiva con las áreas desplazables. Gracias a su arquitectura flexible y a su integración con otros elementos de interfaz, el contenedor es fácil de usar como parte de soluciones gráficas complejas.

    Nuestro siguiente paso será crear un encabezado que permita colocar, por ejemplo, una lista de encabezados de columnas de tabla, mientras que la funcionalidad permitirá cambiar el tamaño de cada celda del encabezado. Esto cambiará automáticamente el tamaño de las columnas de la tabla.

    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  Controls.mqh  Biblioteca de clases  Clases de control
     3  iTestContainer.mq5  Indicador de prueba  Indicador para probar manipulaciones con clases de controles
     4  MQL5.zip  Archivo  Un archivo de los archivos anteriores para descomprimirlos en el directorio MQL5 del terminal del cliente
    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 deseada: \MQL5\Indicators\Tables\.

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

    Archivos adjuntos |
    Base.mqh (258.41 KB)
    Controls.mqh (421.22 KB)
    iTestContainer.mq5 (9.41 KB)
    MQL5.zip (60.39 KB)
    Nguyen Tuấn Anh
    Nguyen Tuấn Anh | 14 abr 2026 en 08:05
    No estoy seguro de lo que Happend pero cuando construir el código original y ejecutarlo no muestran como se describe en la parte inferior de la articale
    Artyom Trishkin
    Artyom Trishkin | 14 abr 2026 en 18:22
    Nguyen Tuấn Anh #:
    No estoy seguro de lo que pasó, pero cuando construyo el código original y ejecutarlo, se muestra diferente de lo que se describe en la parte inferior del artículo.

    Elimine el indicador del gráfico. Pulse Ctrl+B, haga clic en "Todos" en la ventana de lista de objetos gráficos que se abre y elimine todos los objetos. A continuación, vuelva a lanzar el indicador.

    Pero sería mejor especificar lo que está escrito en el registro - lo más probable es que hubo un error al construir objetos.

    Aprendizaje automático y Data Science (Parte 34): Descomposición de series temporales, desglosando el mercado bursátil hasta su núcleo Aprendizaje automático y Data Science (Parte 34): Descomposición de series temporales, desglosando el mercado bursátil hasta su núcleo
    En un mundo repleto de datos ruidosos e impredecibles, identificar patrones significativos puede resultar complicado. En este artículo, exploraremos la descomposición estacional, una potente técnica analítica que ayuda a separar los datos en sus componentes clave: tendencia, patrones estacionales y ruido. Al desglosar los datos de esta manera, podemos descubrir información oculta y trabajar con datos más claros y fáciles de interpretar.
    De novato a experto: Noticias animadas utilizando MQL5 (III) — Información sobre indicadores De novato a experto: Noticias animadas utilizando MQL5 (III) — Información sobre indicadores
    En este artículo, mejoraremos el EA News Headline introduciendo una línea dedicada a la información de los indicadores: una visualización compacta en el gráfico de las señales técnicas clave generadas a partir de indicadores populares como el RSI, el MACD, el estocástico y el CCI. Este enfoque elimina la necesidad de múltiples subventanas de indicadores en la terminal MetaTrader 5, lo que mantiene su espacio de trabajo limpio y eficiente. Al aprovechar la API MQL5 para acceder a los datos de los indicadores en segundo plano, podemos procesar y visualizar información del mercado en tiempo real utilizando una lógica personalizada. Únase a nosotros para explorar cómo manipular los datos de los indicadores en MQL5 para crear un sistema de información inteligente y que ahorra espacio, todo ello en una sola línea horizontal en su gráfico de operaciones.
    Creación de un Panel de administración de operaciones en MQL5 (Parte IX): Organización del código (III): Módulo de comunicación Creación de un Panel de administración de operaciones en MQL5 (Parte IX): Organización del código (III): Módulo de comunicación
    Únase a nosotros para profundizar en los últimos avances en el diseño de la interfaz MQL5, mientras presentamos el panel de comunicaciones rediseñado y continuamos nuestra serie sobre la creación del nuevo Panel de administración utilizando los principios de la modularización. Desarrollaremos la clase CommunicationsDialog paso a paso, explicando detalladamente cómo heredarla de la clase Dialog. Además, aprovecharemos las matrices y la clase ListView en nuestro desarrollo. Obtenga información útil para mejorar sus habilidades de desarrollo en MQL5: ¡lea el artículo y participe en el debate en la sección de comentarios!
    Envío de mensajes desde MQL5 a Discord: creación de un bot Discord–MetaTrader 5 Envío de mensajes desde MQL5 a Discord: creación de un bot Discord–MetaTrader 5
    Al igual que Telegram, Discord es capaz de recibir información y mensajes en formato JSON utilizando sus API de comunicación. En este artículo, vamos a explorar cómo se pueden utilizar las API de Discord para enviar señales de trading y actualizaciones desde MetaTrader 5 a su comunidad de trading en Discord.