Interfaces gráficas VI: Controles "Casilla de verificación", "Campo de edición" y sus tipos combinados (Capítulo 1)
Índice
- Introducción
- Control “casilla de verificación”
- Desarrollo de la clase para la creación del control “casilla de verificación”
- Prueba del control “casilla de verificación”
- Control “campo de edición”
- Desarrollo de la clase para la creación del control “campo de edición”
- Prueba del control “campo de edición”
- Otros controles con las casillas de verificación
- Conclusión
Introducción
El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de los artículos de cada parte se puede encontrar la lista de los capítulos con los enlaces, así como descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.
La sexta parte de la serie va a incluir dos capítulos. En la primera crearemos cuatro controles:
- Casilla de verificación (checkbox)
- Campo de edición
- Campo de edición con checkbox
- Lista combinada (combobox) con casilla de verificación (checkbox)
En este artículo, de estos cuatro controles nosotros vamos a describir sólo “casilla de verificación” y “campo de edición”, porque “campo de edición con checkbox” y “combobox con checkbox” no van a contener nada nuevo de lo que no hemos hablado antes.
En el segundo capítulo hablaremos de los siguientes controles:
- Slider
- Slider doble
Control “Casilla de verificación”
El control “Casilla de verificación” o “Checkbox” está destinado para manejar los parámetros que pueden tener sólo dos estados. El botón con dos iconos se utiliza para identificar el estado actual del parámetro con el que está vinculado el control. El icono con el signo “marca de verificación” significa el estado “activado” (on). El icono sin el signo “marca de verificación” significa el estado “desactivado” (off). Junto con el botón se encuentra la descripción breve del parámetro.
Vamos a utilizar tres objetos gráficos para diseñar este control. Son los siguientes:
- Fondo
- Icono (botón)
- Etiqueta de texto
Fig. 1. Partes integrantes del control “Casilla de verificación”.
Vamos a ver cómo está organizada la clase de este control.
Desarrollo de la clase para la creación del control “Casilla de verificación”
En la tercera parte de esta serie ya hemos considerado un control parecido: “Botón con imagen”, clase CIconButton (véase el artículo Interfaces gráficas III: Botones simples y multifuncionales (Capítulo 1). El control “Checkbox” parece al botón con imagen en el modo con dos estados. La única diferencia es que el botón en diferentes estados (on/off) tiene el color del fondo y del texto diferente (si está establecido), y en caso del checkbox, se cambia sólo el color de la imagen y del texto (si está establecido). E color del fondo del checkbox suele ser el mismo que tiene el fondo de la ventana a la que se adjunta el control. Aquí el fondo va a utilizarse como el área para identificar la posición del cursor respecto a los bordes de esta área, así como para identificar el clic izquierdo en el control. Al mismo tiempo, el control “checkbox” es incluso más sencillo que el control “botón con imagen” porque posee menos propiedades que puede establecer el usuario.
Creamos el archivo CheckBox.mqh y lo incluimos en la librería (archivo WndContainer.mqh):
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "CheckBox.mqh"
Creamos la clase CCheckBox con los métodos estándar para todos los controles de la librería en el archivo CheckBox.mqh:
//+------------------------------------------------------------------+ //| CheckBox.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Clase para crear checkbox | //+------------------------------------------------------------------+ class CCheckBox : public CElement { private: //--- Puntero al formulario al que está adjuntado el control CWindow *m_wnd; //--- public: CCheckBox(void); ~CCheckBox(void); //--- Guarda el puntero del formulario void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Manejador de eventos del gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Temporizador virtual void OnEventTimer(void); //--- Desplazamiento del control virtual void Moving(const int x,const int y); //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Establecer, (2) resetear las prioridades para el clic izquierdo del ratón virtual void SetZorders(void); virtual void ResetZorders(void); //--- Resetear color virtual void ResetColors(void); };
Antes de la creación del control, el usuario tendrá disponibles los métodos para establecer las propiedades listadas a continuación:
- Fondo del control
- Iconos del checkbox en el estado activo y bloqueado
- Márgenes para la etiqueta de texto
- Colores de las etiquetas de texto en diferentes estados
Habrá cuatro imágenes para el botón del checkbox: dos imágenes para el estado activo del control, y otras dos para el estado bloqueado. Al final del artículo se puede descargar las imágenes ya hechas para todos los estados, pero cada uno puede usar sus propias imágenes.
class CCheckBox : public CElement { private: //--- Color del fondo del checkbox color m_area_color; //--- Iconos del checkbox en el estado activo y bloqueado string m_check_bmp_file_on; string m_check_bmp_file_off; string m_check_bmp_file_on_locked; string m_check_bmp_file_off_locked; //--- Texto del checkbox string m_label_text; //--- Márgenes de la etiqueta de texto int m_label_x_gap; int m_label_y_gap; //--- Colores de la etiqueta de texto en diferentes estados color m_label_color; color m_label_color_off; color m_label_color_hover; color m_label_color_locked; color m_label_color_array[]; //--- Prioridades para el clic izquierdo del ratón int m_zorder; int m_area_zorder; //--- public: //--- Establecer los iconos para el botón en el estado activo y bloqueado void CheckFileOn(const string file_path) { m_check_bmp_file_on=file_path; } void CheckFileOff(const string file_path) { m_check_bmp_file_off=file_path; } void CheckFileOnLocked(const string file_path) { m_check_bmp_file_on_locked=file_path; } void CheckFileOffLocked(const string file_path) { m_check_bmp_file_off_locked=file_path; } //--- (1) Color del fondo, (2) márgenes de la etiqueta de texto void AreaColor(const color clr) { m_area_color=clr; } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- Colores del texto en estados diferentes void LabelColor(const color clr) { m_label_color=clr; } void LabelColorOff(const color clr) { m_label_color_off=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } void LabelColorLocked(const color clr) { m_label_color_locked=clr; } //--- Descripción del checkbox string LabelText(void) const { return(m_label.Description()); } };
Para crear el control “” checkbox vamos a necesitar tres métodos privados (private) y uno principal público (public):
class CCheckBox : public CElement { private: //--- Objetos para crear el checkbox CRectLabel m_area; CBmpLabel m_check; CLabel m_label; //--- public: //--- Métodos para crear el checkbox bool CreateCheckBox(const long chart_id,const int subwin,const string text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateCheck(void); bool CreateLabel(void); };
Para gestionar el estado del checkbox, vamos a crear los métodos CCheckBox::CheckButtonState() y CCheckBox::CheckBoxState():
class CCheckBox : public CElement { private: //--- Estado del botón del checkbox bool m_check_button_state; //--- Estado del control (disponible/bloqueado) bool m_checkbox_state; //--- public: //--- Devolver/establecer el estado del checkbox bool CheckBoxState(void) const { return(m_checkbox_state); } void CheckBoxState(const bool state); //--- Devolver/establecer el estado del botón del checkbox bool CheckButtonState(void) const { return(m_check.State()); } void CheckButtonState(const bool state); };
Para bloquear/desbloquear el control hay que usar el método CCheckBox::CheckBoxState():
//+------------------------------------------------------------------+ //| Establecer el estado del control | //+------------------------------------------------------------------+ void CCheckBox::CheckBoxState(const bool state) { //--- Estado del control m_checkbox_state=state; //--- Imagen m_check.BmpFileOn((state)? "::"+m_check_bmp_file_on : "::"+m_check_bmp_file_on_locked); m_check.BmpFileOff((state)? "::"+m_check_bmp_file_off : "::"+m_check_bmp_file_off_locked); //--- Color de la etiqueta de texto m_label.Color((state)? m_label_color : m_label_color_locked); }
Para establecer el estado del botón del checkbox, hay que usar el método CCheckBox::CheckButtonState():
//+------------------------------------------------------------------+ //| Establecer el estado del botón del checkbox | //+------------------------------------------------------------------+ void CCheckBox::CheckButtonState(const bool state) { //--- Salir si el control está bloqueado if(!m_checkbox_state) return; //--- Establecemos el estado para el botón m_check.State(state); m_check_button_state=state; //--- Cambiamos el color de acuerdo con el estado m_label.Color((state)? m_label_color : m_label_color_off); CElement::InitColorArray((state)? m_label_color : m_label_color_off,m_label_color_hover,m_label_color_array); }
Nos queda sólo crear el método para procesar el clic en el control “checkbox”. Este método va a invocarse en el manejador principal del control CCheckBox::OnEvent() por el evento con el identificador CHARTEVENT_OBJECT_CLICK. Este método va a llamarse CCheckBox::OnClickLabel(). En el principio de este método, se comprueba si ha tenido lugar el clic en el área del control (fondo). Para evitar confusiones en cuanto al objeto del control que ha sido pulsado, el valor de la prioridad del clic izquierdo para el fondo es igual a 1, para los demás es 0. Por eso, incluso si el clic ha sido hecho cuando el cursor se encuentra sobre el botón del checkbox o sobre la etiqueta de texto, va a considerarse que el clic se ha hecho en el fondo del control, es decir, en el objeto cuya prioridad es más alta en esta área. Luego, usamos el método CCheckBox::CheckButtonState() para establecer el estado opuesto al botón del checkbox. Al final del método, se envía el evento personalizado con (1) el identificador del evento ON_CLICK_LABEL, (2) identificador del control y (3) la descripción del control.
class CCheckBox : public CElement { private: //--- Procesamiento del clic en el control bool OnClickLabel(const string clicked_object); }; //+------------------------------------------------------------------+ //| Manejo de eventos | //+------------------------------------------------------------------+ void CCheckBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del clic izquierdo en el objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Clic en el checkbox if(OnClickLabel(sparam)) return; } } //+------------------------------------------------------------------+ //| Clic en el encabezado del control | //+------------------------------------------------------------------+ bool CCheckBox::OnClickLabel(const string clicked_object) { //--- Salimos si el nombre del objeto no coincide if(m_area.Name()!=clicked_object) return(false); //--- Salir si el control está bloqueado if(!m_checkbox_state) return(false); //--- Cambiar al modo opuesto CheckButtonState(!m_check.State()); //--- Ahora el curso está sobre el control m_label.Color(m_label_color_hover); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),0,m_label.Description()); return(true); }
Prueba del control “Casilla de verificación”
Todos los métodos de la clase CCheckBox ya están listos, y ahora podemos probar cómo funciona este control. El archivo con la clase de este control ya tienen que estar incluido en la liibrería. Se puede copiar el Asesor Experto para las pruebas desde el artículo anterior. Eliminamos de ahí todos los controles, dejando sólo el menú principal y la barra de estado. Luego, creamos dos casillas de verificación (checkbox) para la prueba. Hagamos que el segundo checkbox esté bloqueado por defecto en el momento de la carga del programa en el gráfico. El primer checkbox estará disponible para la interacción con el usuario, pero en el estado desactivado. La activación del primer checkbox será como señal para el desbloqueo del segundo checkbox. Y al revés, la desactivación del primer checkbox será como señal para el bloqueo del segundo checkbox.
Hay que crear dos instancias de la clase CCheckBox en la clase personalizada CProgram, y declarar dos métodos para la creación de los checkbox indicando los márgenes desde el punto extremos del formulario al que estarán adjuntados:
//+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Checkboxs Casilla de verificación ( checkbox ). CCheckBox m_checkbox2; //--- private: //--- Checkboxs #define CHECKBOX1_GAP_X (7) #define CHECKBOX1_GAP_Y (50) bool CreateCheckBox1(const string text); #define CHECKBOX2_GAP_X (30) #define CHECKBOX2_GAP_Y (75) bool CreateCheckBox2(const string text); };
La única diferencia en la implementación de los métodos consiste en que la disponibilidad del segundo checkbox va a depender del estado del primer checkbox:
//+------------------------------------------------------------------+ //| Crea el combobox 2 | //+------------------------------------------------------------------+ bool CProgram::CreateCheckBox2(string text) { //--- Pasar el objeto del panel m_checkbox2.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+CHECKBOX2_GAP_X; int y=m_window1.Y()+CHECKBOX2_GAP_Y; //--- Establecemos las propiedades antes de la creación m_checkbox2.XSize(90); m_checkbox2.YSize(18); m_checkbox2.AreaColor(clrWhiteSmoke); m_checkbox2.LabelColor(clrBlack); m_checkbox2.LabelColorOff(clrBlack); m_checkbox2.LabelColorLocked(clrSilver); //--- Creamos el control if(!m_checkbox2.CreateCheckBox(m_chart_id,m_subwin,text,x,y)) return(false); //--- La disponibilidad va a depender del estado actual del primer checkbox m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()); //--- Añadimos el objeto al array común de los grupos de objetos CWndContainer::AddToElementsArray(0,m_checkbox2); return(true); }
La llamada a los métodos de la creación de checkboxs debe ubicarse en el método principal de la creación de la interfaz gráfica. En el código de abajo se muestra la versión reducida de este método:
//+------------------------------------------------------------------+ //| Crea el panel de trading | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Creación del formulario 1 para los controles //--- Creación de controles: // Menú principal //--- Menús contextuales //--- Creación de la barra de estado //--- Checkboxs if(!CreateCheckBox1("Checkbox 1")) return(false); if(!CreateCheckBox2("Checkbox 2")) return(false); //--- Redibujar el gráfico m_chart.Redraw(); return(true); }
Vamos a recibir y procesar los mensajes con el identificador ON_CLICK_LABEL en el manejador de eventos de la clase personalizada CProgram. La disponibilidad del segundo checkbox va a definirse por el estado del primer checkbox (véase el código de abajo):
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento del clic en el checkbox if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Si el clic ha sido en el primer checkbox if(lparam==m_checkbox1.Id()) { //--- Establecer el estado para el segundo checkbox m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()); } } }
Compile los archivos e inicie el EA en el gráfico. Debe obtener el resultado como en la captura de pantalla de abajo:
Fig. 2. Prueba del control “Checkbox”.
La captura de pantalla muestra que el segundo checkbox está bloqueado. Podemos definirlo por el color del texto y el imagen del botón correspondiente a este estado (colores descoloridos). Todo funciona perfectamente, y a continuación vamos a dedicarnos al desarrollo de la clase para la creación del control “campo de edición”
Control “Campo de edición”
El control “campo de edición” se utiliza para establecer un valor numérico en el cuadro de texto. Se puede hacerlo manualmente, o bien utilizar los botones de desplazamiento (scrolling). El usuario de la librería puede establecer por sí mismo los valores mínimos y máximos (limitaciones), así como el paso para el scrolling. Si se pulsa el botón con flecha arriba/abajo, entonces el valor será aumentado/reducido de acuerdo con el paso especificado en los ajustes. Si el botón arriba/abajo se mantiene apretado, el valor en el campo de edición va a cambiarse en modo acelerado. Al llegar al máximo/mínimo permitido, el desplazamiento del valor se detiene.
Vamos a utilizar cinco objetos gráficos para diseñar este control. Son los siguientes:
- Fondo
- Etiqueta de texto
- Campo de edición
- Dos botones para el desplazamiento de los valores en el campo de edición
Fig. 3. Partes integrantes del control “Campo de edición”.
A continuación, vamos a empezar el desarrollo de la clase para la creación de este control de la interfaz.
Desarrollo de la clase para la creación del control “Campo de edición”
Igual que el control “Casilla de verificación», el control “Campo de edición» es simple y no compuesto. En otras palabras, se compone de objetos simples, pero no incluye otros controles. Por eso no habrá que introducir ningunas adiciones en el archivo WndContainer.mqh. Será suficiente sólo incluir el archivo con la clase del control “campo de edición”.
Ahora creamos el archivo SpinEdit.mqh y lo incluimos en el archivo WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "SpinEdit.mqh"
Creamos la clase CSpinEdit con los métodos estándar para todos los controles de la librería en el archivo SpinEdit.mqh, igual que ha sido mostrado antes en este artículo en la descripción de la clase CCheckBox. Por eso pasaremos directamente a la descripción de las propiedades del control “campo de edición”.
Abajo se muestra la lista de todas las propiedades del control “campo de edición” que estarán disponibles al usuario para su configuración.
- Color del fondo
- Texto de la descripción del campo de edición
- Márgenes de la etiqueta de texto
- Colores del texto en estados diferentes
- Tamaños del campo de edición
- Margen del campo de edición desde el lado derecho del control
- Colores del campo de edición en diferentes estados
- Colores del texto del campo de edición en diferentes estados
- Colores del marco del campo de edición en diferentes estados
- Iconos de conmutadores en el estado activo y bloqueado
- Márgenes de los botones (desde el lado derecho)
- Modo de reseteo de valores hasta el mínimo
- Valor mínimo y máximo
- Paso para el cambio del valor en el campo de edición usando los botones
- Modo de alineación del texto
- Número de dígitos después de la coma
En el código de abajo se puede ver los campos y métodos de la clase para la configuración de las propiedades del control arriba mencionadas:
//+------------------------------------------------------------------+ //| Clase para crear campo de edición | //+------------------------------------------------------------------+ class CSpinEdit : public CElement { private: //--- Color del fondo del control color m_area_color; //--- Texto de la descripción del campo de edición string m_label_text; //--- Márgenes de la etiqueta de texto int m_label_x_gap; int m_label_y_gap; //--- Colores del texto en estados diferentes color m_label_color; color m_label_color_hover; color m_label_color_locked; color m_label_color_array[]; //--- Tamaños del campo de edición int m_edit_x_size; int m_edit_y_size; //--- Margen del campo de edición desde el lado derecho int m_edit_x_gap; //--- Colores del campo de edición y del texto en diferentes estados color m_edit_color; color m_edit_color_locked; color m_edit_text_color; color m_edit_text_color_locked; color m_edit_text_color_highlight; //--- Colores del marco del campo de edición en diferentes estados color m_edit_border_color; color m_edit_border_color_hover; color m_edit_border_color_locked; color m_edit_border_color_array[]; //--- Iconos de conmutadores en el estado activo y bloqueado string m_inc_bmp_file_on; string m_inc_bmp_file_off; string m_inc_bmp_file_locked; string m_dec_bmp_file_on; string m_dec_bmp_file_off; string m_dec_bmp_file_locked; //--- Márgenes de los botones (desde el lado derecho) int m_inc_x_gap; int m_inc_y_gap; int m_dec_x_gap; int m_dec_y_gap; //--- Modo de reseteo de valores hasta el mínimo bool m_reset_mode; //--- Valor mínimo/máximo double m_min_value; double m_max_value; //--- Paso para el cambio del valor en el campo de edición double m_step_value; //--- Modo de alineación del texto ENUM_ALIGN_MODE m_align_mode; //--- Número de dígitos después de la coma int m_digits; //--- public: //--- (1) Color del fondo, (2) texto de la descripción del campo de edición, (3) márgenes de la etiqueta de texto void AreaColor(const color clr) { m_area_color=clr; } string LabelText(void) const { return(m_label.Description()); } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- Colores de la etiqueta de texto en diferentes estados void LabelColor(const color clr) { m_label_color=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } void LabelColorLocked(const color clr) { m_label_color_locked=clr; } //--- (1) Tamaños del campo de edición, (2) margen del campo de edición desde el lado derecho void EditXSize(const int x_size) { m_edit_x_size=x_size; } void EditYSize(const int y_size) { m_edit_y_size=y_size; } void EditXGap(const int x_gap) { m_edit_x_gap=x_gap; } //--- Colores del campo de edición en diferentes estados void EditColor(const color clr) { m_edit_color=clr; } void EditColorLocked(const color clr) { m_edit_color_locked=clr; } //--- Colores del texto del campo de edición en diferentes estados void EditTextColor(const color clr) { m_edit_text_color=clr; } void EditTextColorLocked(const color clr) { m_edit_text_color_locked=clr; } void EditTextColorHighlight(const color clr) { m_edit_text_color_highlight=clr; } //--- Colores del marco del campo de edición en diferentes estados void EditBorderColor(const color clr) { m_edit_border_color=clr; } void EditBorderColorHover(const color clr) { m_edit_border_color_hover=clr; } void EditBorderColorLocked(const color clr) { m_edit_border_color_locked=clr; } //--- Establecer los iconos para el botón en el estado activo y bloqueado void IncFileOn(const string file_path) { m_inc_bmp_file_on=file_path; } void IncFileOff(const string file_path) { m_inc_bmp_file_off=file_path; } void IncFileLocked(const string file_path) { m_inc_bmp_file_locked=file_path; } void DecFileOn(const string file_path) { m_dec_bmp_file_on=file_path; } void DecFileOff(const string file_path) { m_dec_bmp_file_off=file_path; } void DecFileLocked(const string file_path) { m_dec_bmp_file_locked=file_path; } //--- Márgenes para los botones del campo de edición void IncXGap(const int x_gap) { m_inc_x_gap=x_gap; } void IncYGap(const int y_gap) { m_inc_y_gap=y_gap; } void DecXGap(const int x_gap) { m_dec_x_gap=x_gap; } void DecYGap(const int y_gap) { m_dec_y_gap=y_gap; } //--- Modo del reseteo al hacer clic en la etiqueta de texto bool ResetMode(void) { return(m_reset_mode); } void ResetMode(const bool mode) { m_reset_mode=mode; } //--- Valor mínimo double MinValue(void) const { return(m_min_value); } void MinValue(const double value) { m_min_value=value; } //--- Valor máximo double MaxValue(void) const { return(m_max_value); } void MaxValue(const double value) { m_max_value=value; } //--- Paso del valor double StepValue(void) const { return(m_step_value); } void StepValue(const double value) { m_step_value=(value<=0)? 1 : value; } //--- (1) Número de dígitos después de la coma, (2) modo de alineación del texto void SetDigits(const int digits) { m_digits=::fabs(digits); } void AlignMode(ENUM_ALIGN_MODE mode) { m_align_mode=mode; } };
Para diseñar el control “campo de edición”, vamos a necesitar cinco métodos privados (private) para la creación de los objetos primitivos, y un método principal público (public) que va a invocarse en la clase personalizada durante la creación de la interfaz gráfica. Al final del artículo se puede descargar las imágenes para los botones de desplazamiento. Por defecto, se utilizan estas imágenes, pero si desean, pueden reemplazarlas por las suyas.
class CSpinEdit : public CElement { private: //--- Objetos para crear el campo de edición CRectLabel m_area; CLabel m_label; CEdit m_edit; CBmpLabel m_spin_inc; CBmpLabel m_spin_dec; //--- public: //--- Métodos para crear el campo de edición bool CreateSpinEdit(const long chart_id,const int subwin,const string label_text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabel(void); bool CreateEdit(void); bool CreateSpinInc(void); bool CreateSpinDec(void); };
Ahora vamos a discutir los modos y los métodos que se usan para manejar el estado y las propiedades del control después de su creación. Empezamos con el método CSpinEdit::SetValue() para corregir y guardar el valor para el campo de edición, así como con el método adicional CSpinEdit::HighlightLimit(), que se usa para resaltar (parpadeo) el texto en edición cuando se excede el límite mínimo y el máximo.
Al principio del método CSpinEdit::SetValue() se realiza la corrección tomando en cuenta el paso para el cambio del valor. Luego se hacen las comprobaciones del exceso de los límites establecidos (máximo/mínimo). Si se detecta el exceso de esos límites, se establece el valor correspondiente a la comprobación y se llama al método CSpinEdit::HighlightLimit() para resaltar el texto. Luego, si después de todas las comprobaciones y correcciones resulta que el valor se ha cambiado en comparación con el que ha sido guardado antes, se guarda el valor nuevo.
El método CSpinEdit::HighlightLimit() es bastante sencillo. Primero ahí se establece el color del parpadeo que puede ser redefinido por el usuario (rojo por defecto). Después de eso se mantiene una pausa de 100 milisegundos. Será suficiente para que el cambio temporal del color sea notable. Al final, se establece el color inicial.
Se puede encontrar más detalles sobre estos métodos descritos en el código de abajo:
class CSpinEdit : public CElement { private: //--- Valor actual en el campo de edición double m_edit_value; //--- public: //--- Devolver y establecer el valor del campo de edición double GetValue(void) const { return(m_edit_value); } bool SetValue(const double value); //--- Parpadeo al alcanzar el límite void HighlightLimit(void); }; //+------------------------------------------------------------------+ //| Comprobación del valor actual | //+------------------------------------------------------------------+ bool CSpinEdit::SetValue(double value) { //--- Para la corrección double corrected_value =0.0; //--- Corregimos tomando en cuenta el paso corrected_value=::MathRound(value/m_step_value)*m_step_value; //--- Comprobación del mínimo/máximo if(corrected_value<m_min_value) { //--- Establecer valor mínimo corrected_value=m_min_value; //--- Establecemos el estado On m_spin_dec.State(true); //--- Parpadear avisando sobre el alcance del límite HighlightLimit(); } if(corrected_value>m_max_value) { //--- Establecer valor máximo corrected_value=m_max_value; //--- Establecemos el estado On m_spin_inc.State(true); //--- Parpadear avisando sobre el alcance del límite HighlightLimit(); } //--- Si el valor ha sido cambiado if(m_edit_value!=corrected_value) { m_edit_value=corrected_value; m_edit.Color(m_edit_text_color); return(true); } //--- Valor sin cambios return(false); } //+------------------------------------------------------------------+ //| Resaltar el límite | //+------------------------------------------------------------------+ void CSpinEdit::HighlightLimit(void) { //--- Cambiar temporalmente el color del texto m_edit.Color(m_edit_text_color_highlight); //--- Actualizar ::ChartRedraw(); //--- Retardo antes de volver al color inicial ::Sleep(100); //--- Cambiar el color del texto por el color inicial m_edit.Color(m_edit_text_color); }
El método CSpinEdit::SetValue() está destinado sólo para el ajuste del valor en caso de exceder el límite establecido, mientras que para el cambio del valor en el campo de edición, hay que usar el método CSpinEdit:: ChangeValue():
class CSpinEdit : public CElement { private: //--- Cambio del valor en el campo de edición void ChangeValue(const double value); }; //+------------------------------------------------------------------+ //| Cambio del valor en el campo de edición | //+------------------------------------------------------------------+ void CSpinEdit::ChangeValue(const double value) { //--- Comprobamos, ajustamos y guardamos el valor nuevo SetValue(value); //--- Establecemos el valor nuevo en el campo de edición m_edit.Description(::DoubleToString(GetValue(),m_digits)); }
Igual que en los demás controles, vamos a necesitar el método para manejar la disponibilidad del control “campo de edición” (véase el código de abajo):
class CSpinEdit : public CElement { private: //--- Estado del control (disponible/bloqueado) bool m_spin_edit_state; //--- public: //--- Devolver/establecer el estado de disponibilidad del campo de edición bool SpinEditState(void) const { return(m_spin_edit_state); } void SpinEditState(const bool state); }; //+------------------------------------------------------------------+ //| Establecer el estado del control | //+------------------------------------------------------------------+ void CSpinEdit::SpinEditState(const bool state) { m_spin_edit_state=state; //--- Color de la etiqueta de texto m_label.Color((state)? m_label_color : m_label_color_locked); //--- Color del campo de edición m_edit.Color((state)? m_edit_text_color : m_edit_text_color_locked); m_edit.BackColor((state)? m_edit_color : m_edit_color_locked); m_edit.BorderColor((state)? m_edit_border_color : m_edit_border_color_locked); //--- Imagen de conmutadores m_spin_inc.BmpFileOn((state)? "::"+m_inc_bmp_file_on : "::"+m_inc_bmp_file_locked); m_spin_dec.BmpFileOn((state)? "::"+m_dec_bmp_file_on : "::"+m_dec_bmp_file_locked); //--- Ajuste respecto el estado actual if(!m_spin_edit_state) { //--- Prioridades m_edit.Z_Order(-1); m_spin_inc.Z_Order(-1); m_spin_dec.Z_Order(-1); //--- Campo de edición en el modo “Sólo lectura” m_edit.ReadOnly(true); } else { //--- Prioridades m_edit.Z_Order(m_edit_zorder); m_spin_inc.Z_Order(m_spin_zorder); m_spin_dec.Z_Order(m_spin_zorder); //--- Campo de edición en modo de edición m_edit.ReadOnly(false); } }
Ahora hablaremos de los métodos para procesar los eventos del control “campo de edición”. Al pulsar la etiqueta de texto, va a generarse el evento personalizado con (1) el identificador del evento ON_CLICK_LABEL, (2) identificador del control, (3) índice del control y (4) la descripción de la etiqueta de texto. Antes, ya hemos mencionado el modo para resetear el valor en el campo de edición hasta el mínimo. Por defecto, este modo está desactivado (false). Se activa a través del método CSpinEdit::ResetMode(): ponga el valor true en su único argumento. Si el modo para el reseteo del valor está activado, el clic en la etiqueta de texto va a llamar al método para establecer el valor mínimo en el campo de edición.
Teniendo en cuenta todo lo arriba mencionado, el código del método CSpinEdit::OnClickLabel() para el procesamiento del clic en la etiqueta de texto será como se muestra a continuación:
class CSpinEdit : public CElement { private: //--- Procesamiento del clic en la etiqueta de texto bool OnClickLabel(const string clicked_object); }; //+------------------------------------------------------------------+ //| Clic en el encabezado del control | //+------------------------------------------------------------------+ bool CSpinEdit::OnClickLabel(const string clicked_object) { //--- Salimos si el nombre del objeto no coincide if(m_area.Name()!=clicked_object) return(false); //--- Si el modo de reseteo está activado if(m_reset_mode) { //--- Establecemos el valor mínimo ChangeValue(MinValue()); } //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
El usuario puede modificar el valor en el campo de edición introduciendo un valor nuevo manualmente o usando los botones conmutadores del incremento y decremento. Necesitaremos nuevos identificadores de eventos personalizados. Vamos a añadirlos al archivo Defines.mqh:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_END_EDIT (18) // Fin de edición del valor en el campo de edición #define ON_CLICK_INC (19) // Cambio del contador hacia arriba #define ON_CLICK_DEC (20) // Cambio del contador hacia abajo
Para procesar la entrada de un nuevo valor manualmente, escribiremos el método CSpinEdit::OnEndEdit(). Va a invocarse en el manejador común CSpinEdit::OnEvent() cuando llega el evento con el identificador CHARTEVENT_OBJECT_ENDEDIT. Cuando se introduce un valor nuevo, se corrige en caso de necesidad. Al final del método va a generarse el evento personalizado con (1) el identificador del evento ON_END_EDIT, (2) identificador del control, (3) índice del control y (4) la descripción de la etiqueta de texto (véase el código de abajo).
class CSpinEdit : public CElement { private: //--- Procesamiento de la entrada del valor en el campo de edición bool OnEndEdit(const string edited_object); }; //+------------------------------------------------------------------+ //| Procesamiento de la entrada del valor en el campo de edición | //+------------------------------------------------------------------+ bool CSpinEdit::OnEndEdit(const string edited_object) { //--- Salimos si el nombre del objeto no coincide if(m_edit.Name()!=edited_object) return(false); //--- Obtenemos el valor introducido double entered_value=::StringToDouble(m_edit.Description()); //--- Comprobamos, ajustamos y guardamos el valor nuevo ChangeValue(entered_value); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
Para el procesamiento del clic en los botones de incremento y decremento, hay que crear dos métodos separados CSpinEdit::OnClickSpinInc() y CSpinEdit::OnClickSpinDec(). Ahí todo es muy simple. Después del clic en el objeto gráfico, por su nombre se determina si es el botón de nuestro control. Luego obtenemos el valor actual en el campo de edición y lo aumentamos/reducimos por el paso establecido en las propiedades del control. Al final de los métodos se genera el evento personalizado con (1) el identificador del evento ON_CLICK_INC/ON_CLICK_DEC, (2) identificador del control, (3) índice del control y (4) la descripción de la etiqueta de texto (véase el código de abajo).
class CSpinEdit : public CElement { private: //--- Procesamiento del clic en el botón del campo de edición bool OnClickSpinInc(const string clicked_object); bool OnClickSpinDec(const string clicked_object); }; //+------------------------------------------------------------------+ //| Clic en el conmutador del incremento | //+------------------------------------------------------------------+ bool CSpinEdit::OnClickSpinInc(const string clicked_object) { //--- Salimos si el nombre del objeto no coincide if(m_spin_inc.Name()!=clicked_object) return(false); //--- Obtenemos el valor actual double value=GetValue(); //--- Aumentamos por un paso y comprobamos para el exceso del límite ChangeValue(value+m_step_value); //--- Establecemos el estado On m_spin_inc.State(true); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CLICK_INC,CElement::Id(),CElement::Index(),m_label.Description()); return(true); } //+------------------------------------------------------------------+ //| Clic en el conmutador del decremento | //+------------------------------------------------------------------+ bool CSpinEdit::OnClickSpinDec(const string clicked_object) { //--- Salimos si el nombre del objeto no coincide if(m_spin_dec.Name()!=clicked_object) return(false); //--- Obtenemos el valor actual double value=GetValue(); //--- Reducimos por un paso y comprobamos para el exceso del límite ChangeValue(value-m_step_value); //--- Establecemos el estado On m_spin_dec.State(true); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CLICK_DEC,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
Todos los métodos manejadores deben ser llamados en el método principal de procesamiento de eventos (véase el código de abajo). El acceso al cuerpo de los métodos manejadores se controla con la disponibilidad del control en este momento
//+------------------------------------------------------------------+ //| Manejo de eventos | //+------------------------------------------------------------------+ void CSpinEdit::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del clic izquierdo en el objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Salir si el control está bloqueado if(!m_spin_edit_state) return; //--- Procesamiento del clic en la etiqueta de texto if(OnClickLabel(sparam)) return; //--- Procesamiento del clic en los botones del campo de edición if(OnClickSpinInc(sparam)) return; if(OnClickSpinDec(sparam)) return; //--- return; } //--- Procesamiento del evento del cambio del valor en el campo de edición if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- Salir si el control está bloqueado if(!m_spin_edit_state) return; //--- Procesamiento de la entrada del valor if(OnEndEdit(sparam)) return; } }
Para el avance/retroceso rápido de los valores, cuando el botón izquierdo se mantiene pulsado sobre los botones conmutadores del control, vamos a crear el método especial CSpinEdit::FastSwitching() que debe invocarse en el temporizador del control. Antes ya discutimos un método parecido cuando hablabamos sobre la clase CListView para la creación de las listas con la barra de desplazamiento. Si ahí este método es necesario para el scrolling de la lista, aquí va a servir para aumentar o reducir el valor en el campo de edición. Para más detalles CSpinEdit::FastSwitching() véase el código de abajo.
class CSpinEdit : public CElement { private: //--- Avance/retroceso rápido de los valores en el campo de edición void FastSwitching(void); }; //+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CSpinEdit::OnEventTimer(void) { //--- Si el control es desplegable if(CElement::IsDropdown()) { ChangeObjectsColor(); FastSwitching(); } else { //--- Seguimos el cambio del color y el avance/retroceso de valores, //--- sólo si el formulario no está bloqueado if(!m_wnd.IsLocked()) { ChangeObjectsColor(); FastSwitching(); } } } //+------------------------------------------------------------------+ //| Avance/retroceso rápido de valores en el campo de edición | //+------------------------------------------------------------------+ void CSpinEdit::FastSwitching(void) { //--- Salimos si no hay foco sobre el control if(!CElement::MouseFocus()) return; //--- Volvemos el contador al estado inicial si el botón del ratón está suelto if(!m_mouse_state) m_timer_counter=SPIN_DELAY_MSC; //--- Si el botón del ratón está pulsado else { //--- Aumentamos el contador al intervalo establecido m_timer_counter+=TIMER_STEP_MSC; //--- Salimos si es menos de cero if(m_timer_counter<0) return; //--- Obtenemos el valor actual en el campo de edición double current_value=::StringToDouble(m_edit.Description()); //--- Si se aumenta if(m_spin_inc.State()) SetValue(current_value+m_step_value); //--- Si se reduce else if(m_spin_dec.State()) SetValue(current_value-m_step_value); //--- Cambiamos el valor si el botón conmutador todavía se mantiene pulsado if(m_spin_inc.State() || m_spin_dec.State()) m_edit.Description(::DoubleToString(GetValue(),m_digits)); } }
Prueba del control “campo de edición”
Todos los métodos del control “Campo de edición” ya están implementados. Ahora vamos a testearlo en el programa que tenemos preparado para ello. Creamos la instancia de la clase CSpinEdit en la clase personalizada de la aplicación CProgram y declaramos el método para la creación del control “campo de edición”.
//+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Campos de edición CSpinEdit m_spin_edit1; //--- private: //--- Campos de edición #define SPINEDIT1_GAP_X (150) #define SPINEDIT1_GAP_Y (75) bool CreateSpinEdit1(const string text); };
Hagamos que el primer checkbox gestione la disponibilidad de este control igual que lo hemos hecho respecto al segundo checkbox. Por eso, una vez creado el control, su disponibilidad va a depender del estado actual del primer checkbox (código de abajo).
//+------------------------------------------------------------------+ //| Crea el campo de edición 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSpinEdit1(string text) { //--- Guardamos el puntero a la ventana m_spin_edit1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+SPINEDIT1_GAP_X; int y=m_window1.Y()+SPINEDIT1_GAP_Y; //--- Valor double v=(m_spin_edit1.GetValue()==WRONG_VALUE) ? 4 : m_spin_edit1.GetValue(); //--- Establecemos las propiedades antes de la creación m_spin_edit1.XSize(150); m_spin_edit1.YSize(18); m_spin_edit1.EditXSize(76); m_spin_edit1.MaxValue(1000); m_spin_edit1.MinValue(-1000); m_spin_edit1.StepValue(1); m_spin_edit1.SetDigits(0); m_spin_edit1.SetValue(v); m_spin_edit1.ResetMode(true); m_spin_edit1.AreaColor(clrWhiteSmoke); m_spin_edit1.LabelColor(clrBlack); m_spin_edit1.LabelColorLocked(clrSilver); m_spin_edit1.EditColorLocked(clrWhiteSmoke); m_spin_edit1.EditTextColor(clrBlack); m_spin_edit1.EditTextColorLocked(clrSilver); m_spin_edit1.EditBorderColor(clrSilver); m_spin_edit1.EditBorderColorLocked(clrSilver); //--- Creamos el control if(!m_spin_edit1.CreateSpinEdit(m_chart_id,m_subwin,text,x,y)) return(false); //--- La disponibilidad va a depender del estado actual del primer checkbox m_spin_edit1.SpinEditState(m_checkbox1.CheckButtonState()); //--- Añadimos el objeto al array común de los grupos de objetos CWndContainer::AddToElementsArray(0,m_spin_edit1); return(true); }
Igual que los demás controles, el método CProgram::CreateSpinEdit1() debe ser llamado en el método principal de la creación de la interfaz gráfica del programa. En el código de abajo se muestra la versión reducida de este método:
//+------------------------------------------------------------------+ //| Crea el panel de trading | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Creación del formulario 1 para los controles //--- Creación de controles: // Menú principal //--- Menús contextuales //--- Creación de la barra de estado //--- Casillas de verificación (checkbox) //--- Campos de edición if(!CreateSpinEdit1("Spin Edit 1:")) return(false); //--- Redibujar el gráfico m_chart.Redraw(); return(true); }
En el manejador de eventos CProgram::OnEvent(), añadimos el código para la prueba de la escucha de los mensajes de los campos de edición, también indicamos que el primer campo de edición depende ahora del estado del primer checkbox:
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento del clic en la etiqueta de texto if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Si el clic ha sido en el primer checkbox if(lparam==m_checkbox1.Id()) { //--- Establecer el estado para el segundo checkbox y el primer campo de edición m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()); m_spin_edit1.SpinEditState(m_checkbox1.CheckButtonState()); } } //--- Evento del fin de la entrada del valor en el campo de edición if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } //--- Eventos del clic en los botones conmutadores del campo de edición if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC || id==CHARTEVENT_CUSTOM+ON_CLICK_DEC) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } }
Compile el programa y cárguelo en el gráfico. En la captura de pantalla se muestra el aspecto de la interfaz gráfica de la aplicación en este momento:
Fig. 4. Prueba del control “Campo de edición”.
Otros controles con las casillas de verificación
Al principio del artículo hemos mencionado que aparte de los controles “casilla de verificación” o (“checkbox”) y “campo de edición” serán considerados otros dos controles: “campo de edición con checkbox” y “lista combinada (combobox) con checkbox”. El campo de edición con checkbox es la versión ampliada de la clase CSpinEdit completada con los campos y métodos de la clase CCheckBox, que han sido considerados en este artículo.
Fig. 5. Partes integrantes del control “Campo de edición con checkbox”.
Combobox con checkbox es la combinación similar de las clases CComboBox y CCheckBox:
Fig. 6. Partes integrantes del control “Combobox con checkbox”.
Puede encontrar la implementación de las clases CCheckBoxEdit (campo de edición con checkbox) y CCheckComboBox (combobox con checkbox) en los archivos adjuntos al artículo para su estudio independiente. Puesto que en el control tipo CCheckComboBox contiene la lista desplegable, hay que introducir en el archivo WndContainer.mqh las adiciones correspondientes, igual que ha sido hecho con otros controles que contienen las partes desplegables. En este caso, hay que hacer que el puntero a la lista desplegable se encuentre en el array privado de los punteros m_drop_lists[]. Puede encontrar la descripción detallada de cómo hacerlo en el artículo Interfaces gráficas V - Control “Lista combinada” (Capítulo 3).
Como ejemplo, completaremos la aplicación de prueba con estos controles para que pueda ver cómo funciona eso. Añadiremos dos checkbox tipo CCheckBox uno de CCheckBoxEdit y CCheckComboBox. La disponibilidad del control tipo CCheckBoxEdit va a depender del estado del tercer checkbox, la disponibilidad del control tipo CCheckComboBox va a depender del estado del cuarto checkbox.
Fig. 7. Prueba de controles de tipos combinados.
Conclusión
En este artículo hemos desarrollado los controles bastante comunes que se encuentran a menudo en muchas interfaces gráficas en diferentes entornos: “checkbox”, “campo de edición”, “campo de edición con checkbox” y “combobox con checkbox”. En el segundo capítulo de la sexta parte nos ocuparemos del desarrollo de los controles “Slider” y “Slider doble”.
- Interfaces gráficas VI: Controles “Casilla de verificación”, “Campo de edición” y sus tipos combinados (Capítulo 1)
- Interfaces gráficas VI: Controles “Slider” y “Slider doble” (Capítulo 2)
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2466
- 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