English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas VIII: O Controle Calendário (Capítulo 1)

Interfaces Gráficas VIII: O Controle Calendário (Capítulo 1)

MetaTrader 5Exemplos | 19 outubro 2016, 10:28
1 400 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo


Introdução

A fim de obter uma melhor compreensão do propósito desta biblioteca, leia por favor o primeiro artigo: Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1). Em cada parte, é fornecido uma lista dos capítulos com seus respectivos links no final de cada artigo. Lá, você também poderá baixar a última versão completa da biblioteca. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.

Neste capítulo, nós vamos nos concentrar nos controles compostos complexos:

  • Calendário estático e suspenso
  • Lista hierárquica
  • Navegador de arquivos

Neste artigo, nós vamos considerar as classes usadas na criação do calendário estático e suspenso e também a estrutura (struct) CDateTime da biblioteca padrão para as operações com datas e horas, que serão utilizadas em nossos desenvolvimentos.

 

O Controle Calendário

O calendário é um sistema cíclico de cálculo do tempo apresentado em uma tabela. A interface gráfica deve ter os controles para permitir que o usuário selecione com facilidade a data necessária no calendário. Além dos métodos personalizados de interação com o usuário, outros controles da biblioteca podem ser incluídos na classe do calendário. Por exemplo, nós vamos incluir aqui as classes para criar (1) a caixa de combinação, (2) o campo de entrada e (3) os botões.

Vamos listar todos os componentes do calendário.

  1. Área
  2. Botões para alternar para os meses anteriores e seguintes
  3. Controle caixa de combinação com uma lista dos meses
  4. Campos para inserir o ano
  5. Array de rótulos de texto com abreviações dos dias da semana
  6. Linha de separação
  7. Array bidimensional de rótulos de texto com as datas
  8. Botão para saltar rapidamente para a próxima data

 

Fig. 1. Componentes do calendário.


Por exemplo, um intervalo de datas deve ser selecionado no aplicativo MQL que está sendo desenvolvido, portanto, as datas de início e fim devem ser indicadas. Basta clicar em qualquer data da tabela para escolher um dia. Existem algumas opções para escolher o mês: (1) botão para voltar ao mês anterior, (2) para avançar para o mês seguinte e (3) a caixa de combinação com uma lista de todos os meses. O ano pode ser indicado no campo, inserindo os dados manualmente ou usando as chaves de controle. A fim de mover rapidamente para a data atual, basta clicar no botão "Today: AAAA.MM.DD" na parte inferior do calendário.

Vamos dar uma olhada mais detalhada como a estrutura CDateTime é organizada para se trabalhar com datas e horas.

 

Descrição da estrutura CDateTime

O arquivo DateTime.mqh com a estrutura CDateTime são colocados nas pastas do terminal de negociação MetaTrader:

  • MetaTrader 4: <pasta de dados>\MQL4\Include\Tools 
  • MetaTrader 5: <pasta de dados>\MQL5\Include\Tools

A estrutura da CDateTime é uma derivação (extensão) da estrutura do sistema base das datas e horas chamada MqlDateTime que contém oito campos do tipo int (veja os exemplos de uso na documentação da linguagem MQL):

struct MqlDateTime
  {
   int year;           // ano
   int mon;            // mês
   int day;            // dia
   int hour;           // hora
   int min;            // minutos
   int sec;            // segundos
   int day_of_week;    // dia da semana (0-Domingo, 1-Segunda, ... ,6-Sábado)
   int day_of_year;    // número de um dia do ano (01 de janeiro tem o número 0)
  };

Uma breve descrição dos métodos da estrutura CDateTime sem o código pode ser encontrado na busca local (F1) na seção Referência MQL5/Biblioteca Padrão/ Classes para Painéis de Controle e Diálogos/CDateTime. No processo de trabalho com esta estrutura, você tem que lembrar que a numeração dos meses começa em 1, e a numeração das semanas - do 0. 

Abra o arquivo DateTime.mqh para se familiarizar com o código.

 

Desenvolvimento da classe CCalendar

Nós criamos o arquivo Calendar.mqh e incluímos ele no arquivo chamado WndContainer.mqh, assim como nós fizemos com todos os controles da biblioteca que estamos desenvolvendo.

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Calendar.mqh"

No arquivo Calendar.mqh nós criamos a classe CCalendar com os métodos padrão para todos os controles da biblioteca, e também incluímos os arquivos necessários ao desenvolvimento do controle:

//+------------------------------------------------------------------+
//|                                                     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>
//+------------------------------------------------------------------+
//| Classe para criar o calendário                                       |
//+------------------------------------------------------------------+
class CCalendar : public CElement
  {
private:
   //--- Ponteiro para o formulário ao qual o controle será anexado
   CWindow          *m_wnd;
   //---
public:
                     CCalendar(void);
                    ~CCalendar(void);
   //--- Armazena o ponteiro ao formulário
   void              WindowPointer(CWindow &object)             { m_wnd=::GetPointer(object);            }
   //---
public:
   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Mover o controle
   virtual void      Moving(const int x,const int y);
   //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Resetar a cor
   virtual void      ResetColors(void) {}
  };

Assim como os outros controles da interface da biblioteca, deve haver uma opção para definir o aspecto do calendário. A seguir estão as propriedades que se referem a aparência externa, disponíveis aos usuários.

  • Cor da área
  • Cor da borda da área
  • Cores dos elementos (datas) em diferentes estados
  • Cores das bordas dos elementos em diferentes estados
  • Cores dos elementos de texto em diferentes estados
  • Cor da linha de separação
  • Rótulos dos botões (ativo/bloqueado) para alternar para os mês anterior/seguinte

O código a seguir revela os nomes dos campos e os métodos da classe CCalendar para definir o aspecto do calendário antes dele ser criado:

class CCalendar : public CElement
  {
private:
   //--- Cor da área
   color             m_area_color;
   //--- Cor da borda da área
   color             m_area_border_color;
   //--- Cores dos elementos (datas) em 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;
   //--- Cores das bordas dos elementos em diferentes estados
   color             m_item_border_color;
   color             m_item_border_color_hover;
   color             m_item_border_color_selected;
   //--- Cores dos elementos de texto em diferentes estados
   color             m_item_text_color;
   color             m_item_text_color_off;
   color             m_item_text_color_hover;
   //--- Cor da linha de separação
   color             m_sepline_color;
   //--- Rótulos dos botões (ativo/bloqueado) para alternar para os mês anterior/seguinte
   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:
   //--- Define a cor da (1) área, (2) da borda da área e (3) da linha de separação
   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;                   }
   //--- Cores dos elementos (datas) em 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;        }
   //--- Cores das bordas dos elementos em 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;      }
   //--- Cores dos elementos de texto em 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;           }
   //--- Rótulos dos botões (ativo/bloqueado) para alternar para os mês anterior/seguinte
   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;      }
  };

Nós vamos precisar de nove métodos privados e um público para criar o calendário. Arrays estáticos de objetos do tipo CEdit são necessários para exibir os dias da semana e as datas. 

A tabela com as datas consistirão de 42 elementos. Ela é suficiente para encaixar o número máximo de dias em um mês que é igual a 31 dias, tendo em conta o deslocamento máximo do primeiro dia do mês na tabela quando este cai no domingo (domingo é o sétimo dia da semana nesta implementação).

class CCalendar : public CElement
  {
private:
   //--- Objetos e controles para criar o calendário
   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 criar o calendário
   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);
  };

Nos casos em que o calendário faz parte de outro controle, ele pode ser obrigado a ter acesso aos controles dos componentes do calendário. Por esta razão, nós vamos adicionar os métodos para a classe que retornam ponteiros aos controles listados abaixo.

  • Caixa de combinação (CComboBox)
  • Lista da caixa de combinação (CListView)
  • Barra de rolagem vertical da lista(CScrollV)
  • Campo de entrada(CSpinEdit)
  • Botão (CIconButton)
class CCalendar : public CElement
  {
public:
   //--- (1) Obtém o ponteiro da caixa de combinação 
   //    (2) Obtém o ponteiro da lista, (3) obtém o ponteiro da barra de rolagem, 
   //    (4) obtém o ponteiro do campo de entrada, (5) obtém o ponteiro do botão
   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));  }
  };

