Русский 中文 Español Deutsch 日本語 Português
Graphical Interfaces VIII: The Calendar Control (Chapter 1)

Graphical Interfaces VIII: The Calendar Control (Chapter 1)

MetaTrader 5Examples | 11 August 2016, 11:03
8 629 0
Anatoli Kazharski
Anatoli Kazharski

Contents


 

Introduction

In order to get a better understanding of this library's purpose, please read the first article: Graphical interfaces I: Preparation of the library structure (Chapter 1). A list of chapters with links is provided at the end of articles in each part. You can also download the latest full version of the library from there. Files must be placed under the same directories as they are located in the archive.

In this chapter we will focus on complex composite controls:

  • Static and drop down calendar
  • Tree view
  • File navigator

In this article, we will consider classes used for creating static and drop down calendars and also the structure (struct) of CDateTime from the standard library to operate with dates and time that will be used in our developments.

 


Calendar control

Calendar is a cyclic system of calculating time presented in a table. Graphical interface must have controls to allow user to select the necessary date in the calendar with ease. Apart from custom methods of interacting with a user, other library controls can be included in the calendar class. For example, we will include here classes for creating (1) combo box, (2) entry field and (3) buttons.

Let's list all the components of the calendar.

  1. Background
  2. Buttons for switching to previous and following months
  3. Combo box control with a list of months
  4. Fields to enter a year
  5. Array of text labels with abbreviations of days of the week
  6. Separation line
  7. Two-dimensional array of text labels with dates
  8. Button to quickly jump to the next date

 

Fig. 1. Components of the calendar.


For example, a range of dates needs to be selected in the MQL application that is being developed, therefore, start and end dates must be indicated. It is sufficient to click on any date item from the table to choose a day. There are few options to choose a month: (1) button to switch to a previous month, (2) button to switch to a following month and (3) combo box with a list of all months. Year can be indicated in the field by entering data manually or using the control switches. In order to move quickly to a current date, it is sufficient to click the button "Today: YYYY.MM.DD" at the bottom of the calendar.

Let's have a closer look at how the CDateTime structure is arranged for working with dates and time.

 


Description of the CDateTime structure

The DateTime.mqh file with the CDateTime structure is placed in the directories of MetaTrader trading terminals:

  • MetaTrader 4: <data folder>\MQL4\Include\Tools 
  • MetaTrader 5: <data folder>\MQL5\Include\Tools

The structure of CDateTime is a derivative (extension) from the basic system structure of dates and time called MqlDateTime that contains eight fields of the int type (see the examples of use in documentation for MQL language):

struct MqlDateTime
  {
   int year;           // year
   int mon;            // month
   int day;            // day
   int hour;           // hour
   int min;            // minutes
   int sec;            // seconds
   int day_of_week;    // day of the week (0-Sunday, 1-Monday, ... ,6-Saturday)
   int day_of_year;    // number of a day in the year (1st of January has number 0)
  };

A brief description of methods of the CDateTime structure without a code can be found in the local search (F1) in the section MQL5 Reference/Standard Library/Classes for Control Panels and Dialogs/CDateTime. In the process of working with this structure, you have to remember that numbering of months begins from 1, and numbering of weeks – from 0. 

Open the DateTime.mqh file to familiarize yourself with the code.

 


Development of the CCalendar class

We create the Calendar.mqh file and connect it to the file named WndContainer.mqh, just like we do with all controls of the library we are developing.

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

In the Calendar.mqh file we create the CCalendar class with standard methods for all library controls, and also connect files that are required here for developing the 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>
//+------------------------------------------------------------------+
//| Class for creating the calendar                                      |
//+------------------------------------------------------------------+
class CCalendar : public CElement
  {
private:
   //--- Pointer to the form that the control is attached to.
   CWindow          *m_wnd;
   //---
public:
                     CCalendar(void);
                    ~CCalendar(void);
   //--- Store pointer to the form
   void              WindowPointer(CWindow &object)             { m_wnd=::GetPointer(object);            }
   //---
public:
   //--- Handler of chart events
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Moving the control
   virtual void      Moving(const int x,const int y);
   //--- (1) Show, (2) hide, (3) reset, (4) delete
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Set, (2) reset priorities for clicking the left button to the mouse
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Reset color
   virtual void      ResetColors(void) {}
  };

Just like with other controls of the library interface, there must be an option to set the appearance of the calendar. Below are the properties that refer to external appearance that are available for users.

  • Color of area
  • Color of area border
  • Colors of items (dates) in different states
  • Colors of item borders in different states
  • Colors of item text in different states
  • Color of separation line
  • Labels of buttons (active/blocked) to move to previous/following month

The code listing below reveals the names of fields and methods of the CCalendar class to set the appearance of the calendar before it is created:

class CCalendar : public CElement
  {
private:
   //--- Area color
   color             m_area_color;
   //--- Color of area border
   color             m_area_border_color;
   //--- Colors of calendar items (dates) in different states
   color             m_item_back_color;
   color             m_item_back_color_off;
   color             m_item_back_color_hover;
   color             m_item_back_color_selected;
   //--- Colors of item borders in different states
   color             m_item_border_color;
   color             m_item_border_color_hover;
   color             m_item_border_color_selected;
   //--- Colors of item text in different states
   color             m_item_text_color;
   color             m_item_text_color_off;
   color             m_item_text_color_hover;
   //--- Color of separation line
   color             m_sepline_color;
   //--- Labels of buttons (in active/blocked state) to move to previous/following month
   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:
   //--- Set the color of (1) area, (2) area border, (3) and separation line
   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;                   }
   //--- Colors of calendar items (dates) in different states
   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;        }
   //--- Colors of item borders in different states
   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;      }
   //--- Colors of item text in different states
   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;           }
   //--- Setting labels of buttons (in active/blocked state) to move to a previous/following month
   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;      }
  };

We will need nine private methods and one public method to create the calendar. Static arrays of CEdit type objects are needed to display days of the week and dates. 

The table with dates will consist of 42 items. It is sufficient to fit the maximum number of days in a month that equals 31 days, taking into account the maximum offset of the first day of the month in the table when it falls on Sunday (Sunday is the seventh day of the week in this implementation).

class CCalendar : public CElement
  {
private:
   //--- Objects and controls for creating the calendar
   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:
   //--- Methods for creating the calendar
   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);
  };

In cases when the calendar is a part of another control, it may be required to have access to calendar component controls. For this reason, we will add methods to the class that return pointers to controls listed below.

  • Combo box (CComboBox)
  • List of the combo box (CListView)
  • Vertical scrollbar of the list (CScrollV)
  • Entry field (CSpinEdit)
  • Button (CIconButton)
