![Graphical Interfaces VIII: The Calendar Control (Chapter 1)](https://c.mql5.com/i/articles/overlay.png)
Graphical Interfaces VIII: The Calendar Control (Chapter 1)
Contents
- Introduction
- Calendar control
- Description of the CDateTime structure
- Development of the CCalendar class
- Handlers of calendar events
- Testing the calendar control
- Drop down calendar
- Conclusion
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.
- Background
- Buttons for switching to previous and following months
- Combo box control with a list of months
- Fields to enter a year
- Array of text labels with abbreviations of days of the week
- Separation line
- Two-dimensional array of text labels with dates
- 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.
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:
- Move the mouse cursor — CHARTEVENT_MOUSE_MOVE.
- Select a new date in the calendar — ON_CHANGE_DATE.
- Click the left button of the mouse on a graphical object — CHARTEVENT_OBJECT_CLICK.
- Change properties of the chart — CHARTEVENT_CHART_CHANGE.
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.
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
![Graphical Interfaces VIII: The Tree View Control (Chapter 2)](https://c.mql5.com/2/23/av8__1.png)
![Graphical Interfaces VII: The Tabs Control (Chapter 2)](https://c.mql5.com/2/23/avatar-vii__1.png)
![Cross-Platform Expert Advisor: Introduction](https://c.mql5.com/2/24/Expert_Advisor_Introduction.png)
![Testing trading strategies on real ticks](https://c.mql5.com/2/23/test-real-tick-ava.png)
![MQL5 - Language of trade strategies built-in the MetaTrader 5 client terminal](https://c.mql5.com/i/registerlandings/logo-2.png)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use