Três instâncias da estrutura CDateTime serão necessárias para trabalhar com as datas e horas.

  • Para a interação com os usuários. Uma data de escolha do usuário (selecionado no calendário).
  • Data atual ou do sistema no PC do usuário. Esta data é sempre marcada no calendário.
  • Instância para cálculos e verificações. Ele será usado como um contador em muitos métodos da classe CCalendar.
class CCalendar : public CElement
  {
private:
   //--- Instâncias de estrutura para trabalhar com as datas e horas:
   CDateTime         m_date;      // date selected by a user
   CDateTime         m_today;     // current/ system date on a user's PC
   CDateTime         m_temp_date; // instance for calculations and checks
  };

A inicialização inicial das estruturas de tempo serão realizadas no construtor da classe CCalendar. Nós vamos definir o horário local do PC do usuário (marcado em amarelo no código abaixo) nas estruturas das instâncias m_date e m_today.

//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
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("")
  {
//--- Armazena o nome da classe de controle na classe base
   CElement::ClassName(CLASS_NAME);
//--- Estabelece as prioridades para clicar com o botão esquerdo do mouse
   m_zorder        =0;
   m_area_zorder   =1;
   m_button_zorder =2;
//--- Inicialização das estruturas de tempo
   m_date.DateTime(::TimeLocal());
   m_today.DateTime(::TimeLocal());
  }

Depois de definir o calendário, os valores do mês atual e do ano devem ser definidos nos elementos da tabela (dates). Quatro métodos serão necessários para esse fim.

1. O método CCalendar::OffsetFirstDayOfMonth() é utilizado para calcular a diferença (em dias) desde o primeiro elemento da tabela até o primeiro dia do mês atual. No início deste método nós formamos uma data em uma string com a primeira data do mês e ano atual. Em seguida, convertendo a string para o formato de data e hora, nós vamos definir a data e a estrutura para os cálculos. Se 1 é deduzido do número atual da semana e o resultado for maior ou igual a zero, nós definimos o resultado para retornar (return), mas se estiver abaixo de zero (-1), então, ele retorna 6. No final do método é realizado um deslocamento para trás pela diferença obtida (número de dias).
class CCalendar : public CElement
  {
private:
   //--- Calcula a diferença a partir do primeiro elemento da tabela do calendário até o elemento do primeiro dia do mês atual
   int               OffsetFirstDayOfMonth(void);
  };
//+------------------------------------------------------------------+
//| Define a diferença a partir do primeiro elemento da guia do      |
//| calendário até o elemento do primeiro dia do mês aual            |
//+------------------------------------------------------------------+
int CCalendar::OffsetFirstDayOfMonth(void)
  {
//--- Obtém a data do primeiro dia do ano e mês selecionado em uma string
   string date=string(m_date.year)+"."+string(m_date.mon)+"."+string(1);
//--- Define esta data na estrutura para os cálculos
   m_temp_date.DateTime(::StringToTime(date));
//--- Se o resultado da dedução de 1 a partir do número atual do dia da semana for maior ou igual a 0,
//    retora o resultado, caso contrário — retorna 6
   int diff=(m_temp_date.day_of_week-1>=0) ? m_temp_date.day_of_week-1 : 6;
//--- Armazena a data que está no primeiro elemento da tabela
   m_temp_date.DayDec(diff);
   return(diff);
  }

2. O método CCalendar::SetCalendar(). Este método é usado para preencher os elementos da tabela para o ano e mês selecionado. O método CCalendar::OffsetFirstDayOfMonth() é chamado no início. Em seguida, no loop, nós iteramos sobre todos os elementos da tabela definindo um número do dia a partir da estrutura de cálculos (m_temp_date.day) em cada elemento, onde no método CCalendar::OffsetFirstDayOfMonth() a data usada para contar é definida. Uma mudança para a próxima data do calendário é realizada no final do método.

Class CCalendar : public CElement
  {
private:
   //--- Mostra as últimas mudanças na tabela do calendário
   void              SetCalendar(void);
  };
//+------------------------------------------------------------------+
//| Define os valores do calendário                                  |
//+------------------------------------------------------------------+
void CCalendar::SetCalendar(void)
  {
//--- Calcula a diferença a partir do primeiro elemento da tabela do calendário até o elemento do primeiro dia do mês atual
   int diff=OffsetFirstDayOfMonth();
//--- Itera sobre todos os elementos da tabela do calendário no loop
   for(int i=0; i<42; i++)
     {
      //--- Define o dia no elemento atual da tabela
      m_days[i].Description(string(m_temp_date.day));
      //--- Move para a próxima data
      m_temp_date.DayInc();
     }
  }

3. O método CCalendar::HighlightDate() será usado para destacar a data atual (data do sistema no PC do usuário) e a data selecionada por um usuário na tabela do calendário. A data do primeiro elemento da tabela é definido aqui primeiro, na estrutura para os cálculos (m_temp_date). Então, as cores para (1) a área, (2) área da borda (3) e o texto exibido são determinados no loop (veja o código abaixo). 

Class CCalendar : public CElement
  {
private:
   //--- Realça a data atual e a data do usuário selecionado
   void              HighlightDate(void);
  };
//+------------------------------------------------------------------+
//| Realça a data atual e a data do usuário selecionado              |
//+------------------------------------------------------------------+
void CCalendar::HighlightDate(void)
  {
//--- Calcula a diferença a partir do primeiro elemento da tabela do calendário até o elemento do primeiro dia do mês atual
   OffsetFirstDayOfMonth();
//--- Itera sobre os elementos da tabela no loop
   for(int i=0; i<42; i++)
     {
      //--- Se um mês do elemento corresponde ao mês atual e 
      //    a data condiz com uma data selecionada
      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);
         //--- Avança para o próximo item da tabela
         m_temp_date.DayInc();
         continue;
        }
      //--- Se esta é uma data atual (hoje)
      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);
         //--- Avança para o próximo item da tabela
         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);
      //--- Avança para o próximo item da tabela
      m_temp_date.DayInc();
     }
  }

4. O método CCalendar::UpdateCalendar() será usado em todos os métodos aplicados para interação do usuário com a interface gráfica do calendário. Os métodos CCalendar::SetCalendar() e CCalendar::HighlightDate() que mencionamos anteriormente são consecutivamente chamados aqui. Depois, o ano e mês são definidas na caixa campo de entrada e de combinação do calendário. Por favor note que para selecionar o elemento desejado na lista caixa de combinação, um mês deveria ter 1 dedução porque a enumeração dos meses começa a partir de 1 nas estruturas de data e hora, e a enumeração de itens nas listas (CListView) da biblioteca - Do 0. 

Class CCalendar : public CElement
  {
public:
   //--- Exibe as últimas mudanças no calendário
   void              UpdateCalendar(void);
  };
//+------------------------------------------------------------------+
//| Exibe as últimas mudanças no calendário                          |
//+------------------------------------------------------------------+
void CCalendar::UpdateCalendar(void)
  {
//--- Exibe as alterações na tabela do calendário
   SetCalendar();
//--- Realça a data atual e a data do usuário selecionado
   HighlightDate();
//--- Ajusta o ano no campo de entrada
   m_years.ChangeValue(m_date.year);
//--- Ajusta o mês na lista da caixa de combinação
   m_months.SelectedItemByIndex(m_date.mon-1);
  }

A alteração das cores dos elementos de datas na tabela do calendário quando o mouse estiver sobre ele será realizada com o método CCalendar::ChangeObjectsColor(). As coordenadas x, y terão de ser enviadas para lá. Antes de iniciar o loop que envolve a iteração de todos os elementos, nós determinamos a extensão do deslocamento do elemento com o primeiro dia do mês a partir do primeiro elemento da tabela para definir o valor inicial do contador (a estrutura m_temp_date). Então, o dia selecionado e a data atual (data do sistema no PC do usuário) são ignorados e o resto é utilizado para verificar o foco do cursor do mouse. Quando a cor é alterada, o mês que corresponde o dia é levado em consideração.