class CCalendar : public CElement
  {
public:
   //--- (1) Get combo box pointer 
   //    (2) get list pointer, (3) get list scrollbar pointer, 
   //    (4) get entry field pointer, (5) get button pointer
   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));  }
  };

Three instances of the CDateTime structure will be needed to work with dates and time.

  • For interaction with users. A date that a user chooses (selects in the calendar) himself/herself.
  • Current or system date on a user's PC. This date is always marked in the calendar.
  • Instance for calculations and checks. It will be used as a counter in many methods of the CCalendar class.
class CCalendar : public CElement
  {
private:
   //--- Instances of structure for working with dates and time:
   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
  };

Initial initialization of time structures will be performed in the constructor of the CCalendar class. We will set a local time on a user's PC (marked yellow in the code listing below) in the structures of inctances m_date and 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("")
  {
//--- Store the name of the control class in the base class
   CElement::ClassName(CLASS_NAME);
//--- Set priorities for clicking the left button of the mouse
   m_zorder        =0;
   m_area_zorder   =1;
   m_button_zorder =2;
//--- Initialization of time structures
   m_date.DateTime(::TimeLocal());
   m_today.DateTime(::TimeLocal());
  }

After setting the calendar, values of the current month and year must be set in items of the table (dates). Four methods will be required for this purpose.

1. The CCalendar::OffsetFirstDayOfMonth() method used to calculate a difference (in days) from the first item of the table until the first day item of the current month. In the beginning of this method we form a date in a string with a first date of the current year and month. Then, by converting the string to the datetime format, we set the date and structure for calculations. If 1 is deducted from the current number of the week and the result exceeds or equals zero, we set the result to return, but if it is below zero (-1), then to return 6. At the end of the method a backward shift by the obtained difference is performed (number of days).
class CCalendar : public CElement
  {
private:
   //--- Calculate the difference from the first item of the calendar's table until the item of the first day of the current month
   int               OffsetFirstDayOfMonth(void);
  };
//+------------------------------------------------------------------+
//| Define the difference from the first item of the calendar's tab  |
//| until the item of the first day of the current month             |
//+------------------------------------------------------------------+
int CCalendar::OffsetFirstDayOfMonth(void)
  {
//--- Get the date of the first day of the selected year and month in a string
   string date=string(m_date.year)+"."+string(m_date.mon)+"."+string(1);
//--- Set this date in the structure for calculations
   m_temp_date.DateTime(::StringToTime(date));
//--- If the result of deducting 1 from the current number of the day of the week exceeds or equals 0,
//    return result, otherwise — return 6
   int diff=(m_temp_date.day_of_week-1>=0) ? m_temp_date.day_of_week-1 : 6;
//--- Store date that is in the first item of the table
   m_temp_date.DayDec(diff);
   return(diff);
  }

2. The CCalendar::SetCalendar() method. This method is used to fill items of the table for a selected year and month. The CCalendar::OffsetFirstDayOfMonth() method is called at the beginning. Then in the loop we iterate over all items of the table by setting a number of the day from the structure for calculations (m_temp_date.day) in every item, where in the CCalendar::OffsetFirstDayOfMonth() method the date used to count from is set. A change to the next date of the calendar is performed at the end of the method.

Class CCalendar : public CElement
  {
private:
   //--- Display last changes in the calendar table
   void              SetCalendar(void);
  };
//+------------------------------------------------------------------+
//| Setting calendar values                                          |
//+------------------------------------------------------------------+
void CCalendar::SetCalendar(void)
  {
//--- Calculate the difference from the first item of the calendar's table until the item of the first day of the current month
   int diff=OffsetFirstDayOfMonth();
//--- Iterate over all items of the calendar table in the loop
   for(int i=0; i<42; i++)
     {
      //--- Setting day in the current item of the table
      m_days[i].Description(string(m_temp_date.day));
      //--- Move to the next date
      m_temp_date.DayInc();
     }
  }

3. The CCalendar::HighlightDate() method will be used to highlight the current date (system date on a user's PC) and the date selected by a user in the calendar table. The date of the first item of the table is set here first in the structure for calculations (m_temp_date). Then, colors for (1) area, (2) area border and (3) displayed text are determined in the loop (see the code listing below). 

Class CCalendar : public CElement
  {
private:
   //--- Highlight the current date and the user selected date
   void              HighlightDate(void);
  };
//+------------------------------------------------------------------+
//| Highlight the current date and the user selected date            |
//+------------------------------------------------------------------+
void CCalendar::HighlightDate(void)
  {
//--- Calculate the difference from the first item of the calendar's table until the item of the first day of the current month
   OffsetFirstDayOfMonth();
//--- Iterate over the table's items in the loop
   for(int i=0; i<42; i++)
     {
      //--- If a month of the item matches with a current month and 
      //    the item date matches with a selected date
      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);
         //--- Proceed to the next item of the table
         m_temp_date.DayInc();
         continue;
        }
      //--- If this is a current date (today)
      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);
         //--- Proceed to the next item of the table
         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);
      //--- Proceed to the next item of the table
      m_temp_date.DayInc();
     }
  }

4. The CCalendar::UpdateCalendar() method will be used in all methods applied for user interaction with the graphical interface of the calendar. CCalendar::SetCalendar() and CCalendar::HighlightDate() methods that we have mentioned earlier are consecutively called here. Afterwards, year and month are set in the calendar's entry field and combo box. Please note that to select a required item in the combo box list, a month should have 1 deducted because the enumeration of months begins from 1 in the date and time structures, and the enumeration of items in the library lists (CListView) – from 0. 

Class CCalendar : public CElement
  {
public:
   //--- Display last changes in the calendar
   void              UpdateCalendar(void);
  };
//+------------------------------------------------------------------+
//| Display last changes in the calendar                             |
//+------------------------------------------------------------------+
void CCalendar::UpdateCalendar(void)
  {
//--- Display changes in the calendar table
   SetCalendar();
//--- Highlight the current date and the user selected date
   HighlightDate();
//--- Set the year in the entry field
   m_years.ChangeValue(m_date.year);
//--- Set the month in the combo box list
   m_months.SelectedItemByIndex(m_date.mon-1);
  }

Changing colors of items of dates in the calendar's table when hovering over with the mouse cursor will be performed with CCalendar::ChangeObjectsColor(). Coordinates x, y will have to be sent there. Before starting a loop that involves iteration of all items, we determine the extent of shifting the item with a first day of the month from the first item of the table to set the initial counter value (the m_temp_date structure). Then a selected day and a current (system date on a user's PC) date are skipped, and the rest are used to check the focus of the mouse cursor. When changing a color, a month that the day belongs to is taken into account.

