English 中文 Español Deutsch 日本語 Português
Графические интерфейсы VIII: Элемент "Календарь" (Глава 1)

Графические интерфейсы VIII: Элемент "Календарь" (Глава 1)

MetaTrader 5Примеры | 21 июня 2016, 13:51
4 528 23
Anatoli Kazharski
Anatoli Kazharski

Содержание


 

Введение

О том, для чего предназначена эта библиотека, более подробно можно прочитать в первой статье серии: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). В конце статей каждой части представлен список глав со ссылками. Там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.

В восьмой части серии мы рассмотрим сложные составные элементы управления:

  • Статический и выпадающий календарь
  • Древовидный список
  • Файловый навигатор

В этой статье мы рассмотрим классы для создания статического и выпадающего календаря, а также структуру (struct) CDateTime из стандартной библиотеки для работы с датами и временем, которую будем использовать в своих разработках.

 


Элемент "Календарь"

Календарь — циклическая система исчисления времени в табличном представлении. В графическом интерфейсе должны присутствовать управляющие элементы, чтобы было удобно выбирать нужную дату в календаре. Кроме собственных методов взаимодействия с пользователем, в класс календаря могут быть подключены и другие элементы управления библиотеки. К примеру, мы подключим сюда классы для создания (1) комбо-бокса, (2) поля ввода и (3) кнопки.

Перечислим все составные части элемента «Календарь».

  1. Фон
  2. Кнопки для перехода к предыдущему и следующему месяцу
  3. Элемент «комбо-бокс» со списком месяцев
  4. Поле для ввода года
  5. Массив текстовых меток с сокращёнными названиями дней недели
  6. Разделительная линия
  7. Двухмерный массив текстовых меток с днями месяца
  8. Кнопка для быстрого перехода к текущей дате

 Рис. 1. Составные части элемента «Календарь».

Рис. 1. Составные части элемента «Календарь».


Допустим, в разрабатываемом MQL-приложении нужно выбрать диапазон дат, а для этого — указать начальную и конечную дату. Для выбора дня достаточно нажать на один из пунктов таблицы дней месяца. Для выбора месяца будет представлено несколько возможностей: (1) кнопка для перехода к предыдущему месяцу, (2) кнопка для перехода к следующему месяцу и (3) комбо-бокс со списком всех месяцев года. Указать год можно в поле ввода, введя значение вручную или воспользовавшись переключателями элемента. Чтобы быстро перейти к текущей дате, достаточно будет нажать на кнопку «Today: YYYY.MM.DD» в нижней части календаря.

Рассмотрим подробнее, как устроена структура CDateTime для работы с датами и временем.

 


Описание структуры CDateTime

Файл DateTime.mqh со структурой CDateTime находится в директориях торговых терминалов MetaTrader:

  • MetaTrader 4: <каталог данных>\MQL4\Include\Tools 
  • MetaTrader 5: <каталог данных>\MQL5\Include\Tools

Структура CDateTime — производная (расширение) от базовой системной структуры даты и времени MqlDateTime, которая, в свою очередь, содержит в себе восемь полей типа int (примеры по использованию смотрите в документации по языку MQL):

struct MqlDateTime
  {
   int year;           // год
   int mon;            // месяц
   int day;            // день
   int hour;           // час
   int min;            // минуты
   int sec;            // секунды
   int day_of_week;    // день недели (0-воскресенье, 1-понедельник, ... ,6-суббота)
   int day_of_year;    // порядковый номер в году (1 января имеет номер 0)
  };

Краткое описание методов структуры CDateTime без демонстрации кода можно также посмотреть в локальном справочнике (F1), в разделе Справочник/Стандартная библиотека/Классы для создания панелей и диалогов/CDateTime. При работе с этой структурой нужно помнить, что нумерация месяцев начинается с единицы (1), а нумерация недель – с нуля (0). 

С кодом методов структуры CDateTime можно ознакомиться самостоятельно, открыв файл DateTime.mqh.

 


Разработка класса CCalendar

Создаём файл Calendar.mqh и сразу подключаем его к файлу WndContainer.mqh, как и все элементы разрабатываемой библиотеки.

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

В файле Calendar.mqh создаём класс CCalendar со стандартными для всех элементов библиотеки методами, а также подключим файлы, необходимые здесь для разработки элемента:

//+------------------------------------------------------------------+
//|                                                     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 CCalendar : public CElement
  {
private:
   //--- Указатель на форму, к которой элемент присоединён
   CWindow          *m_wnd;
   //---
public:
                     CCalendar(void);
                    ~CCalendar(void);
   //--- Сохраняет указатель формы
   void              WindowPointer(CWindow &object)             { m_wnd=::GetPointer(object);            }
   //---
public:
   //--- Обработчик событий графика
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Таймер
   virtual void      OnEventTimer(void);
   //--- Перемещение элемента
   virtual void      Moving(const int x,const int y);
   //--- (1) Показ, (2) скрытие, (3) сброс, (4) удаление
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Установка, (2) сброс приоритетов на нажатие левой кнопки мыши
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Сбросить цвет
   virtual void      ResetColors(void) {}
  };

Так же, как и у других элементов интерфейса библиотеки, нужна возможность настраивать внешний вид календаря. Перечислим свойства, относящиеся к внешнему виду, которые будут доступны для настройки пользователем.

  • Цвет фона
  • Цвет рамки фона
  • Цвета пунктов календаря (дней месяца) в разных состояниях
  • Цвета рамок пунктов календаря в разных состояниях
  • Цвета текста пунктов календаря в разных состояниях
  • Цвет разделительной линии
  • Ярлыки кнопок (в активном/заблокированном состоянии) для перехода к предыдущему/следующему месяцу

В листинге кода ниже показаны названия полей и методов класса CCalendar для установки свойств внешнего вида календаря перед его созданием:

class CCalendar : public CElement
  {
private:
   //--- Цвет фона
   color             m_area_color;
   //--- Цвет рамки фона
   color             m_area_border_color;
   //--- Цвета пунктов календаря (дней месяца) в разных состояниях
   color             m_item_back_color;
   color             m_item_back_color_off;
   color             m_item_back_color_hover;
   color             m_item_back_color_selected;
   //--- Цвета рамок пунктов календаря в разных состояниях
   color             m_item_border_color;
   color             m_item_border_color_hover;
   color             m_item_border_color_selected;
   //--- Цвета текста пунктов календаря в разных состояниях
   color             m_item_text_color;
   color             m_item_text_color_off;
   color             m_item_text_color_hover;
   //--- Цвет разделительной линии
   color             m_sepline_color;
   //--- Ярлыки кнопок (в активном/заблокированном состоянии) для перехода к предыдущему/следующему месяцу
   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:
   //--- Установка цвета (1) фона, (2) рамки фона, (3) цвет разделительной линии
   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;                   }
   //--- Цвета пунктов календаря (дней месяца) в разных состояниях
   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;        }
   //--- Цвета рамок пунктов календаря в разных состояниях
   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;      }
   //--- Цвета текста пунктов календаря в разных состояниях
   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;           }
   //--- Установка ярлыков кнопок (в активном/заблокированном состоянии) для перехода к предыдущему/следующему месяцу
   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;      }
  };

Для создания календаря понадобятся девять приватных (private) методов и один публичный (public). Для отображения дней недели и дней месяца понадобятся статические массивы объектов типа CEdit

Таблица дней месяца будет состоять из 42 пунктов. Этого достаточно, чтобы уместить максимальное количество дней месяца, которое равно тридцати одному дню, с учётом максимального смещения первого числа месяца в таблице, когда оно выпадает на воскресенье (в данной реализации воскресенье — седьмой день недели).

class CCalendar : public CElement
  {
private:
   //--- Объекты и элементы для создания календаря
   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:
   //--- Методы для создания календаря
   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);
  };

В случаях, когда календарь будет частью какого-то другого элемента, может понадобится доступ к элементам, из которых состоит календарь. Поэтому добавим в класс методы, которые возвращают указатели на элементы, перечисленные в нижеследующем списке.

  • Комбо-бокс (CComboBox)
  • Список комбо-бокса (CListView)
  • Вертикальная полоса прокрутки списка (CScrollV)
  • Поле ввода (CSpinEdit)
  • Кнопка (CIconButton)

class CCalendar : public CElement
  {
public:
   //--- (1) Получение указателя комбо-бокса, 
   //    (2) получение указателя списка, (3) получение указателя полосы прокрутки списка, 
   //    (4) получение указателя поля ввода, (5) получение указателя кнопки
   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));  }
  };