Class CCalendar : public CElement
  {
public:
   //--- Altera a cor do objeto na tabela do calendário 
   void              ChangeObjectsColor(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Altera a cor dos objetos na tabela do calendário                 |
//| quando estiver sobre ele                                         |
//+------------------------------------------------------------------+
void CCalendar::ChangeObjectsColor(const int x,const int y)
  {
//--- Calcula a diferença a partir do primeiro elemento da tabela do calendário até o elemento do primeiro dia do mês atual
   OffsetFirstDayOfMonth();
//--- Itera sobre os elementos da tabela no loop
   int items_total=::ArraySize(m_days);
   for(int i=0; i<items_total; i++)
     {
      //--- Se um mês do elemento corresponde ao mês atual e 
      //    a data condiz com uma data selecionada
      if(m_temp_date.mon==m_date.mon &&
         m_temp_date.day==m_date.day)
        {
         //--- Avança para o próximo item da tabela
         m_temp_date.DayInc();
         continue;
        }
      //--- Se o ano/mês/dia do elemento corresponder com o ano/mês/dia da data atual (hoje)
      if(m_temp_date.year==m_today.year && 
         m_temp_date.mon==m_today.mon &&
         m_temp_date.day==m_today.day)
        {
         //--- Avança para o próximo item da tabela
         m_temp_date.DayInc();
         continue;
        }
      //--- Se o cursor do mouse está 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);
        }
      //--- Avança para o próximo item da tabela
      m_temp_date.DayInc();
     }
  }

É necessário de uma opção para selecionar uma data no calendário usando o programa, bem como para obter uma data selecionada pelo usuário e o dia atual (hoje). Por isso, será necessário adicionar os métodos públicos CCalendar::SelectedDate() e CCalendar::Today().

Class CCalendar : public CElement
  {
public:
   //--- (1) Define (seleciona) e (2) obtém uma data selecionada, (3) obtém uma data atual no calendário.
   void              SelectedDate(const datetime date);
   datetime          SelectedDate(void)                         { return(m_date.DateTime());             }
   datetime          Today(void)                                { return(m_today.DateTime());            }
  };
//+------------------------------------------------------------------+
//| Seleção de uma nova data                                         |
//+------------------------------------------------------------------+
void CCalendar::SelectedDate(const datetime date)
  {
//--- Armazena a data na estrutura e no campo da classe
   m_date.DateTime(date);
//--- Exibe as últimas mudanças no calendário
   UpdateCalendar();
  }

Por exemplo, 29 de fevereiro de 2016 (2016.02.29) é selecionado no calendário, e um usuário digitou (ou definiu na chave de incremento) 2017 no campo de entrada. Há 28 dias em Fevereiro de 2017. Que dia deve ser selecionado na tabela do calendário neste caso? Normalmente, nestes casos, os calendários com interfaces gráficas diferentes têm o dia mais próximo selecionado, que é o último dia do mês. Em nosso caso, é o dia 28 de Fevereiro de 2017. Então, nós faremos o mesmo. Para esse efeito, o método CCalendar::CorrectingSelectedDay() irá ser adicionado à classe para uma correção semelhante. O código deste método consiste em um par de strings. A estrutura CDateTime já possui um método para receber um certo número de dias em um mês levando em consideração um avanço no ano — CDateTime::DaysInMonth(). Por isso, é suficiente comparar o dia do mês atual com um número de dias no mês atual, e se for verificado que o número de dias excedeu o número selecionado no calendário, então ele deve ser substituído.

Class CCalendar : public CElement
  {
private:
   //--- Corrige o dia selecionado pelo número de dias em um mês
   void              CorrectingSelectedDay(void);
  };
//+------------------------------------------------------------------+
//| Determina o primeiro dia de um mês                               |
//+------------------------------------------------------------------+
void CCalendar::CorrectingSelectedDay(void)
  {
//--- Define o número atual de dias em um mês, se um valor do dia selecionado for maior
   if(m_date.day>m_date.DaysInMonth())
      m_date.day=m_date.DaysInMonth();
  }

 

Manipuladores de eventos do calendário

Agora vamos olhar para os manipuladores dos eventos do calendário. Haverá um total de oito métodos privados para lidar com os eventos da lista abaixo.

  • Clique no botão para ir para o mês anterior
  • Clique no botão para ir para o próximo mês
  • Seleciona um mês a partir de uma lista suspensa da caixa de combinação
  • Digita o valor no campo de entrada do ano
  • Clique no botão para ir para o próximo ano
  • Clique no botão para ir para o ano anterior
  • Clique em um dia do mês
  • Clique no botão para ir para a data atual
  • Cada ação da lista acima leva a alguma mudança no calendário. Vamos ser metódicos sobre o código desses métodos.

Para a operação com o calendário é necessário ter um novo identificador de evento do usuário ON_CHANGE_DATE que será usado para enviar mensagens indicando que o usuário alterou uma data no calendário. Além disso, nós adicionamos mais um identificador para trabalhar com uma caixa de combinação - ON_CLICK_COMBOBOX_BUTTON para determinar o clique no botão deste controle. 

#define ON_CLICK_COMBOBOX_BUTTON  (21) // Clique no botão da caixa de combinação
#define ON_CHANGE_DATE            (22) // Altera a data no calendário

O envio de uma mensagem com o identificador ON_CLICK_COMBOBOX_BUTTON deve ser realizado a partir do método OnClickButton() da classe CComboBox. Vamos adicionar a opção de enviar mensagens para este método (Veja o código abaixo):

//+------------------------------------------------------------------+
//| Clique no botão da caixa de combinação                           |
//+------------------------------------------------------------------+
bool CComboBox::OnClickButton(const string clicked_object)
  {
//--- Sai se o formulário está bloqueado e os identificadores não correspondem
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return(false);
//--- Sai se ele tem um nome do objeto diferente  
   if(clicked_object!=m_button.Name())
      return(false);
//--- Altera o estado de uma lista
   ChangeComboBoxListState();
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_BUTTON,CElement::Id(),0,"");
   return(true);
  }

Será necessário acompanhar o evento personalizado com o identificador ON_CLICK_COMBOBOX_BUTTON na classe CCalendar para gerenciar o estado dos controles (CSpinEdit e CIconButton), que fazem parte do calendário (veja o código abaixo): 

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipula o evento clique no botão da caixa de combinação
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_BUTTON)
     {
      //--- Sai se os identificadores dos controles não coincidem
      if(lparam!=CElement::Id())
         return;
      //--- Ativa ou bloqueia os controles, dependendo do estado atual de visibilidade da lista
      m_years.SpinEditState(!m_months.GetListViewPointer().IsVisible());
      m_button_today.ButtonState(!m_months.GetListViewPointer().IsVisible());
     }
  }

Em interfaces gráficas de vários sistemas, por exemplo, no sistema operacional Windows 7, um deslocamento para o mês anterior/seguinte através do clique nos botões de setas para a direita/esquerda para a seleção do primeiro dia do mês, apesar do fato de que o dia foi previamente selecionado por um usuário na tabela do calendário. Nós implementamos o mesmo comportamento no calendário da biblioteca em desenvolvimento. A fim de proceder com o mês anterior/seguinte, serão utilizados os métodos CCalendar::OnClickMonthDec() e CCalendar::OnClickMonthInc(). O código destes métodos são muito semelhantes. Por exemplo, nós vamos fornecer uma descrição de um deles, ou seja, manipular o evento do clique do botão para ir ao mês anterior.

Um nome do objeto gráfico que o usuário clicou é verificado no início do método. Então, se o ano em curso no calendário for igual ao mínimo especificado, e o mês atual for "Janeiro" (ou seja, nós atingimos a restrição mínima), nós vamos destacar brevemente o texto no campo de entrada e saída do método. 

Se nós não chegamos a restrição mínima, então, ocorre o seguinte.

  • Define o estado do botão para On (ligado).
  • Vai para o próximo mês. 
  • Define o primeiro número do mês.
  • Reseta o tempo (00:00:00). Para este efeito, o método CCalendar::ResetTime() é adicionado à classe.
  • Atualiza o calendário para exibir as últimas alterações.
  • Nós enviamos uma mensagem com o (1) identificador do gráfico, (2) o identificador do evento ON_CHANGE_DATE, (3) o identificador do controle e (4) a data especificada. As mensagens com os mesmos parâmetros também serão enviados a partir de outros métodos de manipulação de eventos de calendário. 