Class CCalendar : public CElement
  {
public:
   //--- Change the object color in the calendar's table 
   void              ChangeObjectsColor(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Change the color of objects in the calendar table                |
//| when hovering over                                               |
//+------------------------------------------------------------------+
void CCalendar::ChangeObjectsColor(const int x,const int y)
  {
//--- Calculate the difference from the first item of the calendar's table until the item of the first day of the current month
   OffsetFirstDayOfMonth();
//--- Iterate over the table's items in the loop
   int items_total=::ArraySize(m_days);
   for(int i=0; i<items_total; i++)
     {
      //--- If a month of the item matches with a current month and 
      //    the item date matches with a selected date
      if(m_temp_date.mon==m_date.mon &&
         m_temp_date.day==m_date.day)
        {
         //--- Proceed to the next item of the table
         m_temp_date.DayInc();
         continue;
        }
      //--- If a year/month/day of the item matches with a year/month/day of a current date (today)
      if(m_temp_date.year==m_today.year && 
         m_temp_date.mon==m_today.mon &&
         m_temp_date.day==m_today.day)
        {
         //--- Proceed to the next item of the table
         m_temp_date.DayInc();
         continue;
        }
      //--- If the mouse cursor is over this item
      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);
        }
      //--- Proceed to the next item of the table
      m_temp_date.DayInc();
     }
  }

An option to select a date in the calendar using the program, as well as to obtain a date selected by a user and a current day (today) is required. Therefore, we will need to add public methods CCalendar::SelectedDate() and CCalendar::Today().

Class CCalendar : public CElement
  {
public:
   //--- (1) Set (select) and (2) get a selected date, (3) get a current date in the calendar
   void              SelectedDate(const datetime date);
   datetime          SelectedDate(void)                         { return(m_date.DateTime());             }
   datetime          Today(void)                                { return(m_today.DateTime());            }
  };
//+------------------------------------------------------------------+
//| Selection of a new date                                          |
//+------------------------------------------------------------------+
void CCalendar::SelectedDate(const datetime date)
  {
//--- Store date in the structure and field of the class
   m_date.DateTime(date);
//--- Display last changes in the calendar
   UpdateCalendar();
  }

For example, 29th of February 2016 (2016.02.29) is selected in the calendar, and a user entered (or set with the increment switch) 2017 in the entry field. There are 28 days in February of 2017. Which day should be selected in the calendar's table in this case? Usually in these cases, calendars with different graphical interfaces have the closest day selected, which is the last day of the month. In our case it is 28th of February of 2017. So we will do the same. To that end, the CCalendar::CorrectingSelectedDay() method will be added to the class for a similar correction. The code of this method consists of a couple of strings. The CDateTime structure has a method already to receive a number of days in a month with consideration of a leap year — CDateTime::DaysInMonth(). Therefore it is sufficient to compare a current day of the month with a number of days in the current month, and if it appears that the number of days exceeds the number selected in the calendar, then it should be replaced.

Class CCalendar : public CElement
  {
private:
   //--- Correct the selected day by the number of days in a month
   void              CorrectingSelectedDay(void);
  };
//+------------------------------------------------------------------+
//| Determine the first day of a month                               |
//+------------------------------------------------------------------+
void CCalendar::CorrectingSelectedDay(void)
  {
//--- Set the current number of days in a month, if a value of the selected day is higher
   if(m_date.day>m_date.DaysInMonth())
      m_date.day=m_date.DaysInMonth();
  }

 

 


Handlers of calendar events

Now let's look at the handlers of the calendar events. There will be a total of 8 private methods to handle the events from the below list.

  • Click the button to go to a previous month
  • Click the button to go to a next month
  • Select a month from a drop down list of the combo box
  • Enter value in the year entry field
  • Click the button to go to a next year
  • Click the button to go to a previous year
  • Click a day of the month
  • Click the button to go to a current date
  • Every action from the above list leads to changes in the calendar. Let's be methodical about the code of these methods.

For operation with the calendar we require a new identifier of the user event ON_CHANGE_DATE that will be used to send messages notifying that a user changed a date in the calendar. In addition to that, we add one more identifier to work with a combo box — ON_CLICK_COMBOBOX_BUTTON to determine a click on the button of this control. 

#define ON_CLICK_COMBOBOX_BUTTON  (21) // Click on the button of combo box
#define ON_CHANGE_DATE            (22) // Change the date in the calendar

Sending a message with the ON_CLICK_COMBOBOX_BUTTON identifier must be performed from the OnClickButton() method of the CComboBox class. Let's add the option to send messages to this method (see the code listing below):

//+------------------------------------------------------------------+
//| Click the combo box button                                       |
//+------------------------------------------------------------------+
bool CComboBox::OnClickButton(const string clicked_object)
  {
//--- Exit if the form is blocked and identifiers don't match
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return(false);
//--- Exit if it has a different object name  
   if(clicked_object!=m_button.Name())
      return(false);
//--- Change the state of a list
   ChangeComboBoxListState();
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_BUTTON,CElement::Id(),0,"");
   return(true);
  }

Tracking custom event with the ON_CLICK_COMBOBOX_BUTTON identifier will be required in the CCalendar class to manage the state of controls (CSpinEdit and CIconButton) that are a part of the calendar (see the code listing below): 

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handle the event of clicking combo box button
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_BUTTON)
     {
      //--- Exit if identifiers of controls don't match
      if(lparam!=CElement::Id())
         return;
      //--- Activate or block controls depending on the current state of the list's visibility
      m_years.SpinEditState(!m_months.GetListViewPointer().IsVisible());
      m_button_today.ButtonState(!m_months.GetListViewPointer().IsVisible());
     }
  }

In graphical interfaces of various systems, for example, in the Windows 7 operating system, a shift to the previous/next month by clicking arrow buttons to the right/left leads to selecting a first day of the month, despite the fact which day was previously selected by a user in the table of the calendar. We implement the same behavior in the calendar of the developing library. In order to proceed to a previous/following month, the CCalendar::OnClickMonthDec() and CCalendar::OnClickMonthInc() methods will be used. A code of these methods is very similar. For example, we will provide a description of one of them, i.e. handle the event of clicking the button to go to a previous month.

A name of the graphical object that a user clicked on is checked at the beginning of the method. Then, if the current year in the calendar equals a minimum specified, and the current month is "January" (i.e. we have reached a minimum restriction), we will briefly highlight the text in the entry field and exit the method. 

If we haven't reached the minimum restriction, then the following occurs.

  • Set the state of the button to On.
  • Go to the next month. 
  • Set a first number of the month.
  • Reset the time (00:00:00). For this purpose the CCalendar::ResetTime() method is added to the class.
  • Update the calendar to display the last changes.
  • We send a message with (1) chart identifier, (2) ON_CHANGE_DATE event identifier, (3) control identifier and (4) specified date. Messages with the same parameters will be also sent from other methods of handling calendar events. 