Для работы с датой и временем нам понадобятся три экземпляра структуры CDateTime.

  • Для взаимодействия с пользователем. Это та дата, которую пользователь выбирает (выделяет в календаре) сам.
  • Текущая или локальная на компьютере пользователя дата. Эта дата всегда будет отдельно выделена в календаре.
  • Экземпляр для расчётов и проверок. Будет использоваться как счётчик во многих методах класса CCalendar.

class CCalendar : public CElement
  {
private:
   //--- Экземпляры структуры для работы с датами и временем:
   CDateTime         m_date;      // выделенная пользователем дата
   CDateTime         m_today;     // текущая (локальная на компьютере пользователя) дата
   CDateTime         m_temp_date; // экземпляр для расчётов и проверок
  };

Первоначальная инициализация структур времени будет осуществляться в конструкторе класса CCalendar. В структуры экземпляров m_date и 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("")
  {
//--- Сохраним имя класса элемента в базовом классе
   CElement::ClassName(CLASS_NAME);
//--- Установим приоритеты на нажатие левой кнопки мыши
   m_zorder        =0;
   m_area_zorder   =1;
   m_button_zorder =2;
//--- Инициализация структур времени
   m_date.DateTime(::TimeLocal());
   m_today.DateTime(::TimeLocal());
  }

Сразу после установки календаря нужно установить значения текущего месяца и года в пункты таблицы (дни месяца). Для этого нам понадобятся четыре метода.

1. Метод CCalendar::OffsetFirstDayOfMonth(), с помощью которого можно определить разницу (в днях) от первого пункта таблицы календаря до пункта первого дня текущего месяца. В начале этого метода формируем дату в строковом формате с первым числом текущего года и месяца. Затем, преобразовывая строку в формат datetime, устанавливаем дату в структуру для расчётов. Далее, если результат вычитания единицы от текущего номера недели больше либо равен нулю, то устанавливаем вернуть результат, но если меньше нуля (-1) – вернуть значение 6. В конце метода производится смещение назад на полученную разницу (количество дней).
class CCalendar : public CElement
  {
private:
   //--- Определение разницы от первого пункта таблицы календаря до пункта первого дня текущего месяца
   int               OffsetFirstDayOfMonth(void);
  };
//+------------------------------------------------------------------+
//| Определение разницы от первого пункта таблицы календаря          |
//| до пункта первого дня текущего месяца                            |
//+------------------------------------------------------------------+
int CCalendar::OffsetFirstDayOfMonth(void)
  {
//--- Получим дату первого дня выделенного года и месяца в виде строки
   string date=string(m_date.year)+"."+string(m_date.mon)+"."+string(1);
//--- Установим эту дату в структуру для расчётов
   m_temp_date.DateTime(::StringToTime(date));
//--- Если результат вычитания единицы от текущего номера дня недели больше либо равен нулю,
//    вернуть результат, иначе — вернуть значение 6
   int diff=(m_temp_date.day_of_week-1>=0) ? m_temp_date.day_of_week-1 : 6;
//--- Запомним дату, которая приходится на первый пункт таблицы
   m_temp_date.DayDec(diff);
   return(diff);
  }

2. Метод CCalendar::SetCalendar(). В этом методе будет осуществляться заполнение пунктов таблицы для выбранного года и месяца. В самом начале вызывается метод CCalendar::OffsetFirstDayOfMonth(). Затем в цикле проходим по всем пунктам таблицы, устанавливая в каждый пункт номер дня из структуры для расчётов (m_temp_date.day), ведь именно в неё в методе CCalendar::OffsetFirstDayOfMonth() была установлена дата, от которой нужно вести счёт. В конце метода осуществляется переход к следующему дню календаря.

Class CCalendar : public CElement
  {
private:
   //--- Отображает последние изменения в таблице календаря
   void              SetCalendar(void);
  };
//+------------------------------------------------------------------+
//| Установка значений календаря                                     |
//+------------------------------------------------------------------+
void CCalendar::SetCalendar(void)
  {
//--- Определение разницы от первого пункта таблицы календаря до пункта первого дня текущего месяца
   int diff=OffsetFirstDayOfMonth();
//--- Пройдёмся в цикле по всем пунктам таблицы календаря
   for(int i=0; i<42; i++)
     {
      //--- Установка дня в текущий пункт таблицы
      m_days[i].Description(string(m_temp_date.day));
      //--- Перейти к следующей дате
      m_temp_date.DayInc();
     }
  }

3. Метод CCalendar::HighlightDate() будет использоваться для подсветки текущей даты (локальная дата на компьютере пользователя) и выбранного пользователем дня в таблице календаря. Здесь также сначала в структуру для расчётов (m_temp_date) устанавливается дата для первого пункта таблицы. Затем в цикле для всех пунктов определяется цвет (1) фона, (2) рамки фона и (3) отображаемого текста (см. листинг кода ниже). 

Class CCalendar : public CElement
  {
private:
   //--- Подсветка текущего дня и выбранного пользователем дня
   void              HighlightDate(void);
  };
//+------------------------------------------------------------------+
//| Подсветка текущего дня и выбранного пользователем дня            |
//+------------------------------------------------------------------+
void CCalendar::HighlightDate(void)
  {
//--- Определение разницы от первого пункта таблицы календаря до пункта первого дня текущего месяца
   OffsetFirstDayOfMonth();
//--- Пройдёмся в цикле по пунктам таблицы календаря
   for(int i=0; i<42; i++)
     {
      //--- Если месяц пункта совпадает с текущим месяцем и 
      //    день пункта совпадает с выделенным днём
      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);
         //--- Перейти к следующему пункту таблицы
         m_temp_date.DayInc();
         continue;
        }
      //--- Если это текущая дата (сегодня)
      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);
         //--- Перейти к следующему пункту таблицы
         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);
      //--- Перейти к следующему пункту таблицы
      m_temp_date.DayInc();
     }
  }

4. Метод CCalendar::UpdateCalendar() будет использоваться во всех методах, предназначенных для взаимодействия пользователя с графическим интерфейсом календаря. Здесь последовательно вызываются методы CCalendar::SetCalendar() и CCalendar::HighlightDate(), которые были рассмотрены выше. После этого в поле ввода и комбо-бокс календаря устанавливаются год и месяц. Обратите внимание, что для выбора нужного пункта в списке комбо-бокса нужно от месяца вычесть единицу, так как в структурах даты и времени перечисление месяцев начинается с единицы, а нумерация пунктов в списках библиотеки (CListView) – с нуля. 

Class CCalendar : public CElement
  {
public:
   //--- Отображение последних изменений в календаре
   void              UpdateCalendar(void);
  };
//+------------------------------------------------------------------+
//| Отображение последних изменений в календаре                      |
//+------------------------------------------------------------------+
void CCalendar::UpdateCalendar(void)
  {
//--- Отобразить изменения в таблице календаря
   SetCalendar();
//--- Подсветка текущего дня и выбранного пользователем дня
   HighlightDate();
//--- Установим год в поле ввода
   m_years.ChangeValue(m_date.year);
//--- Установим месяц в списке комбо-бокса
   m_months.SelectedItemByIndex(m_date.mon-1);
  }

Изменение цвета пунктов-дней месяца в таблице календаря при наведении курсора мыши будет обеспечено методом CCalendar::ChangeObjectsColor(), в который нужно передать координаты курсора (x, y). Перед тем, как начать цикл с перебором всех пунктов, определяется, насколько смещён пункт с первым днём месяца от первого пункта таблицы, для установки начального значения счётчика (структура m_temp_date). Затем в цикле выделенный день и текущая (локальная на компьютере пользователя) дата пропускаются, а на остальных проверяется фокус курсора мыши. При изменении цвета учитывается, к какому месяцу относится день.

Class CCalendar : public CElement
  {
public:
   //--- Изменение цвета объектов в таблице календаря
   void              ChangeObjectsColor(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Изменение цвета объектов в таблице календаря                     |
//| при наведении курсора                                            |
//+------------------------------------------------------------------+
void CCalendar::ChangeObjectsColor(const int x,const int y)
  {
//--- Определение разницы от первого пункта таблицы календаря до пункта первого дня текущего месяца
   OffsetFirstDayOfMonth();
//--- Пройдёмся в цикле по пунктам таблицы календаря
   int items_total=::ArraySize(m_days);
   for(int i=0; i<items_total; i++)
     {
      //--- Если месяц пункта совпадает с текущим месяцем и 
      //    день пункта совпадает с выделенным днём
      if(m_temp_date.mon==m_date.mon &&
         m_temp_date.day==m_date.day)
        {
         //--- Перейти к следующему пункту таблицы
         m_temp_date.DayInc();
         continue;
        }
      //--- Если год/месяц/день пункта совпадает с годом/месяцем/днём текущей даты (сегодня)
      if(m_temp_date.year==m_today.year && 
         m_temp_date.mon==m_today.mon &&
         m_temp_date.day==m_today.day)
        {
         //--- Перейти к следующему пункту таблицы
         m_temp_date.DayInc();
         continue;
        }
      //--- Если курсор мыши находится над этим пунктом
      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);
        }
      //--- Перейти к следующему пункту таблицы
      m_temp_date.DayInc();
     }
  }