Class CCalendar : public CElement
  {
private:
   //--- Manipulação do clique no botão para ir ao mês anterior
   bool              OnClickMonthDec(const string clicked_object);
   //--- Manipulação do clique no botão para ir ao mês seguinte
   bool              OnClickMonthInc(const string clicked_object);
   //--- Reseta o tempo
   void              ResetTime(void);
  };
//+------------------------------------------------------------------+
//| Clique na seta para a esquerda. Vai para o mês anterior.                |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthDec(const string clicked_object)
  {
//--- Sai se ele tem um nome do objeto diferente
   if(::StringFind(clicked_object,m_month_dec.Name(),0)<0)
      return(false);
//--- Se o ano atual do calendário for igual ao mínimo indicado e
//    o mês atual for "Janeiro"
   if(m_date.year==m_years.MinValue() && m_date.mon==1)
     {
      //--- Destaca o valor e sai
      m_years.HighlightLimit();
      return(true);
     }
//--- Define o estado para On (ligado)
   m_month_dec.State(true);
//--- Vai para o mês anterior
   m_date.MonDec();
//--- Define o primeiro dia do mês
   m_date.day=1;
//--- Reseta o tempo
   ResetTime();
//--- Exibe as últimas mudanças no calendário
   UpdateCalendar();
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }
//+------------------------------------------------------------------+
//| Reseta o tempo                                                   |
//+------------------------------------------------------------------+
void CCalendar::ResetTime(void)
  {
   m_date.hour =0;
   m_date.min  =0;
   m_date.sec  =0;
  }

O código no método CCalendar::OnClickMonthInc() é quase o mesmo. A única diferença é que, ao verificar a restrição, é verificado a saída em relação ao ano máximo e o último mês do ano (dezembro) definido nas propriedades do calendário.

A seleção do mês na lista é tratada com o método CCalendar::OnClickMonthList() com a chegada do evento com o identificador ON_CLICK_COMBOBOX_ITEM (veja o código abaixo). No manipulador principal, o parâmetro long que contém o identificador de controle é enviado para este método. Se os identificadores não corresponderem, então o programa sai do método. Já que a seleção do elemento na lista leva ao seu encerramento, então nós devemos desbloquear os controles anteriormente bloqueados (campo e botão de entrada) do calendário. Em seguida, o mês selecionado é definido na estrutura de data e hora, e em caso de necessidade nós corrigimos o dia selecionado dependendo dos dias em um mês. Ele simplesmente continua a definir o tempo, exibir as últimas alterações feitas no calendário e enviar uma mensagem para o manipulador de eventos que a data no calendário foi alterada.

Class CCalendar : public CElement
  {
private:
   //--- Manipula o mês selecionado na lista
   bool              OnClickMonthList(const long id);
  };
