Графические интерфейсы VI: Элементы "Чекбокс", "Поле ввода" и их смешанные типы (Глава 1)
Содержание
Введение
Более подробно о том, для чего предназначена эта библиотека, можно прочитать в самой первой статье: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). В конце статей каждой части представлен список глав со ссылками, и там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.
Шестая часть серии будет состоять из двух глав. В первой из них создадим четыре элемента управления:
- Чекбокс
- Поле ввода
- Поле ввода с чекбоксом
- Комбобокс с чекбоксом
Из этих четырёх элементов опишем в статье только «чекбокс» и «поле ввода», так как «поле ввода с чекбоксом» и «комбобокс с чекбоксом» уже не будут содержать в себе ничего такого, что не было бы описано ранее.
Во второй главе будут следующие элементы:
- Слайдер
- Двухсторонний слайдер
Элемент «Чекбокс»
Элемент управления «чекбокс» предназначен для управления параметрами, у которых может быть только два состояния. Чтобы понять, в каком состоянии сейчас находится параметр, к которому привязан элемент, используется кнопка с двумя изображениями. Изображение с символом «галочка» обозначает состояние «включено» (on). Изображение без этого символа «галочка» обозначит состояние «выключено» (off). Рядом с кнопкой располагается краткое описание параметра.
Собирать этот элемент будем из трёх графических объектов. Перечислим их.
- Фон
- Картинка (кнопка)
- Текстовая метка
Рис. 1. Составные части элемента управления «Чекбокс».
Рассмотрим, как устроен класс этого элемента.
Разработка класса для создания элемента «Чекбокс»
В третьей части этой серии мы уже рассматривали похожий элемент управления – «Кнопка с картинкой», класс CIconButton (см. статью Графические интерфейсы III: Простые и многофункциональные кнопки (Глава 1). Элемент «Чекбокс» похож на кнопку с картинкой в режиме с двумя состояниями. Отличие состоит только в том, что у кнопки в различных состояниях (on/off) изменяется цвет фона и цвет текста (если он задан), а у чекбокса — картинка и цвет текста (если он задан). Цвет фона у чекбокса обычно будет сливаться с цветом фона окна, к которому присоединён элемент. Фон здесь будет использоваться как область для определения местоположения курсора мыши относительно границ этой области, а также для определения нажатия на элементе левой кнопкой мыши. При этом элемент «чекбокс» даже ещё проще, чем элемент «кнопка с картинкой», поскольку имеет меньше свойств, которые может задать пользователь.
Создаём файл CheckBox.mqh и сразу подключаем его к библиотеке (к файлу WndContainer.mqh):
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "CheckBox.mqh"
В файле CheckBox.mqh создаём класс CCheckBox со стандартными для всех элементов библиотеки методами:
//+------------------------------------------------------------------+ //| CheckBox.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Класс для создания чекбокса | //+------------------------------------------------------------------+ class CCheckBox : public CElement { private: //--- Указатель на форму, к которой элемент присоединён CWindow *m_wnd; //--- public: CCheckBox(void); ~CCheckBox(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); };
Перед созданием элемента пользователю будут доступны методы для установки свойств представленных в списке ниже:
- Фон элемента
- Ярлыки чекбокса в активном и заблокированном состоянии
- Отступы для текстовой метки
- Цвета текстовой метки в разных состояниях
Картинка для кнопки чекбокса будет в четырёх вариантах: две иконки, когда элемент пребывает в активном состоянии, и две — когда он заблокирован. В конце статьи можно скачать уже готовые картинки для всех состояний, но вы можете сделать свои версии.
class CCheckBox : public CElement { private: //--- Цвет фона чекбокса color m_area_color; //--- Ярлыки чекбокса в активном и заблокированном состоянии string m_check_bmp_file_on; string m_check_bmp_file_off; string m_check_bmp_file_on_locked; string m_check_bmp_file_off_locked; //--- Текст чекбокса string m_label_text; //--- Отступы текстовой метки int m_label_x_gap; int m_label_y_gap; //--- Цвета текстовой метки в разных состояниях color m_label_color; color m_label_color_off; color m_label_color_hover; color m_label_color_locked; color m_label_color_array[]; //--- Приоритеты на нажатие левой кнопки мыши int m_zorder; int m_area_zorder; //--- public: //--- Установка ярлыков для кнопки в активном и заблокированном состояниях void CheckFileOn(const string file_path) { m_check_bmp_file_on=file_path; } void CheckFileOff(const string file_path) { m_check_bmp_file_off=file_path; } void CheckFileOnLocked(const string file_path) { m_check_bmp_file_on_locked=file_path; } void CheckFileOffLocked(const string file_path) { m_check_bmp_file_off_locked=file_path; } //--- (1) Цвет фона, (2) отступы текстовой метки void AreaColor(const color clr) { m_area_color=clr; } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- Цвета текста в разных состояниях void LabelColor(const color clr) { m_label_color=clr; } void LabelColorOff(const color clr) { m_label_color_off=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } void LabelColorLocked(const color clr) { m_label_color_locked=clr; } //--- Описание чекбокса string LabelText(void) const { return(m_label.Description()); } };
Для создания элемента «чекбокс» понадобятся три приватных (private) метода и один основной публичный (public):
class CCheckBox : public CElement { private: //--- Объекты для создания чек-бокса CRectLabel m_area; CBmpLabel m_check; CLabel m_label; //--- public: //--- Методы для создания чек-бокса bool CreateCheckBox(const long chart_id,const int subwin,const string text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateCheck(void); bool CreateLabel(void); };
Для управления состоянием чекбокса создадим методы CCheckBox::CheckButtonState() и CCheckBox::CheckBoxState():
class CCheckBox : public CElement { private: //--- Состояние кнопки чекбокса bool m_check_button_state; //--- Состояние элемента (доступен/заблокирован) bool m_checkbox_state; //--- public: //--- Возвращение/установка состояния чекбокса bool CheckBoxState(void) const { return(m_checkbox_state); } void CheckBoxState(const bool state); //--- Возвращение/установка состояния кнопки чекбокса bool CheckButtonState(void) const { return(m_check.State()); } void CheckButtonState(const bool state); };
Для блокировки/разблокировки элемента в целом нужно использовать метод CCheckBox::CheckBoxState():
//+------------------------------------------------------------------+ //| Установка состояния элемента | //+------------------------------------------------------------------+ void CCheckBox::CheckBoxState(const bool state) { //--- Состояние элемента m_checkbox_state=state; //--- Картинка m_check.BmpFileOn((state)? "::"+m_check_bmp_file_on : "::"+m_check_bmp_file_on_locked); m_check.BmpFileOff((state)? "::"+m_check_bmp_file_off : "::"+m_check_bmp_file_off_locked); //--- Цвет текстовой метки m_label.Color((state)? m_label_color : m_label_color_locked); }
Для установки состояния кнопки чекбокса нужно использовать метод CCheckBox::CheckButtonState():
//+------------------------------------------------------------------+ //| Установка состояния кнопки чекбокса | //+------------------------------------------------------------------+ void CCheckBox::CheckButtonState(const bool state) { //--- Выйти, если элемент заблокирован if(!m_checkbox_state) return; //--- Установим состояние кнопке m_check.State(state); m_check_button_state=state; //--- Изменим цвета относительно состояния m_label.Color((state)? m_label_color : m_label_color_off); CElement::InitColorArray((state)? m_label_color : m_label_color_off,m_label_color_hover,m_label_color_array); }
Осталось только создать метод-обработчик нажатия на элементе «чекбокс», который будет вызываться в главном обработчике элемента CCheckBox::OnEvent() по событию с идентификатором CHARTEVENT_OBJECT_CLICK. Назовём этот метод CCheckBox::OnClickLabel(). В самом начале этого метода проверяется, было ли произведено нажатие на площади (фоне) элемента. Чтобы не возникало путаницы в том, на каком именно объекте элемента был клик, значение приоритета на нажатие левой кнопкой мыши у фона равно 1, а у всех остальных - 0. Поэтому, даже если нажатие было осуществлено, когда курсор находится над кнопкой чекбокса или над текстовой меткой, будет считаться, что клик был на фоне элемента, то есть, на том объекте, чей приоритет выше в данной области. Далее с помощью метода CCheckBox::CheckButtonState() устанавливается состояние, противоположное кнопке чекбокса. В конце метода отправляется пользовательское событие с (1) идентификатором события ON_CLICK_LABEL, (2) идентификатором элемента и (3) описанием элемента.
class CCheckBox : public CElement { private: //--- Обработка нажатия на элемент bool OnClickLabel(const string clicked_object); }; //+------------------------------------------------------------------+ //| Обработка событий | //+------------------------------------------------------------------+ void CCheckBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Обработка события нажатия левой кнопки мыши на объекте if(id==CHARTEVENT_OBJECT_CLICK) { //--- Нажатие на чекбоксе if(OnClickLabel(sparam)) return; } } //+------------------------------------------------------------------+ //| Нажатие на заголовок элемента | //+------------------------------------------------------------------+ bool CCheckBox::OnClickLabel(const string clicked_object) { //--- Выйдем, если чужое имя объекта if(m_area.Name()!=clicked_object) return(false); //--- Выйти, если элемент заблокирован if(!m_checkbox_state) return(false); //--- Переключить на противоположный режим CheckButtonState(!m_check.State()); //--- Курсор мыши сейчас над элементом m_label.Color(m_label_color_hover); //--- Отправим сообщение об этом ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),0,m_label.Description()); return(true); }
Тест элемента «Чекбокс»
Все методы класса CCheckBox готовы, и теперь можно протестировать, как работает этот элемент управления. Файл с классом этого элемента уже должен быть подключен к библиотеке. Эксперта для тестов можно скопировать из предыдущей статьи. Удалим из него все элементы управления, оставив только главное меню и строку состояния. Затем создадим для теста два чекбокса. Сделаем так, чтобы второй чекбокс по умолчанию был заблокирован при загрузке программы на график. Первый чекбокс будет доступен для взаимодействия с пользователем, но в отключенном состоянии. Включение первого чекбокса будет сигналом для разблокировки второго чекбокса. И наоборот, то есть, отключение первого чекбокса будет сигналом для блокировки второго чекбокса.
В пользовательском классе CProgram нужно создать два экземпляра класса CCheckBox и объявить два метода для создания чекбоксов с указанием отступов от крайней точки формы, к которой они будут привязаны:
//+------------------------------------------------------------------+ //| Класс для создания приложения | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Чек-боксы CCheckBox m_checkbox1; CCheckBox m_checkbox2; //--- private: //--- Чек-боксы #define CHECKBOX1_GAP_X (7) #define CHECKBOX1_GAP_Y (50) bool CreateCheckBox1(const string text); #define CHECKBOX2_GAP_X (30) #define CHECKBOX2_GAP_Y (75) bool CreateCheckBox2(const string text); };
Реализация методов отличается только тем, что доступность второго чекбокса будет зависеть от состояния первого чекбокса:
//+------------------------------------------------------------------+ //| Создаёт чекбокс 2 | //+------------------------------------------------------------------+ bool CProgram::CreateCheckBox2(string text) { //--- Передать объект панели m_checkbox2.WindowPointer(m_window1); //--- Координаты int x=m_window1.X()+CHECKBOX2_GAP_X; int y=m_window1.Y()+CHECKBOX2_GAP_Y; //--- Установим свойства перед созданием m_checkbox2.XSize(90); m_checkbox2.YSize(18); m_checkbox2.AreaColor(clrWhiteSmoke); m_checkbox2.LabelColor(clrBlack); m_checkbox2.LabelColorOff(clrBlack); m_checkbox2.LabelColorLocked(clrSilver); //--- Создадим элемент управления if(!m_checkbox2.CreateCheckBox(m_chart_id,m_subwin,text,x,y)) return(false); //--- Доступность будет зависеть от текущего состояния первого чекбокса m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_checkbox2); return(true); }
Вызов методов создания чекбоксов размещаем в главном методе создания графического интерфейса. В листинге кода ниже показана сокращённая версия этого метода:
//+------------------------------------------------------------------+ //| Создаёт торговую панель | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Создание формы 1 для элементов управления //--- Создание элементов управления: // Главное меню //--- Контекстные меню //--- Создание статусной строки //--- Чек-боксы if(!CreateCheckBox1("Checkbox 1")) return(false); if(!CreateCheckBox2("Checkbox 2")) return(false); //--- Перерисовка графика m_chart.Redraw(); return(true); }
В обработчике событий класса CProgram будем принимать и обрабатывать сообщения с идентификатором ON_CLICK_LABEL. Доступность второго чекбокса будет регулироваться состоянием первого чекбокса (см. листинг кода ниже):
//+------------------------------------------------------------------+ //| Обработчик событий | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Событие нажатия на чек-боксе if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Если нажали на первом чекбоксе if(lparam==m_checkbox1.Id()) { //--- Установить состояние второму чекбоксу m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()); } } }
Скомпилируйте файлы и загрузите эксперта на график. Должен получиться результат, как на скриншоте ниже:
Рис. 2. Тест элемента управления «чекбокс».
На скриншоте выше видно, что второй чекбокс заблокирован, и определить это можно по цвету текста и соответствующей этому состоянию картинке кнопки (блеклые цвета). Всё отлично работает, и далее мы приступим к разработке класса для создания элемента управления «Поле ввода».
Элемент «Поле ввода»
Элемент управления «Поле ввода» предназначен для установки числового значения в текстовое поле. Можно ввести значение вручную или же воспользоваться кнопками для прокрутки. Минимальное и максимальное значение (ограничения), а также шаг для прокрутки пользователь библиотеки может устанавливать самостоятельно. Если нажать кнопку со стрелкой вверх/вниз, то значение будет увеличено/уменьшено на указанный в настройках шаг. Если зажать и удерживать кнопку вверх/вниз, то значение в поле ввода будет изменяться в ускоренном режиме. Дойдя до разрешенного максимума или минимума, прокрутка значения останавливается.
Собирать этот элемент будем из пяти графических объектов. Перечислим их.
- Фон
- Текстовая метка
- Поле ввода
- Две кнопки для прокрутки значений в поле ввода
Рис. 3. Составные части элемента управления «Поле ввода».
Далее приступим к разработке класса для создания этого элемента интерфейса.
Разработка класса для создания элемента «Поле ввода»
Так же, как и элемент «чекбокс», элемент «поле ввода» — простой, а не не составной. Иными словами, он состоит из простых объектов, но не содержит в своём составе других элементов управления. Поэтому в файл WndContainer.mqh не придётся вносить никаких дополнений. Достаточно будет просто подключить файл с классом элемента «поле ввода».
Теперь создаём файл SpinEdit.mqh и подключаем его к файлу WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "SpinEdit.mqh"
В файле SpinEdit.mqh создаём класс CSpinEdit со стандартными для всех элементов библиотеки методами, точно так же, как это уже было показано ранее в этой статье в разделе описания класса CCheckBox. Поэтому сразу перейдём к описанию свойств элемента интерфейса «поле ввода».
Ниже приведён список всех свойств элемента «поле ввода», которые будут доступны пользователю для настройки.
- Цвет фона
- Текст описания поля ввода
- Отступы текстовой метки
- Цвета текста в разных состояниях
- Размеры поля ввода
- Отступ поля ввода от правого края элемента
- Цвета поля ввода в разных состояниях
- Цвета текста поля ввода в разных состояниях
- Цвета рамки поля ввода в разных состояниях
- Ярлыки переключателей в активном и заблокированном состоянии
- Отступы кнопок (от правого края)
- Режим сброса значения до минимального
- Минимальное и максимальное значение
- Шаг для изменения значения в поле ввода с помощью кнопок
- Режим выравнивания текста
- Количество знаков после запятой
В листинге кода ниже можно ознакомиться с полями и методами класса для настройки перечисленных выше свойств элемента:
//+------------------------------------------------------------------+ //| Класс для создания поля ввода | //+------------------------------------------------------------------+ class CSpinEdit : public CElement { private: //--- Цвет фона элемента color m_area_color; //--- Текст описания поля ввода string m_label_text; //--- Отступы текстовой метки int m_label_x_gap; int m_label_y_gap; //--- Цвета текста в разных состояниях color m_label_color; color m_label_color_hover; color m_label_color_locked; color m_label_color_array[]; //--- Размеры поля ввода int m_edit_x_size; int m_edit_y_size; //--- Отступ поля ввода от правого края int m_edit_x_gap; //--- Цвета поля ввода и текста поля ввода в разных состояниях color m_edit_color; color m_edit_color_locked; color m_edit_text_color; color m_edit_text_color_locked; color m_edit_text_color_highlight; //--- Цвета рамки поля ввода в разных состояниях color m_edit_border_color; color m_edit_border_color_hover; color m_edit_border_color_locked; color m_edit_border_color_array[]; //--- Ярлыки переключателей в активном и заблокированном состоянии string m_inc_bmp_file_on; string m_inc_bmp_file_off; string m_inc_bmp_file_locked; string m_dec_bmp_file_on; string m_dec_bmp_file_off; string m_dec_bmp_file_locked; //--- Отступы кнопок (от правого края) int m_inc_x_gap; int m_inc_y_gap; int m_dec_x_gap; int m_dec_y_gap; //--- Режим сброса значения до минимального bool m_reset_mode; //--- Минимальное/максимальное значение double m_min_value; double m_max_value; //--- Шаг для изменения значения в поле ввода double m_step_value; //--- Режим выравнивания текста ENUM_ALIGN_MODE m_align_mode; //--- Количество знаков после запятой int m_digits; //--- public: //--- (1) Цвет фона, (2) текст описания поля ввода, (3) отступы текстовой метки void AreaColor(const color clr) { m_area_color=clr; } string LabelText(void) const { return(m_label.Description()); } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- Цвета текстовой метки в разных состояниях void LabelColor(const color clr) { m_label_color=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } void LabelColorLocked(const color clr) { m_label_color_locked=clr; } //--- (1) Размеры поля ввода, (2) отступ поля ввода от правого края void EditXSize(const int x_size) { m_edit_x_size=x_size; } void EditYSize(const int y_size) { m_edit_y_size=y_size; } void EditXGap(const int x_gap) { m_edit_x_gap=x_gap; } //--- Цвета поля ввода в разных состояниях void EditColor(const color clr) { m_edit_color=clr; } void EditColorLocked(const color clr) { m_edit_color_locked=clr; } //--- Цвета текста поля ввода в разных состояниях void EditTextColor(const color clr) { m_edit_text_color=clr; } void EditTextColorLocked(const color clr) { m_edit_text_color_locked=clr; } void EditTextColorHighlight(const color clr) { m_edit_text_color_highlight=clr; } //--- Цвета рамки поля ввода в разных состояниях void EditBorderColor(const color clr) { m_edit_border_color=clr; } void EditBorderColorHover(const color clr) { m_edit_border_color_hover=clr; } void EditBorderColorLocked(const color clr) { m_edit_border_color_locked=clr; } //--- Установка ярлыков для кнопки в активном и заблокированном состояниях void IncFileOn(const string file_path) { m_inc_bmp_file_on=file_path; } void IncFileOff(const string file_path) { m_inc_bmp_file_off=file_path; } void IncFileLocked(const string file_path) { m_inc_bmp_file_locked=file_path; } void DecFileOn(const string file_path) { m_dec_bmp_file_on=file_path; } void DecFileOff(const string file_path) { m_dec_bmp_file_off=file_path; } void DecFileLocked(const string file_path) { m_dec_bmp_file_locked=file_path; } //--- Отступы для кнопок поля ввода void IncXGap(const int x_gap) { m_inc_x_gap=x_gap; } void IncYGap(const int y_gap) { m_inc_y_gap=y_gap; } void DecXGap(const int x_gap) { m_dec_x_gap=x_gap; } void DecYGap(const int y_gap) { m_dec_y_gap=y_gap; } //--- Режим сброса при нажатии на текстовой метке bool ResetMode(void) { return(m_reset_mode); } void ResetMode(const bool mode) { m_reset_mode=mode; } //--- Минимальное значение double MinValue(void) const { return(m_min_value); } void MinValue(const double value) { m_min_value=value; } //--- Максимальное значение double MaxValue(void) const { return(m_max_value); } void MaxValue(const double value) { m_max_value=value; } //--- Шаг значения double StepValue(void) const { return(m_step_value); } void StepValue(const double value) { m_step_value=(value<=0)? 1 : value; } //--- (1) Количество знаков после запятой, (2) режим выравнивания текста void SetDigits(const int digits) { m_digits=::fabs(digits); } void AlignMode(ENUM_ALIGN_MODE mode) { m_align_mode=mode; } };
Для создания элемента «поле ввода» понадобится пять приватных (private) методов для создания графических объектов-примитивов и один главный публичный (public) метод, который будет вызываться в пользовательском классе при создании графического интерфейса. Картинки для кнопок прокрутки значения можно скачать в архиве в конце статьи. По умолчанию используются именно они, но вы при желании можете заменить их на свои версии.
class CSpinEdit : public CElement { private: //--- Объекты для создания поля ввода CRectLabel m_area; CLabel m_label; CEdit m_edit; CBmpLabel m_spin_inc; CBmpLabel m_spin_dec; //--- public: //--- Методы для создания поля ввода bool CreateSpinEdit(const long chart_id,const int subwin,const string label_text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabel(void); bool CreateEdit(void); bool CreateSpinInc(void); bool CreateSpinDec(void); };
Теперь рассмотрим режимы и методы, с помощью которых можно управлять состоянием и свойствами элемента уже после его создания. Начнём с метода CSpinEdit::SetValue() для корректировки и сохранения значения для поля ввода, а также вспомогательного метода CSpinEdit::HighlightLimit(), который предназначен для подсветки (подмигивания) текста в поле ввода при превышении максимального и минимального ограничений.
В начале метода CSpinEdit::SetValue() производится корректировка с учётом шага для изменения значения. Далее идут проверки на выход за установленные ограничения (максимум/минимум). Если есть выход за ограничение, то устанавливается соответствующее проверке значение и вызывается метод CSpinEdit::HighlightLimit() для подсветки текста. Затем, если после всех проверок и корректировок оказалось, что значение изменилось по сравнению с тем, что было сохранено ранее, то запоминается новое значение.
Метод CSpinEdit::HighlightLimit() устроен довольно просто. В нём сначала устанавливается цвет подмигивания, который может быть переопределён пользователем (по умолчанию это красный цвет). После этого воспроизводится задержка (пауза) на 100 миллисекунд. Этого достаточно, чтобы кратковременное изменение цвета было заметно. В конце снова устанавливается исходный цвет текста.
С кодом описанных выше методов можно ознакомиться в листинге кода ниже:
class CSpinEdit : public CElement { private: //--- Текущее значение в поле ввода double m_edit_value; //--- public: //--- Возвращение и установка значения поля ввода double GetValue(void) const { return(m_edit_value); } bool SetValue(const double value); //--- Подмигивание при достижении лимита void HighlightLimit(void); }; //+------------------------------------------------------------------+ //| Проверка текущего значения | //+------------------------------------------------------------------+ bool CSpinEdit::SetValue(double value) { //--- Для корректировки double corrected_value =0.0; //--- Скорректируем с учетом шага corrected_value=::MathRound(value/m_step_value)*m_step_value; //--- Проверка на минимум/максимум if(corrected_value<m_min_value) { //--- Установить минимальное значение corrected_value=m_min_value; //--- Установим состояние On m_spin_dec.State(true); //--- Подмигнуть, сообщив этим о достижении лимита HighlightLimit(); } if(corrected_value>m_max_value) { //--- Установить максимальное значение corrected_value=m_max_value; //--- Установим состояние On m_spin_inc.State(true); //--- Подмигнуть, сообщив этим о достижении лимита HighlightLimit(); } //--- Если значение было изменено if(m_edit_value!=corrected_value) { m_edit_value=corrected_value; m_edit.Color(m_edit_text_color); return(true); } //--- Значение без изменений return(false); } //+------------------------------------------------------------------+ //| Подсвечивание лимита | //+------------------------------------------------------------------+ void CSpinEdit::HighlightLimit(void) { //--- Временно изменить цвет текста m_edit.Color(m_edit_text_color_highlight); //--- Обновить ::ChartRedraw(); //--- Задержка перед возвращением к исходному цвету ::Sleep(100); //--- Изменить цвет текста на исходный m_edit.Color(m_edit_text_color); }
Метод CSpinEdit::SetValue() предназначен только для корректировки значения в случае выхода за установленный лимит, а для изменения значения в поле ввода нужно использовать метод CSpinEdit:: ChangeValue():
class CSpinEdit : public CElement { private: //--- Изменение значения в поле ввода void ChangeValue(const double value); }; //+------------------------------------------------------------------+ //| Изменение значения в поле ввода | //+------------------------------------------------------------------+ void CSpinEdit::ChangeValue(const double value) { //--- Проверим, скорректируем и запомним новое значение SetValue(value); //--- Установим новое значение в поле ввода m_edit.Description(::DoubleToString(GetValue(),m_digits)); }
Так же, как и в других элементах, понадобится метод для управления доступностью элемента «поле ввода» (см. листинг кода ниже):
class CSpinEdit : public CElement { private: //--- Состояние элемента (доступен/заблокирован) bool m_spin_edit_state; //--- public: //--- Возвращение/установка состояния доступности поля ввода bool SpinEditState(void) const { return(m_spin_edit_state); } void SpinEditState(const bool state); }; //+------------------------------------------------------------------+ //| Установка состояния элемента | //+------------------------------------------------------------------+ void CSpinEdit::SpinEditState(const bool state) { m_spin_edit_state=state; //--- Цвет текстовой метки m_label.Color((state)? m_label_color : m_label_color_locked); //--- Цвет поля ввода m_edit.Color((state)? m_edit_text_color : m_edit_text_color_locked); m_edit.BackColor((state)? m_edit_color : m_edit_color_locked); m_edit.BorderColor((state)? m_edit_border_color : m_edit_border_color_locked); //--- Картинка переключателей m_spin_inc.BmpFileOn((state)? "::"+m_inc_bmp_file_on : "::"+m_inc_bmp_file_locked); m_spin_dec.BmpFileOn((state)? "::"+m_dec_bmp_file_on : "::"+m_dec_bmp_file_locked); //--- Настройка относительно текущего состояния if(!m_spin_edit_state) { //--- Приоритеты m_edit.Z_Order(-1); m_spin_inc.Z_Order(-1); m_spin_dec.Z_Order(-1); //--- Поле ввода в режим "Только чтение" m_edit.ReadOnly(true); } else { //--- Приоритеты m_edit.Z_Order(m_edit_zorder); m_spin_inc.Z_Order(m_spin_zorder); m_spin_dec.Z_Order(m_spin_zorder); //--- Поле ввода в режим редактирования m_edit.ReadOnly(false); } }
Теперь рассмотрим методы для обработки событий элемента «Поле ввода». При нажатии на текстовую метку будет генерироваться пользовательское событие с (1) идентификатором события ON_CLICK_LABEL, (2) идентификатором элемента, (3) индексом элемента и (4) описанием из текстовой метки. Ранее уже был упомянут режим для сброса значения в поле ввода до минимального. По умолчанию этот режим отключен (false). Включается он с помощью метода CSpinEdit::ResetMode(): укажите в единственном его аргументе значение true. Если режим для сброса значения включен, то нажатие на текстовую метку будет вызывать метод для установки минимального значения в поле ввода.
С учётом всего перечисленного код метода CSpinEdit::OnClickLabel() для обработки нажатия на текстовой метке будет таким, как показано в листинге ниже:
class CSpinEdit : public CElement { private: //--- Обработка нажатия на текстовую метку bool OnClickLabel(const string clicked_object); }; //+------------------------------------------------------------------+ //| Нажатие на заголовок элемента | //+------------------------------------------------------------------+ bool CSpinEdit::OnClickLabel(const string clicked_object) { //--- Выйдем, если чужое имя объекта if(m_area.Name()!=clicked_object) return(false); //--- Если включен режим сброса значения if(m_reset_mode) { //--- Установим минимальное значение ChangeValue(MinValue()); } //--- Отправим сообщение об этом ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
Значение в поле ввода пользователь может изменить, введя новое значение вручную либо посредством кнопок-переключателей инкремента и декремента. Нам понадобятся новые идентификаторы пользовательских событий. Добавим их в файл Defines.mqh:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_END_EDIT (18) // Окончание редактирования значения в поле ввода #define ON_CLICK_INC (19) // Изменение счётчика вверх #define ON_CLICK_DEC (20) // Изменение счётчика вниз
Чтобы обработать ввод нового значения вручную, напишем метод CSpinEdit::OnEndEdit(). Вызываться он будет в общем обработчике CSpinEdit::OnEvent() по приходу события с идентификатором CHARTEVENT_OBJECT_ENDEDIT. При вводе нового значение оно при необходимости будет скорректировано. В конце метода будем генерировать пользовательское событие с (1) идентификатором события ON_END_EDIT, (2) идентификатором элемента, (3) индексом элемента и (4) описанием из текстовой метки (см. листинг кода ниже).
class CSpinEdit : public CElement { private: //--- Обработка ввода значения в поле ввода bool OnEndEdit(const string edited_object); }; //+------------------------------------------------------------------+ //| Обработка ввода значения в поле ввода | //+------------------------------------------------------------------+ bool CSpinEdit::OnEndEdit(const string edited_object) { //--- Выйдем, если чужое имя объекта if(m_edit.Name()!=edited_object) return(false); //--- Получим введённое значение double entered_value=::StringToDouble(m_edit.Description()); //--- Проверим, скорректируем и запомним новое значение ChangeValue(entered_value); //--- Отправим сообщение об этом ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
Для обработки нажатия на кнопках инкремента и декремента нужно создать два отдельных метода CSpinEdit::OnClickSpinInc() и CSpinEdit::OnClickSpinDec(). В них всё просто устроено. После нажатия на графическом объекте по его имени определяется, является ли он кнопкой нашего элемента. Далее получаем текущее значение в поле ввода и увеличиваем/уменьшаем его на установленный в свойствах элемента шаг. В конце методов генерируется пользовательское событие с (1) идентификатором события ON_CLICK_INC/ON_CLICK_DEC, (2) идентификатором элемента, (3) индексом элемента и (4) описанием из текстовой метки (см. листинг кода ниже).
class CSpinEdit : public CElement { private: //--- Обработка нажатия кнопки поля ввода bool OnClickSpinInc(const string clicked_object); bool OnClickSpinDec(const string clicked_object); }; //+------------------------------------------------------------------+ //| Нажатие на переключатель инкремента | //+------------------------------------------------------------------+ bool CSpinEdit::OnClickSpinInc(const string clicked_object) { //--- Выйдем, если чужое имя объекта if(m_spin_inc.Name()!=clicked_object) return(false); //--- Получим текущее значение double value=GetValue(); //--- Увеличим на один шаг и проверим на выход за ограничение ChangeValue(value+m_step_value); //--- Установим состояние On m_spin_inc.State(true); //--- Отправим сообщение об этом ::EventChartCustom(m_chart_id,ON_CLICK_INC,CElement::Id(),CElement::Index(),m_label.Description()); return(true); } //+------------------------------------------------------------------+ //| Нажатие на переключатель декремента | //+------------------------------------------------------------------+ bool CSpinEdit::OnClickSpinDec(const string clicked_object) { //--- Выйдем, если чужое имя объекта if(m_spin_dec.Name()!=clicked_object) return(false); //--- Получим текущее значение double value=GetValue(); //--- Уменьшим на один шаг и проверим на выход за ограничение ChangeValue(value-m_step_value); //--- Установим состояние On m_spin_dec.State(true); //--- Отправим сообщение об этом ::EventChartCustom(m_chart_id,ON_CLICK_DEC,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
Вызывать все методы-обработчики нужно в главном методе обработки событий (см. листинг кода ниже). Допуск в тело методов-обработчиков контролируется доступностью элемента в текущий момент.
//+------------------------------------------------------------------+ //| Обработка событий | //+------------------------------------------------------------------+ void CSpinEdit::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Обработка события нажатия левой кнопки мыши на объекте if(id==CHARTEVENT_OBJECT_CLICK) { //--- Выйти, если элемент заблокирован if(!m_spin_edit_state) return; //--- Обработка нажатия на текстовую метку if(OnClickLabel(sparam)) return; //--- Обработка нажатия кнопок поля ввода if(OnClickSpinInc(sparam)) return; if(OnClickSpinDec(sparam)) return; //--- return; } //--- Обработка события изменения значения в поле ввода if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- Выйти, если элемент заблокирован if(!m_spin_edit_state) return; //--- Обработка ввода значения if(OnEndEdit(sparam)) return; } }
Для ускоренной промотки значений при зажатии левой кнопки мыши над кнопками-переключателями элемента, создадим специальный метод CSpinEdit::FastSwitching(), который нужно будет вызывать в таймере элемента. Ранее мы уже рассматривали подобный метод, когда рассматривали класс CListView для создания списков с полосой прокрутки. Если там этот метод нужен для прокрутки списка, то здесь он будет выполнять функцию для изменения значения в поле ввода в большую или меньшую сторону. Для более подробного изучения метода CSpinEdit::FastSwitching() смотрите листинг кода ниже.
class CSpinEdit : public CElement { private: //--- Ускоренная перемотка значений в поле ввода void FastSwitching(void); }; //+------------------------------------------------------------------+ //| Таймер | //+------------------------------------------------------------------+ void CSpinEdit::OnEventTimer(void) { //--- Если элемент выпадающий if(CElement::IsDropdown()) { ChangeObjectsColor(); FastSwitching(); } else { //--- Отслеживаем изменение цвета и перемотку значений, // только если форма не заблокирована if(!m_wnd.IsLocked()) { ChangeObjectsColor(); FastSwitching(); } } } //+------------------------------------------------------------------+ //| Ускоренная промотка значения в поле ввода | //+------------------------------------------------------------------+ void CSpinEdit::FastSwitching(void) { //--- Выйдем, если нет фокуса на элементе if(!CElement::MouseFocus()) return; //--- Вернём счётчик к первоначальному значению, если кнопка мыши отжата if(!m_mouse_state) m_timer_counter=SPIN_DELAY_MSC; //--- Если же кнопка мыши нажата else { //--- Увеличим счётчик на установленный интервал m_timer_counter+=TIMER_STEP_MSC; //--- Выйдем, если меньше нуля if(m_timer_counter<0) return; //--- Получим текущее значение в поле ввода double current_value=::StringToDouble(m_edit.Description()); //--- Если увеличить if(m_spin_inc.State()) SetValue(current_value+m_step_value); //--- Если уменьшить else if(m_spin_dec.State()) SetValue(current_value-m_step_value); //--- Изменим значение, если кнопка-переключатель всё ещё нажата if(m_spin_inc.State() || m_spin_dec.State()) m_edit.Description(::DoubleToString(GetValue(),m_digits)); } }
Тест элемента «Поле ввода»
Все методы элемента управления «Поле ввода» реализованы. Теперь протестируем его в программе, которую подготовили до этого. В пользовательском классе приложения CProgram создаём один экземпляр класса CSpinEdit и объявляем метод для создания элемента «поле ввода».
//+------------------------------------------------------------------+ //| Класс для создания приложения | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Поля ввода CSpinEdit m_spin_edit1; //--- private: //--- Поля ввода #define SPINEDIT1_GAP_X (150) #define SPINEDIT1_GAP_Y (75) bool CreateSpinEdit1(const string text); };
Сделаем так, чтобы первый чекбокс управлял доступностью этого элемента управления так же, как это было сделано по отношению ко второму чекбоксу. Поэтому после создания элемента его доступность будет зависеть от текущего состояния первого чекбокса (см. код ниже).
//+------------------------------------------------------------------+ //| Создаёт поле ввода 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSpinEdit1(string text) { //--- Сохраним указатель на окно m_spin_edit1.WindowPointer(m_window1); //--- Координаты int x=m_window1.X()+SPINEDIT1_GAP_X; int y=m_window1.Y()+SPINEDIT1_GAP_Y; //--- Значение double v=(m_spin_edit1.GetValue()==WRONG_VALUE) ? 4 : m_spin_edit1.GetValue(); //--- Установим свойства перед созданием m_spin_edit1.XSize(150); m_spin_edit1.YSize(18); m_spin_edit1.EditXSize(76); m_spin_edit1.MaxValue(1000); m_spin_edit1.MinValue(-1000); m_spin_edit1.StepValue(1); m_spin_edit1.SetDigits(0); m_spin_edit1.SetValue(v); m_spin_edit1.ResetMode(true); m_spin_edit1.AreaColor(clrWhiteSmoke); m_spin_edit1.LabelColor(clrBlack); m_spin_edit1.LabelColorLocked(clrSilver); m_spin_edit1.EditColorLocked(clrWhiteSmoke); m_spin_edit1.EditTextColor(clrBlack); m_spin_edit1.EditTextColorLocked(clrSilver); m_spin_edit1.EditBorderColor(clrSilver); m_spin_edit1.EditBorderColorLocked(clrSilver); //--- Создадим элемент управления if(!m_spin_edit1.CreateSpinEdit(m_chart_id,m_subwin,text,x,y)) return(false); //--- Доступность будет зависеть от текущего состояния первого чекбокса m_spin_edit1.SpinEditState(m_checkbox1.CheckButtonState()); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_spin_edit1); return(true); }
Как и другие элементы, метод CProgram::CreateSpinEdit1() нужно вызывать в главном методе создания графического интерфейса программы. В листинге кода ниже показана сокращённая версия метода:
//+------------------------------------------------------------------+ //| Создаёт торговую панель | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Создание формы 1 для элементов управления //--- Создание элементов управления: // Главное меню //--- Контекстные меню //--- Создание статусной строки //--- Чекбоксы //--- Поля ввода if(!CreateSpinEdit1("Spin Edit 1:")) return(false); //--- Перерисовка графика m_chart.Redraw(); return(true); }
В обработчике событий CProgram::OnEvent() добавим код для теста прослушивания сообщений от полей ввода, а также укажем, что первое поле ввода теперь зависит от состояния первого чекбокса:
//+------------------------------------------------------------------+ //| Обработчик событий | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Событие нажатия на текстовой метке if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Если нажали на первом чекбоксе if(lparam==m_checkbox1.Id()) { //--- Установить состояние второму чекбоксу и первому полю ввода m_checkbox2.CheckBoxState(m_checkbox1.CheckButtonState()); m_spin_edit1.SpinEditState(m_checkbox1.CheckButtonState()); } } //--- Событие окончания ввода значения в поле ввода if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } //--- События нажатия на кнопках-переключателях поля ввода if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC || id==CHARTEVENT_CUSTOM+ON_CLICK_DEC) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } }
Скомпилируйте программу и загрузите её на график. На скриншоте ниже показано, как выглядит графический интерфейс приложения на текущий момент:
Рис. 4. Тест элемента управления «поле ввода».
Другие элементы управления с чекбоксами
В начале статьи упоминалось о том, что кроме элементов «чекбокс» и «поле ввода» будут рассмотрены ещё два элемента управления, такие как: «поле ввода с чекбоксом» и «комбобокс с чекбоксом». Поле ввода с чекбоксом это расширенный вариант класса CSpinEdit дополненный полями и методами класса CCheckBox, которые были рассмотрены в этой статье.
Рис. 5. Составные части элемента управления «Поле ввода с чекбоксом».
Комбобокс с чекбоксом — это аналогичное совмещение классов CComboBox и CCheckBox:
Рис. 6. Составные части элемента управления «Комбобокс с чекбоксом».
Реализации классов CCheckBoxEdit (поле ввода с чекбоксом) и CCheckComboBox (комбобокс с чекбоксом) вы найдёте в приложенных к статье архивах с файлами библиотеки для самостоятельного изучения. Так как в элементе типа CCheckComboBox содержится выпадающий список, то в файл WndContainer.mqh нужно внести соответствующие дополнения, подобно тому, как это было сделано с другими элементами, у которых есть выпадающие части. В данном случае нужно, чтобы указатель на выпадающий список попал в персональный массив указателей m_drop_lists[]. Более подробное описание того, как это можно сделать, можете посмотреть в статье Графические интерфейсы V - Элемент Комбинированный список (Глава 3).
Здесь в качестве примера дополним тестовое приложение этими элементами, чтобы вы смогли увидеть, как это работает. Добавим ещё два чекбокса типа CCheckBox и по одному элементу типа CCheckBoxEdit и CCheckComboBox. Доступность элемента типа CCheckBoxEdit будет зависеть от состояния третьего чекбокса, а доступность элемента типа CCheckComboBox будет зависеть от состояния четвёртого чекбокса.
Рис. 7. Тест элементов управления смешанных типов.
Заключение
В этой статье были разработаны довольно распространённые элементы управления, которые часто встречаются во многих графических интерфейсах в разных средах, такие как: «чекбокс», «поле ввода», «поле ввода с чекбоксом» и «комбобокс с чекбоксом». Во второй главе шестой части займёмся разработкой элементов управления «слайдер» и «двухсторонний слайдер».
- Графические интерфейсы VI: Элементы «Чекбокс», «Поле ввода» и их смешанные типы (Глава 1)
- Графические интерфейсы VI: Элементы «Слайдер» и «Двухсторонний слайдер» (Глава 2)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Даже если бы и всплыли, ничего страшного. Всегда можно всё исправить.
Так то естественно, но забирает дополнительное время... Потому и подумалось.
Впрочем - рад, что всё движется, и всё хорошо.
Вопрос: Толь, подскажи, планируется ли в очередном обновлении добавить возможность менять цвет фона ячеек CCanvasTable ?
...
Вопрос: Толь, подскажи, планируется ли в очередном обновлении добавить возможность менять цвет фона ячеек CCanvasTable ?
В следующем планируется только то, что анонсировалось. Всё остальное, что обсуждалось, в последующих обновлениях.
В следующем планируется только то, что анонсировалось. Всё остальное, что обсуждалось, в последующих обновлениях.
У меня такая беда - я никогда не слежу за раскладкой клавиатуры :) (Авторереключалка есть) Часто в SpinEdit забиваю "," вместо "."
Немного добавил. Думаю, не помешает...