
Interfaces gráficas VIII: Control "Calendario" (Capítulo 1)
Índice
- Introducción
- Control “Calendario”
- Descripción de la estructura CDateTime
- Desarrollo de la clase CCalendar
- Manejadores de eventos del calendario
- Prueba del control “Calendario”
- Control “Calendario desplegable”
- 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 cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede 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 la octava parte de la serie nosotros vamos a considerar los controles compuestos (complejos):
- Calendario estático y desplegable
- Lista jerárquica (lista en forma de árbol)
- Explorador de archivos
En este artículo vamos a analizar las clases para la creación del calendario estático y desplegable, así como hablaremos de la estructura (struct) CDateTime de la librería estándar para el trabajo con fechas y horas, la que vamos a utilizar en nuestros desarrollos.
Control “Calendario”
El calendario es un sistema cíclico del cálculo de tiempo en forma de una tabla. Los controles deben estar presentes en la interfaz gráfica para facilitar al usuario la selección de la fecha necesaria en el calendario. Aparte de sus propios métodos de interacción con el usuario, en la clase del calendario se puede incluir también otros controles de la librería. Por ejemplo, vamos a incluir aquí las clases para la creación de (1) la lista combinada (combobox), (2) campo de edición y (3) botón.
Vamos a nombrar todas las partes integrantes del control “Calendario”.
- Fondo
- Botones para ir al siguiente mes o al anterior
- Control “Combobox” con la lista de los meses
- Campo de edición para introducir el año
- Array de las etiquetas de texto con nombres abreviados de los días de la semana
- Línea separadora
- Array bidimensional de las etiquetas de texto con las fechas del mes
- Botón para ir rápidamente a la fecha actual
Fig. 1. Partes integrantes del control “Calendario”.
Supongamos que en la aplicación MQL que desarrollamos, hay que seleccionar un rango de fechas, indicando para eso la fecha inicial y final. Para seleccionar el día, sólo hay que pinchar en uno de los elementos de la tabla de las fechas del mes. Para seleccionar el mes, habrá varias opciones: (1) botón para ir al mes anterior, (2) botón para ir al siguiente mes y (3) combobox con la lista de todos los meses del año. Se puede indicar el año en el campo de edición introduciendo el valor manualmente o usando conmutadores del control. Para ir rápidamente a la fecha actual, bastará con hacer clic en el botón “Today: YYYY.MM.DD” en la parte inferior del calendario.
Vamos a ver detalladamente cómo está organizada la estructura para trabajar con las fechas y horas.
Descripción de la estructura CDateTime
El archivo DateTime.mqh con la estructura CDateTime se encuentra en los directorios de los terminales de trading MetaTrader:
- MetaTrader 4: <carpeta de datos>\MQL4\Include\Tools
- MetaTrader 5: <carpeta de datos>\MQL5\Include\Tools
La estructura CDateTime es una estructura derivada (extensión) de la estructura base de sistema de la fecha y la hora MqlDateTime, que a su vez contiene ocho campos tipo int (para ver los ejemplos de su uso, consulte la documentación de referencia del lenguaje MQL):
struct MqlDateTime { int year; // año int mon; // mes int day; // día int hour; // hora int min; // minutos int sec; // segundos int day_of_week; // día de la semana (0-domingo, 1-lunes, ... ,6-sábado) int day_of_year; // número de orden dentro del año (1 de enero tiene el número 0) };
La descripción breve de los métodos de la estructura CDateTime sin la demostración del código se encuentra en el manual de referencia local (F1), en la sección Manual de referencia/Librería estándar/Clases para los paneles de control y cuadros de diálogo/CDateTime. A la hora de trabajar con esta estructura, se debe recordar que la enumeración de los meses se empieza con uno (1), y la enumeración de las semanas, con cero (0).
Usted puede analizar el código de los métodos de la estructura CDateTime por sí mismo, al abrir el archivo DateTime.mqh.
Desarrollo de la clase CCalendar
Creamos el archivo Calendar.mqh y lo incluimos en el archivo WndContainer.mqh), como todos los controles de nuestra librería.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Calendar.mqh"
Creamos la clase CCalendar con los métodos estándar para todos los controles de la librería en el archivo Calendar.mqh, así como incluimos los archivos que vamos a necesitar aquí para el desarrollo de este control:
//+------------------------------------------------------------------+ //| Calendar.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "SpinEdit.mqh" #include "ComboBox.mqh" #include "IconButton.mqh" #include <Tools\DateTime.mqh> //+------------------------------------------------------------------+ //| Clase para crear el calendario | //+------------------------------------------------------------------+ class CCalendar : public CElement { private: //--- Puntero al formulario al que está adjuntado el control CWindow *m_wnd; //--- public: CCalendar(void); ~CCalendar(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) {} };
Igual que para los demás controles de la interfaz de la librería, necesitamos tener la posibilidad de configurar la apariencia del calendario. Vamos a nombrar las propiedades referentes a la apariencia, que estarán disponibles para la configuración por el usuario.
- Color del fondo
- Color del marco del fondo
- Colores de los elementos del calendario (días del mes) en diferentes estados
- Colores de los marcos de los elementos del calendario en diferentes estados
- Colores del texto de los elementos del calendario en diferentes estados
- Color de la línea separadora
- Iconos de los botones (en estado activo/bloqueado) para ir al mes anterior/siguiente
El código de abajo contiene los nombres de los campos y los métodos de la clase CCalendar para establecer las propiedades de la apariencia del calendario antes de su creación:
class CCalendar : public CElement { private: //--- Color del fondo color m_area_color; //--- Color del marco del fondo color m_area_border_color; //--- Colores de los elementos del calendario (días del mes) en diferentes estados color m_item_back_color; color m_item_back_color_off; color m_item_back_color_hover; color m_item_back_color_selected; //--- Colores de los marcos de los elementos del calendario en diferentes estados color m_item_border_color; color m_item_border_color_hover; color m_item_border_color_selected; //--- Colores del texto de los elementos del calendario en diferentes estados color m_item_text_color; color m_item_text_color_off; color m_item_text_color_hover; //--- Color de la línea separadora color m_sepline_color; //--- Iconos de los botones (en estado activo/bloqueado) para ir al mes anterior/siguiente string m_left_arrow_file_on; string m_left_arrow_file_off; string m_right_arrow_file_on; string m_right_arrow_file_off; //--- public: //--- Establecer el color del (1) fondo, (2) marco del fondo y (3) color de la línea separadora void AreaBackColor(const color clr) { m_area_color=clr; } void AreaBorderColor(const color clr) { m_area_border_color=clr; } void SeparateLineColor(const color clr) { m_sepline_color=clr; } //--- Colores de los elementos del calendario (días del mes) en diferentes estados void ItemBackColor(const color clr) { m_item_back_color=clr; } void ItemBackColorOff(const color clr) { m_item_back_color_off=clr; } void ItemBackColorHover(const color clr) { m_item_back_color_hover=clr; } void ItemBackColorSelected(const color clr) { m_item_back_color_selected=clr; } //--- Colores de los marcos de los elementos del calendario en diferentes estados void ItemBorderColor(const color clr) { m_item_border_color=clr; } void ItemBorderColorHover(const color clr) { m_item_border_color_hover=clr; } void ItemBorderColorSelected(const color clr) { m_item_border_color_selected=clr; } //--- Colores del texto de los elementos del calendario en diferentes estados void ItemTextColor(const color clr) { m_item_text_color=clr; } void ItemTextColorOff(const color clr) { m_item_text_color_off=clr; } void ItemTextColorHover(const color clr) { m_item_text_color_hover=clr; } //--- Establecer los iconos de los botones (en estado activo/bloqueado) para ir al mes anterior/siguiente void LeftArrowFileOn(const string file_path) { m_left_arrow_file_on=file_path; } void LeftArrowFileOff(const string file_path) { m_left_arrow_file_off=file_path; } void RightArrowFileOn(const string file_path) { m_right_arrow_file_on=file_path; } void RightArrowFileOff(const string file_path) { m_right_arrow_file_off=file_path; } };
Para crear el control “Calendario”, vamos a necesitar nueve métodos privados (private) y un método público (public). Para visualizar los días de la semana y las fechas del mes, nos harán falta los arrays estáticos de objetos tipo CEdit.
La tabla de las fechas del mes va a componerse de 42 elementos. Eso será suficiente para meter el número máximo de las fechas del mes que se iguala a 31 días, tomando en cuenta el desplazamiento máximo del primer día del mes cuando éste cae en el domingo (en esta implementación el domingo es el séptimo día de la semana).
class CCalendar : public CElement { private: //--- Objetos y controles para crear el calendario CRectLabel m_area; CBmpLabel m_month_dec; CBmpLabel m_month_inc; CComboBox m_months; CSpinEdit m_years; CEdit m_days_week[7]; CRectLabel m_sep_line; CEdit m_days[42]; CIconButton m_button_today; //--- public: //--- Métodos para crear el calendario bool CreateCalendar(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateArea(void); bool CreateMonthLeftArrow(void); bool CreateMonthRightArrow(void); bool CreateMonthsList(void); bool CreateYearsSpinEdit(void); bool CreateDaysWeek(void); bool CreateSeparateLine(void); bool CreateDaysMonth(void); bool CreateButtonToday(void); };
En los casos cuando el calendario va a formar parte de algún otro control, puede que sea necesario acceder a los controles de los que se compone el calendario. Por eso, añadiremos a la clase los métodos que devuelven los punteros a los controles que se listan a continuación.
- Combobox (CComboBox)
- Lista del combobox (CListView)
- Barra de desplazamiento vertical de la lista (CScrollV)
- Campo de edición (CSpinEdit)
- Botón (CIconButton)
class CCalendar : public CElement { public: //--- (1) Obtener el puntero del combobox, // (2) obtener el puntero de la lista, (3) obtener el puntero de la barra de desplazamiento, // (4) obtener el puntero del campo de edición, (5) obtener el puntero del botón, CComboBox *GetComboBoxPointer(void) const { return(::GetPointer(m_months)); } CListView *GetListViewPointer(void) { return(m_months.GetListViewPointer()); } CScrollV *GetScrollVPointer(void) { return(m_months.GetScrollVPointer()); } CSpinEdit *GetSpinEditPointer(void) const { return(::GetPointer(m_years)); } CIconButton *GetIconButtonPointer(void) const { return(::GetPointer(m_button_today)); } };
Para trabajar con la fecha y la hora, nos harán falta tres instancias de la estructura CDateTime.
- Para la interacción con el usuario. Es la fecha que el usuario selecciona (marca en el calendario) personalmente.
- Fecha actual o local del ordenador del usuario. Esta fecha siempre estará marcada separadamente en el calendario.
- La instancia para los cálculos y comprobaciones. Va a utilizarse como contador en muchos métodos de la clase CCalendar.
class CCalendar : public CElement { private: //--- Instancias de la estructura para trabajar con las fechas y horas: CDateTime m_date; // fecha seleccionada por el usuario CDateTime m_today; // fecha actual (local en el ordenador del usuario) CDateTime m_temp_date; // instancia para los cálculos y comprobaciones };
La inicialización inicial de las estructuras de la hora va a realizarse en el constructor de la clase CCalendar. Establecemos la hora local del ordenador del usuario (marcado en amarillo en el código de abajo) en las estructuras de las instancias m_date y m_today
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CCalendar::CCalendar(void) : m_area_color(clrWhite), m_area_border_color(clrSilver), m_sepline_color(clrBlack), m_item_back_color(clrWhite), m_item_back_color_off(clrWhite), m_item_back_color_hover(C'235,245,255'), m_item_back_color_selected(C'193,218,255'), m_item_border_color(clrWhite), m_item_border_color_hover(C'160,220,255'), m_item_border_color_selected(C'85,170,255'), m_item_text_color(clrBlack), m_item_text_color_off(C'200,200,200'), m_item_text_color_hover(C'0,102,204'), m_left_arrow_file_on(""), m_left_arrow_file_off(""), m_right_arrow_file_on(""), m_right_arrow_file_off("") { //--- Guardamos el nombre de la clase del control en la clase base CElement::ClassName(CLASS_NAME); //--- Establecemos las prioridades para el clic izquierdo del ratón m_zorder =0; m_area_zorder =1; m_button_zorder =2; //--- Inicialización de las estructura de la hora m_date.DateTime(::TimeLocal()); m_today.DateTime(::TimeLocal()); }
Inmediatamente después de la instalación del calendario, hay que establecer los valores del mes y del año actual en los elementos de la tabla (días del mes). Para eso vamos a necesitar cuatro métodos.
1. El método CCalendar::OffsetFirstDayOfMonth(), a través del cual se puede determinar la diferencia (en días) desde el primer elemento de la tabla del calendario hasta el elemento del primer día del mes en curso. Al principio de este método, formamos la fecha en el formato string con la primera fecha del año y mes en curso. Luego, convertimos la cadena en el formato datetime, establecemos la fecha en la estructura para los cálculos. Si el resultado de la resta del uno del número actual del día de la semana es más o igual a cero, establecemos devolver el resultado, pero si es menos de cero (-1), devolver el valor 6. Al final del método, se realiza el desplazamiento a la diferencia obtenida (número de días).
class CCalendar : public CElement { private: //--- Determinar la diferencia desde el primer elemento de la tabla del calendario hasta el elemento del primer día del mes en curso int OffsetFirstDayOfMonth(void); }; //+------------------------------------------------------------------+ //| Determinar la diferencia desde el primer elemento de la tabla del calendario | //| hasta el elemento del primer día del mes en curso | //+------------------------------------------------------------------+ int CCalendar::OffsetFirstDayOfMonth(void) { //--- Obtenemos la fecha del primer día del año y mes seleccionados en forma de la cadena string date=string(m_date.year)+"."+string(m_date.mon)+"."+string(1); //--- Establecemos esta fecha en la estructura para los cálculos m_temp_date.DateTime(::StringToTime(date)); //--- Si el resultado de la resta del uno del número actual del día de la semana es más o igual a cero, // devolver el resultado, en caso contrario- devolver el valor 6 int diff=(m_temp_date.day_of_week-1>=0) ? m_temp_date.day_of_week-1 : 6; //--- Guardamos la fecha que cae en el primer elemento de la tabla m_temp_date.DayDec(diff); return(diff); }
2. El método CCalendar::SetCalendar(). En este método se rellenan los elementos de la tabla para el año y mes seleccionados. Al principio se invoca el método CCalendar::OffsetFirstDayOfMonth(). Luego repasamos todos los elementos de la tabla en el ciclo, colocando el número del día desde la estructura para los cálculos (m_temp_date.day) en cada elemento de la tabla, porque precisamente en esta estructura en el método CCalendar::OffsetFirstDayOfMonth() ha sido colocada la fecha desde la que es necesario llevar el cálculo. Al final del método, pasamos al siguiente día del calendario.
Class CCalendar : public CElement { private: //--- Muestra los últimos cambios en la tabla del calendario void SetCalendar(void); }; //+------------------------------------------------------------------+ //| Establecer los valores del calendario | //+------------------------------------------------------------------+ void CCalendar::SetCalendar(void) { //--- Determinar la diferencia desde el primer elemento de la tabla del calendario hasta el elemento del primer día del mes en curso int diff=OffsetFirstDayOfMonth(); //--- Recorremos en el ciclo todos los elementos de la tabla del calendario for(int i=0; i<42; i++) { //--- Colocar el día en el elemento actual de la tabla m_days[i].Description(string(m_temp_date.day)); //--- Ir a la siguiente fecha m_temp_date.DayInc(); } }
3. El método CCalendar::HighlightDate() va utilizarse para resaltar la fecha actual (fecha local en el ordenador del usuario) y el día seleccionado por el usuario en la tabla del calendario. Aquí primero en la estructura para los cálculos (m_temp_date) también se coloca la fecha para el primer elemento de la tabla. Luego, para todos los elementos, se determina en el ciclo el color del (1) fondo, (2) marco del fondo y (3) del texto visualizado (véase el código de abajo)
Class CCalendar : public CElement { private: //--- Resalto del día actual y el día seleccionado por el usuario void HighlightDate(void); }; //+------------------------------------------------------------------+ //| Resalto del día actual y el día seleccionado por el usuario | //+------------------------------------------------------------------+ void CCalendar::HighlightDate(void) { //--- Determinar la diferencia desde el primer elemento de la tabla del calendario hasta el elemento del primer día del mes en curso OffsetFirstDayOfMonth(); //--- Repasamos en el ciclo los elementos de la tabla del calendario for(int i=0; i<42; i++) { //--- Si el mes del elemento coincide con el mes en curso y // día del elemento coincide con el día seleccionado if(m_temp_date.mon==m_date.mon && m_temp_date.day==m_date.day) { m_days[i].Color(m_item_text_color); m_days[i].BackColor(m_item_back_color_selected); m_days[i].BorderColor(m_item_border_color_selected); //--- Ir al siguiente elemento de la tabla m_temp_date.DayInc(); continue; } //--- Si es la fecha actual (hoy) if(m_temp_date.year==m_today.year && m_temp_date.mon==m_today.mon && m_temp_date.day==m_today.day) { m_days[i].BackColor(m_item_back_color); m_days[i].BorderColor(m_item_text_color_hover); m_days[i].Color(m_item_text_color_hover); //--- Ir al siguiente elemento de la tabla m_temp_date.DayInc(); continue; } //--- m_days[i].BackColor(m_item_back_color); m_days[i].BorderColor(m_item_border_color); m_days[i].Color((m_temp_date.mon==m_date.mon)? m_item_text_color : m_item_text_color_off); //--- Ir al siguiente elemento de la tabla m_temp_date.DayInc(); } }
4. El método CCalendar::UpdateCalendar() va a utilizarse en todos los métodos destinados para la interacción del usuario con la interfaz gráfica del calendario. Aquí los métodos CCalendar::SetCalendar() y CCalendar::HighlightDate() que han sido considerados más arriba se llaman consecutivamente. Después de eso, el año y el mes se colocan en el campo de edición y en el combobox del calendario. Preste atención que para seleccionar el elemento necesario en la lista del combobox, hay que restar uno al mes, porque la enumeración de los meses en las estructuras de la fecha y la hora se empieza con uno, y la enumeración en las listas de la librería (CListView, con cero.
Class CCalendar : public CElement { public: //--- Mostrar los últimos cambios en el calendario void UpdateCalendar(void); }; //+------------------------------------------------------------------+ //| Mostrar los últimos cambios en el calendario | //+------------------------------------------------------------------+ void CCalendar::UpdateCalendar(void) { //--- Mostrar cambios en la tabla del calendario SetCalendar(); //--- Resalto del día actual y del día seleccionado por el usuario HighlightDate(); //--- Insertamos el año en el campo de edición m_years.ChangeValue(m_date.year); //--- Establecemos el mes en la lista del combobox m_months.SelectedItemByIndex(m_date.mon-1); }
El método CCalendar::ChangeObjectsColor() se utiliza para cambiar el color de los elementos/días del mes en la tabla del calendario al situar el cursor encima. Para eso, a este método hay que pasarle las coordenadas del cursor (x, y). Antes de empezar el ciclo con el repaso de todos los elementos, se determina a cuánto está desplazado el elemento con el primer día del mes respecto al primer elemento de la tabla, con el fin de establecer el valor inicial del contador (estructura m_temp_date). Luego en el ciclo, el día seleccionado y la fecha actual (fecha local en el ordenador del usuario) se omiten, y en los demás elementos se comprueba el foco del cursor del ratón. Al cambiar de color, se toma en cuaenta a qué mes pertenece el día.
Class CCalendar : public CElement { public: //--- Cambio del color de objetos en la tabla del calendario void ChangeObjectsColor(const int x,const int y); }; //+------------------------------------------------------------------+ //| Cambio del color de objetos en la tabla del calendario | //| al situar el cursor encima | //+------------------------------------------------------------------+ void CCalendar::ChangeObjectsColor(const int x,const int y) { //--- Determinar la diferencia desde el primer elemento de la tabla del calendario hasta el elemento del primer día del mes en curso OffsetFirstDayOfMonth(); //--- Repasamos en el ciclo los elementos de la tabla del calendario int items_total=::ArraySize(m_days); for(int i=0; i<items_total; i++) { //--- Si el mes del elemento coincide con el mes en curso y // día del elemento coincide con el día seleccionado if(m_temp_date.mon==m_date.mon && m_temp_date.day==m_date.day) { //--- Ir al siguiente elemento de la tabla m_temp_date.DayInc(); continue; } //--- Si el año/mes/día del elemento coincide con el año/mes/día de la fecha actual (hoy) if(m_temp_date.year==m_today.year && m_temp_date.mon==m_today.mon && m_temp_date.day==m_today.day) { //--- Ir al siguiente elemento de la tabla m_temp_date.DayInc(); continue; } //--- Si el cursor se encuentra sobre este elemento if(x>m_days[i].X() && x<m_days[i].X2() && y>m_days[i].Y() && y<m_days[i].Y2()) { m_days[i].BackColor(m_item_back_color_hover); m_days[i].BorderColor(m_item_border_color_hover); m_days[i].Color((m_temp_date.mon==m_date.mon)? m_item_text_color_hover : m_item_text_color_off); } else { m_days[i].BackColor(m_item_back_color); m_days[i].BorderColor(m_item_border_color); m_days[i].Color((m_temp_date.mon==m_date.mon)? m_item_text_color : m_item_text_color_off); } //--- Ir al siguiente elemento de la tabla m_temp_date.DayInc(); } }
Hay que tener la posibilidad de seleccionar la fecha en el calendario de forma programada, así como obtener la fecha seleccionada por el usuario y la fecha actual (hoy). Para eso a la clase (public) añadimos los métodos CCalendar::SelectedDate() y CCalendar::Today().
Class CCalendar : public CElement { public: //--- (1) Establecer (seleccionar) y (2) obtener la fecha seleccionada, (3) obtener la fecha actual en el calendario void SelectedDate(const datetime date); datetime SelectedDate(void) { return(m_date.DateTime()); } datetime Today(void) { return(m_today.DateTime()); } }; //+------------------------------------------------------------------+ //| Seleccionar fecha nueva | //+------------------------------------------------------------------+ void CCalendar::SelectedDate(const datetime date) { //--- Guardar fecha en la estructura y el campo de la clase m_date.DateTime(date); //--- Mostrar los últimos cambios en el calendario UpdateCalendar(); }
Supongamos que en el calendario está seleccionada la fecha el 29 febrero de 2016 (2016.02.29), y el usuario ha puesto en el campo de edición (o ha usado el conmutador del incremento) el año 2017. En febrero de 2017 hay 28 días. ¿Qué día debe seleccionarse en la tabla del calendario en esta situación? Normalmente, en estos casos, en calendarios de diferentes interfaces gráficas se selecciona el día más cercano, es decir el último día del mes. En este caso, es el 28 de febrero de 2017. Pues, hagamos lo mismo. Para eso, añadimos el método CCalendar::CorrectingSelectedDay() a nuestra clase para una corrección cómoda. El código de este método cuenta apenas con unas cuantas líneas. En la estructura CDateTime ya existe el método para obtener el número de días en el mes tomando en cuenta el año bisiesto, CDateTime::DaysInMonth(). Por eso basta con comparar el día actual del mes con el número de los días en el mes en curso, y si resulta que el número de días es mayor que el número seleccionado en el calendario, reemplazarlo.
Class CCalendar : public CElement { private: Corrección del día seleccionado según el número de días en el mes void CorrectingSelectedDay(void); }; //+------------------------------------------------------------------+ //| Determinar el primer día del mes | //+------------------------------------------------------------------+ void CCalendar::CorrectingSelectedDay(void) { //--- Establecer el número actual de los días en el mes, si el valor del día seleccionado es mayor if(m_date.day>m_date.DaysInMonth()) m_date.day=m_date.DaysInMonth(); }
Manejadores de eventos del calendario
Ahora hablaremos de los manejadores de eventos del calendario. En total habrá ocho métodos privados (private) para el procesamiento de eventos que se listan a continuación.
- Clic en el botón para ir al mes anterior
- Clic en el botón para ir al mes siguiente
- Seleccionar el mes en la lista desplegable del combobox
- Introducción del valor en el campo de edición del año
- Clic en el botón para ir al año siguiente
- Clic en el botón para ir al año anterior
- Clic en el día del mes
- Clic en el botón para ir a la fecha actual
- Cada acción de la lista de arriba comporta los cambios en el calendario. A continuación, analizaremos más detalladamente el código de estos métodos.
Para trabajar con el calendario, nos hará falta el identificador nuevo del evento personalizado- ON_CHANGE_DATE, que vamos a utilizar para el envío del mensaje sobre el hecho de que la fecha del calendario ha sido cambiada por el usuario. Además de eso, añadiremos otro identificador para el trabajo con el combobox- ON_CLICK_COMBOBOX_BUTTON para determinar el clic en el botón de este control.
#define ON_CLICK_COMBOBOX_BUTTON (21) // Clic en el botón del combobox #define ON_CHANGE_DATE (22) // Cambio de la fecha en el calendario
El envío del mensaje con el identificador ON_CLICK_COMBOBOX_BUTTON debe realizarse desde el método OnClickButton() de la clase CComboBox. Añadimos el envío del mensaje a este método (véase el código de abajo):
//+------------------------------------------------------------------+ //| Clic en el botón del combobox | //+------------------------------------------------------------------+ bool CComboBox::OnClickButton(const string clicked_object) { //--- Salir si el formulario está bloqueado y los identificadores no coinciden if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return(false); //--- Salimos si el nombre del objeto no coincide if(clicked_object!=m_button.Name()) return(false); //--- Cambiar el estado de la lista ChangeComboBoxListState(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_BUTTON,CElement::Id(),0,""); return(true); }
En la clase CCalendar el seguimiento del evento personalizado con el identificador ON_CLICK_COMBOBOX_BUTTON lo vamos a necesitar para controlar el estado de los controles (CSpinEdit y CIconButton) que forman parte del calendario (véase el código de abajo):
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del clic en el botón del combobox if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_BUTTON) { //--- Salir si los identificadores de controles no coinciden if(lparam!=CElement::Id()) return; //--- Activar o bloquear los controles dependiendo del estado actual de la visibilidad de la lista m_years.SpinEditState(!m_months.GetListViewPointer().IsVisible()); m_button_today.ButtonState(!m_months.GetListViewPointer().IsVisible()); } }
En las interfaces gráficas de diferentes sistemas, por ejemplo, en el sistema operativo Windows 7, cuando se pulsa la flecha izquierda/derecha para ir al mes anterior/siguiente, se selecciona el primer día del mes independientemente del día que ha sido seleccionado anteriormente por el usuario en la tabla del calendario. Nosotros vamos a implementar el mismo comportamiento en el calendario de la librería que estamos desarrollando. Para ir al mes anterior/siguiente, se usan los métodos CCalendar::OnClickMonthDec() y CCalendar::OnClickMonthInc(). El código de estos métodos es muy parecido, por eso como ejemplo vamos a mostrar sólo uno de ellos, es decir, el procesamiento del clic del botón para ir al mes anterior.
Al principio del método, se comprueba el nombre del objeto gráfico pulsado por el usuario. Luego, si el año actual en el calendario es igual al año mínimo establecido, y el mes actual es “Enero” (es decir, hemos llegado a la limitación mínima), resaltamos brevemente el texto en el campo de edición y salimos del método.
Si todavía no hemos llegado a la limitación mínima, ocurre lo siguiente.
- Establecemos el estado On para el botón.
- Vamos al mes anterior.
- Establecemos la primera fecha del mes.
- Reseteamos la hora al principio del día (00:00:00). Para eso añadimos el método CCalendar::ResetTime() a la clase.
- Actualizamos el calendario para mostrar los últimos cambios.
- Enviamos el mensaje con (1) el identificador del gráfico, (2) identificador del evento ON_CHANGE_DATE, (3) identificador del control y (4) la fecha establecida. El mensaje con los mismos parámetros va a enviarse desde otros métodos manejadores de eventos del calendario.
Class CCalendar : public CElement { private: //--- Procesamiento del clic en el botón para ir al mes anterior bool OnClickMonthDec(const string clicked_object); //--- Procesamiento del clic en el botón para ir al mes siguiente bool OnClickMonthInc(const string clicked_object); //--- Resetear la hora al principio del día void ResetTime(void); }; //+------------------------------------------------------------------+ //| Clic en la flecha izquierda. Ir al mes anterior. | //+------------------------------------------------------------------+ bool CCalendar::OnClickMonthDec(const string clicked_object) { //--- Salir si el nombre del objeto no coincide if(::StringFind(clicked_object,m_month_dec.Name(),0)<0) return(false); //--- Si el año actual en el calendario es igual al año mínimo y // el mes en curso es “Enero” if(m_date.year==m_years.MinValue() && m_date.mon==1) { //--- Resaltar el valor y salir m_years.HighlightLimit(); return(true); } //--- Establecemos el estado On m_month_dec.State(true); //--- Ir al mes anterior m_date.MonDec(); //--- Establecer la primera fecha del mes m_date.day=1; //--- Establecer la hora al principio del día ResetTime(); //--- Mostrar los últimos cambios en el calendario UpdateCalendar(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); } //+------------------------------------------------------------------+ //| Resetear la hora al principio del día | //+------------------------------------------------------------------+ void CCalendar::ResetTime(void) { m_date.hour =0; m_date.min =0; m_date.sec =0; }
El código en el método CCalendar::OnClickMonthInc() es casi el mismo. La única diferencia es que en la comprobación de la limitación se comprueba la salida fuera del año máximo establecido en el calendario y el último mes del año (diciembre).
El procesamiento de la selección del mes en la lista se realiza a través del método CCalendar::OnClickMonthList() cuando llega el evento con el identificador ON_CLICK_COMBOBOX_ITEM (véase el código de abajo). En el manejador principal, a este método se le pasa el parámetro long que contiene el identificador del control. Si los identificadores no coinciden, el programa saldrá del método. Puesto que la selección del elemento en la lista provoca su cierre, hay que desbloquear los controles bloqueados (campo de edición y botón) del calendario. Luego, colocamos el mes seleccionado en la estructura de la fecha y hora, y si hace falta, corregimos el día seleccionado según el número de días en el mes. Sólo queda establecer la hora al principio del día, mostrar los últimos cambios realizados en el calendario y enviar el mensaje sobre el cambio de la fecha al flujo de eventos.
Class CCalendar : public CElement { private: //--- Procesamiento de la selección del mes en la lista bool OnClickMonthList(const long id); }; //+------------------------------------------------------------------+ //| Procesamiento de la selección del mes en la lista | //+------------------------------------------------------------------+ bool CCalendar::OnClickMonthList(const long id) { //--- Salir si los identificadores de controles no coinciden if(id!=CElement::Id()) return(false); //--- Desbloquear controles m_years.SpinEditState(true); m_button_today.ButtonState(true); //--- Obtenemos el mes seleccionado en la lista int month=m_months.GetListViewPointer().SelectedItemIndex()+1; m_date.Mon(month); //--- Corrección del día seleccionado según el número de días en el mes CorrectingSelectedDay(); //--- Establecer la hora al principio del día ResetTime(); //--- Mostrar cambios en la tabla del calendario UpdateCalendar(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
Para el procesamiento de la introducción del valor en el campo de edición del año, se utiliza el método CCalendar::OnEndEnterYear(). Al principio de este método se encuentra la comprobación del nombre del objeto tipo OBJ_EDIT en el que ha sido introducido el valor nuevo. Luego, va otra comprobación verificando si el valor ha sido modificado. Si el nombre del campo de edición no pertenece al calendario, o el valor no se ha cambiado, el programa sale del método. Si dos primera verificaciones han sido superadas con éxito, el valor introducido se corrige en caso de salir fuera de las limitaciones establecidas. La siguiente corrección corrige la fecha seleccionada por el usuario, si la fecha del día del mes supera el número de días en este mes. Después de eso, se puede colocar la fecha en la estructura de la fecha y hora, mostrar los cambios realizados en el calendario y enviar el mensaje sobre la modificación de la fecha en este calendario. El código del método CCalendar::OnEndEnterYear() se muestra con detalles a continuación:
Class CCalendar : public CElement { private: //--- Procesamiento de la introducción del valor en el campo de edición de los años bool OnEndEnterYear(const string edited_object); }; //+------------------------------------------------------------------+ //| Procesamiento de la introducción del valor en el campo de edición de los años | //+------------------------------------------------------------------+ bool CCalendar::OnEndEnterYear(const string edited_object) { //--- Salimos si el nombre del objeto no coincide if(::StringFind(edited_object,m_years.Object(2).Name(),0)<0) return(false); //--- Salimos si el valor no ha cambiado string value=m_years.Object(2).Description(); if(m_date.year==(int)value) return(false); //--- Corregir el valor en caso de salir fuera de las limitaciones establecidas if((int)value<m_years.MinValue()) { value=(string)int(m_years.MinValue()); //--- Resaltar el valor m_years.HighlightLimit(); } if((int)value>m_years.MaxValue()) { value=(string)int(m_years.MaxValue()); //--- Resaltar el valor m_years.HighlightLimit(); } //--- Determinar el número de días en el mes actual string year =value; string month =string(m_date.mon); string day =string(1); m_temp_date.DateTime(::StringToTime(year+"."+month+"."+day)); //--- Si el valor del día seleccionado es mayor que el número de días en el mes, //--- establecer el número actual de los días en el mes como del día seleccionado if(m_date.day>m_temp_date.DaysInMonth()) m_date.day=m_temp_date.DaysInMonth(); //--- Colocar la fecha en la estructura m_date.DateTime(::StringToTime(year+"."+month+"."+string(m_date.day))); //--- Mostramos cambios en la tabla del calendario UpdateCalendar(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
Los métodos del procesamiento del clic en los botones para ir al año anterior/siguiente se llaman CCalendar::OnClickYearInc() y CCalendar::OnClickYearDec(). Mostraremos aquí sólo uno de ello porque son prácticamente idénticos, salvo la condición de la comprobación de las limitaciones alcanzadas. Al principio se comprueba el estado actual de la lista para la selección del mes. Si está abierta, la cerramos. Luego, si los identificadores de controles no coinciden, el programa sale del método. Luego sigue la condición principal de los métodos, donde según la condición se realiza un paso hacia adelante para CCalendar::OnClickYearInc() o un paso hacia atrás para CCalendar::OnClickYearDec(). Después de eso, si hace falta, (1) se realiza la corrección del día seleccionado según el número de días en el mes, (2) en el calendario se muestran los últimos cambios y (3) se envía el mensaje sobre el cambio de la fecha en el calendario.
Class CCalendar : public CElement { private: //--- Procesamiento del clic en el botón para ir al año siguiente bool OnClickYearInc(const long id); //--- Procesamiento del clic en el botón para ir al año anterior bool OnClickYearDec(const long id); }; //+------------------------------------------------------------------+ //| Procesamiento del clic en el botón para ir al año siguiente | //+------------------------------------------------------------------+ bool CCalendar::OnClickYearInc(const long id) { //--- Si la lista de los meses está abierta, la cerramos if(m_months.GetListViewPointer().IsVisible()) m_months.ChangeComboBoxListState(); //--- Salir si los identificadores de controles no coinciden if(id!=CElement::Id()) return(false); //--- Si el año es menor que el año mínimo especificado, el valor se aumente a uno if(m_date.year<m_years.MaxValue()) m_date.YearInc(); //--- Corrección del día seleccionado según el número de días en el mes CorrectingSelectedDay(); //--- Mostrar cambios en la tabla del calendario UpdateCalendar(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
Ahora vamos a analizar el método CCalendar::OnClickDayOfMonth() que sirve para procesar el clic en el día del mes en la tabla del calendario.
Como se ha dicho antes, la tabla del calendario se compone de 42 elementos. Por eso, dependiendo del elemento en el que cae el primer día del mes actual, a veces en la tabla van a figurar los días del mes anterior o del siguiente. Cuando el usuario va a pinchar en el día que no pertenece al mes en curso, va a realizarse el cambio al mes al que pertenece este día.
Al principio del método, hay que realizar la comprobación de la pertenencia del objeto al elemento de la tabla del calendario, así como verificar los identificadores del control. El identificador se extrae desde el nombre del objeto a través del método CCalendar::IdFromObjectName(), que ya ha sido considerado antes en el ejemplo de otros controles de la librería. Si dos primeras comprobaciones han sido superadas con éxito, entonces luego a través del método CCalendar::OffsetFirstDayOfMonth() se determina la diferencia desde el primer elemento de la tabla del calendario hasta el elemento del primer día del mes en curso. Después de la llamada a este método, el contador m_temp_date está listo para el trabajo, y luego el programa repasará en el ciclo todos los elementos con el fin de encontrar el elemento pulsado por el usuario.
Vamos a ver con más detalles la lógica de este ciclo. Es probable que las fechas en los primeros elementos pertenezcan al año precedente del año establecido en el sistema mínimo (1970). Si es el caso, resaltamos el valor en el campo de edición y vamos al siguiente elemento, puesto que al usuario se le prohíbe salir fuera de las limitaciones establecidas. Si el elemento se encuentra en el área permitida y el clic ha sido hecho en este elemento, (1) en la estructura (m_date) se guarda la fecha, (2) en el calendario se muestran los últimos cambios, (3) el ciclo se interrumpe y (4) al final del método se envía el mensaje sobre el cambio de la fecha en el calendario.
Si en el ciclo ninguna de las primeras dos comprobaciones ha sido superada, el contador de la estructura para los cálculos (m_temp_date) se aumenta a un día, y después de eso se comprueba el hecho de superar el máximo establecido en el calendario. Si el máximo aún no ha sido alcanzado, el programa pasa al siguiente elemento. Al ser alcanzado el máximo, se llama el resalto del valor en el campo de edición de los años, y el programa sale del método.
Class CCalendar : public CElement { private: //--- Procesamiento del clic en el día del mes bool OnClickDayOfMonth(const string clicked_object); }; //+------------------------------------------------------------------+ //| Procesamiento del clic en el día del mes del calendario | //+------------------------------------------------------------------+ bool CCalendar::OnClickDayOfMonth(const string clicked_object) { //--- Salimos si el clic no ha sido hecho en el día del calendario if(::StringFind(clicked_object,CElement::ProgramName()+"_calendar_day_",0)<0) return(false); //--- Obtenemos el identificador e índice desde el nombre del objeto int id=IdFromObjectName(clicked_object); //--- Salir si el identificador no coincide if(id!=CElement::Id()) return(false); //--- Determinar la diferencia desde el primer elemento de la tabla del calendario hasta el elemento del primer día del mes en curso OffsetFirstDayOfMonth(); //--- Repasamos en el ciclo los elementos de la tabla del calendario for(int i=0; i<42; i++) { //--- Si la fecha del elemento actual es menor que el mínimo establecido en el sistema if(m_temp_date.DateTime()<datetime(D'01.01.1970')) { //--- Si es el objeto pulsado if(m_days[i].Name()==clicked_object) { //--- Resaltar el valor y salir m_years.HighlightLimit(); return(false); } //--- Ir a la siguiente fecha m_temp_date.DayInc(); continue; } //--- Si es el objeto pulsado if(m_days[i].Name()==clicked_object) { //--- Guardar la fecha m_date.DateTime(m_temp_date.DateTime()); //--- Mostrar los últimos cambios en el calendario UpdateCalendar(); break; } //--- Ir a la siguiente fecha m_temp_date.DayInc(); //--- Comprobar la superación del máximo establecido en el sistema if(m_temp_date.year>m_years.MaxValue()) { //--- Resaltar el valor y salir m_years.HighlightLimit(); return(false); } } //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
Nos queda sólo analizar el último método manejador de los eventos del calendario CCalendar::OnClickTodayButton(), que sirve para procesar el clic en el botón para ir a la fecha actual. A este método se le pasa el parámetro long (identificador del control) desde el manejador principal de eventos. Al principio del método, se realiza el cierre de la lista si en este momento está abierta. Luego, se verifican los identificadores. Si los identificadores coinciden, establecemos la fecha y la hora local (en el ordenador del usuario) en la estructura de trabajo. Luego, el calendario se actualiza para mostrar los cambios realizados, y se envía el mensaje sobre el cambio de la fecha en este calendario.
Class CCalendar : public CElement { private: //--- Procesamiento del clic en el botón para ir a la fecha actual bool OnClickTodayButton(const long id); }; //+------------------------------------------------------------------+ //| Procesamiento del clic en el botón para ir a la fecha actual | //+------------------------------------------------------------------+ bool CCalendar::OnClickTodayButton(const long id) { //--- Si la lista de los meses está abierta, la cerramos if(m_months.GetListViewPointer().IsVisible()) m_months.ChangeComboBoxListState(); //--- Salir si los identificadores de controles no coinciden if(id!=CElement::Id()) return(false); //--- Establecer la fecha actual m_date.DateTime(::TimeLocal()); //--- Mostrar los últimos cambios en el calendario UpdateCalendar(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
Tenemos ocho bloques del código () en el manejador principal de eventos del calendario CCalendar::OnEvent(). Uno de ellos ya ha sido mostrado al principio de este apartado. Son los siguientes.
- Procesamiento de eventos del desplazamiento del cursor (CHARTEVENT_MOUSE_MOVE). El seguimiento del cursor del ratón se realiza sólo cuando el control está ocultado, así como en los casos cuando el control es desplegable y el formulario no está bloqueado. El programa sale de este bloque cuando la lista de los meses está abierta. Si la lista está ocultada y el botón izquierdo del ratón ha sido pulsado, se activan los controles del calendario bloqueados anteriormente (en el momento de la apertura de la lista), si por lo menos uno de ellos todavía se encuentra bloqueado. Finalmente, al método CCalendar::ChangeObjectsColor() se le pasan las coordenadas del cursor para el cambio del color de los elementos de la tabla del calendario.
//--- Procesamiento del evento del desplazamiento del cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Salir si el control está ocultado if(!CElement::IsVisible()) return; //--- Salir si el control no es desplegable y el formulario está bloqueado if(!CElement::IsDropdown() && m_wnd.IsLocked()) 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); CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_month_dec.MouseFocus(x>m_month_dec.X() && x<m_month_dec.X2() && y>m_month_dec.Y() && y<m_month_dec.Y2()); m_month_inc.MouseFocus(x>m_month_inc.X() && x<m_month_inc.X2() && y>m_month_inc.Y() && y<m_month_inc.Y2()); //--- Salir si la lista de meses está activada if(m_months.GetListViewPointer().IsVisible()) return; //--- Si la lista no está activado y el botón izquierdo ha sido pulsado... else if(m_mouse_state) { //--- ...activamos los controles bloqueados entes (en el momento de la apertura de la lista), //--- si por lo menos uno de ellos todavía se encuentra bloqueado if(!m_button_today.ButtonState()) { m_years.SpinEditState(true); m_button_today.ButtonState(true); } } //--- Cambio del color de objetos ChangeObjectsColor(x,y); return; }
- Procesamiento del evento del clic izquierdo en el objeto (CHARTEVENT_OBJECT_CLICK). Eso ocurre después de pulsar algún objeto del calendario. Luego, si la bandera para el botón izquierdo apretado está establecida, se activan los controles del calendario (campo de edición y botón). Luego, se comprueba qué control ha sido pulsado exactamente. Para eso se utilizan los métodos arriba mencionados: CCalendar::OnClickMonthDec(), CCalendar::OnClickMonthInc() y CCalendar::OnClickDayOfMonth().
//--- Procesamiento del evento del clic izquierdo en el objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Salir si el formulario está bloqueado y los identificadores no coinciden if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return; //--- Salir si la lista de meses está activada if(m_months.GetListViewPointer().IsVisible()) return; //--- Activar controles (lista y campo de edición) si el botón izquierdo del ratón está pulsado if(m_mouse_state) { m_years.SpinEditState(true); m_button_today.ButtonState(true); } //--- Procesamiento del clic en los botones conmutadores de los meses if(OnClickMonthDec(sparam)) return; if(OnClickMonthInc(sparam)) return; //--- Procesamiento del clic en el día del calendario if(OnClickDayOfMonth(sparam)) return; }
- Procesamiento del evento del clic en el elemento de la lista del combobox (ON_CLICK_COMBOBOX_ITEM). Para el procesamiento de este evento se utiliza el método CCalendar::OnClickMonthList().
//--- Procesamiento del evento del clic en el elemento de la lista del combobox if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { //--- Procesamiento de la selección del mes en la lista if(!OnClickMonthList(lparam)) return; //--- return; }
- Procesamiento del evento del clic en los botones del incremento/decremento (ON_CLICK_INC/ON_CLICK_DEC). Par el procesamiento de estos eventos hay dos bloques separados del código para llamar a los métodos: CCalendar::OnClickYearInc() y CCalendar::OnClickYearDec().
//--- Procesamiento del evento del clic en el botón del incremento if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC) { //--- Procesamiento del clic en el botón para ir al año siguiente if(!OnClickYearInc(lparam)) return; //--- return; } //--- Procesamiento del evento del clic en el botón del decremento if(id==CHARTEVENT_CUSTOM+ON_CLICK_DEC) { //--- Procesamiento del clic en el botón para ir al año anterior if(!OnClickYearDec(lparam)) return; //--- return; }
- Procesamiento del evento de la introducción del valor en el campo de edición (CHARTEVENT_OBJECT_ENDEDIT). La introducción de un valor en el campo de edición de los años del calendario llevará al programa a este bloque del código para llamar al método CCalendar::OnEndEnterYear().
//--- Procesamiento del evento la introducción del valor en el campo de edición if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- Procesamiento de la introducción del valor en el campo de edición de los años if(OnEndEnterYear(sparam)) return; //--- return; }
- Procesamiento del evento del clic en el botón (ON_CLICK_BUTTON). En este bloque del código, a través del método CCalendar::OnClickTodayButton(), se procesa el clic en el botón que se utiliza para ir a la fecha actual (fecha local en el ordenador del usuario):
//--- Procesamiento del evento del clic en el botón if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Procesamiento del clic en el botón para ir a la fecha actual if(!OnClickTodayButton(lparam)) return; //--- return; }
Para que el uso de un control tan complejo como “Calendario” sea más cómodo, vamos a añadirle la funcionalidad que permita realizar el avance/retroceso rápido de sus valores. Algo parecido ya ha sido implementado en las clases de la lista (CListView) y en los campos de edición (CSpinEdit). Pero ahora tenemos que asociarlo a la tabla del calendario. Es decir, cuando se cambian los valores durante el avance/retroceso rápido en el campo de edición, hagamos que los valores también se actualicen en la tabla. Añadimos también la posibilidad del avance/retroceso rápido del calendario a través de los botones conmutadores de los meses.
Vamos a mostrar aquí la descripción sólo para la parte principal del método, porque ya hemos analizado las condiciones para la entrada en el bloque de trabajo en otros artículos. Después de que las condiciones hayan sido cumplidas, hay que comprobar cuál de los botones de los controles del calendario se mantiene pulsado en este momento (ver las líneas marcadas en amarillo). Si ningún botón de los mencionados se encuentra apretado en este momento, el programa sale del método. Al encontrar un botón pulsado, es necesario pasar una serie de comprobaciones para averiguar si supera el valor las limitaciones establecidas en el calendario. Si hemos llegado a la limitación, el texto en el campo de edición se resalta y el programa sale del método. Si la limitación no ha sido alcanzada, el calendario se actualiza para mostrar los últimos cambios realizados, y se envía el mensaje sobre el cambio de la fecha.
Class CCalendar : public CElement { private: //--- Avance/retroceso rápido de los valores del calendario void FastSwitching(void); }; //+------------------------------------------------------------------+ //| Avance/retroceso rápido del calendario | //+------------------------------------------------------------------+ void CCalendar::FastSwitching(void) { //--- Salimos si no hay foco sobre el control if(!CElement::MouseFocus()) return; //--- Salir si el formulario está bloqueado y los identificadores no coinciden if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) 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; //--- Si la flecha izquierda se mantiene pulsada if(m_month_dec.State()) { //--- Si el año actual en el calendario es mayor/igual al año mínimo establecido if(m_date.year>=m_years.MinValue()) { //--- Si el año actual en el calendario ya es igual al año mínimo establecido y // el mes en curso es “Enero” if(m_date.year==m_years.MinValue() && m_date.mon==1) { //--- Resaltar el valor y salir m_years.HighlightLimit(); return; } //--- Ir al siguiente mes (descendiendo) m_date.MonDec(); //--- Establecer la primera fecha del mes m_date.day=1; } } //--- Si la flecha derecha se mantiene pulsada else if(m_month_inc.State()) { //--- Si el año actual en el calendario es menor/igual al año máximo establecido if(m_date.year<=m_years.MaxValue()) { //--- Si el año actual en el calendario ya es igual al año máximo establecido y // el mes en curso es “Diciembre” if(m_date.year==m_years.MaxValue() && m_date.mon==12) { //--- Resaltar el valor y salir m_years.HighlightLimit(); return; } //--- Ir al siguiente mes (ascendiendo) m_date.MonInc(); //--- Establecer la primera fecha del mes m_date.day=1; } } //--- Si el botón del incremento del campo de años se mantiene pulsado else if(m_years.StateInc()) { //--- Si es menos que el año máximo especificado, // ir al siguiente año (ascendiendo) if(m_date.year<m_years.MaxValue()) m_date.YearInc(); else { //--- Resaltar el valor y salir m_years.HighlightLimit(); return; } } //--- Si el botón del decremento del campo de años se mantiene pulsado else if(m_years.StateDec()) { //--- Si es más que el año mínimo especificado, // ir al siguiente año (descendiendo) if(m_date.year>m_years.MinValue()) m_date.YearDec(); else { //--- Resaltar el valor y salir m_years.HighlightLimit(); return; } } else return; //--- Mostrar los últimos cambios en el calendario UpdateCalendar(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); } }
En el momento cuando pasamos de un día a otro, es necesario actualizar el día actual (hora local en el ordenador del usuario) en el calendario, así como actualizar la fecha que se muestra en el botón para ir a la fecha actual. Para eso escribiremos el método especial CCalendar::UpdateCurrentDate(), que va a realizar el seguimiento de cada segundo de la llegada del nuevo día (véase el código de abajo).
Class CCalendar : public CElement { public: //--- Actualización de la fecha actual void UpdateCurrentDate(void); }; //+------------------------------------------------------------------+ //| Actualización de la fecha actual | //+------------------------------------------------------------------+ void CCalendar::UpdateCurrentDate(void) { //--- Contador static int count=0; //--- Salir si ha pasado menos de un segundo if(count<1000) { count+=TIMER_STEP_MSC; return; } //--- Poner a cero el contador count=0; //--- Obtenemos la hora actual (local) MqlDateTime local_time; ::TimeToStruct(::TimeLocal(),local_time); //--- Si ha llegado nuevo día if(local_time.day!=m_today.day) { //--- Actualizar la fecha en el calendario m_today.DateTime(::TimeLocal()); m_button_today.Object(2).Description(::TimeToString(m_today.DateTime())); //--- Mostrar los últimos cambios en el calendario UpdateCalendar(); return; } //--- Actualizar la fecha en el calendario m_today.DateTime(::TimeLocal()); }
En este caso, el código del temporizador del control “Calendario” va a ser el siguiente:
//+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CCalendar::OnEventTimer(void) { //--- Si el control es desplegable y la lista está ocultada if(CElement::IsDropdown() && !m_months.GetListViewPointer().IsVisible()) { 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(); } } //--- Actualización de la fecha actual del calendario UpdateCurrentDate(); }
Hemos analizado todos los métodos de la clase CCalendar. Ahora vamos a probar cómo funciona todo eso. No obstante, antes de eso hay que vincular el control con el motor de la librería para su correcto funcionamiento. Las instrucciones detalladas paso a paso de cómo hacerlo ya han sido presentadas en el artículo Interfaces gráficas V: Control “Lista combinada” (Capítulo 3). Por eso, aquí vamos a mostrar sólo las declaraciones (en el cuerpo de la clase CWndContainer) del array en la estructura de los arrays personales de los controles, así como los métodos (1) para obtener el número de calendario en la interfaz gráfica de la aplicación MQL y (2) para guardar los punteros de los controles al añadirlos a la base durante la creación de la interfaz gráfica. Puede estudiar el código de estos métodos en los archivos adjuntos al artículo.
//+------------------------------------------------------------------+ //| Clase para almacenar todos los objetos de la interfaz | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- Estructura de los arrays de controles struct WindowElements { //--- Arrays personales de controles: //--- Array de calendarios CCalendar *m_calendars[]; //--- public: //--- Número de calendarios int CalendarsTotal(const int window_index); //--- private: //--- Guarda los punteros a los controles del calendario en la base bool AddCalendarElements(const int window_index,CElement &object); };
Prueba del control “Calendario”
Se puede coger cualquier EA del artículo anterior para la prueba. Quitamos de ahí todos los controles de la interfaz gráfica, dejando sólo el menú principal y la barra de estado. Creamos dos calendarios para poder monitorear la aparición de conflictos entre ellos durante la interacción.
En la clase personalizada de la aplicación (CProgram), declaramos la instancia de la clase CCalendar y el método para la creación del control “Calendario” con los márgenes desde el punto extremo del formulario:
class CProgram : public CWndEvents { private: //--- Calendarios CCalendar m_calendar1; CCalendar m_calendar2; //--- private: //--- Calendarios #define CALENDAR1_GAP_X (2) #define CALENDAR1_GAP_Y (43) bool CreateCalendar1(void); #define CALENDAR2_GAP_X (164) #define CALENDAR2_GAP_Y (43) bool CreateCalendar2(void); };
Como ejemplo, vamos a mostrar aquí el código sólo de uno de estos métodos. Dejamos las propiedades del calendario por defecto (véase el código de abajo).
//+------------------------------------------------------------------+ //| Crea el calendario 1 | //+------------------------------------------------------------------+ bool CProgram::CreateCalendar1(void) { //--- Pasar el objeto del panel m_calendar1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+CALENDAR1_GAP_X; int y=m_window1.Y()+CALENDAR1_GAP_Y; //--- Creamos el control if(!m_calendar1.CreateCalendar(m_chart_id,m_subwin,x,y)) return(false); //--- Añadimos el objeto al array común de los grupos de objetos CWndContainer::AddToElementsArray(0,m_calendar1); return(true); }
Colocamos la llamada a estos métodos en el método principal de la creación de la interfaz gráfica de la aplicación MQL. Luego la compilamos e iniciamos en el gráfico. El resultado tiene que ser el siguiente:
Fig. 2. Prueba del control “Calendario”.
Pues bien, hemos terminado el desarrollo de la clase CCalendar para la creación del control “Calendario”. En el futuro, sus posibilidades serán ampliadas, y en este artículo vamos a considerar la segunda versión de este control, “Calendario desplegable”.
Control “Calendario desplegable”
El control “Calendario desplegable”, a diferencia del calendario estático (CCalendar), permite ahorrar considerablemente el espacio de la interfaz gráfica de la aplicación, puesto que se encuentra en el estado plegable la mayor parte de tiempo. La fecha seleccionada por el usuario se muestra en el campo de texto del combobox. Si el usuario necesita seleccionar otra fecha, tiene que pulsar el botón de combobox. Gracias a esta acción se abre (se muestra) el calendario. Si el usuario vuelve a pulsar este botón, el calendario se oculta. El clic izquierdo fuera del control o el cambio de las propiedades del gráfico también cierra el calendario.
Fig. 3. Partes integrantes del control “Calendario desplegable”.
A continuación, vamos a ver cómo está organizado este control.
Clase CDropCalendar
Antes, en los artículos Interfaces gráficas V: Control “Lista combinada” (Capítulo 3) y Interfaces gráficas VI: Controles “Casilla de verificación”, “Campo de edición” y sus tipos combinados (Capítulo 1), ya hemos mostrado los controles tipo “combobox” a base de los ejemplos como: “Combobox con la lista” (CComboBox) y “Combobox con la lista y checkbox” (CCheckComboBox). Por eso, simplemente recorremos brevemente los momentos clave del control “Calendario desplegable”.
Creamos el archivo DropCalendar.mqh y lo incluimos en la librería (archivo WndContainer.mqh):
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "DropCalendar.mqh"
Creamos la clase con los métodos estándar que poseen todos los controles de la librería en el archivo DropCalendar.mqh. Incluimos en él el archivo con la clase del calendario— Calendar.mqh:
//+------------------------------------------------------------------+ //| DropCalendar.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "Calendar.mqh"
Pasamos directamente a la lista de los métodos para la creación del control. Vamos a necesitar seis métodos privados (private) y un método público (public ):
//+------------------------------------------------------------------+ //| Clase para crear el calendario desplegable | //+------------------------------------------------------------------+ class CDropCalendar : public CElement { private: //--- Objetos y controles para crear el control CRectLabel m_area; CLabel m_label; CEdit m_field; CEdit m_drop_button; CBmpLabel m_drop_button_icon; CCalendar m_calendar; //--- public: //--- Métodos para crear el calendario desplegable bool CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabel(void); bool CreateEditBox(void); bool CreateDropButton(void); bool CreateDropButtonIcon(void); bool CreateCalendar(void); };
Aquí sólo es necesario mencionar que para crear el calendario, hay que establecer el indicio del control desplegable para él, y en el método principal (público) CDropCalendar::CreateDropCalendar() hay que hacer las siguientes acciones tras la creación de todos los objetos que forman parte del control:
- Ocultar el calendario.
- En el campo informativo del combobox, establecer la fecha seleccionada que ya figura en el calendario después de su creación, y que puede ser obtenida a través del método CCalendar::SelectedDate().
En el código de abajo se muestra la versión reducida del método CDropCalendar::CreateDropCalendar(). Se puede ver su versión completa en los archivos adjuntos al artículo.
//+------------------------------------------------------------------+ //| Crea el calendario desplegable | //+------------------------------------------------------------------+ bool CDropCalendar::CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y) { //--- Salir si no hay puntero al formulario //--- Inicialización de variables //--- Márgenes desde el punto extremo //--- Creación del control //--- Ocultar el calendario m_calendar.Hide(); //--- Mostrar la fecha seleccionada en el calendario m_field.Description(::TimeToString((datetime)m_calendar.SelectedDate(),TIME_DATE)); //--- Ocultar el elemento si es la ventana de diálogo o está minimizada if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); //--- return(true); } //+------------------------------------------------------------------+ //| Crea la lista | //+------------------------------------------------------------------+ bool CDropCalendar::CreateCalendar(void) { //--- Pasar el objeto del panel m_calendar.WindowPointer(m_wnd); //--- Coordenadas int x=m_field.X(); int y=m_field.Y2(); //--- Establecemos el indicio del control desplegable para el calendario m_calendar.IsDropdown(true); //--- Creamos el control if(!m_calendar.CreateCalendar(m_chart_id,m_subwin,x,y)) return(false); //--- return(true); }
Vamos a necesitar los siguientes métodos para el trabajo con combox.
- Cambio del estado de visibilidad del calendario por el contrario — CDropCalendar::ChangeComboBoxCalendarState().
- Procesamiento del clic en el botón del combobox — CDropCalendar::OnClickButton().
- Comprobar si el botón izquierdo del ratón está pulsado sobre el botón del combobox — CDropCalendar::CheckPressedOverButton().
Todos estos métodos son similares a los que hemos analizado en la clase CComboBox, por eso no vamos a mostrar aquí su código.
Entonces, el manejador de eventos del calendario desplegable va a tener el siguiente aspecto. Aquí hay sólo cuatro bloques para el procesamiento de siguientes eventos:
- Desplazamiento del cursor — (CHARTEVENT_MOUSE_MOVE).
- Selección de nueva fecha en el calendario — ON_CHANGE_DATE.
- Clic izquierdo en el objeto gráfico — CHARTEVENT_OBJECT_CLICK.
- Cambio de propiedades del gráfico — CHARTEVENT_CHART_CHANGE.
Cuando llega el evento ON_CHANGE_DATE generado por la clase CCalendar, hay que verificar los identificadores de los controles y establecer nueva fecha en el campo del combobox.
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CDropCalendar::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); CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_drop_button.MouseFocus(x>m_drop_button.X() && x<m_drop_button.X2() && y>m_drop_button.Y() && y<m_drop_button.Y2()); //--- Comprobando si el botón izquierdo del ratón está pulsado sobre el botón del combobox CheckPressedOverButton(); return; } //--- Procesamiento del evento de la selección de nueva fecha en el calendario if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE) { //--- Salir si los identificadores de controles no coinciden if(lparam!=CElement::Id()) return; //--- Establecemos nueva fecha del combobox m_field.Description(::TimeToString((datetime)dparam,TIME_DATE)); return; } //--- Procesamiento del evento del clic izquierdo en el objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Clic en el botón del combobox if(OnClickButton(sparam)) return; //--- return; } //--- Procesamiento del evento del cambio de propiedades del gráfico if(id==CHARTEVENT_CHART_CHANGE) { //--- Salir si el control está bloqueado if(!m_drop_calendar_state) return; Ocultar el calendario m_calendar.Hide(); m_drop_button_icon.State(false); //--- Resetear los colores ResetColors(); return; } }
Para el funcionamiento correcto de este control, igual como en el caso de la clase CCalendar, hay que introducir adiciones a la clase base de la librería CWndContainer:
//+------------------------------------------------------------------+ //| Clase para almacenar todos los objetos de la interfaz | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- Estructura de los arrays de controles struct WindowElements { //--- Arrays personales de controles: //--- Array de calendarios desplegables CDropCalendar *m_drop_calendars[]; //--- public: //--- Número de calendarios desplegables int DropCalendarsTotal(const int window_index); //--- private: //--- Guarda los punteros a los controles del calendario desplegable en la base bool AddDropCalendarElements(const int window_index,CElement &object); };
Aparte de eso, a la clase principal de la librería para el procesamiento de los eventos de todos los controles, en el método CWndEvents::SetChartState(), en el que se controla el estado del gráfico, hay que añadir el bloque del código que permite comprobar los calendarios desplegables (véase la versión reducida de este método más abajo):
//+------------------------------------------------------------------+ //| Establece el estado del gráfico | //+------------------------------------------------------------------+ void CWndEvents::SetChartState(void) { int awi=m_active_window_index; //--- Para determinar el estado cuando hay que desactivar la gestión bool condition=false; //--- Comprobamos las ventanas //--- Comprobamos listas desplegables //--- Comprobamos los calendarios if(!condition) { int drop_calendars_total=CWndContainer::DropCalendarsTotal(awi); for(int i=0; i<drop_calendars_total; i++) { if(m_wnd[awi].m_drop_calendars[i].GetCalendarPointer().MouseFocus()) { condition=true; break; } } } //--- Comprobamos el foco de los menús contextuales //--- Comprobamos el estado de las barras de desplazamiento //--- Establecemos el estado del gráfico en todos los formularios for(int i=0; i<windows_total; i++) m_windows[i].CustomEventChartState(condition); }
Prueba del control “Calendario desplegable”
Añadimos dos calendarios desplegables a la interfaz gráfica del EA que hemos probado anteriormente en este artículo. Declaramos dos instancias del calendario desplegable tipo CDropCalendar en la clase personalizada de la aplicación, así como los márgenes desde el punto extremo (esquina superior izquierda) del formulario.
class CProgram : public CWndEvents { private: //--- Calendarios desplegables CDropCalendar m_drop_calendar1; CDropCalendar m_drop_calendar2; //--- private: //--- Calendarios desplegables #define DROPCALENDAR1_GAP_X (7) #define DROPCALENDAR1_GAP_Y (207) bool CreateDropCalendar1(const string text); #define DROPCALENDAR2_GAP_X (170) #define DROPCALENDAR2_GAP_Y (207) bool CreateDropCalendar2(const string text); };
Como ejemplo, mostraremos el código sólo de uno de estos métodos.
//+------------------------------------------------------------------+ //| Crea el calendario desplegable 1 | //+------------------------------------------------------------------+ bool CProgram::CreateDropCalendar1(const string text) { //--- Pasar el objeto del panel m_drop_calendar1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+DROPCALENDAR1_GAP_X; int y=m_window1.Y()+DROPCALENDAR1_GAP_Y; //--- Establecemos las propiedades antes de la creación m_drop_calendar1.XSize(145); m_drop_calendar1.YSize(20); m_drop_calendar1.AreaBackColor(clrWhiteSmoke); //--- Creamos el control if(!m_drop_calendar1.CreateDropCalendar(m_chart_id,m_subwin,text,x,y)) return(false); //--- Añadimos el puntero al control a la base CWndContainer::AddToElementsArray(0,m_drop_calendar1); return(true); }
La llamada a los métodos de la creación de controles debe ubicarse en el método principal de la creación de la interfaz. En este caso, es el método CProgram::CreateExpertPanel(). Para el ejemplo del procesamiento de eventos de los calendarios, por favor, añada el código de abajo en el método CProgram::OnEvent(). Cada vez que se cambie la fecha en los calendarios, en el registro va a mostrarse el mensaje sobre los valores de los parámetros de este evento.
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento del cambio de la fecha en el calendario if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",datetime(dparam),"; sparam: ",sparam); } }
Por favor, compile el código de la aplicación y cárguela en el gráfico. El resultado se muestra en la captura de pantalla de abajo:
Fig. 4. Prueba del control “Calendario desplegable”
Conclusión
En este artículo (el primer capítulo de la octava parte de la serie), hemos analizado un control de la interfaz gráfica bastante complejo, “Calendario”, y su versión ampliada, “Calendario desplegable”. Dichos controles irán muy bien para los gráficos con la escala de tiempo. No se trata de una versión definitiva. Se planean las actualizaciones que harán del calendario una herramienta aún más cómoda y funcional. Ustedes pueden proponer sus ideas en los comentarios.
En el siguiente artículo vamos a analizar un control de la interfaz tan importante como “Lista en forma de árbol” o “Lista jerárquica”.
Más abajo puede descargar el material de la octava 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 octava parte:
- Interfaces gráficas VIII: Control “Calendario” (Capítulo 1)
- Interfaces gráficas VIII: Control “Lista jerárquica” (Capítulo 2)
- Interfaces gráficas VIII: Control "Explorador de archivos" (Capítulo 3)
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2537





- 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