//+------------------------------------------------------------------+
//| Manipula a seleção do mês na lista                               |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthList(const long id)
  {
//--- Sai se os identificadores dos controles não coincidem
   if(id!=CElement::Id())
      return(false);
//--- Desbloqueia os controles
   m_years.SpinEditState(true);
   m_button_today.ButtonState(true);
//--- Obtém o mês selecionado em uma lista
   int month=m_months.GetListViewPointer().SelectedItemIndex()+1;
   m_date.Mon(month);
//--- Corrige o dia selecionado pelo número de dias em um mês
   CorrectingSelectedDay();
//--- Reseta o tempo
   ResetTime();
//--- Exibe as alterações na tabela do calendário
   UpdateCalendar();
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

O método CCalendar::OnEndEnterYear() é usado para lidar com a entrada do valor para o campo de entrada do ano. No início deste método, é verificado o nome do objeto do tipo OBJ_EDIT, onde um novo valor foi inserido. Além disso, haverá uma outra verificação para confirmar se o valor foi alterado. Se o nome do campo de entrada não pertencer a este calendário ou o valor não foi alterado, então o programa sai do método. Se as duas primeiras verificações são passadas, então o valor inserido ainda é corrigido em caso de exceder as restrições especificadas. A próxima correção altera a data selecionada pelo utilizador, se o número de dias em um mês exceder o número de dias durante o mês. Após isso, você pode definir a data na estrutura operacional de data e hora para mostrar as alterações feitas no calendário e enviar uma mensagem de que a data neste calendário foi alterada. O código do método CCalendar::OnEndEnterYear() pode ser cuidadosamente estudado logo abaixo: 

Class CCalendar : public CElement
  {
private:
   //--- Lida com o valor de entrada no campo de entrada do ano
   bool              OnEndEnterYear(const string edited_object);
  };
//+------------------------------------------------------------------+
//| Lida com o valor de entrada no campo de entrada do ano           |
//+------------------------------------------------------------------+
bool CCalendar::OnEndEnterYear(const string edited_object)
  {
//--- Sai se ele tem um nome do objeto diferente
   if(::StringFind(edited_object,m_years.Object(2).Name(),0)<0)
      return(false);
//--- Sai se o valor não foi alterado
   string value=m_years.Object(2).Description();
   if(m_date.year==(int)value)
      return(false);
//--- Corrige o valor no caso dele ir além das restrições previstas
   if((int)value<m_years.MinValue())
     {
      value=(string)int(m_years.MinValue());
      //--- Destaca o valor
      m_years.HighlightLimit();
     }
   if((int)value>m_years.MaxValue())
     {
      value=(string)int(m_years.MaxValue());
      //--- Destaca o valor
      m_years.HighlightLimit();
     }
//--- Define o número de dias no mês atual
   string year  =value;
   string month =string(m_date.mon);
   string day   =string(1);
   m_temp_date.DateTime(::StringToTime(year+"."+month+"."+day));
//--- Se o valor do dia selecionado exceder o número de dias em um mês,
//    define o número atual de dias em um mês como um dia selecionado
   if(m_date.day>m_temp_date.DaysInMonth())
      m_date.day=m_temp_date.DaysInMonth();
//--- Define uma data na estrutura
   m_date.DateTime(::StringToTime(year+"."+month+"."+string(m_date.day)));
//--- Exibe as mudanças na tabela do calendário
   UpdateCalendar();
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Os métodos para lidar com o clique nos botões para ir para os anos seguintes/anteriores são os CCalendar::OnClickYearInc() e CCalendar::OnClickYearDec(). O código de apenas um deles será fornecida aqui uma vez que eles são praticamente idênticos, com exceção das condições de verificar se as restrições foram atingidas. No início, é verificado o estado atual da lista para a seleção de um mês. Nós vamos fechá-lo, se ele estiver aberto. Além disso, se os identificadores do controle não corresponderem, o programa sai daqui. A condição principal dos métodos é seguida, onde um passo para a frente é executado na condição do CCalendar::OnClickYearInc(), ou um passo para trás para o CCalendar::OnClickYearDec(). Em seguida, se necessário, (1) o dia selecionado é corrigido com base no número de dias em um mês, (2) as últimas alterações são mostradas no calendário e (3) é enviado uma mensagem notificando que a data no calendário foi alterada.  

Class CCalendar : public CElement
  {
private:
   //--- Manipulação do clique no botão para ir ao ano seguinte
   bool              OnClickYearInc(const long id);
   //--- Manipulação do clique no botão para ir ao ano anterior
   bool              OnClickYearDec(const long id);
  };
//+------------------------------------------------------------------+
//| Manipulação do clique no botão para ir ao ano seguinte           |
//+------------------------------------------------------------------+
bool CCalendar::OnClickYearInc(const long id)
  {
//--- Se a lista de meses está aberta, nós fecharemos
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Sai se os identificadores dos controles não coincidem
   if(id!=CElement::Id())
      return(false);
//--- Se um ano está abaixo da máxima especificada, é preciso aumentar o valor por um
   if(m_date.year<m_years.MaxValue())
      m_date.YearInc();
//--- Corrige o dia selecionado pelo número de dias em um mês
   CorrectingSelectedDay();
//--- Exibe as alterações na tabela do calendário
   UpdateCalendar();
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Agora nós estamos considerando o método CCalendar::OnClickDayOfMonth() para lidar com o clique em um dia do mês na tabela do calendário. 

Como mencionado anteriormente, a tabela do calendário consiste de 42 elementos. Portanto, dependendo do elemento do primeiro número do mês atual, que por vezes ocorre que existem dias dos meses anteriores e seguintes na tabela. Em casos quando o usuário clica em um dia que não pertence ao mês atual, será executada a mudança para o mês da data selecionada. 

No início do método nós precisamos passar uma verificação para o objeto pertencente ao elemento da tabela do calendário e também comparar os identificadores do controle. Um identificador é extraído a partir do nome do objeto utilizando o método CCalendar::IdFromObjectName() que tem sido anteriormente considerado no caso de outros controles da biblioteca. Se as duas primeiras verificações são passadas, então, usando o método CCalendar::OffsetFirstDayOfMonth() é determinado uma diferença do primeiro elemento da tabela até o primeiro dia do mês atual. Depois de chamar esse método, o contador m_temp_date estará pronto para o trabalho, e posteriormente no loop o programa irá iterar sobre todos os elementos para encontrar aquele que o usuário clicou. 

Vamos tentar entender a lógica por trás deste loop. É possível que as datas nos primeiros elementos referem-se ao ano anterior àquele definido no sistema como o mínimo (ano de 1970). Se este for o caso, nós destacamos o valor no campo de entrada de ano e imediatamente vamos para o próximo elemento, porque é proibido para o usuário passar as restrições especificadas. Se o elemento está na área disponível e ele foi clicado, então, (1) a data é armazenada na estrutura operacional (m_date), (2) o calendário mostra as últimas mudanças, (3) o loop é interrompido e (4) é enviado uma mensagem indicando que a data no calendário foi alterada no final do método.

Se nenhuma das duas primeiras condições do loop foram satisfeitas, então o contador da estrutura para os cálculos (m_temp_date) é aumentado por um dia, seguindo pela verificação da saída além do máximo definido no sistema do calendário. Se o máximo ainda não foi alcançado, o programa passa para o próximo elemento. Depois de alcançar a máxima, o valor destacado é chamado no campo de entrada do ano, e o programa sai do método. 

Class CCalendar : public CElement
  {
private:
   //--- Manipula o clique em um dia do mês
   bool              OnClickDayOfMonth(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Manipula o clique em um dia do mês                               |
//+------------------------------------------------------------------+
bool CCalendar::OnClickDayOfMonth(const string clicked_object)
  {
//--- Sai se o clique não estiver no dia do calendário
   if(::StringFind(clicked_object,CElement::ProgramName()+"_calendar_day_",0)<0)
      return(false);
//--- Obtém o identificador e o índice do nome do objeto
   int id=IdFromObjectName(clicked_object);
//--- Sai, se o identificador não corresponder
   if(id!=CElement::Id())
      return(false);
//--- Calcula a diferença a partir do primeiro elemento da tabela do calendário até o elemento do primeiro dia do mês atual
   OffsetFirstDayOfMonth();
//--- Itera sobre os elementos da tabela no loop
   for(int i=0; i<42; i++)
     {
      //--- Se a data do elemento atual for inferior ao mínimo estabelecido pelo sistema
      if(m_temp_date.DateTime()<datetime(D'01.01.1970'))
        {
         //--- Se este é o objeto que nós clicamos
         if(m_days[i].Name()==clicked_object)
           {
            //--- Destaca o valor e sai
            m_years.HighlightLimit();
            return(false);
           }
         //--- Move para a próxima data
         m_temp_date.DayInc();
         continue;
        }
      //--- Se este é o objeto que nós clicamos
      if(m_days[i].Name()==clicked_object)
        {
         //--- Armazena a data
         m_date.DateTime(m_temp_date.DateTime());
         //--- Exibe as últimas mudanças no calendário
         UpdateCalendar();
         break;
        }
      //--- Move para a próxima data
      m_temp_date.DayInc();
      //--- Verifica a saída além do máximo definido no sistema
      if(m_temp_date.year>m_years.MaxValue())
        {
         //--- Destaca o valor e sai
         m_years.HighlightLimit();
         return(false);
        }
     }
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

É deixado de considerar o último método do manipulador de eventos do calendário - CCalendar::OnClickTodayButton() que é usado para lidar com o clique no botão para ir para a data atual. O parâmetro long (identificador do controle) é enviado a partir do manipulador de eventos principal. No início do método, a lista será fechada em caso dela estiver aberta no momento. Em seguida, os identificadores são comparados. Se os identificadores corresponderem, nós definimos uma data e hora local (como no PC do usuário) na estrutura operacional. Em seguida, o calendário é atualizado para exibir as últimas alterações e no final do método é enviado uma mensagem informando que a data neste calendário foi alterada

Class CCalendar : public CElement
  {
private:
   //--- Manipulação do clique no botão para ir até a data atual
   bool              OnClickTodayButton(const long id);
  };
//+------------------------------------------------------------------+
//| Manipulação do clique no botão para ir até a data atual          |
//+------------------------------------------------------------------+
bool CCalendar::OnClickTodayButton(const long id)
  {
//--- Se a lista de meses está aberta, nós fecharemos
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Sai se os identificadores dos controles não coincidem
   if(id!=CElement::Id())
      return(false);
//--- Define a data atual
   m_date.DateTime(::TimeLocal());
//--- Exibe as últimas mudanças no calendário
   UpdateCalendar();
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }


Há oito blocos de códigos (um já foi fornecido no início desta seção) no manipulador de eventos principal do calendário CCalendar::OnEvent(). Nós vamos enumerá-los.

  • Manipula os eventos de movimentação do cursor do mouse (CHARTEVENT_MOUSE_MOVE). O acompanhando do cursor do mouse é executada somente quando um controle não estiver oculto, e também no casos em que o controle é suspenso e o formulário não está bloqueado. O programa sai deste bloco, mesmo quando uma lista de meses está ativa (aberta). Quando a lista está oculta e o botão esquerdo do mouse é clicado, os controles do calendário bloqueados anteriormente (no momento da abertura da lista) são ativados, se pelo menos um deles ainda estiver bloqueado.  No final, as coordenadas do cursor do mouse para alterar a cor dos elementos da tabela são enviados para o método CCalendar::ChangeObjectsColor(). 
//--- Manipula o evento de mover o mouse
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai, se o controle está oculto
      if(!CElement::IsVisible())
         return;
      //--- Sai, se não for um controle suspenso e o formulário está bloqueado
      if(!CElement::IsDropdown() && m_wnd.IsLocked())
         return;
      //--- Coordenadas e o estado do botão esquerdo do mouse
      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());
      //--- Sai se a lista de meses estiver ativa
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- Se uma lista não está ativa e o botão esquerdo do mouse é clicado...
      else if(m_mouse_state)
        {
         //--- ... ativa os controles que foram previamente bloqueados (no momento da abertura da lista),
         //       se pelo menos um deles não está bloqueado
         if(!m_button_today.ButtonState())
           {
            m_years.SpinEditState(true);
            m_button_today.ButtonState(true);
           }
        }
      //--- Altera as cores dos objetos
      ChangeObjectsColor(x,y);
      return;
     }

  • Manipulação do evento de clicar no botão esquerdo do mouse sobre o objeto (CHARTEVENT_OBJECT_CLICK). Isto ocorre depois de clicar em qualquer objeto do calendário. Então, se o sinalizador é definido no clique do botão esquerdo do mouse, os controles do calendário são ativados (campo e botão de entrada). Ainda é verificado qual o controle que foi clicado. Para esta questão nós usamos os métodos anteriormente mencionados CCalendar::OnClickMonthDec(), CCalendar::OnClickMonthInc() e CCalendar::OnClickDayOfMonth().  
//--- Manipula o evento de clicar no botão esquerdo do mouse sobre o objeto
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Sai se o formulário está bloqueado e os identificadores não correspondem
      if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
         return;
      //--- Sai se a lista de meses estiver ativa
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- Ativa os controles (lista e campo de entrada), se o botão esquerdo do mouse for clicado 
      if(m_mouse_state)
        {
         m_years.SpinEditState(true);
         m_button_today.ButtonState(true);
        }
      //--- Manipula o clique nos botões para trocar os meses
      if(OnClickMonthDec(sparam))
         return;
      if(OnClickMonthInc(sparam))
         return;
      //--- Manipula o clique no dia do calendário
      if(OnClickDayOfMonth(sparam))
         return;
     }

  • Manipula o evento do clique sobre o elemento da lista caixa de combinação (ON_CLICK_COMBOBOX_ITEM). Para lidar com este evento, é utilizado o método CCalendar::OnClickMonthList().  
//--- Manipula o evento do clique sobre o elemento da lista com a caixa de combinação
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Manipula o mês selecionado na lista
      if(!OnClickMonthList(lparam))
         return;
      //---
      return;
     }

  •  Manipula o evento do clique nos botões de aumentar/diminuir (ON_CLICK_INC/ON_CLICK_DEC). Para lidar com esses eventos, são exibidos dois blocos de código separados para chamar os métodos CCalendar::OnClickYearInc() e CCalendar::OnClickYearDec()
//--- Manipula o evento de clique no botão de incremento
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC)
     {
      //--- Manipulação do clique no botão para ir ao ano seguinte
      if(!OnClickYearInc(lparam))
         return;
      //---
      return;
     }
//--- Manipula o evento de clique no botão de decremento
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      //--- Manipulação do clique no botão para ir ao ano anterior
      if(!OnClickYearDec(lparam))
         return;
      //---
      return;
     }

  • Manipula o evento do valor digitado no campo de entrada (CHARTEVENT_OBJECT_ENDEDIT). Entrando o valor no campo de entrada do ano levará o programa a este bloco de código para chamar o método CCalendar::OnEndEnterYear(). 