Нужна возможность выбирать дату в календаре программно, а также получать выбранную пользователем дату и текущую дату (сегодня). Для этого добавим в класс публичные (public) методы CCalendar::SelectedDate() и CCalendar::Today().

Class CCalendar : public CElement
  {
public:
   //--- (1) Установить (выделить) и (2) получить выделенную дату, (3) получить текущую дату в календаре
   void              SelectedDate(const datetime date);
   datetime          SelectedDate(void)                         { return(m_date.DateTime());             }
   datetime          Today(void)                                { return(m_today.DateTime());            }
  };
//+------------------------------------------------------------------+
//| Выбор новой даты                                                 |
//+------------------------------------------------------------------+
void CCalendar::SelectedDate(const datetime date)
  {
//--- Сохранение даты в структуре и поле класса
   m_date.DateTime(date);
//--- Отображение последних изменений в календаре
   UpdateCalendar();
  }

Допустим, в календаре выбрана дата 29 февраля 2016 года (2016.02.29) и пользователь в поле ввода ввёл (или установил с помощью переключателя инкремента) год 2017. В 2017 году в феврале 28 дней. Какой день должен быть выделен в таблице календаря в этом случае? Обычно в таких случаях в календарях различных графических интерфейсов выделяется ближайший, то есть последний день месяца. В данном случае это 28 февраля 2017 года. Сделаем так же. Для этого добавим в класс метод CCalendar::CorrectingSelectedDay() для подобной корректировки. Код этого метода состоит всего из пары строчек. В структуре CDateTime уже есть метод для получения количества дней в месяце с учётом високосного года — CDateTime::DaysInMonth(). Поэтому достаточно сравнить текущий день месяца с количеством дней в текущем месяце и если окажется, что количество дней больше, чем выделенное в календаре число – заменить его.

Class CCalendar : public CElement
  {
private:
   //--- Корректировка выделенного дня по количеству дней в месяце
   void              CorrectingSelectedDay(void);
  };
//+------------------------------------------------------------------+
//| Определение первого дня месяца                                   |
//+------------------------------------------------------------------+
void CCalendar::CorrectingSelectedDay(void)
  {
//--- Установить текущее количество дней в месяце, если значение выделенного дня больше
   if(m_date.day>m_date.DaysInMonth())
      m_date.day=m_date.DaysInMonth();
  }

 

 


Обработчики событий календаря

Теперь рассмотрим обработчики событий календаря. Всего будет восемь приватных (private) методов для обработки событий из списка, представленного ниже.

  • Нажатие на кнопке перехода к предыдущему месяцу
  • Нажатие на кнопке перехода к следующему месяцу
  • Выбор месяца в выпадающем списке комбо-бокса
  • Ввод значения в поле ввода года
  • Нажатие на кнопке перехода к следующему году
  • Нажатие на кнопке перехода к предыдущему году
  • Нажатие на дне месяца
  • Нажатие на кнопке перехода к текущей дате
  • Каждое действие из списка выше влечёт за собой изменения в календаре. Далее разберём по порядку код этих методов.

Для работы с календарём нам понадобится новый идентификатор пользовательского события - ON_CHANGE_DATE, который будем использовать для отправки сообщения о том, что дата в календаре была изменена пользователем. Кроме этого, добавим ещё один идентификатор для работы с комбо-боксом — ON_CLICK_COMBOBOX_BUTTON, чтобы определять нажатие на кнопке этого элемента. 

#define ON_CLICK_COMBOBOX_BUTTON  (21) // Нажатие на кнопке комбо-бокса
#define ON_CHANGE_DATE            (22) // Изменение даты в календаре

Отправка сообщения с идентификатором ON_CLICK_COMBOBOX_BUTTON должна осуществляться из метода OnClickButton() класса CComboBox. Добавим отправку сообщения в этот метод (см. листинг кода ниже):

//+------------------------------------------------------------------+
//| Нажатие на кнопку комбо-бокса                                    |
//+------------------------------------------------------------------+
bool CComboBox::OnClickButton(const string clicked_object)
  {
//--- Выйти, если форма заблокирована и идентификаторы не совпадают
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return(false);
//--- Выйдем, если чужое имя объекта  
   if(clicked_object!=m_button.Name())
      return(false);
//--- Изменить состояние списка
   ChangeComboBoxListState();
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_BUTTON,CElement::Id(),0,"");
   return(true);
  }

В классе CCalendar отслеживание пользовательского события с идентификатором ON_CLICK_COMBOBOX_BUTTON понадобится для управления состоянием элементов (CSpinEdit и CIconButton), которые являются частью календаря (см. листинг кода ниже): 

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события нажатия на кнопке комбо-бокса
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_BUTTON)
     {
      //--- Выйти, если идентификаторы элементов не совпадают
      if(lparam!=CElement::Id())
         return;
      //--- Активировать или заблокировать элементы в зависимости от текущего состояния видимости списка
      m_years.SpinEditState(!m_months.GetListViewPointer().IsVisible());
      m_button_today.ButtonState(!m_months.GetListViewPointer().IsVisible());
     }
  }

В графических интерфейсах различных систем, например, в операционной системе Windows 7, переход к предыдущему/следующему месяцу посредством нажатия кнопок-стрелок вправо/влево приводит к тому, что выделяется первый день месяца, причем независимо от того, какой день был до этого выбран пользователем в таблице календаря. Реализуем такое же поведение в календаре разрабатываемой библиотеки. Для перехода к предыдущему/следующему месяцу будут использоваться методы CCalendar::OnClickMonthDec() и CCalendar::OnClickMonthInc(). Код этих методов очень похож, поэтому для примера приведём описание только одного из них, то есть, обработку нажатия кнопки для перехода к предыдущему месяцу.

В начале метода производится проверка на имя графического объекта, на который нажал пользователь. Затем, в случае, если текущий год в календаре равен минимальному установленному и текущий месяц «Январь» (то есть, мы дошли до минимального ограничения), то кратковременно подсветим текст в поле ввода и выйдем из метода. 

Если же мы ещё не дошли до минимального ограничения, то происходит следующее.

  • Устанавливаем для кнопки состояние On.
  • Переходим к предыдущему месяцу. 
  • Устанавливаем первое число месяца.
  • Сбрасываем время на начало суток (00:00:00). Для этого добавим в класс метод CCalendar::ResetTime().
  • Обновляем календарь для отображения последних изменений.
  • Отправляем сообщение с (1) идентификатором графика, (2) идентификатором события ON_CHANGE_DATE, (3) идентификатором элемента и (4) установленной датой. Сообщение с такими же параметрами будет отправляться и из других методов-обработчиков событий календаря. 

Class CCalendar : public CElement
  {
private:
   //--- Обработка нажатия на кнопке перехода к предыдущему месяцу
   bool              OnClickMonthDec(const string clicked_object);
   //--- Обработка нажатия на кнопке перехода к следующему месяцу
   bool              OnClickMonthInc(const string clicked_object);
   //--- Сброс времени на начало суток
   void              ResetTime(void);
  };
//+------------------------------------------------------------------+
//| Нажатие на стрелку влево. Переход к предыдущему месяцу.          |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthDec(const string clicked_object)
  {
//--- Выйти, если чужое имя объекта
   if(::StringFind(clicked_object,m_month_dec.Name(),0)<0)
      return(false);
//--- Если текущий год в календаре равен минимальному указанному и
//    текущий месяц "Январь"
   if(m_date.year==m_years.MinValue() && m_date.mon==1)
     {
      //--- Подсветить значение и выйти
      m_years.HighlightLimit();
      return(true);
     }
//--- Установим состояние On
   m_month_dec.State(true);
//--- Перейти к предыдущему месяцу
   m_date.MonDec();
//--- Установить первое число месяца
   m_date.day=1;
//--- Установить время на начало суток
   ResetTime();
//--- Отображение последних изменений в календаре
   UpdateCalendar();
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }
//+------------------------------------------------------------------+
//| Сброс времени на начало суток                                    |
//+------------------------------------------------------------------+
void CCalendar::ResetTime(void)
  {
   m_date.hour =0;
   m_date.min  =0;
   m_date.sec  =0;
  }

