English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas VIII: Control "Calendario" (Capítulo 1)

Interfaces gráficas VIII: Control "Calendario" (Capítulo 1)

MetaTrader 5Ejemplos | 21 julio 2016, 11:00
1 662 0
Anatoli Kazharski
Anatoli Kazharski

Í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 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”.

  1. Fondo
  2. Botones para ir al siguiente mes o al anterior
  3. Control “Combobox” con la lista de los meses
  4. Campo de edición para introducir el año
  5. Array de las etiquetas de texto con nombres abreviados de los días de la semana
  6. Línea separadora
  7. Array bidimensional de las etiquetas de texto con las fechas del mes
  8. 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”.

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: 

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”.

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:

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2537

Archivos adjuntos |
Experto comercial universal: integración con los módulos estándar de señales de MetaTrader (parte 7) Experto comercial universal: integración con los módulos estándar de señales de MetaTrader (parte 7)
Esta parte está dedicada a la integración del motor comercial CStrategy con los módulos de señales incluidos en la biblioteca estándar de MetaTrader. El material describe los métodos de trabajo con las señales y la creación de estrategias de usuario basadas ellas.
Interfaces gráficas VII: Control "Pestañas" (Capítulo 2) Interfaces gráficas VII: Control "Pestañas" (Capítulo 2)
En el primer capítulo de la séptima parte han sido presentadas tres clases de los controles para la creación de las tablas: tabla de las etiquetas de texto (CLabelsTable), tabla de los campos de edición (CTable) y la tabla dibujada (CCanvasTable). En este artículo (capítulo 2) hablaremos del control “Pestañas”.
LifeHack para tráders: indicadores de balance, reducción, carga y ticks durante la simulación LifeHack para tráders: indicadores de balance, reducción, carga y ticks durante la simulación
¿Cómo convertir la simulación en algo más visual? La respuesta es sencilla: hay que usar en el simulador uno o varios indicadores, un indicador de ticks, un indicador de balance y equidad, un indicador de reducción y carga del depósito. Esto permitirá realizar un seguimiento visual de la naturaleza de los ticks, o de los cambios de balance y equidad, o de la reducción y la carga del depósito.
Interfaces gráficas VII: Control "Tablas" (Capítulo 1) Interfaces gráficas VII: Control "Tablas" (Capítulo 1)
En la séptima parte de la serie de los artículos sobre las interfaces gráficas en los terminales MetaTrader serán presentados tres tipos de tablas: tabla a base de las etiquetas de texto, tabla a base de los campos de edición y tabla dibujada. Otro control importante que se usa con bastante frecuencia son las pestañas a través de los cuales se puede ocultar o mostrar los grupos de otros controles. Eso permite al usuario diseñar las interfaces gráficas muy compactas en sus aplicaciones MQL.