Class CCalendar : public CElement
  {
private:
   //--- Handle clicking on the button to go to a previous month
   bool              OnClickMonthDec(const string clicked_object);
   //--- Handle clicking on the button to go to a following month
   bool              OnClickMonthInc(const string clicked_object);
   //--- Reset time
   void              ResetTime(void);
  };
//+------------------------------------------------------------------+
//| Click the arrow to the left. Go to a previous month.                |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthDec(const string clicked_object)
  {
//--- Exit if it has a different object name
   if(::StringFind(clicked_object,m_month_dec.Name(),0)<0)
      return(false);
//--- If the current year in the calendar equals a minimum indicated and
//    the current month is "January"
   if(m_date.year==m_years.MinValue() && m_date.mon==1)
     {
      //--- Highlight value and exit
      m_years.HighlightLimit();
      return(true);
     }
//--- Set the state to On
   m_month_dec.State(true);
//--- Go to a previous month
   m_date.MonDec();
//--- Set a first day of the month
   m_date.day=1;
//--- Reset time
   ResetTime();
//--- Display last changes in the calendar
   UpdateCalendar();
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }
//+------------------------------------------------------------------+
//| Reset time                                                       |
//+------------------------------------------------------------------+
void CCalendar::ResetTime(void)
  {
   m_date.hour =0;
   m_date.min  =0;
   m_date.sec  =0;
  }

The code in the CCalendar::OnClickMonthInc() method is almost the same. The only difference is that when checking the restriction, the exit over maximum year and last month of the year (December) set in the calendar properties is checked.

The month selection from the list is handled with the CCalendar::OnClickMonthList() method upon the arrival of the event with the ON_CLICK_COMBOBOX_ITEM identifier (see the code listing below). In the main handler, the long parameter that contains the control identifier is sent to this method. If identifiers don't match, then the program exits from the method. Since the selection of the item in the list leads to its closure, then we should unblock the previously blocked controls (entry field and button) of the calendar. Then the selected month is set in the structure of date and time, and in case of necessity we correct the selected day depending on days in a month. It simply remains to set the time, display last changes made to the calendar and send a message to the thread of events that the date in the calendar has changed.

Class CCalendar : public CElement
  {
private:
   //--- Handle month selection in the list
   bool              OnClickMonthList(const long id);
  };