Код в методе CCalendar::OnClickMonthInc() почти такой же. Отличие состоит только в том, что в проверке на ограничение проверяется выход за максимальный установленный в свойствах календаря год и последний месяц в году (декабрь).

Обработка выбора месяца в списке осуществляется с помощью метода CCalendar::OnClickMonthList() по приходу события с идентификатором ON_CLICK_COMBOBOX_ITEM (см. листинг кода ниже). В главном обработчике в этот метод передаётся long-параметр, в котором содержится идентификатор элемента. Если идентификаторы не совпадают, то программа выйдет из метода. Так как выбор пункта в списке приводит к его закрытию, то нужно разблокировать ранее заблокированные элементы (поле ввода и кнопка) календаря. Затем устанавливаем выбранный месяц в структуру даты и времени и в случае необходимости корректируем выделенный день по количеству дней месяца. Осталось только установить время на начало суток, отобразить последние внесённые изменения в календаре и отправить в поток событий сообщение о том, что дата в календаре изменилась.

Class CCalendar : public CElement
  {
private:
   //--- Обработка выбора месяца в списке
   bool              OnClickMonthList(const long id);
  };
//+------------------------------------------------------------------+
//| Обработка выбора месяца в списке                                 |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthList(const long id)
  {
//--- Выйти, если идентификаторы элементов не совпадают
   if(id!=CElement::Id())
      return(false);
//--- Разблокировать элементы
   m_years.SpinEditState(true);
   m_button_today.ButtonState(true);
//--- Получим выбранный месяц в списке
   int month=m_months.GetListViewPointer().SelectedItemIndex()+1;
   m_date.Mon(month);
//--- Корректировка выделенного дня по количеству дней в месяце
   CorrectingSelectedDay();
//--- Установить время на начало суток
   ResetTime();
//--- Отобразить изменения в таблице календаря
   UpdateCalendar();
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Для обработки ввода значения в поле ввода года предназначен метод CCalendar::OnEndEnterYear(). В начале этого метода стоит проверка на имя объекта типа OBJ_EDIT, в который был осуществлён ввод нового значения. Далее — ещё одна проверка на то, было ли изменено значение. Если имя поля ввода не принадлежит этому календарю или значение не изменилось, то программа выходит из метода. Если же первые две проверки пройдены, то далее введённое значение корректируется в случае выхода за установленные ограничения. Следующая корректировка исправляет выделенную пользователем дату, если число дня месяца больше количества дней в этом месяце. После этого можно установить дату в рабочую структуру даты и времени, отобразить в календаре внесённые изменения и отправить сообщение о том, что дата в этом календаре была изменена. Код метода CCalendar::OnEndEnterYear() можно подробнее изучить в листинге ниже: 

Class CCalendar : public CElement
  {
private:
   //--- Обработка ввода значения в поле ввода лет
   bool              OnEndEnterYear(const string edited_object);
  };
//+------------------------------------------------------------------+
//| Обработка ввода значения в поле ввода лет                        |
//+------------------------------------------------------------------+
bool CCalendar::OnEndEnterYear(const string edited_object)
  {
//--- Выйдем, если чужое имя объекта
   if(::StringFind(edited_object,m_years.Object(2).Name(),0)<0)
      return(false);
//--- Выйдем, если значение не изменилось
   string value=m_years.Object(2).Description();
   if(m_date.year==(int)value)
      return(false);
//--- Скорректируем значение в случае выхода за установленные ограничения
   if((int)value<m_years.MinValue())
     {
      value=(string)int(m_years.MinValue());
      //--- Подсветить значение
      m_years.HighlightLimit();
     }
   if((int)value>m_years.MaxValue())
     {
      value=(string)int(m_years.MaxValue());
      //--- Подсветить значение
      m_years.HighlightLimit();
     }
//--- Определим количество дней в текущем месяце
   string year  =value;
   string month =string(m_date.mon);
   string day   =string(1);
   m_temp_date.DateTime(::StringToTime(year+"."+month+"."+day));
//--- Если значение выделенного дня больше, чем количество дней в месяце,
//    установить текущее количество дней в месяце в качестве выделенного дня
   if(m_date.day>m_temp_date.DaysInMonth())
      m_date.day=m_temp_date.DaysInMonth();
//--- Установим дату в структуру
   m_date.DateTime(::StringToTime(year+"."+month+"."+string(m_date.day)));
//--- Отобразим изменения в таблице календаря
   UpdateCalendar();
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Методы для обработки нажатия на кнопках перехода к следующему/предыдущему годам называются CCalendar::OnClickYearInc() и CCalendar::OnClickYearDec(). Приведём здесь код только одного из них, так как они практически идентичны, кроме условия с проверкой на достижение ограничения. В самом начале проверяется текущее состояние списка для выбора месяца. Если он открыт, закроем его. Далее, если идентификаторы элементов не совпадают, программа выходит отсюда. Затем следует основное условие методов, где по условию осуществляется один шаг вперёд для CCalendar::OnClickYearInc() или один шаг назад для CCalendar::OnClickYearDec(). После этого в случае необходимости (1) осуществляется корректировка выделенного дня по количеству дней в месяце, (2) в календаре отображаются последние изменения и (3) отправляется сообщение о том, что дата в календаре была изменена.  

Class CCalendar : public CElement
  {
private:
   //--- Обработка нажатия на кнопке перехода к следующему году
   bool              OnClickYearInc(const long id);
   //--- Обработка нажатия на кнопке перехода к предыдущему году
   bool              OnClickYearDec(const long id);
  };
//+------------------------------------------------------------------+
//| Обработка нажатия на кнопке перехода к следующему году           |
//+------------------------------------------------------------------+
bool CCalendar::OnClickYearInc(const long id)
  {
//--- Если список месяцев открыт, закроем его
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Выйти, если идентификаторы элементов не совпадают
   if(id!=CElement::Id())
      return(false);
//--- Если год меньше максимального указанного, увеличить значение на один
   if(m_date.year<m_years.MaxValue())
      m_date.YearInc();
//--- Корректировка выделенного дня по количеству дней в месяце
   CorrectingSelectedDay();
//--- Отобразить изменения в таблице календаря
   UpdateCalendar();
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Теперь рассмотрим метод CCalendar::OnClickDayOfMonth() для обработки нажатия на дне месяца в таблице календаря. 

Как уже ранее упоминалось, таблица календаря состоит из 42 пунктов. Поэтому, в зависимости от того, на какой пункт будет приходиться первое число текущего месяца, иногда будет получаться так, что в таблице будут дни предыдущего и следующего месяцев. В случаях, когда пользователь будет нажимать на день, который относится не к текущему месяцу, будет осуществляться переключение на тот месяц, которому принадлежит этот день. 

В начале метода нужно пройти проверку на принадлежность объекта к пункту таблицы календаря, а также сверить идентификаторы элемента. Идентификатор извлекается из имени объекта с помощью метода CCalendar::IdFromObjectName(), который уже рассматривался ранее на примере других элементов библиотеки. Если первые две проверки пройдены, то далее с помощью метода CCalendar::OffsetFirstDayOfMonth() определяется разница от первого пункта таблицы календаря до пункта первого дня текущего месяца. После вызова этого метода счётчик m_temp_date готов к работе, и далее в цикле программа пройдёт по всем пунктам с целью поиска пункта, на который нажал пользователь. 

Рассмотрим подробнее, какая логика содержится в этом цикле. Возможно, что даты в первых пунктах относятся к году раньше, чем установленный в системе минимум (1970 год). Если это так, то подсветим значение в поле ввода лет и сразу перейдём к следующему пункту, так как за установленные ограничения пользователю запрещено переходить. Если же пункт оказался в доступной зоне и нажатие было осуществлено на нём, то (1) в рабочей структуре (m_date) сохраняется дата, (2) в календаре отображаются последние изменения, (3) цикл прерывается и (4) в конце метода отправляется сообщение о том, что дата в календаре была изменена.

Если же не исполнилось ни одно из первых двух условий в цикле, то счётчик структуры для расчётов (m_temp_date) увеличивается на один день, после чего проверяется выход за установленный в системе календаря максимум. Если максимум всё ещё не достигнут, программа переходит к следующему пункту. После достижения максимума вызывается подсветка значения в поле ввода лет, и программа выходит из метода. 

Class CCalendar : public CElement
  {
private:
   //--- Обработка нажатия на дне месяца
   bool              OnClickDayOfMonth(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Обработка нажатия на дне месяца календаря                        |
//+------------------------------------------------------------------+
bool CCalendar::OnClickDayOfMonth(const string clicked_object)
  {
//--- Выйдем, если нажатие было не на дне календаря
   if(::StringFind(clicked_object,CElement::ProgramName()+"_calendar_day_",0)<0)
      return(false);
//--- Получим идентификатор и индекс из имени объекта
   int id=IdFromObjectName(clicked_object);
//--- Выйти, если идентификатор не совпадает
   if(id!=CElement::Id())
      return(false);
//--- Определение разницы от первого пункта таблицы календаря до пункта первого дня текущего месяца
   OffsetFirstDayOfMonth();
//--- Пройдёмся в цикле по пунктам таблицы календаря
   for(int i=0; i<42; i++)
     {
      //--- Если дата текущего пункта меньше, чем установленный в системе минимум
      if(m_temp_date.DateTime()<datetime(D'01.01.1970'))
        {
         //--- Если это объект, на который нажали
         if(m_days[i].Name()==clicked_object)
           {
            //--- Подсветить значение и выйти
            m_years.HighlightLimit();
            return(false);
           }
         //--- Перейти к следующей дате
         m_temp_date.DayInc();
         continue;
        }
      //--- Если это объект, на который нажали
      if(m_days[i].Name()==clicked_object)
        {
         //--- Сохраним дату
         m_date.DateTime(m_temp_date.DateTime());
         //--- Отображение последних изменений в календаре
         UpdateCalendar();
         break;
        }
      //--- Перейти к следующей дате
      m_temp_date.DayInc();
      //--- Проверка выхода за установленный в системе максимум
      if(m_temp_date.year>m_years.MaxValue())
        {
         //--- Подсветить значение и выйти
         m_years.HighlightLimit();
         return(false);
        }
     }
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Осталось рассмотреть последний метод-обработчик событий календаря — CCalendar::OnClickTodayButton(), который предназначен для обработки нажатия на кнопке перехода к текущей дате. Из главного обработчика событий в этот метод передаётся long-параметр (идентификатор элемента). В начале метода осуществляется закрытие списка, если он сейчас открыт. Затем сверяются идентификаторы. Если идентификаторы совпадают, устанавливаем локальную (на компьютере пользователя) дату и время в рабочую структуру. Затем календарь обновляется для отображения последних изменений, и в конце метода отправляется сообщение о том, что дата в этом календаре изменилась

Class CCalendar : public CElement
  {
private:
   //--- Обработка нажатия на кнопке перехода к текущей дате
   bool              OnClickTodayButton(const long id);
  };
//+------------------------------------------------------------------+
//| Обработка нажатия на кнопке перехода к текущей дате              |
//+------------------------------------------------------------------+
bool CCalendar::OnClickTodayButton(const long id)
  {
//--- Если список месяцев открыт, закроем его
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Выйти, если идентификаторы элементов не совпадают
   if(id!=CElement::Id())
      return(false);
//--- Установить текущую дату
   m_date.DateTime(::TimeLocal());
//--- Отображение последних изменений в календаре
   UpdateCalendar();
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

В главном обработчике событий календаря CCalendar::OnEvent() получилось восемь блоков кода (один уже был приведён в самом начале этого раздела). Перечислим их.

  • Обработка событий перемещения курсора мыши (CHARTEVENT_MOUSE_MOVE). Отслеживание курсора мыши осуществляется только тогда, когда элемент не скрыт, а также в случаях, когда элемент выпадающий и форма при этом не заблокирована. Программа выходит из этого блока также тогда, когда список месяцев активен (открыт). Если же список скрыт и левая кнопка мыши нажата, активируются заблокированные ранее (в момент открытия списка) элементы календаря, если хотя бы один из них ещё заблокирован.  В самом конце в метод CCalendar::ChangeObjectsColor() передаются координаты курсора мыши для изменения цвета пунктов таблицы календаря. 
//--- Обработка события перемещения курсора
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Выйти, если элемент скрыт
      if(!CElement::IsVisible())
         return;
      //--- Выйти, если элемент не выпадающий и форма заблокирована
      if(!CElement::IsDropdown() && m_wnd.IsLocked())
         return;
      //--- Координаты и состояние левой кнопки мыши
      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());
      //--- Выйти, если список месяцев активен
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- Если список неактивен и левая кнопка мыши нажата...
      else if(m_mouse_state)
        {
         //--- ...активируем заблокированные ранее (в момент открытия списка) элементы,
         //       если хотя бы один из них ещё не разблокирован
         if(!m_button_today.ButtonState())
           {
            m_years.SpinEditState(true);
            m_button_today.ButtonState(true);
           }
        }
      //--- Изменение цвета объектов
      ChangeObjectsColor(x,y);
      return;
     }

  • Обработка события нажатия левой кнопки мыши на объекте (CHARTEVENT_OBJECT_CLICK). Это происходит после нажатия на каком-либо объекте календаря. Затем, если установлен флаг на зажатую левую кнопку мыши, активируются элементы календаря (поле ввода и кнопка). Далее проверяется, на каком именно элементе был осуществлено нажатие. Для этого используются ранее рассмотренные в этой статье методы: CCalendar::OnClickMonthDec(), CCalendar::OnClickMonthInc() и CCalendar::OnClickDayOfMonth().  

//--- Обработка события нажатия левой кнопки мыши на объекте
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Выйти, если форма заблокирована и идентификаторы не совпадают
      if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
         return;
      //--- Выйти, если список месяцев активирован
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- Активировать элементы (список и поле ввода), если левая кнопка мыши нажата 
      if(m_mouse_state)
        {
         m_years.SpinEditState(true);
         m_button_today.ButtonState(true);
        }
      //--- Обработка нажатия на кнопках переключения месяцев
      if(OnClickMonthDec(sparam))
         return;
      if(OnClickMonthInc(sparam))
         return;
      //--- Обработка нажатия на дне календаря
      if(OnClickDayOfMonth(sparam))
         return;
     }

  • Обработка события нажатия на пункте списка комбо-бокса (ON_CLICK_COMBOBOX_ITEM). Для обработки этого события используется метод CCalendar::OnClickMonthList().  

//--- Обработка события нажатия на пункте списка комбо-бокса
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Обработка выбора месяца в списке
      if(!OnClickMonthList(lparam))
         return;
      //---
      return;
     }

  •  Обработка события нажатия на кнопках инкремента/декремента (ON_CLICK_INC/ON_CLICK_DEC). Для обработки этих событий оформлены два отдельных блока кода для вызова методов: CCalendar::OnClickYearInc() и CCalendar::OnClickYearDec()

//--- Обработка события нажатия на кнопке инкремента
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC)
     {
      //--- Обработка нажатия на кнопке перехода к следующему году
      if(!OnClickYearInc(lparam))
         return;
      //---
      return;
     }
//--- Обработка события нажатия на кнопке декремента
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      //--- Обработка нажатия на кнопке перехода к предыдущему году
      if(!OnClickYearDec(lparam))
         return;
      //---
      return;
     }

  • Обработка события ввода значения в поле ввода (CHARTEVENT_OBJECT_ENDEDIT). Ввод значения в поле ввода лет календаря приведёт программу в этот блок кода для вызова метода CCalendar::OnEndEnterYear(). 

//--- Обработка события ввода значения в поле ввода
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      //--- Обработка ввода значения в поле ввода лет
      if(OnEndEnterYear(sparam))
         return;
      //---
      return;
     }

  • Обработка события нажатия на кнопке (ON_CLICK_BUTTON). В этом блоке кода с помощью метода CCalendar::OnClickTodayButton() обрабатывается нажатие на кнопку, предназначенную для перехода на текущую дату (локальная дата на компьютере пользователя):

