El componente View para tablas en el paradigma MQL5 MVC: Contenedores
Contenido
- Introducción
- Clase Singleton como gestor de datos compartidos
- Clases para organizar la repetición automática de clics de botones
- Refinando las clases base
- Clase de lista de objetos
- Clase base de un elemento gráfico
- Refinando controles simples
- Clases de contenedor para colocar controles
- Clase Panel
- Clase GroupBox
- Clases para crear barras de desplazamiento
- Clases de control deslizante
- Clase de barra de desplazamiento horizontal
- Clase de barra de desplazamiento vertical
- Clase Container
- Probando el resultado
- Conclusión
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
- Un constructor oculto: El constructor de la clase se declara privado o protegido para impedir la creación de instancias desde el exterior.
- Variable estática: Una variable estática se crea dentro de la clase y almacena una única instancia de la clase.
- 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.
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)
- Barra de desplazamiento horizontal:
- 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.
-
El nombre del elemento se divide en partes mediante el carácter "_" para obtener una jerarquía de objetos anidados.
-
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.
-
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.
-
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
-
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 |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/18658
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Aprendizaje automático y Data Science (Parte 34): Descomposición de series temporales, desglosando el mercado bursátil hasta su núcleo
De novato a experto: Noticias animadas utilizando MQL5 (III) — Información sobre indicadores
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
Envío de mensajes desde MQL5 a Discord: creación de un bot Discord–MetaTrader 5
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
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.