El componente View para tablas en el paradigma MQL5 MVC: Controles sencillos
Contenido
- Introducción
- Componente controlador. Refinando las clases base
- Controles simples
- Clases auxiliares
- Etiqueta de texto
- Botón simple
- Botón bidireccional
- Botón de flecha hacia arriba
- Botón de flecha hacia abajo
- Botón de flecha izquierda
- Botón de flecha derecha
- Casilla de verificación
- Botón de selección
- Probando el resultado
- Conclusión
Introducción
Como parte del desarrollo del control View de tabla en el paradigma MVC (Modelo-Vista-Controlador), creamos un modelo de tabla (el componente Modelo) y comenzamos a crear el componente Vista. En la primera etapa, se creó un objeto básico, que es el progenitor de todos los demás elementos gráficos.
Hoy comenzaremos a desarrollar controles sencillos que más adelante servirán como bloques de construcción para elementos compuestos de la interfaz de usuario. Cada elemento de control tendrá la funcionalidad de interactuar con el usuario y con otros elementos. En otras palabras, esto corresponde esencialmente a la funcionalidad del componente Controlador.
Dado que en el lenguaje MQL el modelo de eventos está integrado en objetos creados mediante eventos de gráfico, el manejo de eventos se organizará en todos los controles posteriores para implementar la conexión entre el componente Vista y el componente Controlador. Para ello, refine la clase base de los elementos gráficos.
A continuación, cree controles sencillos: una etiqueta de texto y varios botones. Cada elemento admitirá iconos de dibujo. Esto permitirá crear controles completamente diferentes a partir de botones simples. Si observa la cadena de la vista de árbol, donde hay un ícono a la izquierda y el texto a la derecha, entonces parece ser un control separado. Pero podemos crear fácilmente dicho control utilizando un botón normal como base. Al mismo tiempo, será posible ajustar los parámetros de la cadena para que reaccione cambiando el color cuando el cursor del mouse esté enfocado y se haga clic, o sea estática, pero reaccione a los clics.
Todo esto se puede implementar con sólo unas pocas líneas de configuración después de crear el objeto. Y a partir de estos elementos, continuaremos creando controles compuestos complejos que sean totalmente interactivos y listos para usar.
Componente controlador. Refinando las clases base
Entonces, para implementar nuestros planes, debemos refinar ligeramente las clases, las sustituciones de macros y las enumeraciones ya implementadas. La mayor parte de la funcionalidad necesaria se ubicará en el objeto base de elementos gráficos. Por lo tanto, es esto lo que principalmente se va a refinar.
Anteriormente, esta clase estaba en MQL5\Scripts\Tables\Controls\Base.mqh.
Hoy escribiremos un indicador de prueba, por lo que queremos crear una nueva carpeta \Tables\Controls\ en el directorio del indicador \MQ5\Indicators\ y ubicar el archivo Base.mqh en ella. En eso trabajaremos hoy.
Además, los objetos contendrán listas de controles adjuntos. Los contenedores pueden ser tales objetos, por ejemplo: Para que estas listas manejen los archivos correctamente, es decir, creen objetos almacenados en listas, es necesario declarar con antelación todas las clases de elementos que se crearán. Escriba una declaración de clase, nuevas sustituciones de macros, enumeraciones y constantes de enumeración en el archivo Base.mqh:
//+------------------------------------------------------------------+ //| Base.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Canvas\Canvas.mqh> // Класс СБ CCanvas #include <Arrays\List.mqh> // Класс СБ CList //--- Форвард-декларация классов элементов управления class CImagePainter; // Класс рисования изображений class CLabel; // Класс текстовой метки class CButton; // Класс простой кнопки class CButtonTriggered; // Класс двухпозиционной кнопки class CButtonArrowUp; // Класс кнопки со стрелкой вверх class CButtonArrowDown; // Класс кнопки со стрелкой вниз class CButtonArrowLeft; // Класс кнопки со стрелкой влево class CButtonArrowRight; // Класс кнопки со стрелкой вправо class CCheckBox; // Класс элемента управления CheckBox class CRadioButton; // Класс элемента управления RadioButton //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define clrNULL 0x00FFFFFF // Прозрачный цвет для CCanvas #define MARKER_START_DATA -1 // Маркер начала данных в файле #define DEF_FONTNAME "Calibri" // Шрифт по умолчанию #define DEF_FONTSIZE 10 // Размер шрифта по умолчанию //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ 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_CANVAS_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 }; enum ENUM_ELEMENT_STATE // Состояние элемента { ELEMENT_STATE_DEF, // По умолчанию (напр. кнопка отжата, и т.п.) ELEMENT_STATE_ACT, // Активирован (напр. кнопка нажата, и т.п.) }; enum ENUM_COLOR_STATE // Перечисление цветов состояний элемента { COLOR_STATE_DEFAULT, // Цвет обычного состояния COLOR_STATE_FOCUSED, // Цвет при наведении курсора на элемент COLOR_STATE_PRESSED, // Цвет при нажатии на элемент COLOR_STATE_BLOCKED, // Цвет заблокированного элемента }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+
Al crear métodos para guardar y cargar objetos en/desde archivos, cada método tiene cadenas que se repiten constantemente sin cambiar de un método a otro:
//--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false;
Y existen los mismos métodos Description e Print. Por lo tanto, es razonable transferir estas cadenas a los métodos de carga/guardado en el objeto base. Entonces no tendrán que escribirse en cada nuevo método de carga/guardado en cada nueva clase donde se proporcionan manipulaciones con archivos.
Declare estos métodos en el objeto base:
public: //--- Устанавливает (1) наименование, (2) идентификатор void SetName(const string name) { ::StringToShortArray(name,this.m_name); } void SetID(const int id) { this.m_id=id; } //--- Возвращает (1) наименование, (2) идентификатор string Name(void) const { return ::ShortArrayToString(this.m_name); } int ID(void) const { return this.m_id; } //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (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_BASE); } //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); virtual void Print(void); //--- Конструктор/деструктор CBaseObj (void) : m_id(-1) { this.SetName(""); } ~CBaseObj (void) {} };
Y escribe su implementación:
//+------------------------------------------------------------------+ //| CBaseObj::Возвращает описание объекта | //+------------------------------------------------------------------+ string CBaseObj::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); return ::StringFormat("%s%s ID %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID()); } //+------------------------------------------------------------------+ //| CBaseObj::Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CBaseObj::Print(void) { ::Print(this.Description()); } //+------------------------------------------------------------------+ //| CBaseObj::Сохранение в файл | //+------------------------------------------------------------------+ bool CBaseObj::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CBaseObj::Загрузка из файла | //+------------------------------------------------------------------+ bool CBaseObj::Load(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=-1) return false; //--- Загружаем тип объекта if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return false; //--- Загружаем идентификатор this.m_id=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем наименование if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; }
Ahora, para cada nueva clase, los métodos Description e Print pueden declararse e implementarse sólo si su lógica difiere de la lógica prescrita en esta clase.
Y en los métodos de trabajo con archivos en clases derivadas, en lugar de escribir repetidamente las mismas líneas de código en cada método de cada clase, simplemente nos dirigiremos a los métodos de trabajo con archivos de este objeto base.
De todas las clases posteriores de este archivo (Base.mqh), elimina todos los métodos Print, ya que ya se encuentran en el objeto base y lo repiten por completo.
En todos los métodos de trabajo con archivos, elimine estas cadenas:
//+------------------------------------------------------------------+ //| CColor::Сохранение в файл | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем цвет if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; }
Ahora, en lugar de estas cadenas, solo tenemos una llamada al método de la clase base:
//+------------------------------------------------------------------+ //| CColor::Сохранение в файл | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем цвет if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; }
Estos cambios ya se han realizado en todos los métodos de trabajo con archivos de este archivo. Tendremos en cuenta estos cambios al escribir las clases posteriores.
En la clase CColorElement, sustituya cadenas duplicadas idénticas en los constructores de clase.
//+------------------------------------------------------------------+ //| CColorControl::Конструктор с установкой прозрачных цветов объекта| //+------------------------------------------------------------------+ CColorElement::CColorElement(void) { this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL); this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); }
Utilizando un método Init():
public: //--- Возвращает новый цвет color NewColor(color base_color, int shift_red, int shift_green, int shift_blue); //--- Инициализация класса void Init(void); //--- Инициализация цветов различных состояний
...
Su implementación:
//+------------------------------------------------------------------+ //| CColorControl::Инициализация класса | //+------------------------------------------------------------------+ void CColorElement::Init(void) { this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); } //+------------------------------------------------------------------+
Si se pasa un color transparente al método de inicialización del color, no es necesario cambiarlo de ninguna manera para ningún estado.
Téngalo en cuenta en la implementación del método:
//+------------------------------------------------------------------+ //| CColorControl::Устанавливает цвета для всех состояний по текущему| //+------------------------------------------------------------------+ void CColorElement::InitColors(const color clr) { this.InitDefault(clr); this.InitFocused(clr!=clrNULL ? this.NewColor(clr,-20,-20,-20) : clrNULL); this.InitPressed(clr!=clrNULL ? this.NewColor(clr,-40,-40,-40) : clrNULL); this.InitBlocked(clrWhiteSmoke); }
En la clase CBound, añada un método que devuelva un indicador de la presencia del cursor dentro de un área rectangular. Esto es necesario al implementar el componente Controlador:
//+------------------------------------------------------------------+ //| Класс прямоугольной области | //+------------------------------------------------------------------+ class CBound : public CBaseObj { protected: CRect m_bound; // Структура прямоугольной области public: //--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника void ResizeW(const int size) { this.m_bound.Width(size); } void ResizeH(const int size) { this.m_bound.Height(size); } void Resize(const int w,const int h) { this.m_bound.Width(w); this.m_bound.Height(h); } //--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника void SetX(const int x) { this.m_bound.left=x; } void SetY(const int y) { this.m_bound.top=y; } void SetXY(const int x,const int y) { this.m_bound.LeftTop(x,y); } //--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения void Move(const int x,const int y) { this.m_bound.Move(x,y); } void Shift(const int dx,const int dy) { this.m_bound.Shift(dx,dy); } //--- Возвращает координаты, размеры и границы объекта int X(void) const { return this.m_bound.left; } int Y(void) const { return this.m_bound.top; } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } int Right(void) const { return this.m_bound.right-(this.m_bound.Width() >0 ? 1 : 0);} int Bottom(void) const { return this.m_bound.bottom-(this.m_bound.Height()>0 ? 1 : 0);} //--- Возвращает флаг нахождения курсора внутри области bool Contains(const int x,const int y) const { return this.m_bound.Contains(x,y); } //--- Возвращает описание объекта 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_RECTANGLE_AREA); } //--- Конструкторы/деструктор CBound(void) { ::ZeroMemory(this.m_bound); } CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); } ~CBound(void) { ::ZeroMemory(this.m_bound); } };
Ahora es necesario añadir todo lo necesario para la implementación del componente Controller a la clase base del elemento gráfico CCanvasBase canvas.
Cuando los elementos gráficos interactúan con el ratón, es necesario desactivar algunas propiedades del gráfico, como el desplazamiento del gráfico con la rueda, el menú del botón derecho del ratón, etc. Cada objeto de los elementos gráficos hará esto. Pero cuando comience por primera vez, debe recordar el estado de las propiedades del gráfico tal como estaba antes de iniciar el programa. Y después de terminar el trabajo, devuelva todo a su lugar.
Para ello, en la sección privada de la clase CCanvasBase, declare variables para almacenar valores de propiedades de gráficos almacenadas y declare un método para establecer restricciones en las propiedades de los gráficos:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ 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; // Флаг прокрутки графика левой кнопкой и колёсиком мышки //--- Установка запретов для графика (прокрутка колёсиком, контекстное меню и перекрестие) void SetFlags(const bool flag); protected:
Los elementos de la interfaz de usuario pueden tener dos estados (quizás más, pero por ahora, dos). Por ejemplo, para un botón: presionado, liberado. Esto significa que debemos controlar los estados de color del elemento en sus dos estados. En la sección protegida de la clase, define una variable para almacenar el estado del elemento, otro conjunto de objetos de gestión del color y un control de transparencia independiente para el lienzo de fondo y el de primer plano:
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; // Объект управления цветом рамки активированного элемента 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; // Ширина рамки string m_program_name; // Имя программы bool m_hidden; // Флаг скрытого объекта bool m_blocked; // Флаг заблокированного элемента bool m_focused; // Флаг элемента в фокусе
Aquí, también declara métodos para controlar el cursor del ratón, la gestión del color y controladores de eventos virtuales:
//--- Ограничивает графический объект по размерам контейнера virtual void ObjectTrim(void); //--- Возвращает флаг нахождения курсора внутри объекта bool Contains(const int x,const int y); //--- Проверяет установленный цвет на равенство указанному bool CheckColor(const ENUM_COLOR_STATE state) const; //--- Изменяет цвета фона, текста и рамки в зависимости от условия void ColorChange(const ENUM_COLOR_STATE state); //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), (3) прокрутки колёсика (Wheel), //--- (4) ухода из фокуса (Release), (5) создания графического объекта (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 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; } // обработчик здесь отключен public:
En la sección pública de la clase, añadir métodos para obtener objetos de gestión del color de los elementos en estado activado y métodos para obtener colores en diversos estados del elemento:
public: //--- Возвращает указатель на канвас (1) фона, (2) переднего плана CCanvas *GetBackground(void) { return &this.m_background; } CCanvas *GetForeground(void) { return &this.m_foreground; } //--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки CColorElement *GetBackColorControl(void) { return &this.m_color_background; } CColorElement *GetForeColorControl(void) { return &this.m_color_foreground; } CColorElement *GetBorderColorControl(void) { return &this.m_color_border; } //--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки активированного элемента CColorElement *GetBackColorActControl(void) { return &this.m_color_background_act; } CColorElement *GetForeColorActControl(void) { return &this.m_color_foreground_act; } CColorElement *GetBorderColorActControl(void) { return &this.m_color_border_act; } //--- Возврат текущего цвета (1) фона, (2) переднего плана, (3) рамки color BackColor(void) const { return(!this.State() ? this.m_color_background.GetCurrent() : this.m_color_background_act.GetCurrent()); } color ForeColor(void) const { return(!this.State() ? this.m_color_foreground.GetCurrent() : this.m_color_foreground_act.GetCurrent()); } color BorderColor(void) const { return(!this.State() ? this.m_color_border.GetCurrent() : this.m_color_border_act.GetCurrent()); } //--- Возврат предустановленного цвета DEFAULT (1) фона, (2) переднего плана, (3) рамки color BackColorDefault(void) const { return(!this.State() ? this.m_color_background.GetDefault() : this.m_color_background_act.GetDefault()); } color ForeColorDefault(void) const { return(!this.State() ? this.m_color_foreground.GetDefault() : this.m_color_foreground_act.GetDefault()); } color BorderColorDefault(void)const { return(!this.State() ? this.m_color_border.GetDefault() : this.m_color_border_act.GetDefault()); } //--- Возврат предустановленного цвета FOCUSED (1) фона, (2) переднего плана, (3) рамки color BackColorFocused(void) const { return(!this.State() ? this.m_color_background.GetFocused() : this.m_color_background_act.GetFocused()); } color ForeColorFocused(void) const { return(!this.State() ? this.m_color_foreground.GetFocused() : this.m_color_foreground_act.GetFocused()); } color BorderColorFocused(void)const { return(!this.State() ? this.m_color_border.GetFocused() : this.m_color_border_act.GetFocused()); } //--- Возврат предустановленного цвета PRESSED (1) фона, (2) переднего плана, (3) рамки color BackColorPressed(void) const { return(!this.State() ? this.m_color_background.GetPressed() : this.m_color_background_act.GetPressed()); } color ForeColorPressed(void) const { return(!this.State() ? this.m_color_foreground.GetPressed() : this.m_color_foreground_act.GetPressed()); } color BorderColorPressed(void)const { return(!this.State() ? this.m_color_border.GetPressed() : this.m_color_border_act.GetPressed()); } //--- Возврат предустановленного цвета BLOCKED (1) фона, (2) переднего плана, (3) рамки color BackColorBlocked(void) const { return this.m_color_background.GetBlocked(); } color ForeColorBlocked(void) const { return this.m_color_foreground.GetBlocked(); } color BorderColorBlocked(void) const { return this.m_color_border.GetBlocked(); } //--- Установка цветов фона для всех состояний
Ahora, en cada uno de los métodos de recuperación de color, se comprueba el estado del elemento (activado/desactivado) y se devuelve el color requerido según el estado del elemento.
Añadir métodos para establecer los colores del elemento activado y perfeccionar los métodos para establecer los colores de los estados de los elementos en relación con el cursor del ratón, dado el estado del elemento como activado/no activado:
//--- Установка цветов фона для всех состояний void InitBackColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_background_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBackColorsAct(const color clr) { this.m_color_background_act.InitColors(clr); } //--- Установка цветов переднего плана для всех состояний void InitForeColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_foreground_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitForeColorsAct(const color clr) { this.m_color_foreground_act.InitColors(clr); } //--- Установка цветов рамки для всех состояний void InitBorderColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_border_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBorderColorsAct(const color clr) { this.m_color_border_act.InitColors(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки начальными значениями void InitBackColorActDefault(const color clr) { this.m_color_background_act.InitDefault(clr); } void InitForeColorActDefault(const color clr) { this.m_color_foreground_act.InitDefault(clr); } void InitBorderColorActDefault(const color clr){ this.m_color_border_act.InitDefault(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при наведении курсора начальными значениями void InitBackColorActFocused(const color clr) { this.m_color_background_act.InitFocused(clr); } void InitForeColorActFocused(const color clr) { this.m_color_foreground_act.InitFocused(clr); } void InitBorderColorActFocused(const color clr){ this.m_color_border_act.InitFocused(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при щелчке по объекту начальными значениями void InitBackColorActPressed(const color clr) { this.m_color_background_act.InitPressed(clr); } void InitForeColorActPressed(const color clr) { this.m_color_foreground_act.InitPressed(clr); } void InitBorderColorActPressed(const color clr){ this.m_color_border_act.InitPressed(clr); } //--- Установка текущего цвета фона в различные состояния bool BackColorToDefault(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BackColorToFocused(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BackColorToPressed(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BackColorToBlocked(void) { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Установка текущего цвета переднего плана в различные состояния bool ForeColorToDefault(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool ForeColorToFocused(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool ForeColorToPressed(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool ForeColorToBlocked(void) { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Установка текущего цвета рамки в различные состояния bool BorderColorToDefault(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BorderColorToFocused(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BorderColorToPressed(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BorderColorToBlocked(void) { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED); }
Añadir métodos para establecer y devolver el estado de un elemento:
//--- Создаёт OBJ_BITMAP_LABEL bool Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h); //--- (1) Устанавливает, (2) возвращает состояние void SetState(ENUM_ELEMENT_STATE state) { this.m_state=state; this.ColorsToDefault(); } ENUM_ELEMENT_STATE State(void) const { return this.m_state; } //--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного элемента (4) в фокусе, (5) имя графического объекта (фон, текст)
Al configurar el estado de un elemento, después de configurar el indicador de estado, todos los colores del elemento deben registrarse como actuales. Si el elemento está activado, por ejemplo, si se presiona el botón, todos los colores actuales se establecen como colores para el botón presionado. De lo contrario, los colores actuales se toman de la lista de colores del botón liberado.
Ahora que hemos separado la configuración y el retorno de la transparencia para el fondo y el primer plano, añade nuevos métodos para la configuración y el retorno de la transparencia:
string NameBG(void) const { return this.m_background.ChartObjectName(); } string NameFG(void) const { return this.m_foreground.ChartObjectName(); } //--- (1) Возвращает, (2) устанавливает прозрачность фона uchar AlphaBG(void) const { return this.m_alpha_bg; } void SetAlphaBG(const uchar value) { this.m_alpha_bg=value; } //--- (1) Возвращает, (2) устанавливает прозрачность переднего плана uchar AlphaFG(void) const { return this.m_alpha_fg; } void SetAlphaFG(const uchar value) { this.m_alpha_fg=value; } //--- Устанавливает прозрачность для фона и переднего плана void SetAlpha(const uchar value) { this.m_alpha_fg=this.m_alpha_bg=value; } //--- (1) Возвращает, (2) устанавливает ширину рамки
Declare un controlador de eventos que se supone que debe ser llamado desde el controlador de eventos del programa de control:
//--- Обработчик событий | void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Конструкторы/деструктор 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_border_width(0), m_wnd_y(0), m_state(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); };
En el constructor, especifique correctamente las propiedades de la fuente que se dibuja en el lienzo y llame al método Init() para almacenar las propiedades del gráfico y del ratón:
//+------------------------------------------------------------------+ //| 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_border_width(0), m_state(0) { //--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y //--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика this.m_chart_id=this.CorrectChartID(chart_id); this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); //--- Если графический ресурс и графический объект созданы if(this.Create(this.m_chart_id,this.m_wnd,object_name,x,y,w,h)) { //--- Очищаем канвасы фона и переднего плана и устанавливаем начальные значения координат, //--- наименования графических объектов и свойства текста, рисуемого на переднем плане this.Clear(false); this.m_obj_x=x; this.m_obj_y=y; this.m_color_background.SetName("Background"); this.m_color_foreground.SetName("Foreground"); this.m_color_border.SetName("Border"); this.m_foreground.FontSet(DEF_FONTNAME,-DEF_FONTSIZE*10,FW_MEDIUM); this.m_bound.SetName("Perimeter"); //--- Запоминаем разрешения для мышки и инструментов графика this.Init(); } }
En el destructor de clase, destruye el objeto gráfico creado y restaura las propiedades almacenadas del gráfico y los permisos del ratón:
//+------------------------------------------------------------------+ //| CCanvasBase::Деструктор | //+------------------------------------------------------------------+ CCanvasBase::~CCanvasBase(void) { //--- Уничтожаем объект this.Destroy(); //--- Возвращаем разрешения для мышки и инструментов графика ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, this.m_chart_mouse_wheel_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, this.m_chart_mouse_move_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, this.m_chart_object_create_flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, this.m_chart_mouse_scroll_flag); ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, this.m_chart_context_menu_flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL, this.m_chart_crosshair_tool_flag); }
En el método de creación de un elemento gráfico, el nombre del objeto gráfico que se va a crear no debe contener espacios. Esto se puede corregir sustituyendo los espacios del nombre por guiones bajos:
//+------------------------------------------------------------------+ //| CCanvasBase::Создаёт графические объекты фона и переднего плана | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Получаем скорректированный идентификатор графика long id=this.CorrectChartID(chart_id); //--- Корректируем переданное имя для объекта string nm=object_name; ::StringReplace(nm," ","_"); //--- Создаём имя графического объекта для фона и создаём канвас string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Создаём имя графического объекта для переднего плана и создаём канвас obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); //--- Устанавливаем размеры прямоугольной области и возвращаем true this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
Un método que devuelve el indicador de ubicación del cursor dentro de un objeto:
//+------------------------------------------------------------------+ //| CCanvasBase::Возвращает флаг нахождения курсора внутри объекта | //+------------------------------------------------------------------+ bool CCanvasBase::Contains(const int x,const int y) { //--- check and return the result int left=::fmax(this.X(),this.ObjectX()); int right=::fmin(this.Right(),this.ObjectRight()); int top=::fmax(this.Y(),this.ObjectY()); int bottom=::fmin(this.Bottom(),this.ObjectBottom()); return(x>=left && x<=right && y>=top && y<=bottom); }
Dado que el tamaño del objeto y el tamaño del lienzo pueden variar (el método ObjectTrim cambia el tamaño del lienzo sin cambiar el tamaño del objeto), aquí uno de los valores se toma como los límites dentro de los cuales se encuentra el cursor: ya sea el límite del objeto o un borde correspondiente del lienzo. El método devuelve un indicador de la ubicación de las coordenadas pasadas al método dentro de los límites recibidos.
En el método de bloqueo de elementos, la configuración del indicador de bloqueo debe realizarse antes de llamar al método de dibujo de elementos, corrección:
//+------------------------------------------------------------------+ //| CCanvasBase::Блокирует элемент | //+------------------------------------------------------------------+ void CCanvasBase::Block(const bool chart_redraw) { //--- Если элемент уже заблокирован - уходим if(this.m_blocked) return; //--- Устанавливаем текущие цвета как цвета заблокированного элемента, //--- устанавливаем флаг блокировки и перерисовываем объект this.ColorsToBlocked(); this.m_blocked=true; this.Draw(chart_redraw); }
Esta corrección te permite dibujar correctamente el elemento bloqueado. Antes de esta corrección, al dibujar un elemento, los colores se tomaban del estado predeterminado, no del bloqueado, ya que el indicador se establecía después del bloqueo.
Método para establecer prohibiciones en el gráfico:
//+------------------------------------------------------------------+ //| CCanvasBase::Установка запретов для графика | //| (прокрутка колёсиком, контекстное меню и перекрестие) | //+------------------------------------------------------------------+ void CCanvasBase::SetFlags(const bool flag) { //--- Если нужно установить флаги, и они уже были установлены ранее - уходим if(flag && this.m_flags_state) return; //--- Если нужно сбросить флаги, и они уже были сброшены ранее - уходим if(!flag && !this.m_flags_state) return; //--- Устанавливаем требуемый флаг для контекстного меню, //--- инструмента "перекрестие" и прокрутки графика колёсиком мышки. //--- После установки запоминаем значение установленного флага ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL,flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, flag); this.m_flags_state=flag; //--- Делаем обновление графика для немедленного применения установленных флагов ::ChartRedraw(this.m_chart_id); }
Método de inicialización de clase:
//+------------------------------------------------------------------+ //| 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(); }
Método de inicialización del color predeterminado del objeto:
//+------------------------------------------------------------------+ //| CCanvasBase::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CCanvasBase::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrDarkGray); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
Método que comprueba si el color establecido es igual al especificado:
//+------------------------------------------------------------------+ //| CCanvasBase::Проверяет установленный цвет на равенство указанному| //+------------------------------------------------------------------+ bool CCanvasBase::CheckColor(const ENUM_COLOR_STATE state) const { bool res=true; //--- В зависимости от проверяемого события switch(state) { //--- проверяем равенство всех STANDARD цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_DEFAULT : res &=this.BackColor()==this.BackColorDefault(); res &=this.ForeColor()==this.ForeColorDefault(); res &=this.BorderColor()==this.BorderColorDefault(); break; //--- проверяем равенство всех FOCUSED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_FOCUSED : res &=this.BackColor()==this.BackColorFocused(); res &=this.ForeColor()==this.ForeColorFocused(); res &=this.BorderColor()==this.BorderColorFocused(); break; //--- проверяем равенство всех PRESSED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_PRESSED : res &=this.BackColor()==this.BackColorPressed(); res &=this.ForeColor()==this.ForeColorPressed(); res &=this.BorderColor()==this.BorderColorPressed(); break; //--- проверяем равенство всех BLOCKED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_BLOCKED : res &=this.BackColor()==this.BackColorBlocked(); res &=this.ForeColor()==this.ForeColorBlocked(); res &=this.BorderColor()==this.BorderColorBlocked(); break; default: res=false; break; } return res; }
Para cambiar los colores de los elementos solo cuando se alterna el estado del elemento, este método devuelve la bandera de los colores ya configurados correspondientes al estado del elemento. Si los colores actuales del elemento no son iguales a los establecidos para el estado comprobado, el método permite cambiar el color y redibujar el elemento gráfico. Si los colores ya están configurados según el estado del elemento, no es necesario cambiar los colores y volver a dibujar el objeto; el método prohíbe el cambio de color.
Un método que cambia los colores de los elementos de un objeto en función de un evento:
//+------------------------------------------------------------------+ //| CCanvasBase::Смена цвета элементов объекта по событию | //+------------------------------------------------------------------+ void CCanvasBase::ColorChange(const ENUM_COLOR_STATE state) { //--- В зависимости от события устанавливаем цвета события как основные switch(state) { case COLOR_STATE_DEFAULT : this.ColorsToDefault(); break; case COLOR_STATE_FOCUSED : this.ColorsToFocused(); break; case COLOR_STATE_PRESSED : this.ColorsToPressed(); break; case COLOR_STATE_BLOCKED : this.ColorsToBlocked(); break; default : break; } }
Dependiendo del evento para el cual se debe cambiar el color, los colores actuales se establecen de acuerdo con el evento (estado del elemento).
Controlador de eventos:
//+------------------------------------------------------------------+ //| 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(this.IsBlocked() || this.IsHidden()) return; //--- Координаты курсора мышки int x=(int)lparam; int y=(int)dparam-this.m_wnd_y; // Корректируем Y по высоте окна индикатора //--- Событие перемещения курсора, либо щелчка кнопкой мышки if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_OBJECT_CLICK) { //--- Если курсор в пределах объекта if(this.Contains(x, y)) { //--- Если объект не в составе контейнера - запрещаем прокрутку графика, контекстное меню и инструмент "Перекрестие" if(this.m_container==NULL) this.SetFlags(false); //--- Получаем состояние кнопок мышки, если нажаты - вызываем обработчик нажатий if(sparam=="1" || sparam=="2" || sparam=="16") this.OnPressEvent(id, lparam, dparam, sparam); //--- кнопки не нажаты - обрабатываем перемещение курсора else this.OnFocusEvent(id, lparam, dparam, sparam); } //--- Курсор за пределами объекта else { //--- Обрабатываем увод курсора за границы объекта this.OnReleaseEvent(id,lparam,dparam,sparam); //--- Если объект не в составе контейнера - разрешаем прокрутку графика, контекстное меню и инструмент "Перекрестие" if(this.m_container==NULL) this.SetFlags(true); } } //--- Событие прокрутки колёсика мышки if(id==CHARTEVENT_MOUSE_WHEEL) { this.OnWheelEvent(id,lparam,dparam,sparam); } //--- Событие создания графического объекта if(id==CHARTEVENT_OBJECT_CREATE) { this.OnCreateEvent(id,lparam,dparam,sparam); } //--- Если пришло пользовательское событие графика if(id>CHARTEVENT_CUSTOM) { //--- собственные события не обрабатываем if(sparam==this.NameBG()) 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); } } }
La lógica del manejo de la interacción del cursor del ratón con elementos gráficos está organizada en el objeto base de elementos gráficos. Se llaman controladores virtuales para varios eventos que se monitorean. Algunos controladores se implementan directamente en esta clase, y otros simplemente no hacen nada y deben implementarse en objetos descendientes de esta clase.
Los controladores de eventos, cuyo nombre termina en *Handler, están diseñados para manejar interacciones dentro de los controles entre sus componentes constituyentes. Mientras que los controladores con *Event en su nombre manejan directamente eventos del gráfico y envían eventos personalizados al gráfico, que pueden usarse para determinar el tipo de evento y desde qué control se envió. Esto permitirá al usuario manejar dichos eventos en su programa.
Controlador fuera de foco:
//+------------------------------------------------------------------+ //| 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); } }
Controlador de desplazamiento del cursor:
//+------------------------------------------------------------------+ //| 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); } }
Manejador de pulsación de objetos:
//+------------------------------------------------------------------+ //| 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); } //--- отправляем пользовательское событие на график с передаенными значениями в lparam, dparam, и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameBG()); }
Controlador para el evento de creación de objetos gráficos:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик события создания графического объекта | //+------------------------------------------------------------------+ void CCanvasBase::OnCreateEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- если это объект, принадлежащий этой программе - уходим if(this.IsBelongsToThis(sparam)) return; //--- переносим объект на передний план this.BringToTop(true); }
La lógica de todos los controladores se comenta en detalle en el código. De hecho, aquí solo se organiza una reacción a un evento en forma de cambio de color del elemento gráfico y, cuando es necesario, el envío de eventos personalizados al gráfico. El último controlador reacciona a la creación de un objeto gráfico en el gráfico y transfiere elementos gráficos al primer plano. Esto permitirá, por ejemplo, que el panel permanezca siempre en primer plano.
Todos estos controladores son virtuales y deben redefinirse en clases heredadas si es necesario.
Hemos terminado con el refinamiento del objeto base de todos los elementos gráficos. Ahora, basándose en el componente Controlador creado en el objeto base y el componente Vista creado anteriormente, comience a crear los elementos gráficos más simples (que también son parte del componente Vista). Y se convertirán en los "bloques de construcción" a partir de los cuales eventualmente se crearán controles complejos, y en particular, el control Vista de tabla, en cuya implementación hemos estado trabajando durante varios artículos.
Controles sencillos
En la misma carpeta \MQL5\Indicators\Tables\Controls\ cree un nuevo archivo de inclusión Controls.mqh.
Al archivo creado, conecte el archivo del objeto base de los elementos gráficos Base.mqh y añada algunas sustituciones de macros y enumeraciones:
//+------------------------------------------------------------------+ //| Controls.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Base.mqh" //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define DEF_LABEL_W 40 // Ширина текстовой метки по умолчанию #define DEF_LABEL_H 16 // Высота текстовой метки по умолчанию #define DEF_BUTTON_W 50 // Ширина кнопки по умолчанию #define DEF_BUTTON_H 16 // Высота кнопки по умолчанию //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_COMPARE_BY // Сравниваемые свойства { ELEMENT_SORT_BY_ID = 0, // Сравнение по идентификатору элемента ELEMENT_SORT_BY_NAME, // Сравнение по наименованию элемента ELEMENT_SORT_BY_TEXT, // Сравнение по тексту элемента ELEMENT_SORT_BY_COLOR, // Сравнение по цвету элемента ELEMENT_SORT_BY_ALPHA, // Сравнение по прозрачности элемента ELEMENT_SORT_BY_STATE, // Сравнение по состоянию элемента }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Классы | //+------------------------------------------------------------------+
En la sustitución macro, hemos definido tamaños predeterminados para las etiquetas de texto y los botones. En la enumeración, hemos indicado las propiedades disponibles del elemento gráfico base. Puede utilizar estas propiedades para buscar objetos, ordenarlos y compararlos. Al añadir nuevas propiedades a cualquier objeto, añada nuevas constantes a esta enumeración.
Clases auxiliares
Cada elemento gráfico puede tener una imagen en su composición. Esto permitirá dibujar iconos para botones, líneas de texto, etc.
Crea una clase especial para dibujar imágenes, que será parte integral de los controles simples.
Una clase para dibujar imágenes dentro de un área definida
//+------------------------------------------------------------------+ //| Классы | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Класс рисования изображений | //+------------------------------------------------------------------+ class CImagePainter : public CBaseObj { protected: CCanvas *m_canvas; // Указатель на канвас, где рисуем CBound m_bound; // Координаты и границы изображения uchar m_alpha; // Прозрачность //--- Проверяет валидность холста и корректность размеров bool CheckBound(void); public: //--- (1) Назначает канвас для рисования, (2) устанавливает, (3) возвращает прозрачность void CanvasAssign(CCanvas *canvas) { this.m_canvas=canvas; } void SetAlpha(const uchar value) { this.m_alpha=value; } uchar Alpha(void) const { return this.m_alpha; } //--- (1) Устанавливает координаты, (2) изменяет размеры области void SetXY(const int x,const int y) { this.m_bound.SetXY(x,y); } void SetSize(const int w,const int h) { this.m_bound.Resize(w,h); } //--- Устанавливает координаты и размеры области void SetBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.SetSize(w,h); } //--- Возвращает границы и размеры рисунка int X(void) const { return this.m_bound.X(); } int Y(void) const { return this.m_bound.Y(); } int Right(void) const { return this.m_bound.Right(); } int Bottom(void) const { return this.m_bound.Bottom(); } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } //--- Очищает область 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); //--- Виртуальные методы (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_IMAGE_PAINTER); } //--- Конструкторы/деструктор CImagePainter(void) : m_canvas(NULL) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas) : m_canvas(canvas) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas,const int id,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); } CImagePainter(CCanvas *canvas,const int id,const int dx,const int dy,const int w,const int h,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(dx,dy,w,h); } ~CImagePainter(void) {} };
Consideremos los métodos de clase.
Un método para comparar dos objetos de dibujo:
//+------------------------------------------------------------------+ //| CImagePainter::Сравнение двух объектов | //+------------------------------------------------------------------+ int CImagePainter::Compare(const CObject *node,const int mode=0) const { 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 : return(this.Alpha()>obj.Alpha() ? 1 : this.Alpha()<obj.Alpha()? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Este método es necesario para encontrar el objeto de dibujo requerido. De forma predeterminada, la búsqueda se realiza por el ID del objeto. El método será necesario cuando los objetos de los controles contengan listas donde se almacenan los objetos de dibujo. Por el momento, se declarará un objeto de dibujo en cada control. Este objeto de dibujo está destinado a dibujar el icono principal del elemento.
Un método que verifica la validez del lienzo y el tamaño correcto del área de la imagen:
//+------------------------------------------------------------------+ //|CImagePainter::Проверяет валидность холста и корректность размеров| //+------------------------------------------------------------------+ bool CImagePainter::CheckBound(void) { if(this.m_canvas==NULL) { ::PrintFormat("%s: Error. First you need to assign the canvas using the CanvasAssign() method",__FUNCTION__); return false; } if(this.Width()==0 || this.Height()==0) { ::PrintFormat("%s: Error. First you need to set the area size using the SetSize() or SetBound() methods",__FUNCTION__); return false; } return true; }
Si no se pasa un puntero al lienzo al objeto, o si no se establecen el ancho y el alto del área de la imagen, el método devuelve false. De lo contrario, true.
Un método que limpia el área de la imagen:
//+------------------------------------------------------------------+ //| CImagePainter::Очищает область | //+------------------------------------------------------------------+ bool CImagePainter::Clear(const int x,const int y,const int w,const int h,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Очищаем прозрачным цветом всю область изображения this.m_canvas.FillRectangle(x,y,x+w-1,y+h-1,clrNULL); //--- Если указано - обновляем канвас if(update) this.m_canvas.Update(false); //--- Всё успешно return true; }
El método borra completamente toda el área de la imagen, rellenándola con un color transparente.
Un método que dibuja una flecha sombreada hacia arriba:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вверх | //+------------------------------------------------------------------+ bool CImagePainter::ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hw=(int)::floor(w/2); // Половина ширины if(hw==0) hw=1; int x1 = x + 1; // X. Основание (левая точка) int y1 = y + h - 4; // Y. Левая точка основания int x2 = x1 + hw; // X. Вершина (центральная верхняя точка) int y2 = y + 3; // Y. Вершина (верхняя точка) int x3 = x1 + w - 1; // X. Основание (правая точка) int y3 = y1; // Y. Основание (правая точка) //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Un método que dibuja una flecha hacia abajo sombreada:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вниз | //+------------------------------------------------------------------+ bool CImagePainter::ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hw=(int)::floor(w/2); // Половина ширины if(hw==0) hw=1; int x1=x+1; // X. Основание (левая точка) int y1=y+4; // Y. Левая точка основания int x2=x1+hw; // X. Вершина (центральная нижняя точка) int y2=y+h-3; // Y. Вершина (нижняя точка) int x3=x1+w-1; // X. Основание (правая точка) int y3=y1; // Y. Основание (правая точка) //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Un método que dibuja una flecha izquierda sombreada:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку влево | //+------------------------------------------------------------------+ bool CImagePainter::ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hh=(int)::floor(h/2); // Половина высоты if(hh==0) hh=1; int x1=x+w-4; // X. Основание (правая сторона) int y1=y+1; // Y. Верхний угол основания int x2=x+3; // X. Вершина (левая центральная точка) int y2=y1+hh; // Y. Центральная точка (вершина) int x3=x1; // X. Нижний угол основания int y3=y1+h-1; // Y. Нижний угол основания //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Un método que dibuja una flecha derecha sombreada:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вправо | //+------------------------------------------------------------------+ bool CImagePainter::ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Рассчитываем координаты углов стрелки внутри области изображения int hh=(int)::floor(h/2); // Половина высоты if(hh==0) hh=1; int x1=x+4; // X. Основание треугольника (левая сторона) int y1=y+1; // Y. Верхний угол основания int x2=x+w-3; // X. Вершина (правая центральная точка) int y2=y1+hh; // Y. Центральная точка (вершина) int x3=x1; // X. Нижний угол основания int y3=y1+h-1; // Y. Нижний угол основания //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Dentro del área de la imagen, se define un área para una flecha con una sangría de un píxel a cada lado del área rectangular, y se dibuja una flecha sombreada en su interior.
Un método que dibuja un cuadro de verificación marcado:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует отмеченный CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты прямоугольника int x1=x+1; // Левый верхний угол, X int y1=y+1; // Левый верхний угол, Y int x2=x+w-2; // Правый нижний угол, X int y2=y+h-2; // Правый нижний угол, Y //--- Рисуем прямоугольник this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); //--- Координаты "галочки" int arrx[3], arry[3]; arrx[0]=x1+(x2-x1)/4; // X. Левая точка arrx[1]=x1+w/3; // X. Центральная точка arrx[2]=x2-(x2-x1)/4; // X. Правая точка arry[0]=y1+1+(y2-y1)/2; // Y. Левая точка arry[1]=y2-(y2-y1)/3; // Y. Центральная точка arry[2]=y1+(y2-y1)/3; // Y. Правая точка //--- Рисуем "галочку" линией двойной толщины this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); arrx[0]++; arrx[1]++; arrx[2]++; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Un método que dibuja una casilla de verificación sin marcar:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует неотмеченный CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты прямоугольника int x1=x+1; // Левый верхний угол, X int y1=y+1; // Левый верхний угол, Y int x2=x+w-2; // Правый нижний угол, X int y2=y+h-2; // Правый нижний угол, Y //--- Рисуем прямоугольник this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Un método que dibuja un botón de selección marcado:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует отмеченный RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты и радиус окружности int x1=x+1; // Левый верхний угол области окружности, X int y1=y+1; // Левый верхний угол области окружности, Y int x2=x+w-2; // Правый нижний угол области окружности, X int y2=y+h-2; // Правый нижний угол области окружности, Y //--- Координаты и радиус окружности int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота) int r=d/2; // Радиус int cx=x1+r; // Координата X центра int cy=y1+r; // Координата Y центра //--- Рисуем окружность this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); //--- Радиус "метки" r/=2; if(r<1) r=1; //--- Рисуем метку this.m_canvas.FillCircle(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Un método que dibuja un botón de selección sin marcar:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует неотмеченный RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты и радиус окружности int x1=x+1; // Левый верхний угол области окружности, X int y1=y+1; // Левый верхний угол области окружности, Y int x2=x+w-2; // Правый нижний угол области окружности, X int y2=y+h-2; // Правый нижний угол области окружности, Y //--- Координаты и радиус окружности int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота) int r=d/2; // Радиус int cx=x1+r; // Координата X центра int cy=y1+r; // Координата Y центра //--- Рисуем окружность this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Se trata de métodos sencillos que simplemente permiten dibujar las formas deseadas sin tener que implementarlas uno mismo. A continuación, agregue aquí otros métodos que dibujen otros íconos para el diseño de elementos gráficos.
Métodos para guardar un área de dibujo en un archivo y cargarla desde un archivo:
//+------------------------------------------------------------------+ //| CImagePainter::Сохранение в файл | //+------------------------------------------------------------------+ bool CImagePainter::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем прозрачность if(::FileWriteInteger(file_handle,this.m_alpha,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем данные области if(!this.m_bound.Save(file_handle)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CImagePainter::Загрузка из файла | //+------------------------------------------------------------------+ bool CImagePainter::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CBaseObj::Load(file_handle)) return false; //--- Загружаем прозрачность this.m_alpha=(uchar)::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем данные области if(!this.m_bound.Load(file_handle)) return false; //--- Всё успешно return true; }
Ahora podemos comenzar con las clases de controles simples. El objeto mínimo de este tipo será la clase de etiqueta de texto. Las clases de otros controles se heredarán de este elemento.
En el mismo archivo Controls.mqh, continúe escribiendo códigos de clase.
La clase de control "etiqueta de texto"
Esta clase tendrá un conjunto de variables y métodos que permitirán trabajar con cualquier control, establecer y recibir parámetros del objeto, guardar y cargar sus propiedades. Hoy se ha agregado a la clase base de todos los controles la interactividad de todos los controles (el componente Controlador). Ahora, analicemos la clase 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) {} };
La clase define dos matrices ushort de caracteres para los textos de las etiquetas actuales y anteriores. Esto le permite acceder a las dimensiones del texto anterior al dibujar y borrar correctamente el área cubierta por el texto antes de mostrar un nuevo texto en el lienzo.
Considere los métodos declarados.
La clase tiene cuatro constructores que permiten crear un objeto utilizando diferentes conjuntos de parámetros:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в главном окне | //| текущего графика с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,::ChartID(),0,x,y,w,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(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-------------------------------------------------------------------+ //| 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) : CCanvasBase(object_name,::ChartID(),wnd,x,y,w,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(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CLabel::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) : CCanvasBase(object_name,chart_id,wnd,x,y,w,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(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); }
El área de dibujo (icono del elemento) se establece en dimensiones cero, lo que significa que el elemento no tiene icono. Se establece el texto del elemento y se asigna transparencia total al fondo, mientras que se asigna opacidad total al primer plano.
Método para comparar dos objetos:
//+------------------------------------------------------------------+ //| CLabel::Сравнение двух объектов | //+------------------------------------------------------------------+ int CLabel::Compare(const CObject *node,const int mode=0) const { 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_COLOR : return(this.ForeColor()>obj.ForeColor() ? 1 : this.ForeColor()<obj.ForeColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaFG() >obj.AlphaFG() ? 1 : this.AlphaFG() <obj.AlphaFG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
La comparación es posible por nombre de objeto, texto de etiqueta, color, transparencia e identificador. De forma predeterminada, los objetos se comparan por su ID, ya que cuando los objetos se encuentran en la misma lista, es mejor distinguirlos por sus ID para acceder rápidamente al que se necesita.
Un método que borra el texto de la etiqueta:
//+------------------------------------------------------------------+ //| CLabel::Стирает текст | //+------------------------------------------------------------------+ void CLabel::ClearText(void) { int w=0, h=0; string text=this.TextPrev(); //--- Получаем размеры прошлого текста if(text!="") this.m_foreground.TextSize(text,w,h); //--- Если размеры получены - рисуем на месте текста прозрачный прямоугольник, стирая текст if(w>0 && h>0) this.m_foreground.FillRectangle(this.AdjX(this.m_text_x),this.AdjY(this.m_text_y),this.AdjX(this.m_text_x+w),this.AdjY(this.m_text_y+h),clrNULL); //--- Иначе - очищаем полностью весь передний план else this.m_foreground.Erase(clrNULL); }
Si el texto se escribió anteriormente, puede borrarlo pintando sobre él con un rectángulo completamente transparente del mismo tamaño que el texto. Si antes no había texto, se borra toda el área del lienzo del objeto.
Método para mostrar texto en lienzo:
//+------------------------------------------------------------------+ //| CLabel::Выводит текст | //+------------------------------------------------------------------+ void CLabel::DrawText(const int dx,const int dy,const string text,const bool chart_redraw) { //--- Очищаем прошлый текст и устанавливаем новый this.ClearText(); this.SetText(text); //--- Выводим установленный текст this.m_foreground.TextOut(this.AdjX(dx),this.AdjY(dy),this.Text(),::ColorToARGB(this.ForeColor(),this.AlphaFG())); //--- Если текст выходит за правую границу объекта if(this.Width()-dx<this.m_foreground.TextWidth(text)) { //--- Получаем размеры текста "троеточие" int w=0,h=0; this.m_foreground.TextSize("... ",w,h); if(w>0 && h>0) { //--- Стираем текст у правой границы объекта по размеру текста "троеточие" и заменяем троеточием окончание текста метки this.m_foreground.FillRectangle(this.AdjX(this.Width()-w),this.AdjY(this.m_text_y),this.AdjX(this.Width()),this.AdjY(this.m_text_y+h),clrNULL); this.m_foreground.TextOut(this.AdjX(this.Width()-w),this.AdjY(dy),"...",::ColorToARGB(this.ForeColor(),this.AlphaFG())); } } //--- Обновляем канвас переднего плана и запоминаем новые координаты текста this.m_foreground.Update(chart_redraw); this.m_text_x=dx; this.m_text_y=dy; //--- Запоминаем нарисованный текст как прошлый this.SetTextPrev(text); }
Aquí, primero se borra el texto anterior del lienzo y, a continuación, se muestra uno nuevo. Si el nuevo texto sobrepasa los límites del objeto, se muestra un signo de dos puntos a la derecha, donde sobrepasa el elemento, indicando que el texto no cabe en el área del objeto, algo así como esto: «Este texto no cabe...».
Un método que dibuja la apariencia:
//+------------------------------------------------------------------+ //| CLabel::Рисует внешний вид | //+------------------------------------------------------------------+ void CLabel::Draw(const bool chart_redraw) { this.DrawText(this.m_text_x,this.m_text_y,this.Text(),chart_redraw); }
Aquí, simplemente se llama al método para dibujar el texto de una etiqueta.
Métodos de manipulación con archivos:
//+------------------------------------------------------------------+ //| CLabel::Сохранение в файл | //+------------------------------------------------------------------+ bool CLabel::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CCanvasBase::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(!CCanvasBase::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; }
Basándose en la clase considerada, cree una clase de botón simple.
La clase de control de "botón simple"
//+------------------------------------------------------------------+ //| Класс простой кнопки | //+------------------------------------------------------------------+ 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); } //--- Конструкторы/деструктор 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 long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButton (void) {} };
La clase de botón simple se diferencia de la clase de etiqueta de texto únicamente en el método de dibujo de la apariencia.
La clase tiene cuatro constructores que permiten crear un botón con parámetros específicos:
//+------------------------------------------------------------------+ //| CButton::Конструктор по умолчанию. Строит кнопку в главном окне | //| текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButton::CButton(void) : CLabel("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+-------------------------------------------------------------------+ //| 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,::ChartID(),0,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+---------------------------------------------------------------------+ //| 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,::ChartID(),wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+---------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+---------------------------------------------------------------------+ CButton::CButton(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(object_name,chart_id,wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); }
Establece el estado «botón no pulsado» y configura la opacidad total para el fondo y el primer plano.
Un método para comparar dos objetos:
//+------------------------------------------------------------------+ //| CButton::Сравнение двух объектов | //+------------------------------------------------------------------+ int CButton::Compare(const CObject *node,const int mode=0) const { const CButton *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_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
El método es idéntico al de la clase de etiqueta de texto. Lo más probable es que, si los botones no tienen otras propiedades, se pueda eliminar este método de la clase y se utilice el método de la clase principal.
Un método que dibuja la apariencia del botón:
//+------------------------------------------------------------------+ //| CButton::Рисует внешний вид | //+------------------------------------------------------------------+ void CButton::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); //--- Выводим текст кнопки CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Primero, rellene el fondo con el color establecido, luego dibuje un borde y muestre el texto del botón.
Basándose en esta clase, cree una clase de botón de dos posiciones.
La clase de control de "botón de dos posiciones"
//+------------------------------------------------------------------+ //| Класс двухпозиционной кнопки | //+------------------------------------------------------------------+ class CButtonTriggered : public CButton { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Виртуальные методы (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); } //--- Инициализация цветов объекта по умолчанию 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 long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButtonTriggered (void) {} };
El objeto tiene cuatro constructores que le permiten crear un botón con los parámetros especificados:
//+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(void) : CButton("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); } //+------------------------------------------------------------------+ //| 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,::ChartID(),0,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| 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,::ChartID(),wnd,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(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) : CButton(object_name,chart_id,wnd,text,x,y,w,h) { this.InitColors(); }
El método de inicialización de color predeterminado se invoca en cada constructor:
//+------------------------------------------------------------------+ //| CButtonTriggered::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CButtonTriggered::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrLightBlue); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrGreen); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
Estos son los colores predeterminados que se establecen para el botón recién creado. Después de crear un objeto, todos los colores se pueden personalizar a su discreción.
Se ha añadido el método de comparacióncon una comparación según el estado del botón:
//+------------------------------------------------------------------+ //| CButtonTriggered::Сравнение двух объектов | //+------------------------------------------------------------------+ int CButtonTriggered::Compare(const CObject *node,const int mode=0) const { const CButtonTriggered *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_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); case ELEMENT_SORT_BY_STATE : return(this.State() >obj.State() ? 1 : this.State() <obj.State() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Un método que dibuja la apariencia del botón:
//+------------------------------------------------------------------+ //| CButtonTriggered::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonTriggered::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); //--- Выводим текст кнопки CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
El método es idéntico al de la clase padre, y si no hay más mejoras en la clase, entonces el método se puede eliminar: se utilizará el método de dibujo de la clase padre.
El botón de dos posiciones tiene dos estados:
- Presionado,
- Liberado.
Para realizar un seguimiento y cambiar sus estados, se ha redefinido aquí el controlador de clics del ratón OnPressEvent de la clase principal:
//+------------------------------------------------------------------+ //| CButtonTriggered::Обработчик событий нажатий кнопок мышки (Press)| //+------------------------------------------------------------------+ void CButtonTriggered::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Устанавливаем состояние кнопки, обратное уже установленному ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
Basándose en la clase CButton, cree cuatro botones de flecha: arriba, abajo, izquierda y derecha. Los objetos utilizarán la clase de dibujo de imágenes para dibujar flechas.
Clase de control «botón de flecha 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);} //--- Конструкторы/деструктор CButtonArrowUp(void); CButtonArrowUp(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowUp (void) {} };
Cuatro constructores le permiten crear un objeto con los parámetros especificados:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
Los colores predeterminados se inicializan en los constructores y se establecen las coordenadas y las dimensiones del área de la imagen.
Un método que dibuja la apariencia del botón:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowUp::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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowUp(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
El método es similar al de dibujar un botón, pero además se muestra una flecha hacia arriba utilizando el método ArrowUp del objeto de dibujo.
Todas las demás clases son idénticas a la considerada, pero los métodos de dibujo utilizan iconos correspondientes a la finalidad del botón.
Clase de control «botón de flecha hacia abajo»
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вниз | //+------------------------------------------------------------------+ class CButtonArrowDown : 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_DOWN); } //--- Конструкторы/деструктор CButtonArrowDown(void); CButtonArrowDown(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowDown (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowDown::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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowDown(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Clase de control «botón de flecha izquierda»
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой влево | //+------------------------------------------------------------------+ class CButtonArrowLeft : 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_DOWN); } //--- Конструкторы/деструктор CButtonArrowLeft(void); CButtonArrowLeft(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowLeft (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowLeft::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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowLeft(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Clase de control «botón de flecha derecha»
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вправо | //+------------------------------------------------------------------+ class CButtonArrowRight : 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_DOWN); } //--- Конструкторы/деструктор CButtonArrowRight(void); CButtonArrowRight(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowRight (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowRight::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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowRight(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
La clase de control "casilla de verificación"
La clase de control "casilla de verificación" es similar a las clases de botones de flecha. Aquí, el fondo será completamente transparente. Es decir, solo se dibujarán el texto y el icono de la casilla de verificación. La casilla de verificación tiene dos estados: marcada y desmarcada, lo que significa que se heredará de la clase de botón de dos posiciones:
//+------------------------------------------------------------------+ //| Класс элемента управления Checkbox | //+------------------------------------------------------------------+ class CCheckBox : public CButtonTriggered { 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_CHECKBOX); } //--- Инициализация цветов объекта по умолчанию virtual void InitColors(void); //--- Конструкторы/деструктор CCheckBox(void); CCheckBox(const string object_name, const string text, const int x, const int y, const int w, const int h); CCheckBox(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CCheckBox(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); ~CCheckBox (void) {} };
Todas las clases de controles tienen cuatro constructores cada una:
//+------------------------------------------------------------------+ //| CCheckBox::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(void) : CButtonTriggered("CheckBox",::ChartID(),0,"CheckBox",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),0,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),wnd,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(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) : CButtonTriggered(object_name,chart_id,wnd,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
Aquí se inicializan los colores de objeto predeterminados, se establece un fondo totalmente transparente y un primer plano opaco. Luego se establecen las dimensiones y coordenadas del área de la imagen.
El método de comparación devuelve el resultado de llamar al método de comparación de la clase padre:
//+------------------------------------------------------------------+ //| CCheckBox::Сравнение двух объектов | //+------------------------------------------------------------------+ int CCheckBox::Compare(const CObject *node,const int mode=0) const { return CButtonTriggered::Compare(node,mode); }
Método de inicialización del color predeterminado del objeto:
//+------------------------------------------------------------------+ //| CCheckBox::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CCheckBox::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrNULL); this.InitBackColorsAct(clrNULL); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.InitForeColorFocused(clrNavy); this.InitForeColorActFocused(clrNavy); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrNULL); this.InitBorderColorsAct(clrNULL); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrSilver); }
El método para dibujar la apariencia de la casilla de verificación:
//+------------------------------------------------------------------+ //| CCheckBox::Рисует внешний вид | //+------------------------------------------------------------------+ void CCheckBox::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); //--- Выводим текст кнопки CLabel::Draw(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); //--- Рисуем отмеченный значок для активного состояния кнопки, if(this.m_state) this.m_painter.CheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- и неотмеченный - для неактивного else this.m_painter.UncheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Dependiendo del estado del elemento, se dibuja un cuadrado marcado con una marca de verificación o simplemente un cuadrado vacío.
Ahora, basándote en este objeto, crea una clase de control «botón de selección».
La clase de control "botón de selección"
Dado que el botón de selección siempre funciona en grupo (solo se puede desactivar cuando se activa otro botón del grupo), aquí también debemos redefinir el controlador para hacer clic en un objeto de la clase principal.
//+------------------------------------------------------------------+ //| Класс элоемента управления Radio Button | //+------------------------------------------------------------------+ class CRadioButton : public CCheckBox { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Виртуальные методы (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_RADIOBUTTON); } //--- Конструкторы/деструктор CRadioButton(void); CRadioButton(const string object_name, const string text, const int x, const int y, const int w, const int h); CRadioButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CRadioButton(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); ~CRadioButton (void) {} };
Constructores:
//+------------------------------------------------------------------+ //| CRadioButton::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(void) : CCheckBox("RadioButton",::ChartID(),0,"",0,0,DEF_BUTTON_H,DEF_BUTTON_H) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),0,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),wnd,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(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) : CCheckBox(object_name,chart_id,wnd,text,x,y,w,h) { }
No es necesario realizar ninguna acción adicional después de llamar al constructor de la clase padre. Por lo tanto, los constructores tienen un cuerpo vacío.
El método de comparación devuelve el resultado de llamar al método de comparación de la clase padre:
//+------------------------------------------------------------------+ //| CRadioButton::Сравнение двух объектов | //+------------------------------------------------------------------+ int CRadioButton::Compare(const CObject *node,const int mode=0) const { return CCheckBox::Compare(node,mode); }
El método para dibujar la apariencia del botón:
//+------------------------------------------------------------------+ //| CRadioButton::Рисует внешний вид | //+------------------------------------------------------------------+ void CRadioButton::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); //--- Выводим текст кнопки CLabel::Draw(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); //--- Рисуем отмеченный значок для активного состояния кнопки, if(this.m_state) this.m_painter.CheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- и неотмеченный - для неактивного else this.m_painter.UncheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Este método es idéntico al método de la clase principal, pero aquí se dibujan los iconos de los botones de opción: seleccionados y no seleccionados.
Controlador de eventos de clic del botón del ratón:
//+------------------------------------------------------------------+ //| CRadioButton::Обработчик событий нажатий кнопок мышки (Press) | //+------------------------------------------------------------------+ void CRadioButton::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если кнопка уже отмечена - уходим if(this.m_state) return; //--- Устанавливаем состояние кнопки, обратное уже установленному ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
Aquí, si el botón ya tiene un estado habilitado, no se debe realizar ninguna acción; abandona el controlador. Si el botón está desactivado, invierte su estado y llama al controlador de la clase principal (el objeto base de todos los controles CCanvasBase).
Por hoy, estos son todos los controles que eran mínimamente necesarios para implementar controles complejos.
Probemos lo que tenemos aquí.
Probando el resultado
En la carpeta \MQL5\Indicators\Tables\ cree un nuevo indicador llamado iTestLabel.mq5.
Establezca el número de búferes calculados y series gráficas del indicador en cero — no es necesario dibujar gráficos. Conecta la biblioteca de elementos gráficos creada. En su propia ventana independiente, el indicador dibujará elementos gráficos que, una vez creados, se guardarán en una lista, cuyo archivo de clase está conectado al archivo del indicador:
//+------------------------------------------------------------------+ //| iTestLabel.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 <Arrays\ArrayObj.mqh> #include "Controls\Controls.mqh" CArrayObj list; // Список для хранения тестируемых объектов CCanvasBase *base =NULL; // Указатель на базовый графический элемент CLabel *label1=NULL; // Указатель на графический элемент Label CLabel *label2=NULL; // Указатель на графический элемент Label CLabel *label3=NULL; // Указатель на графический элемент Label CButton *button1=NULL; // Указатель на графический элемент Button CButtonTriggered *button_t1=NULL; // Указатель на графический элемент ButtonTriggered CButtonTriggered *button_t2=NULL; // Указатель на графический элемент ButtonTriggered CButtonArrowUp *button_up=NULL; // Указатель на графический элемент CButtonArrowUp CButtonArrowDown *button_dn=NULL; // Указатель на графический элемент CButtonArrowDown CButtonArrowLeft *button_lt=NULL; // Указатель на графический элемент CButtonArrowLeft CButtonArrowRight*button_rt=NULL; // Указатель на графический элемент CButtonArrowRight CCheckBox *checkbox_lt=NULL; // Указатель на графический элемент CCheckBox CCheckBox *checkbox_rt=NULL; // Указатель на графический элемент CCheckBox CRadioButton *radio_bt_lt=NULL; // Указатель на графический элемент CRadioButton CRadioButton *radio_bt_rt=NULL; // Указатель на графический элемент CRadioButton //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+
Aquí, para simplificar, se crean inmediatamente punteros a los elementos gráficos creados. Después de crear el elemento, usaremos estos punteros para trabajar con objetos.
Crea el controlador del indicador de todos los objetos OnInit(). Hagamos esto: creemos un objeto base y coloreémoslo para que se parezca a un panel.
Dentro de este "sustrato" implementa todos los elementos gráficos y especifica este objeto base para ellos como contenedor.
En OnInit() implemente el siguiente código:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Создаём базовый графический элемент list.Add(base=new CCanvasBase("Rectangle",0,wnd,100,40,260,160)); base.SetAlphaBG(250); // Прозрачность base.SetBorderWidth(6); // Ширина рамки //--- Инициализируем цвет фона, указываем цвет для заблокированного элемента //--- и делаем текущим цветом фона элемента цвет фона, заданный по умолчанию base.InitBackColors(clrWhiteSmoke); base.InitBackColorBlocked(clrLightGray); base.BackColorToDefault(); //--- Заливаем цветом фон и рисуем рамку с отступом в один пиксель от установленной ширины рамки base.Fill(base.BackColor(),false); uint wd=base.BorderWidth(); base.GetBackground().Rectangle(0,0,base.Width()-1,base.Height()-1,ColorToARGB(clrDimGray)); base.GetBackground().Rectangle(wd-2,wd-2,base.Width()-wd+1,base.Height()-wd+1,ColorToARGB(clrLightGray)); base.Update(false); //--- Устанавливаем наименование и идентификатор элемента и выводим в журнал его описание base.SetName("Rectangle 1"); base.SetID(1); base.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент string text="Simple button:"; int shift_x=20; int shift_y=8; int x=base.X()+shift_x-10; int y=base.Y()+shift_y+2; int w=base.GetForeground().TextWidth(text); int h=DEF_LABEL_H; list.Add(label1=new CLabel("Label 1",0,wnd,text,x,y,w,h)); label1.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label1.InitForeColorFocused(clrRed); label1.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label1.SetID(2); label1.Draw(false); label1.Print(); //--- Внутри базового объекта создаём простую кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=label1.Right()+shift_x; y=label1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button1=new CButton("Simple Button",0,wnd,"Button 1",x,y,w,h)); button1.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button1.SetTextShiftH(2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button1.SetID(3); button1.Draw(false); button1.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент text="Triggered button:"; x=label1.X(); y=label1.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label2=new CLabel("Label 2",0,wnd,text,x,y,w,h)); label2.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label2.InitForeColorFocused(clrRed); label2.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label2.SetID(4); label2.Draw(false); label2.Print(); //--- Внутри базового объекта создаём двухпозиционную кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=button1.X(); y=button1.Bottom()+shift_y; w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t1=new CButtonTriggered("Triggered Button 1",0,wnd,"Button 2",x,y,w,h)); button_t1.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button_t1.SetTextShiftH(2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. button_t1.SetID(5); button_t1.SetState(true); button_t1.Draw(false); button_t1.Print(); //--- Внутри базового объекта создаём двухпозиционную кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_t1.Right()+4; y=button_t1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t2=new CButtonTriggered("Triggered Button 2",0,wnd,"Button 3",x,y,w,h)); button_t2.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button_t2.SetTextShiftH(2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_t2.SetID(6); button_t2.Draw(false); button_t2.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент text="Arrowed buttons:"; x=label1.X(); y=label2.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label3=new CLabel("Label 3",0,wnd,text,x,y,w,h)); label3.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label3.InitForeColorFocused(clrRed); label3.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label3.SetID(7); label3.Draw(false); label3.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вверх //--- и указываем для кнопки в качестве контейнера базовый элемент x=button1.X(); y=button_t1.Bottom()+shift_y; w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_up=new CButtonArrowUp("Arrow Up Button",0,wnd,x,y,w,h)); button_up.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_up.SetImageBound(1,1,w-4,h-3); //--- Здесь можно настроить внешний вид кнопки, например, убрать рамку //button_up.InitBorderColors(button_up.BackColor(),button_up.BackColorFocused(),button_up.BackColorPressed(),button_up.BackColorBlocked()); //button_up.ColorsToDefault(); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_up.SetID(8); button_up.Draw(false); button_up.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вниз //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_up.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_dn=new CButtonArrowDown("Arrow Down Button",0,wnd,x,y,w,h)); button_dn.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_dn.SetImageBound(1,1,w-4,h-3); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_dn.SetID(9); button_dn.Draw(false); button_dn.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой влево //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_dn.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_lt=new CButtonArrowLeft("Arrow Left Button",0,wnd,x,y,w,h)); button_lt.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_lt.SetImageBound(1,1,w-3,h-4); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_lt.SetID(10); button_lt.Draw(false); button_lt.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вправо //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_lt.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_rt=new CButtonArrowRight("Arrow Right Button",0,wnd,x,y,w,h)); button_rt.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_rt.SetImageBound(1,1,w-3,h-4); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_rt.SetID(11); button_rt.Draw(false); button_rt.Print(); //--- Внутри базового объекта создаём чекбокс с заголовком справа (левый чекбокс) //--- и указываем для кнопки в качестве контейнера базовый элемент x=label1.X(); y=label3.Bottom()+shift_y; w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_lt=new CCheckBox("CheckBoxL",0,wnd,"CheckBox L",x,y,w,h)); checkbox_lt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения checkbox_lt.SetImageBound(2,1,h-2,h-2); //--- Задаём смещение текста кнопки по оси X checkbox_lt.SetTextShiftH(checkbox_lt.ImageRight()+2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. checkbox_lt.SetID(12); checkbox_lt.Draw(false); checkbox_lt.Print(); //--- Внутри базового объекта создаём чекбокс с заголовком слева (правый чекбокс) //--- и указываем для кнопки в качестве контейнера базовый элемент x=checkbox_lt.Right()+4; y=checkbox_lt.Y(); w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_rt=new CCheckBox("CheckBoxR",0,wnd,"CheckBox R",x,y,w,h)); checkbox_rt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения checkbox_rt.SetTextShiftH(2); //--- Задаём смещение текста кнопки по оси X checkbox_rt.SetImageBound(checkbox_rt.Width()-h+2,1,h-2,h-2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. checkbox_rt.SetID(13); checkbox_rt.SetState(true); checkbox_rt.Draw(false); checkbox_rt.Print(); //--- Внутри базового объекта создаём радиокнопку с заголовком справа (левый RadioButton) //--- и указываем для кнопки в качестве контейнера базовый элемент x=checkbox_lt.X(); y=checkbox_lt.Bottom()+shift_y; w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_lt=new CRadioButton("RadioButtonL",0,wnd,"RadioButton L",x,y,w,h)); radio_bt_lt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения radio_bt_lt.SetImageBound(2,1,h-2,h-2); //--- Задаём смещение текста кнопки по оси X radio_bt_lt.SetTextShiftH(radio_bt_lt.ImageRight()+2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. radio_bt_lt.SetID(14); radio_bt_lt.SetState(true); radio_bt_lt.Draw(false); radio_bt_lt.Print(); //--- Внутри базового объекта создаём радиокнопку с заголовком слева (правый RadioButton) //--- и указываем для кнопки в качестве контейнера базовый элемент x=radio_bt_lt.Right()+4; y=radio_bt_lt.Y(); w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_rt=new CRadioButton("RadioButtonR",0,wnd,"RadioButton R",x,y,w,h)); radio_bt_rt.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X radio_bt_rt.SetTextShiftH(2); //--- Задаём координаты и размеры области изображения radio_bt_rt.SetImageBound(radio_bt_rt.Width()-h+2,1,h-2,h-2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. radio_bt_rt.SetID(15); radio_bt_rt.Draw(true); radio_bt_rt.Print(); //--- Успешная инициализация return(INIT_SUCCEEDED); }
Estudie cuidadosamente todos los comentarios del código. Aquí se describen con suficiente detalle todos los pasos de creación de objetos.
En el controlador del indicador OnDeinit() destruye todos los objetos de la lista:
//+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { list.Clear(); }
El controlador OnCalculate() está vacío: no estamos calculando ni mostrando nada en el gráfico:
//+------------------------------------------------------------------+ //| 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); }
Para animar los elementos gráficos creados, recorra la lista de objetos creados en el controlador OnChartEvent() y llame al controlador similar para cada elemento. Dado que los botones de radio aún no están conectados en grupos de ninguna manera (esto se tratará en los siguientes artículos), emule el cambio de botones de radio, como debería ser en un grupo de elementos:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик событий каждого из созданных объектов for(int i=0;i<list.Total();i++) { CCanvasBase *obj=list.At(i); if(obj!=NULL) obj.OnChartEvent(id,lparam,dparam,sparam); } //--- Эмулируем работу радиокнопок в группе --- //--- Если получено пользовательское событие if(id>=CHARTEVENT_CUSTOM) { //--- Если нажата левая радиокнопка if(sparam==radio_bt_lt.NameBG()) { //--- Если состояние кнопки изменено (было не выбрано) if(radio_bt_lt.State()) { //--- делаем правую радиокнопку невыбранной и перерисовываем её radio_bt_rt.SetState(false); radio_bt_rt.Draw(true); } } //--- Если нажата правая радиокнопка if(sparam==radio_bt_rt.NameBG()) { //--- Если состояние кнопки изменено (было не выбрано) if(radio_bt_rt.State()) { //--- делаем левую радиокнопку невыбранной и перерисовываем её radio_bt_lt.SetState(false); radio_bt_lt.Draw(true); } } } }
Compilemos el indicador y ejecutémoslo en el gráfico:

Todos los controles responden a la interacción del mouse y los botones de opción se alternan como si estuvieran agrupados. Las etiquetas de texto se crearon para que cambien de color cuando se pasa el cursor sobre ellas para representar visualmente que puedes personalizar los controles a tu discreción. En el estado normal, los textos de las etiquetas son estáticos.
Pero hay una omisión aquí: cuando se pasa el cursor del ratón por encima de un control, aparece una información sobre herramientas innecesaria con el nombre del indicador. Para eliminar este comportamiento, es necesario que cada objeto gráfico introduzca el valor «\n» en su propiedad OBJPROP_TOOLTIP. Arréglalo.
En la clase CCanvasBase, en el método Create, introduzca dos líneas con la instalación de información sobre herramientas para objetos gráficos de fondo y primer plano:
//+------------------------------------------------------------------+ //| CCanvasBase::Создаёт графические объекты фона и переднего плана | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Получаем скорректированный идентификатор графика long id=this.CorrectChartID(chart_id); //--- Корректируем переданное имя для объекта string nm=object_name; ::StringReplace(nm," ","_"); //--- Создаём имя графического объекта для фона и создаём канвас string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Создаём имя графического объекта для переднего плана и создаём канвас obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameBG(),OBJPROP_TOOLTIP,"\n"); ::ObjectSetString(id,this.NameFG(),OBJPROP_TOOLTIP,"\n"); //--- Устанавливаем размеры прямоугольной области и возвращаем true this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
Recompila el indicador y comprueba:

Ahora todo está correcto.
Conclusión
Hoy hemos dado otro paso hacia la creación del control de tabla. Todos los controles complejos se ensamblarán a partir de objetos simples pero altamente funcionales.
Hoy hemos agregado el componente Controlador a todos los objetos. Esto permite al usuario interactuar con los controles y los elementos pueden interactuar entre sí.
En el siguiente artículo, prepararemos los elementos del panel y del contenedor, que son los componentes principales para alojar otros elementos. Al mismo tiempo, el contenedor permite desplazar elementos secundarios dentro de sí mismo.
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 | iTestLabel.mq5 | Indicador de prueba | Indicador para probar manipulaciones con clases de controles. |
| 4 | MQL5.zip | Archivo | Un archivo con los archivos anteriores para descomprimir en el directorio MQL5 del terminal del cliente. |
Clases dentro de la biblioteca Base.mqh:
| # | Nombre | Descripción |
|---|---|---|
| 1 | CBaseObj | Una clase base para todos los objetos gráficos. |
| 2 | CColor | Curso de gestión del color. |
| 3 | CColorElement | Una clase para gestionar los colores de los distintos estados de un elemento gráfico. |
| 4 | CBound | Clase de control de área rectangular |
| 5 | CCanvasBase | Una clase base para manipular elementos gráficos en canvas. |
Clases dentro de la biblioteca Controls.mqh:
| # | Nombre | Descripción |
|---|---|---|
| 1 | CImagePainter | Una clase para dibujar imágenes en un área definida por coordenadas y dimensiones. |
| 2 | CLabel | La clase de control "etiqueta de texto" |
| 3 | CButton | La clase de control de "botón simple" |
| 4 | CButtonTriggered | La clase de control de "botón de dos posiciones" |
| 5 | CButtonArrowUp | Clase de control «botón de flecha arriba» |
| 6 | CButtonArrowDown | Clase de control «botón de flecha hacia abajo» |
| 7 | CButtonArrowLeft | Clase de control «botón de flecha izquierda» |
| 8 | CButtonArrowRight | Clase de control «botón de flecha derecha» |
| 9 | CCheckBox | La clase de control "casilla de verificación" |
| 10 | CRadioButton | La clase de control "botón de selección" |
Todos los archivos creados se adjuntan al artículo para autoaprendizaje. El archivo comprimido se puede descomprimir en la carpeta terminal, y todos los archivos se ubicarán en la carpeta deseada: \MQL5\Indicators\Tables\.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/18221
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.
Modelo matricial de pronóstico basado en cadenas de Márkov
Visión por computadora para el trading (Parte 2): Complicamos la arquitectura para el análisis 2D de imágenes RGB
Particularidades del trabajo con números del tipo double en MQL4
Algoritmo de camello — Camel Algorithm (CA)
- 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