//--- Manipula o evento do valor digitado no campo de entrada
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      //--- Lida com o valor de entrada no campo de entrada do ano
      if(OnEndEnterYear(sparam))
         return;
      //---
      return;
     }

  • Manipula o evento de clique no botão (ON_CLICK_BUTTON). Neste bloco de código, um clique sobre o botão para avançar para a próxima data (data local em um PC do usuário) é tratado com o método CCalendar::OnClickTodayButton():
//--- Manipula o evento de clique no botão
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Manipulação do clique no botão para ir até a data atual
      if(!OnClickTodayButton(lparam))
         return;
      //---
      return;
     }

Para tornar o uso de tal controle complexo como um calendário mais conveniente, nós vamos adicionar as funcionalidades que iriam permitir a troca rápida de seus valores. Algo semelhante já foi implementado nas classes da lista (CListView) e o campo de entrada (CSpinEdit). Ele deve ser conectado à tabela do calendário agora para obter as atualizações dos valores na tabela para todas as alterações que ocorrem quando há a troca rápida de valores no campo de entrada. Nós também vamos adicionar a opção de troca rápida do calendário, utilizando os botões para a troca dos meses. 

Nós vamos fornecer aqui uma descrição somente para a parte principal (operacional) do método, porque as condições para a entrada do bloco operacional já foram consideradas em outros artigos. Após as condições serem atendidas, é preciso verificar qual botão dos controles do calendário que foi clicado (veja as strings destacadas em amarelo). Se nenhum dos botões listado está premido, então o programa sairá do método. Após a detecção de que um botão foi clicado, algumas verificações devem ser feitas para determinar se o valor vai além das restrições definidas no calendário. Se nós chegamos até a restrição, então, o texto no campo de entrada é destacado e o programa sairá do método. Se a restrição não é atingida, o calendário é atualizado para exibir as últimas alterações, e uma mensagem para notificar que a data mudou é enviada

Class CCalendar : public CElement
  {
private:
   //--- Troca rápida dos valores no calendário
   void              FastSwitching(void);
  };
//+------------------------------------------------------------------+
//| Troca rápida dos valores no calendário                           |
//+------------------------------------------------------------------+
void CCalendar::FastSwitching(void)
  {
//--- Sai se não houver foco no controle
   if(!CElement::MouseFocus())
      return;
//--- Sai se o formulário está bloqueado e os identificadores não correspondem
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return;
//--- Retorna o contador para o valor inicial quando o botão do mouse for liberado
   if(!m_mouse_state)
      m_timer_counter=SPIN_DELAY_MSC;
//--- Se o botão do mouse foi pressionado
   else
     {
      //--- Aumenta o contador pelo intervalo definido
      m_timer_counter+=TIMER_STEP_MSC;
      //--- Sai, se menor que zero
      if(m_timer_counter<0)
         return;
      //--- Se houve o clique esquerdo do mouse
      if(m_month_dec.State())
        {
         //--- Se o ano atual do calendário for igual/exceder ao mínimo indicado e
         if(m_date.year>=m_years.MinValue())
           {
            //--- Se o ano atual do calendário já for igual ao mínimo indicado e
            //    o mês atual for "Janeiro"
            if(m_date.year==m_years.MinValue() && m_date.mon==1)
              {
               //--- Destaca o valor e sai
               m_years.HighlightLimit();
               return;
              }
            //--- Avança para o próximo mês (para baixo)
            m_date.MonDec();
            //--- Define o primeiro dia do mês
            m_date.day=1;
           }
        }
      //--- Se a seta para a direita foi clicada
      else if(m_month_inc.State())
        {
         //--- Se o ano no calendário for inferior/igual a máxima especificada
         if(m_date.year<=m_years.MaxValue())
           {
            //--- Se o ano atual do calendário já for igual ao máxima indicada e
            //    o mês atual for "Dezembro"
            if(m_date.year==m_years.MaxValue() && m_date.mon==12)
              {
               //--- Destaca o valor e sai
               m_years.HighlightLimit();
               return;
              }
            //--- Vai para o próximo mês (avança).
            m_date.MonInc();
            //--- Define o primeiro dia do mês
            m_date.day=1;
           }
        }
      //--- Se o botão de incremento do campo de entrada do ano foi clicado
      else if(m_years.StateInc())
        {
         //--- Se está abaixo do ano máximo especificado,
         //--- Vai para o próximo ano (avança).
         if(m_date.year<m_years.MaxValue())
            m_date.YearInc();
         else
           {
            //--- Destaca o valor e sai
            m_years.HighlightLimit();
            return;
           }
        }
      //--- Se o botão de decremento do campo de entrada foi clicado
      else if(m_years.StateDec())
        {
         //--- Se excedeu o ano mínimo especificado,
         //--- Vai para o próximo ano (avança).
         if(m_date.year>m_years.MinValue())
            m_date.YearDec();
         else
           {
            //--- Destaca o valor e sai
            m_years.HighlightLimit();
            return;
           }
        }
      else
        return;
      //--- Exibe as últimas mudanças no calendário
      UpdateCalendar();
      //--- Envia uma mensagem sobre ele
      ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
     }
  }

No momento de mudar de um dia para o outro, o dia atual deve ser atualizado (hora local do PC do usuário) no calendário. Além disso, a data exibida no botão para ir para a data atual deve ser atualizada. Nós vamos escrever um método especial chamado CCalendar::UpdateCurrentDate() para esta finalidade, e que irá acompanhar cada segundo da mudança para um novo dia (ver o código abaixo). 

Class CCalendar : public CElement
  {
public:
   //--- Atualiza a data atual
   void              UpdateCurrentDate(void);
  };
//+------------------------------------------------------------------+
//| Atualiza a data atual                                            |
//+------------------------------------------------------------------+
void CCalendar::UpdateCurrentDate(void)
  {
//--- Contador
   static int count=0;
//--- Sai se for menos de um segundo
   if(count<1000)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//--- Zera o contador
   count=0;
//--- Obtém a hora atual (local)
   MqlDateTime local_time;
   ::TimeToStruct(::TimeLocal(),local_time);
//--- Se começou um novo dia
   if(local_time.day!=m_today.day)
     {
      //--- Atualiza a data no calendário
      m_today.DateTime(::TimeLocal());
      m_button_today.Object(2).Description(::TimeToString(m_today.DateTime()));
      //--- Exibe as últimas mudanças no calendário
      UpdateCalendar();
      return;
     }
//--- Atualiza a data no calendário
   m_today.DateTime(::TimeLocal());
  }