//--- Обработка события нажатия на кнопке
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Обработка нажатия на кнопке перехода к текущей дате
      if(!OnClickTodayButton(lparam))
         return;
      //---
      return;
     }

Чтобы сделать использование такого сложного элемента, как «Календарь», более удобным, добавим в него функционал, который позволит осуществлять быструю перемотку его значений. Подобное уже реализовано в классах списка (CListView) и поля ввода (CSpinEdit). Но теперь это нужно связать с таблицей календаря, чтобы при каждом изменении, при перемотке значений в поле ввода, значения в таблице тоже обновлялись. Добавим также возможность ускоренно перематывать календарь с помощью кнопок переключения месяцев. 

Приведём здесь описание только для основной (рабочей) части метода, так как условия для входа в рабочий блок уже рассматривались в других статьях. После того, как условия исполнились, нужно проверить, какая кнопка элементов календаря сейчас нажата (см. выделенные жёлтым маркером строки). Если ни одна из перечисленных кнопок сейчас не нажата, программа выходит из метода. При обнаружении нажатой кнопки нужно пройти ряд проверок для определения, не выходит ли значение за ограничения, установленные в календаре. Если мы дошли до ограничения, то подсвечивается текст в поле ввода, и программа выходит из метода. Если же ограничение не достигнуто, календарь обновляется для отображения последних изменений, и отправляется сообщение о том, что дата изменилась