//+------------------------------------------------------------------+
//| Handle a selection of the month in the list                      |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthList(const long id)
  {
//--- Exit if identifiers of controls don't match
   if(id!=CElement::Id())
      return(false);
//--- Unblock controls
   m_years.SpinEditState(true);
   m_button_today.ButtonState(true);
//--- Obtain a selected month in a list
   int month=m_months.GetListViewPointer().SelectedItemIndex()+1;
   m_date.Mon(month);
//--- Correct the selected day by the number of days in a month
   CorrectingSelectedDay();
//--- Reset time
   ResetTime();
//--- Display changes in the calendar table
   UpdateCalendar();
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

The CCalendar::OnEndEnterYear() method is used to handle the value entry to the year entry field. In the beginning of this method, the object's name of the OBJ_EDIT type where a new value was entered is checked. Further, there will be another check to confirm whether the value has changed. If the name of the entry field doesn't belong to this calendar or the value hasn't changed, then the program exits from the method. If the first two checks are passed, then the value entered further is corrected in case of exiting beyond the specified restrictions. The next correction amends the date selected by a user, if the number of days in a month exceeds the number of days in this month. After this you can set the date in the operational structure of date and time to show the changes made in the calendar and send a message that the date in this calendar was changed. The code of the CCalendar::OnEndEnterYear() method can be thoroughly studied in the listing below: 

Class CCalendar : public CElement
  {
private:
   //--- Handle value entry in the year entry field
   bool              OnEndEnterYear(const string edited_object);
  };
//+------------------------------------------------------------------+
//| Handle value entry in the year entry field                       |
//+------------------------------------------------------------------+
bool CCalendar::OnEndEnterYear(const string edited_object)
  {
//--- Exit if it has a different object name
   if(::StringFind(edited_object,m_years.Object(2).Name(),0)<0)
      return(false);
//--- Exit if the value hasn't changed
   string value=m_years.Object(2).Description();
   if(m_date.year==(int)value)
      return(false);
//--- Correct value in case of going beyond the specified restrictions
   if((int)value<m_years.MinValue())
     {
      value=(string)int(m_years.MinValue());
      //--- Highlight value
      m_years.HighlightLimit();
     }
   if((int)value>m_years.MaxValue())
     {
      value=(string)int(m_years.MaxValue());
      //--- Highlight value
      m_years.HighlightLimit();
     }
//--- Define the number of days in a current month
   string year  =value;
   string month =string(m_date.mon);
   string day   =string(1);
   m_temp_date.DateTime(::StringToTime(year+"."+month+"."+day));
//--- If value of the selected day exceeds the number of days in a month,
//    then set the current number of days in a month as a selected day
   if(m_date.day>m_temp_date.DaysInMonth())
      m_date.day=m_temp_date.DaysInMonth();
//--- Set a date in the structure
   m_date.DateTime(::StringToTime(year+"."+month+"."+string(m_date.day)));
//--- Display changes in the table of the calendar
   UpdateCalendar();
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Methods to handle the clicking on buttons to go to the following/previous years are CCalendar::OnClickYearInc() and CCalendar::OnClickYearDec(). The code of just one of them will be provided here since they are practically identical, apart from the conditions to check that restrictions are reached. At the beginning, a current state of the list for selecting a month is checked. We will close it, if it's open. Further, if control identifiers don't match, the program exits from here. The main condition of methods is then followed, where one step forward is performed on condition for CCalendar::OnClickYearInc(), or one step back for CCalendar::OnClickYearDec(). Then, if necessary, (1) a selected day is corrected based on the number of days in a month, (2) the last changes are shown in the calendar and (3) a message is sent notifying that the date in the calendar was changed.  

Class CCalendar : public CElement
  {
private:
   //--- Handle clicking on the button to switch to a following year
   bool              OnClickYearInc(const long id);
   //--- Handle clicking on the button to switch to a previous year
   bool              OnClickYearDec(const long id);
  };
//+------------------------------------------------------------------+
//| Handle clicking on the button to switch to a following year      |
//+------------------------------------------------------------------+
bool CCalendar::OnClickYearInc(const long id)
  {
//--- If the list of months is open, we will close it
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Exit if identifiers of controls don't match
   if(id!=CElement::Id())
      return(false);
//--- If a year is below the maximum specified, we need to increase value by one
   if(m_date.year<m_years.MaxValue())
      m_date.YearInc();
//--- Correct the selected day by the number of days in a month
   CorrectingSelectedDay();
//--- Display changes in the calendar table
   UpdateCalendar();
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Now we are considering the CCalendar::OnClickDayOfMonth() method to handle clicking on a day of the month in the table of the calendar. 

As previously mentioned, the calendar table consists of 42 items. Therefore, depending on the item of the first number of a current month, it sometimes occurs that there are days of previous and following months in the table. In cases when a user clicks on a day that doesn't belong to the current month, a switch to the month of the selected day will be performed. 

In the beginning of the method we need to pass a check for the object belonging to the item of the table of the calendar and also compare control identifiers. An identifier is extracted from the object name using the CCalendar::IdFromObjectName() method that has been previously considered in the case of other library controls. If the first two checks are passed, then using the CCalendar::OffsetFirstDayOfMonth() method a difference from the first item of the table until the first day of the current month is determined. After calling this method, the m_temp_date counter is ready for work, and further in the loop the program will iterate over all items to find the item that a user clicked on. 

Let's try to understand the logic behind this loop. It is possible that dates in first items refer to a year prior to the one set in the system as minimum (year 1970). If this is the case, we highlight the value in the year entry field and immediately go to the next item, because it is prohibited for a user to pass the specified restrictions. If the item is in the available area and it was clicked, then (1) the date is stored in operational structure (m_date), (2) the calendar displays the latest changes, (3) the loop is interrupted and (4) a message is sent stating that the date in the calendar was changed in the end of the method.

If none of the first two conditions in the loop are met, then the structure counter for calculations (m_temp_date) is increased by one day, which is followed by checking the exit beyond the maximum set in the calendar system. If the maximum hasn't been reached yet, the program moves to the next item. After reaching the maximum, highlighting value is called in the year entry field, and the program exits from the method. 

Class CCalendar : public CElement
  {
private:
   //--- Handle clicking on a day of the month
   bool              OnClickDayOfMonth(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Handle clicking on a day of the month                            |
//+------------------------------------------------------------------+
bool CCalendar::OnClickDayOfMonth(const string clicked_object)
  {
//--- Exit if clicking wasn't on the day of the calendar
   if(::StringFind(clicked_object,CElement::ProgramName()+"_calendar_day_",0)<0)
      return(false);
//--- Get identifier and index from the object name
   int id=IdFromObjectName(clicked_object);
//--- Leave, if identifier does not match
   if(id!=CElement::Id())
      return(false);
//--- Calculate the difference from the first item of the calendar's table until the item of the first day of the current month
   OffsetFirstDayOfMonth();
//--- Iterate over the table's items in the loop
   for(int i=0; i<42; i++)
     {
      //--- If the date of the current item is lower than a minimum set in the system
      if(m_temp_date.DateTime()<datetime(D'01.01.1970'))
        {
         //--- If this is the object that we have clicked on
         if(m_days[i].Name()==clicked_object)
           {
            //--- Highlight value and exit
            m_years.HighlightLimit();
            return(false);
           }
         //--- Move to the next date
         m_temp_date.DayInc();
         continue;
        }
      //--- If this is the object that we have clicked on
      if(m_days[i].Name()==clicked_object)
        {
         //--- Store date
         m_date.DateTime(m_temp_date.DateTime());
         //--- Display last changes in the calendar
         UpdateCalendar();
         break;
        }
      //--- Move to the next date
      m_temp_date.DayInc();
      //--- Check exit beyond the maximum set in the system
      if(m_temp_date.year>m_years.MaxValue())
        {
         //--- Highlight value and exit
         m_years.HighlightLimit();
         return(false);
        }
     }
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

It is left to consider the last handler method of calendar events — CCalendar::OnClickTodayButton() that is used to handle clicking on the button to go to a current date. The long parameter (control identifier) is sent from the main handler of events. In the beginning of the method, the list is being closed in case it is currently open. Then, identifiers are compared. If identifiers match, we set a local date and time (as on user's PC) in the operational structure. Then the calendar is updated to display last changes and at the end of the method a message is sent stating that the date in this calendar has changed

Class CCalendar : public CElement
  {
private:
   //--- Handle clicking on the button to go to a current date
   bool              OnClickTodayButton(const long id);
  };
//+------------------------------------------------------------------+
//| Handle clicking on the button to go to a current date            |
//+------------------------------------------------------------------+
bool CCalendar::OnClickTodayButton(const long id)
  {
//--- If the list of months is open, we will close it
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Exit if identifiers of controls don't match
   if(id!=CElement::Id())
      return(false);
//--- Set the current date
   m_date.DateTime(::TimeLocal());
//--- Display last changes in the calendar
   UpdateCalendar();
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

There are eight blocks of codes (one was already provided in the beginning of this section) in the main handler of calendar events CCalendar::OnEvent(). We will list them.

  • Handle events of moving the mouse cursor (CHARTEVENT_MOUSE_MOVE). Tracking the mouse cursor is performed only when a control is not hidden, and also in cases when it is a drop down control and the form is not blocked. The program exits from this block even when a list of months is active (open). When the list is hidden and the left mouse button is clicked, previously blocked (at the moment of opening the list) controls of the calendar are activated, if at least one of them is still blocked.  At the very end, coordinates of the mouse cursor for changing the color of table items are sent to the CCalendar::ChangeObjectsColor() method. 
//--- Handle the event of moving the mouse
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Exit if the control is hidden
      if(!CElement::IsVisible())
         return;
      //--- Exit if it is not a drop-down control and the form is blocked
      if(!CElement::IsDropdown() && m_wnd.IsLocked())
         return;
      //--- Coordinates and state of the left button of the 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());
      //--- Exit if a list of months is active
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- If a list is not active and the left button of the mouse is clicked...
      else if(m_mouse_state)
        {
         //--- ...activate controls that were previously blocked (at the moment of opening the list),
         //       if at least one of them is not blocked
         if(!m_button_today.ButtonState())
           {
            m_years.SpinEditState(true);
            m_button_today.ButtonState(true);
           }
        }
      //--- Change of colors of objects
      ChangeObjectsColor(x,y);
      return;
     }
  • Handling the event of clicking the left button of the mouse on the object (CHARTEVENT_OBJECT_CLICK). This occurs after clicking on any object of the calendar. Then if the flag is set on the clicked left button of the mouse, calendar controls are activated (entry field and button). It is further checked, which control was clicked. For this matter we use previously mentioned methods CCalendar::OnClickMonthDec(), CCalendar::OnClickMonthInc() and CCalendar::OnClickDayOfMonth().  
//--- Handle the event of clicking the left button of the mouse on the object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Exit if the form is blocked and identifiers don't match
      if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
         return;
      //--- Exit if a list of months is activated
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- Activate controls (list and entry field), if the left button of the mouse is clicked 
      if(m_mouse_state)
        {
         m_years.SpinEditState(true);
         m_button_today.ButtonState(true);
        }
      //--- Handle clicking on buttons for switching months
      if(OnClickMonthDec(sparam))
         return;
      if(OnClickMonthInc(sparam))
         return;
      //--- Handle clicking on the day of the calendar
      if(OnClickDayOfMonth(sparam))
         return;
     }
  • Handle the event of clicking on the item of the combo box list (ON_CLICK_COMBOBOX_ITEM). To handle this event, the CCalendar::OnClickMonthList() method is used.  
//--- Handle the event of clicking on the item of the combo box list
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Handle month selection in the list
      if(!OnClickMonthList(lparam))
         return;
      //---
      return;
     }
  •  Handle the event of clicking on the buttons of increment/decrement (ON_CLICK_INC/ON_CLICK_DEC). To handle these events, two separate code blocks for calling methods CCalendar::OnClickYearInc() and CCalendar::OnClickYearDec() are displayed. 
//--- Handle the event of clicking on the increment button
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC)
     {
      //--- Handle clicking on the button to switch to a following year
      if(!OnClickYearInc(lparam))
         return;
      //---
      return;
     }
//--- Handle the event of clicking the decrement button
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      //--- Handle clicking on the button to switch to a previous year
      if(!OnClickYearDec(lparam))
         return;
      //---
      return;
     }
  • Handle the event of entering value in the entry field (CHARTEVENT_OBJECT_ENDEDIT). Entering value in the year entry field will lead the program to this code block for calling the CCalendar::OnEndEnterYear() method. 
//--- Handle the event of entering value in the entry field
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      //--- Handle value entry in the year entry field
      if(OnEndEnterYear(sparam))
         return;
      //---
      return;
     }
  • Handle event of clicking on the button (ON_CLICK_BUTTON). In this code block, a clicking on the button to move to the next date (local date on a user PC) is handled with the CCalendar::OnClickTodayButton() method:
//--- Handle the event of clicking on the button
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Handle clicking on the button to go to a current date
      if(!OnClickTodayButton(lparam))
         return;
      //---
      return;
     }

To make the use of such complex control as calendar more convenient, we will add functionality that would allow to fast switch its values. Something similar was already implemented in the classes of the list (CListView) and entry field (CSpinEdit). It must be connected to the calendar table now to get updates of values in the table for all changes occurring when fast switching values in the entry field. We will also add the option to fast switch the calendar using the buttons for switching months. 

We will provide here a description for only the main (operational) part of the method, because the conditions for entering the operational block have already been considered in other articles. After the conditions are met, we must check which button of the calendar controls is currently clicked on (see strings highlighted yellow). If none of the listed buttons is currently pressed, then the program exits from the method. After a clicked button has been detected, few checks must be made to determine whether the value goes beyonds the restrictions set in the calendar. If we have reached the restriction then the text in the entry field is highlighted and the program exits from the method. If the restriction is not achieved, the calendar is updated to display the last changes, and a message to notify that the date has changed is sent

Class CCalendar : public CElement
  {
private:
   //--- Fast switching the calendar values
   void              FastSwitching(void);
  };
//+------------------------------------------------------------------+
//| Fast switching the calendar                                      |
//+------------------------------------------------------------------+
void CCalendar::FastSwitching(void)
  {
//--- Exit if there is no focus on the control
   if(!CElement::MouseFocus())
      return;
//--- Exit if the form is blocked and identifiers don't match
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return;
//--- Return counter to the initial value if the mouse button is released
   if(!m_mouse_state)
      m_timer_counter=SPIN_DELAY_MSC;
//--- If the mouse button is clicked
   else
     {
      //--- Increase counter to the specified interval
      m_timer_counter+=TIMER_STEP_MSC;
      //--- Exit if below zero
      if(m_timer_counter<0)
         return;
      //--- If the left arrow is clicked on
      if(m_month_dec.State())
        {
         //--- If the current year in the calendar exceeds/equals the minimum specified
         if(m_date.year>=m_years.MinValue())
           {
            //--- If a current year in the calendar already equals a specified minimum and
            //    the current month is "January"
            if(m_date.year==m_years.MinValue() && m_date.mon==1)
              {
               //--- Highlight value and exit
               m_years.HighlightLimit();
               return;
              }
            //--- Proceed to the next month (downwards)
            m_date.MonDec();
            //--- Set a first day of the month
            m_date.day=1;
           }
        }
      //--- If the right arrow is clicked
      else if(m_month_inc.State())
        {
         //--- If a current year in the calendar is below/equal to a specified maximum
         if(m_date.year<=m_years.MaxValue())
           {
            //--- If a current year in the calendar already equals a specified maximum and
            //    a current month is "December"
            if(m_date.year==m_years.MaxValue() && m_date.mon==12)
              {
               //--- Highlight value and exit
               m_years.HighlightLimit();
               return;
              }
            //--- Go to the following month (upwards)
            m_date.MonInc();
            //--- Set a first day of the month
            m_date.day=1;
           }
        }
      //--- If the increment button of the year entry field is clicked
      else if(m_years.StateInc())
        {
         //--- If below maximum specified year,
         //    go to the next year (upwards)
         if(m_date.year<m_years.MaxValue())
            m_date.YearInc();
         else
           {
            //--- Highlight value and exit
            m_years.HighlightLimit();
            return;
           }
        }
      //--- If the decrement button of the entry field is clicked
      else if(m_years.StateDec())
        {
         //--- If a minimum specified year is exceeded,
         //    go to a following year (downward)
         if(m_date.year>m_years.MinValue())
            m_date.YearDec();
         else
           {
            //--- Highlight value and exit
            m_years.HighlightLimit();
            return;
           }
        }
      else
        return;
      //--- Display last changes in the calendar
      UpdateCalendar();
      //--- Send a message about it
      ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
     }
  }

At the moment of changing from one day to another, a current day should be updated (local time on a user PC) in the calendar. Also, a displayed date on the button to go to a current date should be updated. We will write a special method called CCalendar::UpdateCurrentDate() for this purpose, and it will track every second of the change into a new day (see the code listing below). 

Class CCalendar : public CElement
  {
public:
   //--- Update the current date
   void              UpdateCurrentDate(void);
  };
//+------------------------------------------------------------------+
//| Update the current date                                          |
//+------------------------------------------------------------------+
void CCalendar::UpdateCurrentDate(void)
  {
//--- Counter
   static int count=0;
//--- Exit if was less than a second
   if(count<1000)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//--- Zero the counter
   count=0;
//--- Obtain current (local) time
   MqlDateTime local_time;
   ::TimeToStruct(::TimeLocal(),local_time);
//--- If a new day has started
   if(local_time.day!=m_today.day)
     {
      //--- Update the date in the calendar
      m_today.DateTime(::TimeLocal());
      m_button_today.Object(2).Description(::TimeToString(m_today.DateTime()));
      //--- Display last changes in the calendar
      UpdateCalendar();
      return;
     }
//--- Update the date in the calendar
   m_today.DateTime(::TimeLocal());
  }

A timer code of the calendar control will look accordingly in this case: 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CCalendar::OnEventTimer(void)
  {
//--- If it is a drop down control and the list is hidden
   if(CElement::IsDropdown() && !m_months.GetListViewPointer().IsVisible())
     {
      ChangeObjectsColor();
      FastSwitching();
     }
   else
     {
      //--- Track the change of color and fast switching values, 
      //    only if the form is not blocked
      if(!m_wnd.IsLocked())
        {
         ChangeObjectsColor();
         FastSwitching();
        }
     }
//--- Update of the current date of the calendar
   UpdateCurrentDate();
  }

We have considered all methods of the CCalendar class. Let's test now how everything operates. However, before that we need to connect the control with the engine of the library for its correct operation. A detailed step-by-step guide on how this is done has been previously provided in the article Graphical interfaces V: the combobox control (Chapter 3). Therefore, we will only provide here declarations (in the body of the CWndContainer class) of the array in the structure of personal arrays of controls, and also methods (1) to receive a number of calendars in the graphical interface of MQL application and (2) to save indicators of controls when adding them to the base while creating a graphical interface. See the code of these methods in the files attached to this article.  

//+------------------------------------------------------------------+
//| Class for storing all interface objects                          |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Structure of control arrays
   struct WindowElements
     {
      //--- Personal arrays of controls:
      //--- Array of calendars
      CCalendar        *m_calendars[];
   //---
public:
   //--- Number of calendars
   int               CalendarsTotal(const int window_index);
   //---
private:
   //--- Store pointers to the calendar controls in the base
   bool              AddCalendarElements(const int window_index,CElement &object);
     };

 

 

 

Testing the calendar control

For testing purposes you can use any Expert Advisor from the previous article. From the controls of graphical interface attached to the form, we will keep only main menu and a state indicating string. We will create two calendars to analyze whether there are any conflicts between them during their operation.

In the custom class of the application (CProgram) we will declare 2 instances of the CCalendar class and a method to create the calendar control with offsets from the extreme point of the form:

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

For example, we will provide here a code of one of these methods. We will keep calendar properties by default (see the code listing below). 

//+------------------------------------------------------------------+
//| Create calendar 1                                                |
//+------------------------------------------------------------------+
bool CProgram::CreateCalendar1(void)
  {
//--- Pass the object to the panel
   m_calendar1.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+CALENDAR1_GAP_X;
   int y=m_window1.Y()+CALENDAR1_GAP_Y;
//--- Create control
   if(!m_calendar1.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Add the object to the common array of groups of objects
   CWndContainer::AddToElementsArray(0,m_calendar1);
   return(true);
  }

We will place the calling of these methods in the main method of creating a graphical interface of the MQL application, and then compile it and upload it to the chart. Eventually we should receive the following result:

 Fig. 2. Testing the calendar control.

Fig. 2. Testing the calendar control.


Development of the CCalendar class for creating the calendar control is complete at this stage. In the future its functionality will be expanded, and in this article we will consider a second version of this control, which is a drop down calendar. 

 

 


Drop down calendar

The control of a drop down calendar, unlike the static calendar (CCalendar), allows to save space of the graphical interface of the application drastically since it is minimized for most of the time. A date that was selected by a user appears in the text field of the combo box. If another date must be selected, then the combo box button must be clicked. This action calls (displays) the calendar. If you click the button once again, the calendar is opened. A calendar can be minimized when the left button of the mouse is clicked outside the area of the control, or the qualities of the chart are changed.

 

Fig. 3. Components of the drop down calendar.


The structure of this control will be further analyzed.

 


The CDropCalendar class

Earlier, in the articles Graphical interfaces V: The combobox control (Chapter 3) and Graphical interfaces VI: the checkbox control, the edit control and their mixed types (Chapter 1), combo box controls have been already presented in the case of library controls like combo box with a list (CComboBox) and combo box with a list and a check box (CCheckComboBox). Therefore, we will simply touch on the key aspects of the drop down calendar control.

We create the DropCalendar.mqh file and connect it to the library (the WndContainer.mqh file):

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

In the DropCalendar.mqh file we create a class with standard methods that all library controls have, and connect the file with the calendar class Calendar.mqh to it:

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

We will immediately proceed to the list of methods needed for creating the control. Six private methods and one public method will be required: 

//+------------------------------------------------------------------+
//| Class for creating a drop down calendar                          |
//+------------------------------------------------------------------+
class CDropCalendar : public CElement
  {
private:
   //--- Objects and controls for creating a control
   CRectLabel        m_area;
   CLabel            m_label;
   CEdit             m_field;
   CEdit             m_drop_button;
   CBmpLabel         m_drop_button_icon;
   CCalendar         m_calendar;
   //---
public:
   //--- Methods for creating a drop down calendar
   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);
  };

Please note that during the process of creating the calendar, we need to set a sign of the drop down control for it. After creating all objects that the control consists of in the main (public) method CDropCalendar::CreateDropCalendar(), the following is required:

  • Hide the calendar.
  • Set a selected date that is already in the calendar after being created in the informative field of the combo box and that can be obtained using the CCalendar::SelectedDate() method.

A shortened version of the CDropCalendar::CreateDropCalendar() method is provided in the code listing below. The complete version can be viewed in the files attached to the article.

//+------------------------------------------------------------------+
//| Create a drop down calendar                                      |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y)
  {
//--- Exit if there is no pointer to the form
//--- Initialization of variables
//--- Offsets from the extreme point
//--- Create control
//--- Hide calendar
   m_calendar.Hide();
//--- Display selected date in the calendar
   m_field.Description(::TimeToString((datetime)m_calendar.SelectedDate(),TIME_DATE));
//--- Hide the control for a dialog window or for a minimized window
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Create a list                                                    |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateCalendar(void)
  {
//--- Pass the object to the panel
   m_calendar.WindowPointer(m_wnd);
//--- Coordinates
   int x=m_field.X();
   int y=m_field.Y2();
//--- Set the sign of a drop down control for the calendar
   m_calendar.IsDropdown(true);
//--- Create control
   if(!m_calendar.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//---
   return(true);
  }

The following methods are required to operate with the combo box.

  • Change of visibility of the calendar to the opposite state — CDropCalendar::ChangeComboBoxCalendarState().
  • Handle clicking on the combo box button — CDropCalendar::OnClickButton().
  • Check the clicked left mouse button over the combo box button — CDropCalendar::CheckPressedOverButton().

All these methods are similar to the ones considered in the CComboBox class, therefore their code won't be provided here. 

Handler of events of the drop down calendar will look as shown in the listing below. There are four blocks for handling the following events: 

When the ON_CHANGE_DATE event that is generated by the CCalendar class arrives, we need to compare identifiers of controls and set a new date in the combo box field.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CDropCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handle the event of moving the mouse
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Exit if the control is hidden
      if(!CElement::IsVisible())
         return;
      //--- Coordinates and state of the left button of the 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());
      //--- Check the clicked left button of the mouse over the combo box button
      CheckPressedOverButton();
      return;
     }
//--- Handle event of a new date selection in the calendar
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      //--- Exit if identifiers of controls don't match
      if(lparam!=CElement::Id())
         return;
      //--- Set a new date in the combo box field
      m_field.Description(::TimeToString((datetime)dparam,TIME_DATE));
      return;
     }
//--- Handle the event of clicking the left button of the mouse on the object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Click on the combo box button
      if(OnClickButton(sparam))
         return;
      //---
      return;
     }