O código do timer do controle do calendário será de acordo com o seguinte caso: 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CCalendar::OnEventTimer(void)
  {
//--- Se este for um controle suspenso e a lista estiver oculta.
   if(CElement::IsDropdown() && !m_months.GetListViewPointer().IsVisible())
     {
      ChangeObjectsColor();
      FastSwitching();
     }
   else
     {
      //--- Acompanha a mudança da cor e a troca rápida dos valores, 
      //    somente se o formulário não estiver bloqueado
      if(!m_wnd.IsLocked())
        {
         ChangeObjectsColor();
         FastSwitching();
        }
     }
//--- Atualiza a data atual no calendário
   UpdateCurrentDate();
  }

Nós consideramos todos os métodos da classe CCalendar. Agora vamos testar todo o seu funcionamento. No entanto, antes disso nós precisamos conectar o controle com o motor da biblioteca para o seu funcionamento correto. Um guia passo-a-passo detalhado sobre como isso é feito foi previamente fornecido no artigo Interfaces Gráficas V: O Controle Combobox (Capítulo 3). Portanto, nós só forneceremos aqui as declarações (no corpo da classe CWndContainer) do array na estrutura de arrays privados do controle, e também os métodos (1) para receber o número de calendários na interface gráfica da aplicação MQL e (2) para salvar os indicadores de controles quando adicionarmos eles à base, enquanto a interface gráfica é criada. Veja o código desses métodos nos arquivos anexados deste artigo.  

//+------------------------------------------------------------------+
//| Classe para armazenar todos os objetos da interface              |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Estrutura de arrays de controle
   struct WindowElements
     {
      //--- Arrays privados dos controles:
      //--- Array dos calendários
      CCalendar        *m_calendars[];
   //---
public:
   //--- Número de calendários
   int               CalendarsTotal(const int window_index);
   //---
private:
   //--- Armazena os ponteiros para os objetos de controle do calendário na base
   bool              AddCalendarElements(const int window_index,CElement &object);
     };

 

Teste do controle calendário

Para fins de teste você pode usar qualquer Expert Advisor do artigo anterior. A partir dos controles da interface gráfica anexada ao formulário, nós vamos manter apenas o menu principal e uma string indicando o estado. Nós vamos criar dois calendários para analisar se existem conflitos entre eles durante a sua operação.

Na classe personalizada da aplicação (CProgram) nós vamos declarar 2 instâncias da classe CCalendar e um método para criar o controle do calendário com os deslocamentos do ponto extremo da formulário:

class CProgram : public CWndEvents
  {
private:
   //--- Calendários
   CCalendar         m_calendar1;
   CCalendar         m_calendar2;
   //---
private:
   //--- Calendários
#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);
  };

Por exemplo, nós vamos fornecer aqui um código de um desses métodos. Nós vamos manter as propriedades do calendário por padrão (veja o código abaixo). 

//+------------------------------------------------------------------+
//| Cria o calendário 1                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateCalendar1(void)
  {
//--- Passa o objeto para o painel
   m_calendar1.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+CALENDAR1_GAP_X;
   int y=m_window1.Y()+CALENDAR1_GAP_Y;
//--- Cria o controle
   if(!m_calendar1.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Adiciona o objeto para o array comum dos grupos de objetos
   CWndContainer::AddToElementsArray(0,m_calendar1);
   return(true);
  }

Nós vamos colocar a chamada desses métodos no método principal para criar uma interface gráfica da aplicação MQL, em seguida, compile-o e anexe-o ao gráfico. Eventualmente, nós devemos receber o seguinte resultado:

 Fig. 2. Teste do controle calendário.

Fig. 2. Teste do controle calendário.


O desenvolvimento da classe CCalendar para criar o controle do calendário está completo nesta fase. No futuro a sua funcionalidade será ampliada, e neste artigo, nós vamos considerar uma segunda versão deste controle, que é um calendário suspenso. 

 

O Calendário Suspenso

O controle calendário suspenso, ao contrário do calendário estático (CCalendar), permite economizar drasticamente o espaço da interface gráfica da aplicação, uma vez que ele é minimizado para a maior parte do tempo. Uma data que foi selecionada por um usuário aparece no campo de texto da caixa de combinação. Se outra data deve ser selecionada, então, o botão da caixa de combinação deve ser clicado. Esta ação chama (exibe) o calendário. Se você clicar no botão mais uma vez, o calendário é aberto. Um calendário pode ser minimizado quando o botão do lado esquerdo do mouse for clicado no exterior da área do controle, ou se as propriedades do gráfico foram alteradas.

 

Fig. 3. Partes integrantes do calendário suspenso.


A estrutura deste controle irá continuar a ser analisada.

 

A classe CDropCalendar

Anteriormente, nos artigos Interfaces Gráficas V: O Controle Combobox (Capítulo 3) e Interfaces Gráficas VI: Os Controles Caixa de Seleção, Campo de Edição e seus Tipos Combinados (Capítulo 1), os controles de caixa de combinação já foram apresentadas no caso dos controles de biblioteca como a caixa de combinação com uma lista (CComboBox) e caixa de combinação com uma caixa de seleção e com uma lista e (CCheckComboBox). Portanto, nós simplesmente vamos tocar nos principais aspectos do controle suspenso do calendário.

Nós criamos o arquivo DropCalendar.mqh e conectamos ele a biblioteca (o arquivo WndContainer.mqh):

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "DropCalendar.mqh"

No arquivo DropCalendar.mqh nós criamos uma classe com os métodos padrão que todos os controles da biblioteca têm, e conectamos o arquivo com a classe do calendário Calendar.mqh a ele:

//+------------------------------------------------------------------+
//|                                                 DropCalendar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "Calendar.mqh"

Nós vamos proceder imediatamente à lista de métodos necessários para criar o controle. Serão necessários seis métodos privados e um público

//+------------------------------------------------------------------+
//| Classe para criar um calendário suspenso                         |
//+------------------------------------------------------------------+
class CDropCalendar : public CElement
  {
private:
   //--- Objetos e controles para criar um controle
   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 criar um calendário suspenso
   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);
  };

Por favor, note que durante o processo de criação do calendário, nós precisamos definir um sinal do controle suspenso para ele. Depois de criar todos os objetos que consiste o controle no método principal (público) CDropCalendar::CreateDropCalendar(), é necessário o seguinte:

  • Ocultar o calendário.
  • Defina uma data selecionada que já está no calendário depois de ter sido criado no campo informativo da caixa de combinação, isso pode ser obtido usando o método CCalendar::SelectedDate().

Uma versão resumida do método CDropCalendar::CreateDropCalendar() é fornecido no código abaixo. Uma versão completa pode ser encontrada nos arquivos anexados neste artigo.

//+------------------------------------------------------------------+
//| Cria um calendário suspenso                                      |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y)
  {
//--- Sai, se não houver um ponteiro para o formulário
//--- Inicialização das variáveis
//--- Deslocamentos do ponto extremo
//--- Cria o controle
//--- Oculta o calendário
   m_calendar.Hide();
//--- Exibe a data selecionada no calendário
   m_field.Description(::TimeToString((datetime)m_calendar.SelectedDate(),TIME_DATE));
//--- Oculta o controle para uma janela de diálogo ou de uma janela minimizada
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Cria uma lista                                                   |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateCalendar(void)
  {
//--- Passa o objeto para o painel
   m_calendar.WindowPointer(m_wnd);
//--- Coordenadas
   int x=m_field.X();
   int y=m_field.Y2();
//--- Define o sinal de um controle suspenso para o calendário
   m_calendar.IsDropdown(true);
//--- Cria o controle
   if(!m_calendar.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//---
   return(true);
  }

Os métodos a seguir são necessários para operar com a caixa de combinação.

  • Muda a visibilidade do calendário para o estado oposto - CDropCalendar::ChangeComboBoxCalendarState().
  • Lidar com um clique no botão caixa de combinação - CDropCalendar::OnClickButton().
  • Verifica o botão esquerdo do mouse clicado sobre o botão da caixa de combinação - CDropCalendar::CheckPressedOverButton().

Todos estes métodos são semelhantes aos considerados na classe CComboBox, portanto, seu código não será fornecido aqui. 

Manipulador de eventos do calendário suspenso será parecido como é mostrado abaixo. Há quatro blocos para lidar com os seguintes eventos: 

Quando o evento ON_CHANGE_DATE, gerado pela classe CCalendar aparece, nós precisamos comparar os identificadores dos controles e definir uma nova data no campo da caixa de combinação.

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CDropCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipula o evento de mover o mouse
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai, se o controle está oculto
      if(!CElement::IsVisible())
         return;
      //--- Coordenadas e o estado do botão esquerdo do mouse
      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());
      //--- Verifica o clique do botão esquerdo do mouse sobre o botão da caixa de combinação
      CheckPressedOverButton();
      return;
     }