Class CCalendar : public CElement
  {
private:
   //--- Ускоренная перемотка значений календаря
   void              FastSwitching(void);
  };
//+------------------------------------------------------------------+
//| Ускоренная промотка календаря                                    |
//+------------------------------------------------------------------+
void CCalendar::FastSwitching(void)
  {
//--- Выйдем, если нет фокуса на элементе
   if(!CElement::MouseFocus())
      return;
//--- Выйти, если форма заблокирована и идентификаторы не совпадают
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return;
//--- Вернём счётчик к первоначальному значению, если кнопка мыши отжата
   if(!m_mouse_state)
      m_timer_counter=SPIN_DELAY_MSC;
//--- Если же кнопка мыши нажата
   else
     {
      //--- Увеличим счётчик на установленный интервал
      m_timer_counter+=TIMER_STEP_MSC;
      //--- Выйдем, если меньше нуля
      if(m_timer_counter<0)
         return;
      //--- Если левая стрелка нажата
      if(m_month_dec.State())
        {
         //--- Если текущий год в календаре больше/равен минимального указанного
         if(m_date.year>=m_years.MinValue())
           {
            //--- Если текущий год в календаре уже равен минимальному указанному и
            //    текущий месяц "Январь"
            if(m_date.year==m_years.MinValue() && m_date.mon==1)
              {
               //--- Подсветить значение и выйти
               m_years.HighlightLimit();
               return;
              }
            //--- Перейти к следующему месяцу (в сторону уменьшения)
            m_date.MonDec();
            //--- Установить первое число месяца
            m_date.day=1;
           }
        }
      //--- Если правая стрелка нажата
      else if(m_month_inc.State())
        {
         //--- Если текущий в календаре год меньше/равен максимального указанного
         if(m_date.year<=m_years.MaxValue())
           {
            //--- Если текущий год в календаре уже равен максимальному указанному и
            //    текущий месяц "Декабрь"
            if(m_date.year==m_years.MaxValue() && m_date.mon==12)
              {
               //--- Подсветить значение и выйти
               m_years.HighlightLimit();
               return;
              }
            //--- Перейти к следующему месяцу (в сторону увеличения)
            m_date.MonInc();
            //--- Установить первое число месяца
            m_date.day=1;
           }
        }
      //--- Если кнопка инкремента поля ввода лет нажата
      else if(m_years.StateInc())
        {
         //--- Если меньше максимального указанного года,
         //    перейти к следующему году (в сторону увеличения)
         if(m_date.year<m_years.MaxValue())
            m_date.YearInc();
         else
           {
            //--- Подсветить значение и выйти
            m_years.HighlightLimit();
            return;
           }
        }
      //--- Если кнопка декремента поля ввода лет нажата
      else if(m_years.StateDec())
        {
         //--- Если больше минимального указанного года,
         //    перейти к следующему году (в сторону уменьшения)
         if(m_date.year>m_years.MinValue())
            m_date.YearDec();
         else
           {
            //--- Подсветить значение и выйти
            m_years.HighlightLimit();
            return;
           }
        }
      else
        return;
      //--- Отображение последних изменений в календаре
      UpdateCalendar();
      //--- Отправим сообщение об этом
      ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
     }
  }

В момент, когда происходит переход с одного дня на другой, нужно обновить текущий день (локальное время на компьютере пользователя) в календаре, а также обновить отображаемую дату в кнопке для перехода к текущей дате. Для этого напишем специальный метод CCalendar::UpdateCurrentDate(), который будет отслеживать каждую секунду наступления нового дня (см. листинг кода ниже). 

Class CCalendar : public CElement
  {
public:
   //--- Обновление текущей даты
   void              UpdateCurrentDate(void);
  };
//+------------------------------------------------------------------+
//| Обновление текущей даты                                          |
//+------------------------------------------------------------------+
void CCalendar::UpdateCurrentDate(void)
  {
//--- Счётчик
   static int count=0;
//--- Выйти, если прошло меньше секунды
   if(count<1000)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//--- Обнулить счётчик
   count=0;
//--- Получим текущее (локальное) время
   MqlDateTime local_time;
   ::TimeToStruct(::TimeLocal(),local_time);
//--- Если наступил новый день
   if(local_time.day!=m_today.day)
     {
      //--- Обновить дату в календаре
      m_today.DateTime(::TimeLocal());
      m_button_today.Object(2).Description(::TimeToString(m_today.DateTime()));
      //--- Отобразить последние изменения в календаре
      UpdateCalendar();
      return;
     }
//--- Обновить дату в календаре
   m_today.DateTime(::TimeLocal());
  }

Код таймера элемента «Календарь» в этом случае будет выглядеть так: 

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CCalendar::OnEventTimer(void)
  {
//--- Если элемент выпадающий и список скрыт
   if(CElement::IsDropdown() && !m_months.GetListViewPointer().IsVisible())
     {
      ChangeObjectsColor();
      FastSwitching();
     }
   else
     {
      //--- Отслеживаем изменение цвета и перемотку значений, 
      //    только если форма не заблокирована
      if(!m_wnd.IsLocked())
        {
         ChangeObjectsColor();
         FastSwitching();
        }
     }
//--- Обновление текущей даты календаря
   UpdateCurrentDate();
  }

Мы рассмотрели все методы класса CCalendar.  Теперь протестируем, как всё работает. Однако перед этим нужно связать элемент с движком библиотеки для его корректной работы. Подробное пошаговое руководство, как это делается, было представлено ранее в статье Графические интерфейсы V: Элемент "Комбинированный список" (Глава 3). Поэтому здесь приведём только объявления (в теле класса CWndContainer) массива в структуре персональных массивов элементов, а также методов (1) для получения количества календарей в графическом интерфейсе MQL-приложения и (2) для сохранения указателей элементов при добавлении их в базу во время создания графического интерфейса. С кодом этих методов можно самостоятельно ознакомиться в приложенных к статье файлах.  

//+------------------------------------------------------------------+
//| Класс для хранения всех объектов интерфейса                      |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Структура массивов элементов
   struct WindowElements
     {
      //--- Персональные массивы элементов:
      //--- Массив календарей
      CCalendar        *m_calendars[];
   //---
public:
   //--- Количество календарей
   int               CalendarsTotal(const int window_index);
   //---
private:
   //--- Сохраняет указатели на элементы календаря в базу
   bool              AddCalendarElements(const int window_index,CElement &object);
     };

 

 

 

Тест элемента "Календарь"

Для теста можно взять любого эксперта из предыдущей статьи. Из элементов графического интерфейса, присоединённых к форме, оставим только главное меню и строку состояния. Создадим два календаря, чтобы во время взаимодействия с ними можно было проследить, не возникает ли между ними конфликтов.

В пользовательском классе приложения (CProgram) объявим два экземпляра класса CCalendar и метод для создания элемента «Календарь» с отступами от крайней точки формы:

class CProgram : public CWndEvents
  {
private:
   //--- Календари
   CCalendar         m_calendar1;
   CCalendar         m_calendar2;
   //---
private:
   //--- Календари
#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);
  };

В качестве примера приведём здесь код только одного из этих методов. Оставим свойства календаря по умолчанию (см. листинг кода ниже). 

//+------------------------------------------------------------------+
//| Создаёт календарь 1                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateCalendar1(void)
  {
//--- Передать объект панели
   m_calendar1.WindowPointer(m_window1);
//--- Координаты
   int x=m_window1.X()+CALENDAR1_GAP_X;
   int y=m_window1.Y()+CALENDAR1_GAP_Y;
//--- Создадим элемент управления
   if(!m_calendar1.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_calendar1);
   return(true);
  }

Поместим вызов этих методов в главном методе создания графического интерфейса MQL-приложения, а затем скомпилируем и загрузим его на график. В итоге должен получиться такой результат:

 Рис. 2. Тест элемента «Календарь».

Рис. 2. Тест элемента «Календарь».


На этом разработка класса CCalendar для создания элемента «Календарь» завершена. В дальнейшем его возможности будут расширены, а в этой статье рассмотрим ещё вторую версию этого элемента – «Выпадающий календарь». 

 

 


Элемент "Выпадающий календарь"