//--- Handle the event of changing chart properties
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Exit if the control is blocked
      if(!m_drop_calendar_state)
         return;
      //--- Hide calendar
      m_calendar.Hide();
      m_drop_button_icon.State(false);
      //--- Reset colors
      ResetColors();
      return;
     }
  }

To ensure correct operation of this control, additions should be entered to the base class of the CWndContainer library, the same as with the CCalendar class:

//+------------------------------------------------------------------+
//| Class for storing all interface objects                          |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Structure of control arrays
   struct WindowElements
     {
      //--- Personal arrays of controls:
      //--- Array of drop down calendars
      CDropCalendar    *m_drop_calendars[];
   //---
public:
   //--- Total of drop down calendars
   int               DropCalendarsTotal(const int window_index);
   //---
private:
   //--- Store pointers to the drop down calendar controls in the base
   bool              AddDropCalendarElements(const int window_index,CElement &object);
     };

Furthermore, we must add the code of the block used to check drop down calendars to the main class of the library for handling events of all controls to the CWndEvents::SetChartState() method where state of a chart is being controlled (see a shortened version of this method in the listing below):

//+------------------------------------------------------------------+
//| Set the state of a chart                                         |
//+------------------------------------------------------------------+
void CWndEvents::SetChartState(void)
  {
   int awi=m_active_window_index;
//--- For identifying the event when management should be disabled
   bool condition=false;
//--- Check windows
//--- Check drop down lists
//--- Check calendar
   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;
           }
        }
     }