//--- Manipulador do evento de uma nova seleção da data no calendário
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      //--- Sai se os identificadores dos controles não coincidem
      if(lparam!=CElement::Id())
         return;
      //--- Define uma nova data no campo da caixa de combinação
      m_field.Description(::TimeToString((datetime)dparam,TIME_DATE));
      return;
     }
//--- Manipula o evento de clicar no botão esquerdo do mouse sobre o objeto
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Clique no botão da caixa de combinação
      if(OnClickButton(sparam))
         return;
      //---
      return;
     }
//--- Evento de alteração das propriedades do gráfico
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Sai, se o controle está bloqueado
      if(!m_drop_calendar_state)
         return;
      //--- Oculta o calendário
      m_calendar.Hide();
      m_drop_button_icon.State(false);
      //--- Reseta as cores
      ResetColors();
      return;
     }
  }

Para garantir um funcionamento correto desse controle, as adições devem ser digitadas para a classe base da biblioteca CWndContainer, o mesmo que com a classe CCalendar:

//+------------------------------------------------------------------+
//| Classe para armazenar todos os objetos da interface              |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Estrutura de arrays de controle
   struct WindowElements
     {
      //--- Arrays privados dos controles:
      //--- Array dos calendários suspensos
      CDropCalendar    *m_drop_calendars[];
   //---
public:
   //--- Total de calendários suspensos
   int               DropCalendarsTotal(const int window_index);
   //---
private:
   //--- Armazena os ponteiros aos controles do calendário suspenso na base
   bool              AddDropCalendarElements(const int window_index,CElement &object);
     };

Além disso, nós devemos adicionar o código do bloco usado para verificar os calendários suspensos para a classe principal da biblioteca que manipula os eventos de todos os controles para o método CWndEvents::SetChartState() onde o estado do gráfico é controlado (veja uma versão resumida deste método abaixo):

//+------------------------------------------------------------------+
//| Define o estado do gráfico                                       |
//+------------------------------------------------------------------+
void CWndEvents::SetChartState(void)
  {
   int awi=m_active_window_index;
//--- Para identificar o evento quando o gerenciamento deve ser desativado
   bool condition=false;
//--- Verifica a janela
//--- Verifica as listas suspensas
//--- Verifica o calendário
   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;
           }
        }
     }
//--- Verifica o foco dos menus de contexto
//--- Verifica o estado de uma barra de rolagem
//--- Define o estado de um gráfico em todas os formulários
   for(int i=0; i<windows_total; i++)
      m_windows[i].CustomEventChartState(condition);
  }

 

Teste do controle suspenso do calendário

Nós vamos adicionar dois calendários suspensos que foram previamente testados neste artigo para a interface gráfica do Expert Advisor. Em uma classe personalizada da aplicação, nós declaramos duas instâncias do calendário suspenso do tipo CDropCalendar e também deslocamos ele para o ponto extremo (ângulo superior esquerdo) do formulário. 

class CProgram : public CWndEvents
  {
private:
   //--- Calendários suspensos
   CDropCalendar     m_drop_calendar1;
   CDropCalendar     m_drop_calendar2;
   //---
private:
   //--- Calendários suspensos
#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);
  };

Por exemplo, nós fornecemos um código de apenas um dos métodos seguintes:

//+------------------------------------------------------------------+
//| Cria o calendário suspenso 1                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar1(const string text)
  {
//--- Passa o objeto para o painel
   m_drop_calendar1.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+DROPCALENDAR1_GAP_X;
   int y=m_window1.Y()+DROPCALENDAR1_GAP_Y;
//--- Define as propriedades antes da criação
   m_drop_calendar1.XSize(145);
   m_drop_calendar1.YSize(20);
   m_drop_calendar1.AreaBackColor(clrWhiteSmoke);
//--- Cria o controle
   if(!m_drop_calendar1.CreateDropCalendar(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Adiciona o ponteiro para o controle na base
   CWndContainer::AddToElementsArray(0,m_drop_calendar1);
   return(true);
  }

Os métodos de chamada para a criação dos controles deve ser colocado no método principal da criação da interface. Neste caso é o método CProgram::CreateExpertPanel(). Por exemplo, lidar com os eventos no método CProgram::OnEvent(), adicione o código como é mostrado abaixo. Toda vez que se muda uma data nos calendários, uma mensagem com os valores dos parâmetros contidos neste evento serão exibidos no log.

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento de mudar a data no calendário
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",datetime(dparam),"; sparam: ",sparam);
     }
  }

Compile o código da aplicação e carregue-o ao gráfico do terminal. O resultado é mostrado na imagem abaixo:

 Fig. 4. Teste do controle suspenso.

Fig. 4. Teste do controle suspenso.

 

 

Conclusão

Neste artigo (o primeiro capítulo da parte oito), nós discutimos um controle integrado relativamente complexo da interface gráfica do calendário e a sua versão estendida - um calendário suspenso. Tais controles serão muito adequados para os gráficos com uma escala de tempo. Esta não é uma versão final e futuras atualizações destinadas a tornar o calendário mais conveniente e funcional está planejado para ser liberado. Você também pode sugerir suas ideias nos comentários.

No próximo artigo nós vamos analisar um controle importante da interface gráfica, a lista hierárquica ou lista em forma de árvore. 

Você pode baixar todas as informações relacionadas à parte oito, para testar como as coisas funcionam. Para qualquer dúvida relacionadas com a utilização do material fornecido nestes arquivos, você pode referir-se a descrição detalhada do processo de desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer perguntas nos comentários do artigo.

Lista de artigos (capítulos) da parte 8:

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2537

Arquivos anexados |
LifeHack para traders: otimização "silenciosa" ou traço da distribuição de negociações LifeHack para traders: otimização "silenciosa" ou traço da distribuição de negociações
Análise do histórico de negociação e construção de gráficos HTML de distribuição de resultados de negociação, dependendo do momento da entrada no mercado. Os gráficos são exibidos em três seções, isto é: por horas, dias, semanas e meses.
Rede neural: Expert Advisor auto-otimizável Rede neural: Expert Advisor auto-otimizável
Será que é possível criar um Expert Advisor que, de acordo com os comandos do código, otimize os critérios de abertura e fechamento das posições automaticamente e em intervalos regulares? O que acontecerá se nós implementarmos no EA uma rede neural (um perceptron multi-camada) que, sendo módulo, analise o histórico e avalie a estratégia? É possível dar ao código um comando para uma otimização mensal (semanal, diária ou por hora) de rede neural com um processo subsequente. Assim, é possível criar um Expert Advisor que se auto-otimize.
MQL5 vs QLUA - Por que operações de negociação no MQL5 são até 28 vezes mais rápidas? MQL5 vs QLUA - Por que operações de negociação no MQL5 são até 28 vezes mais rápidas?
Você já se perguntou como a sua ordem é entregue tão rápida na bolsa? Também já se perguntou quanto tempo seu terminal precisa para receber o resultado da operação? Nós fizemos uma comparação da velocidade de execução da operação de negociação, pois ninguém nunca mediu esses valores utilizando aplicações nas linguagens de programação MQL5 ou QLUA.
Trabalhando com cesta de moedas no mercado Forex Trabalhando com cesta de moedas no mercado Forex
O artigo descreve como os pares de moedas podem ser divididos em grupos (cestas), bem como a forma de obter dados sobre o seu status (por exemplo, compradas e vendidas) usando certos indicadores e como aplicar esses dados na negociação.