Элемент управления «Выпадающий календарь», в отличие от статического календаря (CCalendar), позволяет существенно сэкономить пространство графического интерфейса приложения, поскольку большую часть времени находится в свёрнутом состоянии. В текстовом поле комбо-бокса отображается выбранная пользователем дата. Если необходимо выбрать другую дату, то нужно нажать на кнопку комбо-бокса. Это действие вызывает (показывает) календарь. Если нажать на кнопку ещё раз, то календарь скрывается. К скрытию календаря также приводит нажатие левой кнопки мыши вне области элемента либо изменение свойств графика.

 Рис. 3. Составные части элемента «Выпадающий календарь».

Рис. 3. Составные части элемента «Выпадающий календарь».


Далее рассмотрим, как устроен этот элемент.

 


Класс CDropCalendar

Ранее, в статьях Графические интерфейсы V: Элемент "Комбинированный список" (Глава 3) и Графические интерфейсы VI: Элементы "Чекбокс", "Поле ввода" и их смешанные типы (Глава 1), уже были представлены элементы типа «комбо-бокс» на примере таких элементов библиотеки, как: «Комбо-бокс со списком» (CComboBox) и «Комбо-бокс со списком и чекбоксом» (CCheckComboBox). Поэтому лишь кратко пройдёмся по ключевым моментам элемента «Выпадающий календарь».

Создаём файл DropCalendar.mqh и подключаем его к библиотеке (файл WndContainer.mqh):

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

В файле DropCalendar.mqh создаём класс со стандартными методами, как у всех элементов библиотеки и подключаем к нему файл с классом календаря — Calendar.mqh:

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

Сразу перейдём к списку методов для создания элемента. Понадобится шесть приватных (private) методов и один публичный (public): 

//+------------------------------------------------------------------+
//| Класс для создания выпадающего календаря                         |
//+------------------------------------------------------------------+
class CDropCalendar : public CElement
  {
private:
   //--- Объекты и элементы для создания элемента
   CRectLabel        m_area;
   CLabel            m_label;
   CEdit             m_field;
   CEdit             m_drop_button;
   CBmpLabel         m_drop_button_icon;
   CCalendar         m_calendar;
   //---
public:
   //--- Методы для создания выпадающего календаря
   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);
  };

Здесь нужно только отметить, что во время создания календаря нужно установить для него признак выпадающего элемента, а в главном (публичном) методе CDropCalendar::CreateDropCalendar() после создания всех объектов, из которых состоит элемент, нужно:

  • Скрыть календарь.
  • В информационное поле комбо-бокса установить выделенную дату, которая уже есть в календаре после его создания, и которую можно получить с помощью метода CCalendar::SelectedDate().

В листинге кода ниже представлена сокращённая версия метода CDropCalendar::CreateDropCalendar(). Полную версию смотрите в приложенных к статье файлах.

//+------------------------------------------------------------------+
//| Создаёт выпадающий календарь                                     |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y)
  {
//--- Выйти, если нет указателя на форму
//--- Инициализация переменных
//--- Отступы от крайней точки
//--- Создание элемента
//--- Скрыть календарь
   m_calendar.Hide();
//--- Отобразить выделенную дату в календаре
   m_field.Description(::TimeToString((datetime)m_calendar.SelectedDate(),TIME_DATE));
//--- Скрыть элемент, если окно диалоговое или оно минимизировано
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Создаёт список                                                   |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateCalendar(void)
  {
//--- Передать объект панели
   m_calendar.WindowPointer(m_wnd);
//--- Координаты
   int x=m_field.X();
   int y=m_field.Y2();
//--- Установим календарю признак выпадающего элемента
   m_calendar.IsDropdown(true);
//--- Создадим элемент управления
   if(!m_calendar.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//---
   return(true);
  }

Для работы с комбо-боксом понадобятся следующие методы.

  • Изменение состояния видимости календаря на противоположное — CDropCalendar::ChangeComboBoxCalendarState().
  • Обработка нажатия на кнопку комбо-бокса — CDropCalendar::OnClickButton().
  • Проверка нажатой левой кнопки мыши над кнопкой комбо-бокса — CDropCalendar::CheckPressedOverButton().

Все эти методы подобны тем, что ранее были рассмотрены в классе CComboBox, поэтому не будем приводить здесь их код. 

Обработчик событий выпадающего календаря тогда будет выглядеть так, как показано в листинге ниже. Здесь всего четыре блока для обработки следующих событий: 

  • Перемещение курсора мыши — CHARTEVENT_MOUSE_MOVE.
  • Выбор новой даты в календаре — ON_CHANGE_DATE.
  • Нажатие левой кнопки мыши на графическом объекте — CHARTEVENT_OBJECT_CLICK.
  • Изменение свойств графика — CHARTEVENT_CHART_CHANGE.

По приходу события ON_CHANGE_DATE, которое генерируется классом CCalendar, нужно сверить идентификаторы элементов и установить новую дату в поле комбо-бокса.

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CDropCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события перемещения курсора
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Выйти, если элемент скрыт
      if(!CElement::IsVisible())
         return;
      //--- Координаты и состояние левой кнопки мыши
      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());
      //--- Проверка нажатой левой кнопки мыши над кнопкой комбо-бокса
      CheckPressedOverButton();
      return;
     }
//--- Обработка события выбора новой даты в календаре
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      //--- Выйти, если идентификаторы элементов не совпадают
      if(lparam!=CElement::Id())
         return;
      //--- Установим новую дату в поле комбо-бокса
      m_field.Description(::TimeToString((datetime)dparam,TIME_DATE));
      return;
     }
//--- Обработка события нажатия левой кнопки мыши на объекте
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Нажатие на кнопке комбо-бокса
      if(OnClickButton(sparam))
         return;
      //---
      return;
     }
//--- Обработка события изменения свойств графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Выйти, если элемент заблокирован
      if(!m_drop_calendar_state)
         return;
      //--- Скрыть календарь
      m_calendar.Hide();
      m_drop_button_icon.State(false);
      //--- Сбросить цвета
      ResetColors();
      return;
     }
  }

Для правильной работы этого элемента, так же, как и в случае с классом CCalendar, нужно внести дополнения в базовый класс библиотеки CWndContainer:

//+------------------------------------------------------------------+
//| Класс для хранения всех объектов интерфейса                      |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Структура массивов элементов
   struct WindowElements
     {
      //--- Персональные массивы элементов:
      //--- Массив выпадающих календарей
      CDropCalendar    *m_drop_calendars[];
   //---
public:
   //--- Количество выпадающих календарей
   int               DropCalendarsTotal(const int window_index);
   //---
private:
   //--- Сохраняет указатели на элементы выпадающего календаря в базу
   bool              AddDropCalendarElements(const int window_index,CElement &object);
     };

Кроме этого, в главный класс библиотеки для обработки событий всех элементов, в метод CWndEvents::SetChartState(), в котором контролируется состояние графика, нужно добавить блок кода, с помощью которого можно проверить выпадающие календари (см. сокращённую версию этого метода в листинге ниже):

//+------------------------------------------------------------------+
//| Устанавливает состояние графика                                  |
//+------------------------------------------------------------------+
void CWndEvents::SetChartState(void)
  {
   int awi=m_active_window_index;
//--- Для определения события, когда нужно отключить управление
   bool condition=false;
//--- Проверим окна
//--- Проверим выпадающие списки
//--- Проверим календари
   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;
           }
        }
     }
//--- Проверим фокус контекстных меню
//--- Проверим состояние полос прокрутки
//--- Устанавливаем состояние графика во всех формах
   for(int i=0; i<windows_total; i++)
      m_windows[i].CustomEventChartState(condition);
  }

 

 

 

Тест элемента "Выпадающий календарь

Добавим в графический интерфейс протестированного ранее в этой статье эксперта два выпадающих календаря. В пользовательском классе приложения объявляем два экземпляра выпадающего календаря типа CDropCalendar, а также отступы от крайней точки (верхний левый угол) формы. 

class CProgram : public CWndEvents
  {
private:
   //--- Выпадающие календари
   CDropCalendar     m_drop_calendar1;
   CDropCalendar     m_drop_calendar2;
   //---
private:
   //--- Выпадающие календари
#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);
  };

Для примера приведём код только одного из этих методов:

//+------------------------------------------------------------------+
//| Создаёт выпадающий календарь 1                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar1(const string text)
  {
//--- Передать объект панели
   m_drop_calendar1.WindowPointer(m_window1);
//--- Координаты
   int x=m_window1.X()+DROPCALENDAR1_GAP_X;
   int y=m_window1.Y()+DROPCALENDAR1_GAP_Y;
//--- Установим свойства перед созданием
   m_drop_calendar1.XSize(145);
   m_drop_calendar1.YSize(20);
   m_drop_calendar1.AreaBackColor(clrWhiteSmoke);
//--- Создадим элемент управления
   if(!m_drop_calendar1.CreateDropCalendar(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_drop_calendar1);
   return(true);
  }

Вызов методов создания элементов нужно разместить в главном методе создания интерфейса. В данном случае это метод CProgram::CreateExpertPanel(). Для примера обработки событий календарей в методе CProgram::OnEvent() добавьте код, как показано в листинге ниже. Каждый раз при изменении даты в календарях в журнал будет выводиться сообщение со значениями параметров, содержащихся в этом событии.

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Событие изменения даты в календаре
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",datetime(dparam),"; sparam: ",sparam);
     }
  }

Скомпилируйте код приложения и загрузите его на график в терминале. Результат показан на скриншоте ниже:

 Рис. 4. Тест элемента «Выпадающий календарь».

Рис. 4. Тест элемента «Выпадающий календарь».

 

 


Заключение

В этой статье (первой главе восьмой части серии) мы рассмотрели довольно сложный составной элемент графического интерфейса «Календарь» и его расширенную версию — «Выпадающий календарь». Для графиков с временной шкалой такие элементы управления будут весьма кстати. Это не окончательная версия и планируется выпустить обновления, которые сделают календарь ещё более удобным и функциональным. Вы можете также предлагать и свои идеи в комментариях.

В следующей статье мы разберём такой важный элемент графического интерфейса, как «Древовидный список» или «Иерархический список». 

Ниже вы можете загрузить к себе на компьютер весь материал восьмой части серии, чтобы у Вас сразу была возможность протестировать, как всё работает. Если у Вас возникают вопросы по использованию материала, предоставленного в этих файлах, то Вы можете обратиться к подробному описанию процесса разработки библиотеки в одной из статей в представленном списке ниже или задать вопрос в комментариях к статье.

Список статей (глав) восьмой части:

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (23)
Vasiliy Sokolov
Vasiliy Sokolov | 23 июн. 2016 в 11:53
Anatoli Kazharski:

...

Я посмотрел материалы по ссылкам, которые были представлены выше. Там сотни файлов. Без комментариев. Вообще ничего не понятно. Может быть есть ещё какой-нибудь, более интересный вариант? )

Так и есть. Вообще говоря знание той или иной библиотеки - это целая профессия. Например есть специальные программисты под Qt. И это высокооплачиваемый скилл.
Andrey Khatimlianskii
Andrey Khatimlianskii | 23 июн. 2016 в 23:27
Anatoli Kazharski:

Возможно, не стоит полагаться только на "внутренние ощущения". Если бы была лёгкая возможность портирования подобной библиотеки, разработчики MQ, скорее всего, так бы и сделали изначально, а не написали хоть какую-то, но свою версию стандартной библиотеки

Даже если и есть где-то исходники, то без подробного описания, подобного тому, как я это предоставляю в статьях, самостоятельное изучение материала и подгонка кода под среду терминала намного более трудозатратно, чем написать всё с нуля. 

Я посмотрел материалы по ссылкам, которые были представлены выше. Там сотни файлов. Без комментариев. Вообще ничего не понятно. Может быть есть ещё какой-нибудь, более интересный вариант? )

Ваше "скорее всего" ничем не отличается от моего "наверняка".

Было бы круто, если бы ответили сами разработчики, или хотя бы кто-то, кто действительно в теме.

Ну не может не быть таких базовых вещей, не может.

 

Содержимое ссылки обсуждать смысла не вижу. Во-первых, это первое, на что я наткнулся, не более того, а во-вторых, там вроде документация есть. Но тратить время на это не хочется в любом случае. 

Alexander Bereznyak
Alexander Bereznyak | 23 июн. 2016 в 23:57
Andrey Khatimlianskii:

Содержимое ссылки обсуждать смысла не вижу. Во-первых, это первое, на что я наткнулся, не более того, а во-вторых, там вроде документация есть. Но тратить время на это не хочется в любом случае. 

Вот никто не хочет и пишут своё...
Anatoli Kazharski
Anatoli Kazharski | 24 июн. 2016 в 00:53
Andrey Khatimlianskii:

Ваше "скорее всего" ничем не отличается от моего "наверняка".

Было бы круто, если бы ответили сами разработчики, или хотя бы кто-то, кто действительно в теме.

Ну не может не быть таких базовых вещей, не может.

Содержимое ссылки обсуждать смысла не вижу. Во-первых, это первое, на что я наткнулся, не более того, а во-вторых, там вроде документация есть. Но тратить время на это не хочется в любом случае. 

Ответ от разработчиков по этому вопросу конечно же в любом случае интересно было бы прочитать. 

А по поводу документации... Из всего, что просмотрел я, есть только краткая документация, как использовать библиотеку. Подробные описания и комментарии, как она внутри устроена, схема и т.д., всего этого лично мне не встречалось.

  • Нашёл вот такой ресурс со списком ссылок на бесплатные GUI библиотеки c исходными кодами: Free GUI Libraries and Source Code >>> 
  • Вот такая ещё GUI-библиотека есть: Nana C++ library >>>
  • А ещё есть классная библиотека для WEB-разработчиков с очень богатыми возможностями: Ext Js >>>. Сложно даже представить, чего нельзя сделать с её помощью. По ссылке можно посмотреть примеры, как это всё работает. Можно позаимствовать идеи оттуда для своих разработок. ;)

Возможно, кто-то захочет всё это изучить, досконально в этом разобраться и портировать всё на MQL. Было бы классно. ))

Yuriy Zaytsev
Yuriy Zaytsev | 29 июн. 2016 в 23:25

С большим вниманием слежу за статьями Анатолия, то, что он творит заслуживает уважения. 

В MQL программах  , в классическом варианте исполнения  обычно очень убогий интерфейс,  Анатолий показывает как можно поднять его на должную высоту.

Во вторых , в статьях очень подробно и детально  все описывается , порой это сложнее чем собственно написать сам код.

Рассматриваю его работу как очень хороший обучающий материал.

 И конечно , можно что то портировать , собственно не сильно вникая и понимая как это устроено внутри.

 Но когда возникает проблема после какого либо " портирования ",  а такое бывает , и что то пойдет не по плану , то найти проблему бывает  достаточно трудно. Потому предпочтение будет отдано коду который прозрачен и хорошо документирован. А именно такой вариант и предложен.

 

И наконец последнее , если взять его пример   ,  в сравнении с портированием которое займет в разы больше по времени,

То лучше использовать его готовый пример , чем рыться по всему интернету в поисках подходящего варианта 

а потом еще разгребать проблемы после "неудачного портирования"  предполагающие импортировать еще  мегабайты дополнительных библиотек, некоторые из которых могут быть вообще без исходника , хуже описаны и предполагать даже использование каких то DLL. 

LifeHack для трейдера: индикаторы баланса, просадки, загрузки и тиков во время тестирования LifeHack для трейдера: индикаторы баланса, просадки, загрузки и тиков во время тестирования
Как сделать тестирование более наглядным? Ответ прост: нужно использовать в тестере один или несколько индикаторов — тиковый индикатор, индикатор баланса и эквити, индикатор просадки и загрузки депозита. Это позволит визуально отслеживать или природу тиков, или изменение баланса и эквити, или просадку и загрузку депозита.
С чего начать при создании торгового робота для Московской биржи MOEX С чего начать при создании торгового робота для Московской биржи MOEX
Многие трейдеры на Московской бирже хотели бы автоматизировать свои торговые алгоритмы, но не знают с чего начать. Язык MQL5 предлагает не только огромный набор торговых функций, но и готовые классы, которые максимально облегчают первые шаги в алготрейдинге.
Универсальный торговый эксперт: Интеграция со стандартными модулями сигналов MetaTrader (часть 7) Универсальный торговый эксперт: Интеграция со стандартными модулями сигналов MetaTrader (часть 7)
Эта часть статьи посвящена интеграции торгового движка CStrategy с модулями сигналов, входящих в стандартную библиотеку MetaTrader. Материал описывает способы работы с сигналами и создание пользовательских стратегий на их основе.
Графические интерфейсы VII: Элементы "Вкладки" (Глава 2) Графические интерфейсы VII: Элементы "Вкладки" (Глава 2)
В первой главе седьмой части были представлены три класса элементов управления для создания таблиц: таблица из текстовых меток (CLabelsTable), таблица из полей ввода (CTable) и нарисованная таблица (CCanvasTable). В этой статье (второй главе) рассмотрим такой элемент интерфейса, как «Вкладки».