//--- Check the focus of context menus
//--- Check the state of a scrollbar
//--- Set the state of a chart in all forms
   for(int i=0; i<windows_total; i++)
      m_windows[i].CustomEventChartState(condition);
  }

 

 

 

Test of the drop down calendar control

We will add two drop down calendars that have been previously tested in this article to the graphical interface of the Expert Advisor. In a custom class of the application we declare two instances of the drop down calendar of the CDropCalendar type and also offsets from the extreme point (upper left angle) of the form. 

class CProgram : public CWndEvents
  {
private:
   //--- Drop down calendars
   CDropCalendar     m_drop_calendar1;
   CDropCalendar     m_drop_calendar2;
   //---
private:
   //--- Drop down calendars
#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);
  };

For example, we provide a code of just one of these methods:

//+------------------------------------------------------------------+
//| Create drop down calendar 1                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar1(const string text)
  {
//--- Pass the object to the panel
   m_drop_calendar1.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+DROPCALENDAR1_GAP_X;
   int y=m_window1.Y()+DROPCALENDAR1_GAP_Y;
//--- Set properties before creation
   m_drop_calendar1.XSize(145);
   m_drop_calendar1.YSize(20);
   m_drop_calendar1.AreaBackColor(clrWhiteSmoke);
//--- Create control
   if(!m_drop_calendar1.CreateDropCalendar(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Add the pointer to control in the base
   CWndContainer::AddToElementsArray(0,m_drop_calendar1);
   return(true);
  }

Calling methods for creating controls must be placed in the main method of creating the interface. It is the CProgram::CreateExpertPanel() method in this case . For example of handling the calendar events in the CProgram::OnEvent() method, add the code as shown in the listing below. Every time when changing a date in the calendars a message with values of parameters contained in this event will be displayed in the log.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Event of changing the date in the calendar
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",datetime(dparam),"; sparam: ",sparam);
     }
  }

