Interfaces gráficas VI: Controles "Slider" y "Slider doble" (Capítulo 2)
Anatoli Kazharski | 30 junio, 2016
Índice
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.
En el artículo anterior nuestra librería ha sido completada con cuatro controles que se encuentran con bastante frecuencia en las interfaces gráficas: se trata de “checkbox”, “campo de edición”, “campo de edición con checkbox” y “combobox con checkbox”. El segundo capítulo de la sexta parte estará dedicado a los controles como Slider y Slider doble.
Control “Slider”
Un slider es una variante del control tipo “campo de edición”, que contiene un rango limitado con valores máximos y mínimos. A diferencia del control “campo de edición”, que hemos considerado antes, el slider no tiene botones conmutadores para cambiar el valor en el campo de edición. En vez de eso, aquí se utiliza una banda y un deslizador que se mueve en sus límites. Este control conviene para los casos cuando no es necesario indicar un valor preciso. Es decir, será suficiente especificar un valor aproximado dentro de un rango conocido. A pesar de eso, se guarda la posibilidad de introducir un valor con precisión en el campo de edición.
Vamos a utilizar seis objetos gráficos para diseñar este control. Son los siguientes:
- Fondo
- Título (etiqueta de texto)
- Campo de edición
- Banda del slider
- Deslizador del slider
- Indicador del slider
Fig. 1. Partes integrantes del control “Slider”.
Ahora vamos a ver cómo está organizada la clase de este control.
Desarrollo de la clase para la creación del control “Slider”
Creamos el archivo Slider.mqh y lo incluimos en el archivo WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Slider.mqh"
En el archivo Slider.mqh, creamos la clase CSlider con el conjunto estándar de los métodos que deben estar presentes en cada control de nuestra librería. Aparte de los archivos de inclusión Element.mqh y Window.mqh, vamos a incluir también el archivo SeparateLine.mqh con la clase CSeparateLine para crear una línea separadora. Antes, ya hemos considerado esta clase (CSeparateLine) en el artículo Interfaces gráficas II: Controles “Línea separadora” y “Menú contextual” (Capítulo 2), por eso no vamos a volver a analizarla al detalle. Lo único que quiero recordar es que si el alto se establece más de dos píxeles, entre dos líneas trazadas aparece un espacio vacío. Visualmente, eso parece a una cavidad que nos va muy bien para la creación de la banda del slider (raja o ranura) dentro de la cual va a desplazarse el deslizador.
//+------------------------------------------------------------------+ //| Slider.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "SeparateLine.mqh" //+------------------------------------------------------------------+ //| Clase para crear el slider con el campo de edición | //+------------------------------------------------------------------+ class CSlider : public CElement { private: //--- Puntero al formulario al que está adjuntado el control CWindow *m_wnd; public: CSlider(void); ~CSlider(void); //--- public: //--- 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 el color virtual void ResetColors(void); };
Vamos a dar al usuario de la librería la posibilidad de configurar las propiedades de todos los objetos que van a formar parte del control “Slider”. Son las siguientes propiedade:
- Color del fondo del control
- Texto de la descripción del slider
- Colores de las etiquetas de texto en diferentes estados
- Valor actual en el campo de edición
- Tamaños del campo de edición
- 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
- Tamaño de la ranura por el eje Y (alto)
- Colores de las líneas de la ranura
- Colores del indicador del slider en diferentes estados
- Tamaños del deslizador del slider
- Colores del deslizador del slider
- Prioridades para el clic izquierdo del ratón
El código de abajo contiene los campos y los métodos de la clase de las propiedades de los objetos del control mencionadas:
class CSlider : public CElement { private: //Color del fondo general color m_area_color; //--- Texto para la descripción del slider string m_label_text; //--- Colores de la etiqueta de texto en diferentes estados 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; //--- Colores del campo de edición en diferentes estados color m_edit_color; color m_edit_color_locked; //--- Colores del texto del campo de edición en diferentes estados color m_edit_text_color; color m_edit_text_color_locked; //--- 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[]; //--- Tamaño de la ranura int m_slot_y_size; //--- Colores de la ranura color m_slot_line_dark_color; color m_slot_line_light_color; //--- Colores del indicador en diferentes estados color m_slot_indicator_color; color m_slot_indicator_color_locked; //--- Tamaños del deslizador del slider int m_thumb_x_size; int m_thumb_y_size; //--- Colores del deslizador del slider color m_thumb_color; color m_thumb_color_hover; color m_thumb_color_locked; color m_thumb_color_pressed; //--- Prioridades para el clic izquierdo del ratón int m_zorder; int m_area_zorder; int m_edit_zorder; //--- public: //--- (1) Color del fondo, (2) colores de la etiqueta de texto void AreaColor(const color clr) { m_area_color=clr; } 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; } //--- Tamaños del (1) campo de edición y de (2) la ranura 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 SlotYSize(const int y_size) { m_slot_y_size=y_size; } //--- 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; } //--- 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; } //--- Color (1) oscuro y (2) claro de la línea separadora (de la ranura) void SlotLineDarkColor(const color clr) { m_slot_line_dark_color=clr; } void SlotLineLightColor(const color clr) { m_slot_line_light_color=clr; } //--- Colores del indicador del slider en diferentes estados void SlotIndicatorColor(const color clr) { m_slot_indicator_color=clr; } void SlotIndicatorColorLocked(const color clr) { m_slot_indicator_color_locked=clr; } //--- Tamaños del deslizador del slider void ThumbXSize(const int x_size) { m_thumb_x_size=x_size; } void ThumbYSize(const int y_size) { m_thumb_y_size=y_size; } //--- Colores del deslizador del slider void ThumbColor(const color clr) { m_thumb_color=clr; } void ThumbColorHover(const color clr) { m_thumb_color_hover=clr; } void ThumbColorLocked(const color clr) { m_thumb_color_locked=clr; } void ThumbColorPressed(const color clr) { m_thumb_color_pressed=clr; } };
Las propiedades desde la lista anterior se refieren principalmente al color y tamaños de los objetos del control. Las propiedades que se refieren al valor y al rango en el campo de edición del slider mostraremos en un grupo separado. Éstas son las siguientes:
- Valor mínimo
- Valor máximo
- Paso para el cambio del valor en el campo de edición
- Modo de alineación del texto
- Número de dígitos después de la coma
class CSlider : public CElement { private: //--- Valor (1) mínimo y (2) máximo, (3) paso para el cambio del valor double m_min_value; double m_max_value; double m_step_value; //--- Número de dígitos después de la coma int m_digits; //--- Modo de alineación del texto ENUM_ALIGN_MODE m_align_mode; //--- public: //--- 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 cambio 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 obtener el valor actual, así como para ajustar y establecer un valor nuevo en el campo de edición, van a utilizarse los métodos CSlider::GetValue(), CSlider::SetValue() y CSlider::ChangeValue():
class CSlider : 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); //--- Cambio del valor en el campo de edición void ChangeValue(const double value); }; //+------------------------------------------------------------------+ //| Establecer el valor actual | //+------------------------------------------------------------------+ bool CSlider::SetValue(const 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) corrected_value=m_min_value; if(corrected_value>=m_max_value) corrected_value=m_max_value; //--- Si el valor ha sido cambiado if(m_edit_value!=corrected_value) { m_edit_value=corrected_value; return(true); } //--- Valor sin cambios return(false); } //+------------------------------------------------------------------+ //| Cambio del valor en el campo de edición | //+------------------------------------------------------------------+ void CSlider::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)); }
Cuando el deslizador se mueve, el valor en el campo de edición debe calcularse en relación a la coordenada X. Si el valor se introduce en el campo de edición manualmente, la coordenada X del deslizador debe calcularse en relación al nuevo valor en el campo de edición. En otras palabras, a la hora de desarrollar el control hay que prever la posibilidad de conversiones inversas de los valores de estas variables.
Para el cálculo correcto, vamos a necesitar campos auxiliares (variables) de la clase que van a utilizarse en los cálculos. Los valores de estas variables deben calcularse sólo una vez, inmediatamente después de la creación del control. Abajo se muestran las descripciones de estas variables:
- Número de píxeles en el área de trabajo (m_pixels_total).
- Número de pasos en el rango de valores del área de trabajo (m_value_steps_total).
- Tamaño del paso respecto al ancho del área de trabajo (m_position_step).
Para el cálculo de los valores de estas variables escribiremos el método CSlider::CalculateCoefficients():
class CSlider : public CElement { private: //--- Número de píxeles en el área de trabajo int m_pixels_total; //--- Número de pasos en el rango de valores del área de trabajo int m_value_steps_total; //--- Tamaño del paso respecto al ancho del área de trabajo double m_position_step; //--- private: //--- Cálculo de valores (pasos y coeficientes) bool CalculateCoefficients(void); }; //+------------------------------------------------------------------+ //| Cálculo de valores (pasos y coeficientes) | //+------------------------------------------------------------------+ bool CSlider::CalculateCoefficients(void) { //--- Salir si el ancho del control es menos que el ancho del deslizador del slider if(CElement::XSize()<m_thumb_x_size) return(false); //--- Número de píxeles en el área de trabajo m_pixels_total=CElement::XSize()-m_thumb_x_size; //--- Número de pasos en el rango de valores del área de trabajo m_value_steps_total=int((m_max_value-m_min_value)/m_step_value); //--- Tamaño del paso respecto al ancho del área de trabajo m_position_step=m_step_value*(double(m_value_steps_total)/double(m_pixels_total)); return(true); }
Ahora podemos utilizar los valores de las variables arriba mencionadas para el cálculo de la coordenada X del deslizador en relación al valor en el campo de edición, y viceversa. Para eso escribiremos dos métodos separados: CSlider::CalculateThumbX() y CSlider::CalculateThumbPos().
En el método CSlider::CalculateThumbX(), primero se calculan los valores de la variable local auxiliar (neg_range) para la corrección, en caso si el valor del límite mínimo es negativo. Luego se calcula la coordenada X para el deslizador del slider. Después de eso, si la banda del slider ha sido excedida, el valor se ajusta. Al final del método, al deslizador del slider se le asigna un valor nuevo de la coordenada X y el margen desde el punto extremos del formulario al que está adjuntado el control vuelve a calcularse.
Al principio del método CSlider::CalculateThumbPos(), obtenemos la posición del deslizador del slider en el rango de valores. Luego, se realiza la corrección en caso si el valor del límite mínimo es negativo y el valor de la variable m_current_pos_x es correcto. Luego, si el área de trabajo ha sido excedida, se realiza la corrección correspondiente del valor.
class CSlider : public CElement { private: //--- Posición actual del deslizador del slider: (1) valor, (2) voordinada X double m_current_pos; double m_current_pos_x; //--- private: //--- Cálculo de la coordenada X del deslizador del slider void CalculateThumbX(void); //--- Cambia la posición del slider en relación al valor actual void CalculateThumbPos(void); }; //+------------------------------------------------------------------+ //| Cálculo de la coordenada X del deslizador del slider | //+------------------------------------------------------------------+ void CSlider::CalculateThumbX(void) { //--- Ajuste tomando en cuenta que el valor mínimo puede ser negativo double neg_range=(m_min_value<0)? ::fabs(m_min_value/m_position_step) : 0; //--- Calculamos la coordenada X del deslizador del slider m_current_pos_x=m_area.X()+(m_edit_value/m_position_step)+neg_range; //--- Si salimos fuera del área de trabajo hacia la izquierda if(m_current_pos_x<m_area.X()) m_current_pos_x=m_area.X(); //--- Si salimos fuera del área de trabajo hacia la derecha if(m_current_pos_x+m_thumb.XSize()>m_area.X2()) m_current_pos_x=m_area.X2()-m_thumb.XSize(); //--- Guardamos y establecemos la nueva coordenada X m_thumb.X(int(m_current_pos_x)); m_thumb.X_Distance(int(m_current_pos_x)); m_thumb.XGap(m_thumb.X()-m_wnd.X()); } //+------------------------------------------------------------------+ //| Cálculo de la posición del deslizador del slider en el rango de valores | //+------------------------------------------------------------------+ void CSlider::CalculateThumbPos(void) { //--- Obtenemos el número de la posición del deslizador del slider m_current_pos=(m_thumb.X()-m_area.X())*m_position_step; //--- Ajuste tomando en cuenta que el valor mínimo puede ser negativo if(m_min_value<0 && m_current_pos_x!=WRONG_VALUE) m_current_pos+=int(m_min_value); //--- Comprobar la superación del rango del área de trabajo hacia la derecha/izquierda if(m_thumb.X2()>=m_area.X2()) m_current_pos=int(m_max_value); if(m_thumb.X()<=m_area.X()) m_current_pos=int(m_min_value); }
Durante el desplazamiento del deslizador del slider, hay que calcular y actualizar el ancho del indicador del slider. El lado derecho del indicador tiene que “estar anclado” al deslizador. Para eso escribiremos el método CSlider::UpdateIndicator():
class CSlider : public CElement { private: //--- Actualización del indicador del slider void UpdateIndicator(void); }; //+------------------------------------------------------------------+ //| Actualización del indicador del slider | //+------------------------------------------------------------------+ void CSlider::UpdateIndicator(void) { //--- Calculamos el tamaño int x_size=m_thumb.X()-m_indicator.X(); //--- Corrección en caso de valores inadmisibles if(x_size<=0) x_size=1; //--- Establecer el tamaño nuevo m_indicator.X_Size(x_size); }
Para crear el control “Slider”, vamos a necesitar seis métodos privados (private) y un método principal público (public):
class CSlider : public CElement { public: //--- Métodos para crear el control bool CreateSlider(const long chart_id,const int subwin,const string text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabel(void); bool CreateEdit(void); bool CreateSlot(void); bool CreateIndicator(void); bool CreateThumb(void); };
Aquí vamos a mostrar sólo el código del método CSlider::CreateThumb(), porque todos los métodos para los cálculos considerados anteriormente van a llamarse precisamente en este método por primera vez. Todos los demás métodos de la creación de los objetos del control no tienen nada distinto de los que hemos hablado antes en otros artículos de la serie.
//+------------------------------------------------------------------+ //| Crea el deslizador del slider | //+------------------------------------------------------------------+ bool CSlider::CreateThumb(void) { //--- Formación del nombre del objeto string name=CElement::ProgramName()+"_slider_thumb_"+(string)CElement::Id(); //--- Coordenadas int x=CElement::X(); int y=m_slot.Y()-((m_thumb_y_size-m_slot_y_size)/2); //--- Establecemos el objeto if(!m_thumb.Create(m_chart_id,name,m_subwin,x,y,m_thumb_x_size,m_thumb_y_size)) return(false); //--- Establecemos las propiedades m_thumb.Color(m_thumb_color); m_thumb.BackColor(m_thumb_color); m_thumb.BorderType(BORDER_FLAT); m_thumb.Corner(m_corner); m_thumb.Selectable(false); m_thumb.Z_Order(m_zorder); m_thumb.Tooltip("\n"); //--- Guardamos los tamaños (en el objeto) m_thumb.XSize(m_thumb.X_Size()); m_thumb.YSize(m_thumb.Y_Size()); //--- Guardamos las coordenadas m_thumb.X(x); m_thumb.Y(y); //--- Márgenes desde el punto extremo m_thumb.XGap(x-m_wnd.X()); m_thumb.YGap(y-m_wnd.Y()); //--- Cálculo de valores de las variables auxiliares CalculateCoefficients(); //--- Cálculo de la coordenada X del deslizador en relación al valor actual en el campo de edición CalculateThumbX(); //--- Cálculo de la posición del deslizador del slider en el rango de valores CalculateThumbPos(); //--- Обновляем индикатор слайдера UpdateIndicator(); //--- Guardamos el puntero del objeto CElement::AddToArray(m_thumb); return(true); }
Los métodos para el desplazamiento del deslizador del slider prácticamente no se diferencian en nada de los métodos homónimos en las clases CScroll и CScrollH, que han sido considerados detalladamente en el artículo Interfaces gráficas V: Barra de desplazamiento vertical y horizontal (Capítulo 1). Por eso, no vamos a mostrar su código aquí. Nos limitaremos sólo con su declaración el cuerpo de la clase CSlider.
class CSlider : public CElement { private: //--- Estado del botón del ratón (pulsado/suelto) ENUM_THUMB_MOUSE_STATE m_clamping_area_mouse; //--- Para identificar el modo de desplazamiento del deslizador del slider bool m_slider_thumb_state; //--- Variables relacionadas con el desplazamiento del deslizador int m_slider_size_fixing; int m_slider_point_fixing; //--- private: //--- Proceso del desplazamiento del deslizador del slider void OnDragThumb(const int x); //--- Actualización de la posición del deslizador del slider void UpdateThumb(const int new_x_point); //--- Comprueba el estado del botón del ratón void CheckMouseButtonState(void); //--- Poner a cero las variables relacionadas con el desplazamiento del deslizador del slider void ZeroThumbVariables(void); };
Para el procesamiento de la entrada del valor en el campo de edición, vamos a crear el método CSlider::OnEndEdit() que va a llamarse en el manejador de eventos del control CSlider::OnEvent().
En el inicio del método CSlider::OnEndEdit() se comprueba por el nombre del objeto si ha sido introducido algún valor en el campo de este slider. Luego obtenemos el valor actual en el campo de edición. A continuación, se realiza la comprobación y la corrección obligatoria de la entrada del valor inaceptable, se calcula la coordenada X del deslizador del slider y la posición en el rango de valores. Después de eso se actualiza el indicador del slider, y al final del método hay que enviar el mensaje con (1) el identificador del evento personalizado ON_END_EDIT, (2) identificador del control, (3) índice del control y (4) la descripción de la etiqueta de texto.
class CSlider : public CElement { private: //--- Procesamiento de la entrada del valor en el campo de edición bool OnEndEdit(const string object_name); }; //+------------------------------------------------------------------+ //| Procesamiento de la entrada del valor en el campo de edición | //+------------------------------------------------------------------+ bool CSlider::OnEndEdit(const string object_name) { //--- Salimos si el nombre del objeto no coincide if(object_name!=m_edit.Name()) return(false); //--- Obtenemos el valor recién introducido double entered_value=::StringToDouble(m_edit.Description()); //--- Comprobamos, ajustamos y guardamos el valor nuevo ChangeValue(entered_value); //--- Cálculo de la coordenada X del deslizador CalculateThumbX(); //--- Calculamos la posición dentro del rango de valores CalculateThumbPos(); //--- Actualizamos el indicador del slider UpdateIndicator(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
Hay que enviar el mismo mensaje personalizado después de finalizar el desplazamiento del deslizador para cambiar el valor en el campo de edición. El mejor método para eso es CSlider::ZeroThumbVariables() que se invoca dentro del método CSlider::CheckMouseButtonState(), donde se monitorea el área del clic izquierdo del ratón. Todo está organizado de tal manera que la llamada al método CSlider::ZeroThumbVariables() ya supone que el botón izquierdo está suelto. Si antes de eso ya estaba pulsado sobre el área del deslizador, eso significa que el desplazamiento del deslizador ya está finalizado y ahora se puede enviar el mensaje sobre el hecho de que el valor en el campo de edición ha sido cambiado.
//+------------------------------------------------------------------+ //| Poner a cero las variables relacionadas con el desplazamiento de la barra de desplazamiento | //+------------------------------------------------------------------+ void CSlider::ZeroThumbVariables(void) { //--- Si hemos entrado aquí, significa que el botón izquierdo del ratón está suelto. // Si el botón izquierdo ha sido pulsado sobre el deslizador del slider... if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE) { //--- ... enviamos el mensaje que el cambio del valor en el campo de edición mediante el deslizador ha sido finalizado ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); } //--- m_slider_size_fixing =0; m_clamping_area_mouse =THUMB_NOT_PRESSED; //--- Si el identificador del control coincide con el identificador activador, //--- desbloqueamos el formulario y reseteamos el identificador del elemento activo if(CElement::Id()==m_wnd.IdActivatedElement()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } }
En este caso, el código completo del manejador CSlider::OnEvent() será el siguiente:
//+------------------------------------------------------------------+ //| Manejador del evento del gráfico | //+------------------------------------------------------------------+ void CSlider::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del desplazamiento del cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Salir si el control está ocultado if(!CElement::IsVisible()) return; //--- Coordenadas y el estado del botón izquierdo del ratón int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); //--- Comprobación del foco sobre los controles CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_thumb.MouseFocus(x>m_thumb.X() && x<m_thumb.X2() && y>m_thumb.Y() && y<m_thumb.Y2()); //--- Salir si el control está bloqueado if(!m_slider_state) return; //--- Comprobamos y recordamos el estado del botón del ratón CheckMouseButtonState(); //--- Cambiamos el color del deslizador ChangeThumbColor(); //--- Si el control se pasa a la banda del slider, determinamos su posición if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE) { //--- Desplazamiento del deslizador del slider OnDragThumb(x); //--- Cálculo de la posición del deslizador del slider en el rango de valores CalculateThumbPos(); //--- Establecer el valor nuevo en el campo de edición ChangeValue(m_current_pos); //--- Actualizamos el indicador del slider UpdateIndicator(); return; } } //--- Procesamiento del evento del cambio del valor en el campo de edición if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- Procesamiento de la entrada del valor if(OnEndEdit(sparam)) return; } }
Todos los métodos para la creación y el manejo del control de la interfaz “Slider” están implementados. Ahora vamos a probarlo en la aplicación MQL que hemos utilizado en el artículo anterior.
Prueba del control “Slider”
En el artículo anterior, en la aplicación de prueba hemos creado cuatro casillas de verificación (checkbox) que controlan la disponibilidad de otros controles. Ahora vamos a añadir el control “Slider” y otro (quinto) checkbox para controlar su disponibilidad. Para eso, declaramos las instancias de las clases CCheckBox y CSlider en la clase personalizada CProgram, aí como los métodos con las márgenes desde el punto extremo del formulario al que van a adjuntarse estos controles.
//+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Casillas de verificación (checkbox) CCheckBox m_checkbox5; //--- Sliders CSlider m_slider1; //--- private: //--- Casillas de verificación (checkbox) #define CHECKBOX5_GAP_X (7) #define CHECKBOX5_GAP_Y (200) bool CreateCheckBox5(const string text); //--- Sliders #define SLIDER1_GAP_X (32) #define SLIDER1_GAP_Y (225) bool CreateSlider1(const string text); };
El código para la creación de los checkboxs ya ha sido considerado en el artículo anterior, por eso pasaremos directamente al método de la creación del control “Slider” CProgram::CreateSlider1(). Usamos los métodos CSlider::MinValue() y CSlider::MaxValue() para establecer el rango de valores de -1 a 1. Establecemos el paso con la precisión de 8 dígitos (0,00000001). La disponibilidad del control va a depender del estado actual del quinto checkbox
//+------------------------------------------------------------------+ //| Crea el slider 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSlider1(const string text) { //--- Guardamos el puntero a la ventana m_slider1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+SLIDER1_GAP_X; int y=m_window1.Y()+SLIDER1_GAP_Y; //--- Valor double v=(m_slider1.GetValue()==WRONG_VALUE) ? 0.84615385 : m_slider1.GetValue(); //--- Establecemos las propiedades antes de la creación m_slider1.XSize(264); m_slider1.YSize(40); m_slider1.EditXSize(87); m_slider1.MaxValue(1); m_slider1.StepValue(0.00000001); m_slider1.MinValue(-1); m_slider1.SetDigits(8); m_slider1.SetValue(v); m_slider1.AreaColor(clrWhiteSmoke); m_slider1.LabelColor(clrBlack); m_slider1.LabelColorLocked(clrSilver); m_slider1.EditColorLocked(clrWhiteSmoke); m_slider1.EditBorderColor(clrSilver); m_slider1.EditBorderColorLocked(clrSilver); m_slider1.EditTextColorLocked(clrSilver); m_slider1.SlotLineDarkColor(clrSilver); m_slider1.SlotLineLightColor(clrWhite); m_slider1.SlotYSize(4); m_slider1.ThumbColorLocked(clrLightGray); m_slider1.ThumbColorPressed(clrSilver); m_slider1.SlotIndicatorColor(C'85,170,255'); m_slider1.SlotIndicatorColorLocked(clrLightGray); //--- Creamos el control if(!m_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y)) return(false); La disponibilidad va a depender del estado actual del quinto checkbox m_slider1.SliderState(m_checkbox5.CheckButtonState()); //--- Añadimos el objeto al array común de los grupos de objetos CWndContainer::AddToElementsArray(0,m_slider1); return(true); }
La llamada a los nuevos métodos de creación de los controles debe realizarse en el método principal de 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(!CreateCheckBox5("Checkbox 5")) return(false); //--- Sliders if(!CreateSlider1("Slider 1:")) return(false); //--- Redibujar el gráfico m_chart.Redraw(); return(true); }
Nosotros vamos a monitorear el cambio del estado del quinto checkbox para el control de la disponibilidad del slider en el manejador de eventos de la aplicación CProgram::OnEvent(). El evento con el identificador personalizado ON_END_EDIT va a indicar cuando se cambia el valor en el campo de edición.
//+------------------------------------------------------------------+ //| 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 quinto checkbox if(lparam==m_checkbox5.Id()) { //--- Establecer el estado para el primer slider m_slider1.SliderState(m_checkbox5.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); } }
Ahora se puede compilar el programa y cargarlo en el gráfico. Por favor, intente interactuar con los controles de la interfaz gráfica. Si ha hecho todo bien, en el gráfico aparece el resultado que se muestra a continuación:
Fig. 2. Prueba del control “Slider”.
Control “Slider doble”
El control “Slider doble” se diferencia del slider común en que permite seleccionar el rango de valores dentro del rango establecido por el usuario. Para eso en la banda del slider hay dos deslizadores. El deslizador izquierdo puede desplazarse desde el lado izquierdo de la banda del slider hasta el deslizador derecho. El deslizador derecho, respectivamente, puede desplazarse desde el lado derecho de la banda del slider hasta el deslizador izquierdo. Además. aquí hay dos campos de edición en los que se muestran los valores respecto a las posiciones de los deslizadores en la banda del slider. Usted puede introducir los valores en estos campos manualmente, al mismo tiempo van a cambiarse las posiciones de los deslizadores.
Este control va a componerse de ocho objetos primitivos. Son los siguientes:
- Fondo
- Título (etiqueta de texto)
- Campo de edición izquierdo
- Campo de edición derecho
- Banda del slider
- Deslizador izquierdo del slider
- Deslizador dereho del slider
- Indicador del slider
Fig. 3. Partes integrantes del control “Slider doble”.
A continuación vamos a ver cómo está organizado el control “Slider doble” y en qué se diferencia del slider habitual.
Desarrollo de la clase para la creación del control “Slider doble”
Incluimos el archivo DualSlider.mqh con la clase del control (CDualSlider) en el archivo WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "DualSlider.mqh"
En cuanto a las propiedades del control, la clase CDualSlider es la copia absoluta de la clase CSlider. La diferencia consiste en que era necesario implementar los campos y los métodos separados para el campo de edición izquierdo y derecho y para los deslizadores del slider. Los cambios y las deferencias en estos métodos son insignificantes, por eso no vamos a presentar su código aquí. Se puede verlos en los archivos adjuntos al artículo.
class CDualSlider : public CElement { private: //--- Valores actuales en los campos de edición (izquierdo y derecho) double m_left_edit_value; double m_right_edit_value; //--- Posición actual de los deslizadores del slider (izquierdo y derecho) double m_left_current_pos; double m_left_current_pos_x; double m_right_current_pos; double m_right_current_pos_x; //--- Estado del botón del ratón (pulsado/suelto) para los deslizadores del slider (izquierdo y derecho) ENUM_THUMB_MOUSE_STATE m_clamping_mouse_left_thumb; ENUM_THUMB_MOUSE_STATE m_clamping_mouse_right_thumb; //--- public: //--- Devolver y establecer el valor en los campos de edición (izquierdo y derecho) double GetLeftValue(void) const { return(m_left_edit_value); } double GetRightValue(void) const { return(m_right_edit_value); } bool SetLeftValue(double value); bool SetRightValue(double value); //--- Cambio del valor en los campos de edición (izquierdo y derecho) void ChangeLeftValue(const double value); void ChangeRightValue(const double value); //--- private: //--- Proceso del desplazamiento del deslizador del slider (izquierdo y derecho) void OnDragLeftThumb(const int x); void OnDragRightThumb(const int x); //--- Actualización de la posición del deslizador del slider (izquierdo y derecho) void UpdateLeftThumb(const int new_x_point); void UpdateRightThumb(const int new_x_point); //--- Comprueba el estado del botón del ratón sobre el deslizador del slider void CheckMouseOnLeftThumb(void); void CheckMouseOnRightThumb(void); //--- Cálculo de la coordenada X del deslizador del slider (izquierdo y derecho) void CalculateLeftThumbX(void); void CalculateRightThumbX(void); //--- Cambia la posición del deslizador izquierdo respecto al valor(izquierdo y derecho) void CalculateLeftThumbPos(void); void CalculateRightThumbPos(void); };
Vamos a mostrar aquí el código de los métodos donde se consideran sólo dos campos de edición y dos deslizadores del slider. Por ejemplo, el método CDualSlider::OnEndEdit():
class CDualSlider : public CElement { private: //--- Procesamiento de la entrada del valor en el campo de edición bool OnEndEdit(const string object_name); }; //+------------------------------------------------------------------+ //| Fin de la entrada del valos | //+------------------------------------------------------------------+ bool CDualSlider::OnEndEdit(const string object_name) { //--- Si el valor se introduce en el campo izquierdo if(object_name==m_left_edit.Name()) { //--- Obtenemos el valor recién introducido double entered_value=::StringToDouble(m_left_edit.Description()); //--- Comprobamos, ajustamos y guardamos el valor nuevo ChangeLeftValue(entered_value); //--- Cálculo de la coordenada X del deslizador CalculateLeftThumbX(); //--- Actualización de la posición del deslizador del slider UpdateLeftThumb(m_left_thumb.X()); //--- Calculamos la posición dentro del rango de valores CalculateLeftThumbPos(); //--- Comprobamos, ajustamos y guardamos el valor nuevo ChangeLeftValue(m_left_current_pos); //--- Actualizamos el indicador del slider UpdateIndicator(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); } //--- Si el valor se introduce en el campo derecho if(object_name==m_right_edit.Name()) { //--- Obtenemos el valor recién introducido double entered_value=::StringToDouble(m_right_edit.Description()); //--- Comprobamos, ajustamos y guardamos el valor nuevo ChangeRightValue(entered_value); //--- Cálculo de la coordenada X del deslizador CalculateRightThumbX(); //--- Actualización de la posición del deslizador del slider UpdateRightThumb(m_right_thumb.X()); //--- Calculamos la posición dentro del rango de valores CalculateRightThumbPos(); //--- Comprobamos, ajustamos y guardamos el valor nuevo ChangeRightValue(m_right_current_pos); //--- Actualizamos el indicador del slider UpdateIndicator(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); } //--- return(false); }
Lo mismo se refiere al desplazamiento del deslizador izquierdo y derecho. Para cada uno de ellos, en el manejador de eventos CDualSlider::OnEvent() hay sus comprobaciones y bloques del código:
//+------------------------------------------------------------------+ //| Manejador del evento del gráfico | //+------------------------------------------------------------------+ void CDualSlider::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del desplazamiento del cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Salir si el control está ocultado if(!CElement::IsVisible()) return; //--- Coordenadas y el estado del botón izquierdo del ratón int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); //--- Comprobación del foco sobre los controles CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_left_thumb.MouseFocus(x>m_left_thumb.X() && x<m_left_thumb.X2() && y>m_left_thumb.Y() && y<m_left_thumb.Y2()); m_right_thumb.MouseFocus(x>m_right_thumb.X() && x<m_right_thumb.X2() && y>m_right_thumb.Y() && y<m_right_thumb.Y2()); //--- Salir si el control está bloqueado if(!m_slider_state) return; //--- Comprobamos y recordamos el estado del botón del ratón CheckMouseOnLeftThumb(); CheckMouseOnRightThumb(); //--- Cambiamos el color del deslizador ChangeThumbColor(); //--- Si el control se pasa a la banda del slider (deslizador izquierdo) if(m_clamping_mouse_left_thumb==THUMB_PRESSED_INSIDE) { //--- Desplazamiento del deslizador del slider OnDragLeftThumb(x); //--- Cálculo de la posición del deslizador del slider en el rango de valores CalculateLeftThumbPos(); //--- Establecer el valor nuevo en el campo de edición ChangeLeftValue(m_left_current_pos); //--- Actualizamos el indicador del slider UpdateIndicator(); return; } //--- Si el control se pasa a la barra de desplazamiento (deslizador derecho) if(m_clamping_mouse_right_thumb==THUMB_PRESSED_INSIDE) { //--- Desplazamiento del deslizador del slider OnDragRightThumb(x); //--- Cálculo de la posición del deslizador del slider en el rango de valores CalculateRightThumbPos(); //--- Establecer el valor nuevo en el campo de edición ChangeRightValue(m_right_current_pos); //--- Actualizamos el indicador del slider UpdateIndicator(); return; } } }
Para analizar al detalle el código de la clase CDualSlider, por favor, descargue los archivos adjuntos.
Prueba del control “Slider doble”
Añadimos un control “Slider doble” a la interfaz gráfica de la aplicación de prueba. En la clase personalizada de la aplicación CProgram, hay que declarar la instancia de su clase (CDualSlider) y el método con las márgenes desde el punto extremo del formulario:
//+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Sliders CDualSlider m_dual_slider1; //--- private: //--- Sliders #define DUALSLIDER1_GAP_X (32) #define DUALSLIDER1_GAP_Y (275) bool CreateDualSlider1(const string text); };
Abajo se muestra el código del método CProgram::CreateDualSlider1(). Establecemos el rango de valores de -2000 a 1000. La disponibilidad del control va a depender del estado actual del quinto checkbox, como en el caso del slider simple que ha sido creado en este artículo.
//+------------------------------------------------------------------+ //| Crea el slider doble 1 | //+------------------------------------------------------------------+ bool CProgram::CreateDualSlider1(const string text) { //--- Guardamos el puntero a la ventana m_dual_slider1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+DUALSLIDER1_GAP_X; int y=m_window1.Y()+DUALSLIDER1_GAP_Y; //--- Valores double v1=(m_dual_slider1.GetLeftValue()==WRONG_VALUE) ? 0 : m_dual_slider1.GetLeftValue(); double v2=(m_dual_slider1.GetRightValue()==WRONG_VALUE) ? 500 : m_dual_slider1.GetRightValue(); //--- Establecemos las propiedades antes de la creación m_dual_slider1.XSize(264); m_dual_slider1.YSize(40); m_dual_slider1.EditXSize(87); m_dual_slider1.MaxValue(1000); m_dual_slider1.StepValue(1); m_dual_slider1.MinValue(-2000); m_dual_slider1.SetDigits(0); m_dual_slider1.SetLeftValue(v1); m_dual_slider1.SetRightValue(v2); m_dual_slider1.AreaColor(clrWhiteSmoke); m_dual_slider1.LabelColor(clrBlack); m_dual_slider1.LabelColorLocked(clrSilver); m_dual_slider1.EditColorLocked(clrWhiteSmoke); m_dual_slider1.EditBorderColor(clrSilver); m_dual_slider1.EditBorderColorLocked(clrSilver); m_dual_slider1.EditTextColorLocked(clrSilver); m_dual_slider1.SlotLineDarkColor(clrSilver); m_dual_slider1.SlotLineLightColor(clrWhite); m_dual_slider1.SlotYSize(4); m_dual_slider1.ThumbColorLocked(clrLightGray); m_dual_slider1.ThumbColorPressed(clrSilver); m_dual_slider1.SlotIndicatorColor(C'85,170,255'); m_dual_slider1.SlotIndicatorColorLocked(clrLightGray); //--- Creamos el control if(!m_dual_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y)) return(false); La disponibilidad va a depender del estado actual del quinto checkbox m_dual_slider1.SliderState(m_checkbox5.CheckButtonState()); //--- Añadimos el objeto al array común de los grupos de objetos CWndContainer::AddToElementsArray(0,m_dual_slider1); return(true); }
No olvidamos colocar la llamada al método del control nuevo en el método principal de la creación de la interfaz gráfica, tal como se muestra en la versión reducida del código de abajo.
//+------------------------------------------------------------------+ //| 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 //--- Sliders if(!CreateDualSlider1("Dual Slider 1:")) return(false); //--- Redibujar el gráfico m_chart.Redraw(); return(true); }
Ahora dos controles van a depender del estado actual del quinto checkbox:slider simple y slider doble.
//+------------------------------------------------------------------+ //| 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 quinto checkbox if(lparam==m_checkbox5.Id()) { //--- Establecer el estado para los sliders m_slider1.SliderState(m_checkbox5.CheckButtonState()); m_dual_slider1.SliderState(m_checkbox5.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); } }
Nos queda compilar el programa y cargarlo en el gráfico. En la captura de pantalla de abajo se puede ver el resultado de nuestro trabajo:
Fig. 4. Prueba del control “Slider doble”.
Conclusión
En la sexta parte de la serie hemos analizado seis 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)
- Slider
- Slider doble
En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema tiene el siguiente aspecto:
Fig. 5. Estructura de la librería en la fase actual del desarrollo.
En la siguiente (séptima) parte de la serie, vamos a enriquecer nuestra librería con tablas y pestañas.
Más abajo puede descargar el material de la sexta parte de la serie para poder probar cómo funciona todo eso. Si le surgen algunas preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.
Lista de artículos (capítulos) de la sexta parte:
- 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)