Compile the code of application and upload it on the chart in the terminal. The result is shown on the screenshot below:

 Fig. 4. Test of the drop down control.

Fig. 4. Test of the drop down control.

 

 


Conclusion

In this article (first chapter of part eight) we have considered a relatively complex integrated control of the calendar graphical interface and its expanded version — a drop down calendar. Such controls for charts with a time scale will be very suitable. This is not a final version and future updates aimed to make the calendar more convenient and functional are planning to be released. You can also suggest your ideas in comments.

In the next article we will analyze such an important control of the graphical interface as a tree view and hierarchic list. 

You can download all information related to the part eight, to have the ability to test how things operate. For any questions you may have related to using the material provided in these files, you may refer to the detailed description of the process of developing library in one of the articles in the below list or ask questions in comments to the article.

List of articles (chapters) of part 8:

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2537

Attached files |
Graphical Interfaces VIII: The Tree View Control (Chapter 2) Graphical Interfaces VIII: The Tree View Control (Chapter 2)
The previous chapter of part VIII on graphical interfaces has focused on the elements of static and drop-down calendar. The second chapter will be dedicated to an equally complex element — a tree view, that is included in every complete library used for creating graphical interfaces. A tree view implemented in this article contains multiple flexible settings and modes, thus allowing to adjust this element of control to your needs.
Graphical Interfaces VII: The Tabs Control (Chapter 2) Graphical Interfaces VII: The Tabs Control (Chapter 2)
The first chapter of seventh part introduced three classes of controls for creating tables: text label table (CLabelsTable), edit box table (CTable) and rendered table (CCanvasTable). In this article (chapter two) we are going to consider the Tabs control.
Cross-Platform Expert Advisor: Introduction Cross-Platform Expert Advisor: Introduction
This article details a method by which cross-platform expert advisors can be developed faster and easier. The proposed method consolidates the features shared by both versions into a single class, and splits the implementation on derived classes for incompatible features.
Testing trading strategies on real ticks Testing trading strategies on real ticks
The article provides the results of testing a simple trading strategy in three modes: "1 minute OHLC", "Every tick" and "Every tick based on real ticks" using actual historical data.