Графические интерфейсы X: Текстовое поле ввода, слайдер картинок и простые элементы управления (build 5)

Anatoli Kazharski | 9 ноября, 2016


Содержание

Введение

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

В этой статье рассмотрим новые элементы управления: «Текстовое поле ввода», «Слайдер картинок», а также простые дополнительные элементы: «Текстовая метка» и «Картинка», которые тоже могут пригодиться в различных случаях. Библиотека продолжает развиваться, и кроме добавления новых элементов управления, улучшаются и те, которые уже были созданы ранее. Поскольку библиотеку применяют в своей работе уже множество пользователей, то от них поступают различные замечания и предложения. Многие из этих просьб реализованы в новой версии библиотеки. Плюс ко всему были оптимизированы некоторые алгоритмы. Это ещё сильнее уменьшило потребление ресурсов процессора. Подробнее обо всём этом читайте далее в статье.

 

Элемент «Текстовое поле ввода»

До сих пор в разрабатываемой библиотеке уже присутствовал элемент «Поле ввода» (класс CSpinEdit), но он предназначен только для ввода числовых значений. Пополним библиотеку ещё одним элементом, который позволит вводить в поле любой текст. Элемент «Текстовое поле ввода» может понадобиться в разных ситуациях. Например, можно создать поиск строки в файлах «песочницы» терминала. Еще одна возможность применения — предоставить конечному пользователю MQL-приложения возможность самому ввести массив символов для торговли. Словом, это могут быть любые текстовые данные, необходимые для работы.

Перечислим все составные части, из которых будет собираться элемент «Текстовое поле ввода»:

  1. Фон
  2. Ярлык
  3. Описание
  4. Поле ввода

 Рис. 1. Составные части элемента «Текстовое поле ввода»

Рис. 1. Составные части элемента «Текстовое поле ввода».


Рассмотрим подробнее, как устроен класс этого элемента.

 

Класс для создания элемента «Текстовое поле ввода»

Создаём файл TextEdit.mqh с классом CTextEdit со стандартными для всех элементов методами и подключаем его к движку библиотеки (файл WndContainer.mqh). В списке ниже перечислены свойства элемента, которые доступны для настройки пользователем:

//+------------------------------------------------------------------+
//| Класс для создания текстового поля ввода                         |
//+------------------------------------------------------------------+
class CTextEdit : public CElement
  {
private:
   //--- Цвет фона элемента
   color             m_area_color;
   //--- Ярлыки элемента в активном и заблокированном состоянии
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- Отступы ярлыка
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- Текст описания поля ввода
   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[];
   //--- Текущее значение в поле ввода
   string            m_edit_value;
   //--- Размеры поля ввода
   int               m_edit_x_size;
   int               m_edit_y_size;
   //--- Отступы поля ввода
   int               m_edit_x_gap;
   int               m_edit_y_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[];
   //--- Режим сброса значения (пустая строка)
   bool              m_reset_mode;
   //--- Режим показа указателя выделения текста
   bool              m_show_text_pointer_mode;
   //--- Режим выравнивания текста
   ENUM_ALIGN_MODE   m_align_mode;
   //---
public:
   //--- Отступы ярлыка
   void              IconXGap(const int x_gap)                      { m_icon_x_gap=x_gap;                 }
   void              IconYGap(const int y_gap)                      { m_icon_y_gap=y_gap;                 }
   //--- (1) Цвет фона, (2) текст описания поля ввода, (3) отступы текстовой метки
   void              AreaColor(const color clr)                     { m_area_color=clr;                   }
   string            LabelText(void)                          const { return(m_label.Description());      }
   void              LabelText(const string text)                   { m_label.Description(text);          }
   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              EditYGap(const int y_gap)                      { m_edit_y_gap=y_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;     }
   //--- (1) Режим сброса при нажатии на текстовой метке, (2) режим показа указателя выделения текста
   bool              ResetMode(void)                                { return(m_reset_mode);               }
   void              ResetMode(const bool mode)                     { m_reset_mode=mode;                  }
   void              ShowTextPointerMode(const bool mode)           { m_show_text_pointer_mode=mode;      }
   //--- Режим выравнивания текста
   void              AlignMode(ENUM_ALIGN_MODE mode)                { m_align_mode=mode;                  }
   //--- Установка ярлыков для элемента в активном и заблокированном состояниях
   void              IconFileOn(const string file_path);
   void              IconFileOff(const string file_path);
  };

Режим показа указателя выделения текста означает, что при наведении курсора мыши на текстовое поле он будет дополняться ярлыком, показывающим, что здесь можно ввести текст. Ччтобы это работало, в перечисление ENUM_MOUSE_POINTER был добавлен ещё один идентификатор (MP_TEXT_SELECT).

//+------------------------------------------------------------------+
//| Перечисление типов указателей                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM      =0,
   MP_X_RESIZE    =1,
   MP_Y_RESIZE    =2,
   MP_XY1_RESIZE  =3,
   MP_XY2_RESIZE  =4,
   MP_X_SCROLL    =5,
   MP_Y_SCROLL    =6,
   MP_TEXT_SELECT =7
  };

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

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//--- Ресурсы
...
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_text_select.bmp"

//+------------------------------------------------------------------+
//| Установка картинок для указателя по типу указателя               |
//+------------------------------------------------------------------+
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      case MP_X_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs.bmp";
         break;
      case MP_Y_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs.bmp";
         break;
      case MP_XY1_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs.bmp";
         break;
      case MP_XY2_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs.bmp";
         break;
      case MP_X_SCROLL :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp";
         break;
      case MP_Y_SCROLL :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_scroll_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_scroll.bmp";
         break;
      case MP_TEXT_SELECT :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_text_select.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_text_select.bmp";
         break;

     }
//--- Если указан пользовательский тип (MP_CUSTOM)
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Для указателя курсора должны быть установлены обе картинки!");
  }

Для создания элемента «Текстовое поле ввода» понадобятся пять приватных (private) методов и один публичный (public): 

class CTextEdit : public CElement
  {
private:
   //--- Объекты для создания поля ввода
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CEdit             m_edit;
   CPointer          m_text_select;
   //---
public:
   //--- Методы для создания текстового поля ввода
   bool              CreateTextEdit(const long chart_id,const int subwin,const string label_text,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateEdit(void);
   bool              CreateTextSelectPointer(void);
  };


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

 

Элемент «Слайдер картинок»

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

Перечислим все составные части, из которых будет собираться элемент «Слайдер картинок».

  1. Фон
  2. Кнопки последовательного переключения картинок
  3. Группа радио-кнопок
  4. Группа картинок, связанная с группой радио-кнопок


 Рис. 2. Составные части элемента «Слайдер картинок»

Рис. 2. Составные части элемента «Слайдер картинок».

 

Класс для создания элемента «Слайдер картинок»

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

//+------------------------------------------------------------------+
//| Класс для создания слайдера картинок                             |
//+------------------------------------------------------------------+
class CPicturesSlider : public CElement
  {
private:
   //--- Цвет фона и рамки элемента
   color             m_area_color;
   color             m_area_border_color;
   //--- Отступ для картинок по оси Y
   int               m_pictures_y_gap;
   //--- Отступы для кнопок
   int               m_arrows_x_gap;
   int               m_arrows_y_gap;
   //--- Отступы для радио-кнопок
   int               m_radio_buttons_x_gap;
   int               m_radio_buttons_y_gap;
   int               m_radio_buttons_x_offset;
   //---
public:
   //--- (1) Цвет фона и (2) рамки фона
   void              AreaColor(const color clr)              { m_area_color=clr;                      }
   void              AreaBorderColor(const color clr)        { m_area_border_color=clr;               }
   //--- Отступы для кнопок-стрелок
   void              ArrowsXGap(const int x_gap)             { m_arrows_x_gap=x_gap;                  }
   void              ArrowsYGap(const int y_gap)             { m_arrows_y_gap=y_gap;                  }
   //--- Отступ для картинок по оси Y
   void              PictureYGap(const int y_gap)            { m_pictures_y_gap=y_gap;                }
   //--- (1) Отступы радио-кнопок, (2) расстояние между радио-кнопками
   void              RadioButtonsXGap(const int x_gap)       { m_radio_buttons_x_gap=x_gap;           }
   void              RadioButtonsYGap(const int y_gap)       { m_radio_buttons_y_gap=y_gap;           }
   void              RadioButtonsXOffset(const int x_offset) { m_radio_buttons_x_offset=x_offset;     }
  };

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

class CPicturesSlider : public CElement
  {
private:
   //--- Объекты для создания элемента
   CRectLabel        m_area;
   CBmpLabel         m_pictures[];
   CRadioButtons     m_radio_buttons;
   CIconButton       m_left_arrow;
   CIconButton       m_right_arrow;
   //---
public:
   //--- Методы для создания слайдера картинок
   bool              CreatePicturesSlider(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreatePictures(void);
   bool              CreateRadioButtons(void);
   bool              CreateLeftArrow(void);
   bool              CreateRightArrow(void);
  };

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

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

//+------------------------------------------------------------------+
//|                                               PicturesSlider.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Картинка по умолчанию
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\no_image.bmp"
//+------------------------------------------------------------------+
//| Класс для создания слайдера картинок                             |
//+------------------------------------------------------------------+
class CPicturesSlider : public CElement
  {
private:
   //--- Массив картинок (путь к картинкам)
   string            m_file_path[];
   //--- Путь к картинке по умолчанию
   string            m_default_path;
   //---
public:
   //--- Добавляет картинку
   void              AddPicture(const string file_path="");
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPicturesSlider::CPicturesSlider(void) : m_default_path("Images\\EasyAndFastGUI\\Icons\\bmp64\\no_image.bmp"),
                                         m_area_color(clrNONE),
                                         m_area_border_color(clrNONE),
                                         m_arrows_x_gap(2),
                                         m_arrows_y_gap(2),
                                         m_radio_button_width(12),
                                         m_radio_buttons_x_gap(25),
                                         m_radio_buttons_y_gap(1),
                                         m_radio_buttons_x_offset(20),
                                         m_pictures_y_gap(25)
  {
//--- Сохраним имя класса элемента в базовом классе
   CElement::ClassName(CLASS_NAME);
//--- Установим приоритеты на нажатие левой кнопки мыши
   m_zorder=0;
  }
//+------------------------------------------------------------------+
//| Добавляет картинку                                               |
//+------------------------------------------------------------------+
void CPicturesSlider::AddPicture(const string file_path="")
  {
//--- Увеличим размер массивов на один элемент
   int array_size=::ArraySize(m_pictures);
   int new_size=array_size+1;
   ::ArrayResize(m_pictures,new_size);
   ::ArrayResize(m_file_path,new_size);
//--- Сохраним значения переданных параметров
   m_file_path[array_size]=(file_path=="")? m_default_path : file_path;
  }

Для показа картинки из группы нужно использовать метод CPicturesSlider::SelectPicture(). Этот метод будет вызываться при нажатии кнопок-переключателей и радио-кнопок в обработчике класса CPicturesSlider.  

class CPicturesSlider : public CElement
  {
public:
   //--- Переключает картинку по указанному индексу
   void              SelectPicture(const uint index);
  };
//+------------------------------------------------------------------+
//| Указывает, какая картинка должна быть показана                   |
//+------------------------------------------------------------------+
void CPicturesSlider::SelectPicture(const uint index)
  {
//--- Получим количество картинок
   uint pictures_total=PicturesTotal();
//--- Если нет ни одной картинки в группе, сообщить об этом
   if(pictures_total<1)
     {
      ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, "
              "когда в группе есть хотя бы одна картинка! Воспользуйтесь методом CPicturesSlider::AddPicture()");
      return;
     }
//--- Скорректировать значение индекса, если выходит из диапазона
   uint correct_index=(index>=pictures_total)? pictures_total-1 : index;
//--- Выделить радио-кнопку по этому индексу
   m_radio_buttons.SelectRadioButton(correct_index);
//--- Переключить картинку
   for(uint i=0; i<pictures_total; i++)
     {
      if(i==correct_index)
         m_pictures[i].Timeframes(OBJ_ALL_PERIODS);
      else
         m_pictures[i].Timeframes(OBJ_NO_PERIODS);
     }
  }

При нажатии на кнопки для последовательного переключения картинок в обработчике элемента вызываются методы CPicturesSlider::OnClickLeftArrow() и CPicturesSlider::OnClickRightArrow(). В листинге ниже показан код метода для левой кнопки. События нажатия на кнопки слайдера картинок можно при необходимости отследить в пользовательском классе MQL-приложения. 

class CPicturesSlider : public CElement
  {
public:
private:
   //--- Обработка нажатия на левой кнопке
   bool              OnClickLeftArrow(const string clicked_object);
   //--- Обработка нажатия на правой кнопке
   bool              OnClickRightArrow(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Нажатие на левую кнопку                                          |
//+------------------------------------------------------------------+
bool CPicturesSlider::OnClickLeftArrow(const string clicked_object)
  {
//--- Выйдем, если нажатие было не на кнопке
   if(::StringFind(clicked_object,CElement::ProgramName()+"_icon_button_",0)<0)
      return(false);
//--- Получим идентификатор элемента из имени объекта
   int id=CElement::IdFromObjectName(clicked_object);
//--- Получим индекс элемента из имени объекта
   int index=CElement::IndexFromObjectName(clicked_object);
//--- Выйти, если идентификаторы элементов не совпадают
   if(id!=CElement::Id())
      return(false);
//--- Выйти, если индексы элементов не совпадают
   if(index!=0)
      return(false);
//--- Получим текущий индекс выделенной радио-кнопки
   int selected_radio_button=m_radio_buttons.SelectedButtonIndex();
//--- Переключение картинки
   SelectPicture(--selected_radio_button);
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),CElement::Index(),"");
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CPicturesSlider::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события перемещения курсора
...
//--- Обработка события нажатия на радио-кнопке
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL)
     {
      //--- Если это радио-кнопка слайдера, переключить картинку
      if(lparam==CElement::Id())
         SelectPicture(m_radio_buttons.SelectedButtonIndex());
      //---
      return;
     }
//--- Обработка события нажатия левой кнопки мыши на объекте
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Если нажатие на кнопках-стрелках слайдера, переключить картинку
      if(OnClickLeftArrow(sparam))
         return;
      if(OnClickRightArrow(sparam))
         return;
      //---
      return;
     }
  }

 

Элементы «Текстовая метка» и «Картинка»

В качестве дополнения в библиотеку включены два новых класса CTextLabel и CPicture для создания простых элементов интерфейса «Текстовая метка» и «Картинка». Они могут использоваться как отдельные объекты, без жёсткой привязки, к какому-либо другому элементу. Их содержание очень простое. В классе CPicture пользователь может изменить только одно свойство – это путь к картинке. Для этого предназначен метод CPicture::Path(). Если не указать свой путь, то будет использоваться картинка по умолчанию. Картинку можно изменить программно в любой момент и после создания графического интерфейса MQL-приложения.

//+------------------------------------------------------------------+
//|                                                      Picture.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//--- Ресурсы
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\no_image.bmp"
//+------------------------------------------------------------------+
//| Класс для создания картинки                                      |
//+------------------------------------------------------------------+
class CPicture : public CElement
  {
private:
   //--- Путь к картинке
   string            m_path;
   //---
public:
   //--- Возвращает/устанавливает путь к картинке
   string            Path(void)               const { return(m_path);             }
   void              Path(const string path);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPicture::CPicture(void) : m_path("Images\\EasyAndFastGUI\\Icons\\bmp64\\no_image.bmp")

  {
//--- Сохраним имя класса элемента в базовом классе
   CElement::ClassName(CLASS_NAME);
//--- Установим приоритеты на нажатие левой кнопки мыши
   m_zorder=0;
  }
//+------------------------------------------------------------------+
//| Установка картинки                                               |
//+------------------------------------------------------------------+
void CPicture::Path(const string path)
  {
   m_path=path;
   m_picture.BmpFileOn("::"+path);
   m_picture.BmpFileOff("::"+path);
  }

Что касается элемента «Текстовая метка», то здесь тоже всё довольно просто и пользователь может установить всего лишь четыре свойства:

//+------------------------------------------------------------------+
//| Класс для создания текстовой метки                               |
//+------------------------------------------------------------------+
class CTextLabel : public CElement
  {
public:
   //--- Возвращает/устанавливает текст метки
   string            LabelText(void)             const { return(m_label.Description()); }
   void              LabelText(const string text)      { m_label.Description(text);     }
   //--- Установка (1) цвета, (2) шрифта и (3) размера ширифта текстовой метки
   void              LabelColor(const color clr)       { m_label.Color(clr);            }
   void              LabelFont(const string font)      { m_label.Font(font);            }
   void              LabelFontSize(const int size)     { m_label.FontSize(size);        }
  };

 

Класс CFonts для работы со шрифтами

Для более простого выбора шрифта был написан дополнительный класс CFonts. В нём доступны 187 шрифтов. Это системные шрифты терминала, список которых вы уже наверняка видели в настройках некоторых графических объектов.

 Рис. 3. Системные шрифты терминала.

Рис. 3. Системные шрифты терминала.


Файл со шрифтами (Fonts.mqh) находится в директории "MetaTrader 5\MQL5\Include\EasyAndFastGUI\Fonts.mqh" и для полного доступа во всей схеме библиотеки подключен к файлу Objects.mqh:

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Enums.mqh"
#include "Defines.mqh"
#include "..\Fonts.mqh"
#include "..\Canvas\Charts\LineChart.mqh"
#include <ChartObjects\ChartObjectSubChart.mqh>
#include <ChartObjects\ChartObjectsBmpControls.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

В классе CFonts есть только два публичных метода для получения размера массива шрифтов и получения названия шрифта по индексу. Массив шрифтов инициализируется в конструкторе класса

//+------------------------------------------------------------------+
//|                                                        Fonts.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс для работы с шрифтом                                       |
//+------------------------------------------------------------------+
class CFonts
  {
private:
   //--- Массив шрифотов
   string            m_fonts[];
   //---
public:
                     CFonts(void);
                    ~CFonts(void);
   //--- Возвращает количество шрифтов
   int               FontsTotal(void) const { return(::ArraySize(m_fonts)); }
   //--- Возвращает шрифт по индексу
   string            FontsByIndex(const uint index);
   //---
private:
   //--- Инициализация массива шрифтов
   void              InitializeFontsArray(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CFonts::CFonts(void)
  {
//--- Инициализация массива шрифтов
   InitializeFontsArray();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CFonts::~CFonts(void)
  {
   ::ArrayFree(m_fonts);
  }

При вызове метода CFonts::FontsByIndex() осуществляется корректировка, предотвращающая выход за пределы массива:

//+------------------------------------------------------------------+
//| Возвращает шрифт по индексу                                      |
//+------------------------------------------------------------------+
string CFonts::FontsByIndex(const uint index)
  {
//--- Размер массива
   uint array_size=FontsTotal();
//--- Корректировка в случае выхода из диапазона
   uint i=(index>=array_size)? array_size-1 : index;
//--- Вернуть шрифт
   return(m_fonts[i]);
  }

 

Список дополнительных обновлений библиотеки

1. Исправлен некорректный показ всплывающих подсказок в диалоговых окнах. Теперь статус кнопки подсказок на главном окне при нажатии распространяется на все окна графического интерфейса. При нажатии на кнопку генерируется сообщение с новым идентификатором события ON_WINDOW_TOOLTIPS (см. файл Defines.mqh).

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_WINDOW_TOOLTIPS         (29) // Нажатие на кнопке "Всплывающие подсказки"

Соответственно, в класс CWindow добавлен метод OnClickTooltipsButton() для обработки нажатия на кнопке всплывающих подсказок: 

//+------------------------------------------------------------------+
//| Класс создания формы для элементов управления                    |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- Обработка нажатия на кнопке "Всплывающие подсказки"
   bool              OnClickTooltipsButton(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события нажатия на объекте
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Если нажатие на кнопке "Всплывающие подсказки"
      if(OnClickTooltipsButton(sparam))
         return;
     }
  }
//+------------------------------------------------------------------+
//| Обработка нажатия на кнопке "Всплывающие подсказки"              |
//+------------------------------------------------------------------+
bool CWindow::OnClickTooltipsButton(const string clicked_object)
  {
//--- Эта кнопка не нужна, если окно диалоговое
   if(m_window_type==W_DIALOG)
      return(false);
//--- Выйдем, если нажатие было не на радио-кнопке
   if(::StringFind(clicked_object,CElement::ProgramName()+"_window_tooltip_",0)<0)
      return(false);
//--- Получим идентификатор элемента из имени объекта
   int id=CElement::IdFromObjectName(clicked_object);
//--- Выйти, если идентификаторы элементов не совпадают
   if(id!=CElement::Id())
      return(false);
//--- Запомним состояние в поле класса
   m_tooltips_button_state=m_button_tooltip.State();
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_WINDOW_TOOLTIPS,CElement::Id(),CElement::Index(),"");
   return(true);
  }

Для того, чтобы всё это работало в движке библиотеки (класс CWndEvents), добавлен метод OnWindowTooltips() для обработки события с идентификатором ON_WINDOW_TOOLTIPS

class CWndEvents : public CWndContainer
  {
private:
   //--- Включение/отключение всплывающих подсказок
   bool              OnWindowTooltips(void);
  };
//+------------------------------------------------------------------+
//| Событие CHARTEVENT_CUSTOM                                        |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Если сигнал свернуть форму
//--- Если сигнал развернуть форму
//--- Если сигнал изменить размер элементов по оси X
//--- Если сигнал изменить размер элементов по оси Y
//--- Если сигнал включить/выключить всплывающие подсказки
   if(OnWindowTooltips())
      return;

//--- Если сигнал на скрытие контекстных меню от пункта инициатора
//--- Если сигнал на скрытие всех контекстных меню
//--- Если сигнал на открытие диалогового окна
//--- Если сигнал на закрытие диалогового окна
//--- Если сигнал на сброс цвета элементов на указанной форме
//--- Если сигнал на сброс приоритетов на нажатие левой кнопкой мыши
//--- Если сигнал на восстановление приоритетов на нажатие левой кнопкой мыши
  }
//+------------------------------------------------------------------+
//| Событие ON_WINDOW_TOOLTIPS                                       |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowTooltips(void)
  {
//--- Если сигнал "Включить/отключить всплывающие подсказки"
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_TOOLTIPS)
      return(false);
//--- Если идентификаторы окна совпадают
   if(m_lparam!=m_windows[0].Id())
      return(true);
//--- Синхронизировать режим всплывающих подсказок между всеми окнами
   int windows_total=WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      if(w>0)
         m_windows[w].TooltipButtonState(m_windows[0].TooltipButtonState());
     }
//---
   return(true);
  }

2. Добавлена возможность изменять текст в описании следующих элементов после их создания: 

 Рис. 4. Список элементов с возможностью изменения текста после создания.

Рис. 4. Список элементов с возможностью изменения текста после создания.


3. Во всех элементах, где это могло понадобиться, теперь можно установить картинку (см. таблицу ниже). Кроме этого, добавлена возможность изменять картинки элементов уже после их создания:

Рис. 5. Список элементов с возможностью изменения картинки после создания. 

Рис. 5. Список элементов с возможностью изменения картинки после создания.

 

Для замены картинки во всех перечисленных в таблице выше элементах есть методы IconFileOn() и IconFileOff().


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

 Рис. 6. Список элементов с возможностью изменения состояния (нажата/отжата) после создания.

Рис. 6. Список элементов с возможностью изменения состояния (нажата/отжата) после создания.


5. Оптимизирован алгоритм подсветки пунктов при наведении курсора мыши в следующих элементах:

 Рис. 7. Элементы с оптимизацией алгоритма подсветки пунктов элемента.

Рис. 7. Элементы с оптимизацией алгоритма подсветки пунктов элемента.

 

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

Далее в качестве примера рассмотрим, как это было реализовано в классе CListView. Для реализации описанного выше нужно было добавить (1) поле класса m_prev_item_index_focus для сохранения индекса последнего в фокусе пункта, (2) метод CListView::CheckItemFocus() для проверки фокуса над пунктом и (3) изменить алгоритм в методе CListView::ChangeItemsColor().  

//+------------------------------------------------------------------+
//| Класс для создания списка                                        |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
private:
   //--- Для определения момента перехода курсора мыши с одного пункта на другой
   int               m_prev_item_index_focus;
   //---
private:
   //--- Изменение цвета пунктов списка при наведении
   void              ChangeItemsColor(void);
   //--- Проверка фокуса строки списка при наведении курсора
   void              CheckItemFocus(void);
  };

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

//+------------------------------------------------------------------+
//| Проверка фокуса строки списка при наведении курсора              |
//+------------------------------------------------------------------+
void CListView::CheckItemFocus(void)
  {
//--- Получим текущую позицию ползунка полосы прокрутки
   int v=m_scrollv.CurrentPos();
//--- Определим, над каким пунктом сейчас находится курсор, и подсветим его
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- Увеличим счётчик, если внутри диапазона списка
      if(v>=0 && v<m_items_total)
         v++;
      //--- Пропускаем выделенный пункт
      if(m_selected_item_index==v-1)
        {
         m_items[i].BackColor(m_item_color_selected);
         m_items[i].Color(m_item_text_color_selected);
         continue;
        }
      //--- Если курсор над этим пунктом, то подсветим его
      if(m_mouse.X()>m_items[i].X() && m_mouse.X()<m_items[i].X2() &&
         m_mouse.Y()>m_items[i].Y() && m_mouse.Y()<m_items[i].Y2())
        {
         m_items[i].BackColor(m_item_color_hover);
         m_items[i].Color(m_item_text_color_hover);
         //--- Запомнить пункт
         m_prev_item_index_focus=i;
         break;
        }
     }
  }

Вызов метода CListView::CheckItemFocus() осуществляется в методе CListView::ChangeItemsColor() в случаях, как это было описано в предыдущем абзаце (см. листинг кода ниже):

//+------------------------------------------------------------------+
//| Изменение цвета строки списка при наведении курсора              |
//+------------------------------------------------------------------+
void CListView::ChangeItemsColor(void)
  {
//--- Выйдем, если отключена подсветка при наведении курсора или прокрутка списка активна
   if(!m_lights_hover || m_scrollv.ScrollState())
      return;
//--- Выйдем, если элемент не выпадающий и форма заблокирована
   if(!CElement::IsDropdown() && m_wnd.IsLocked())
      return;
//--- Если зашли в список заново
   if(m_prev_item_index_focus==WRONG_VALUE)
     {
      //--- Проверка фокуса на текущем пункте
      CheckItemFocus();
     }
   else
     {
      //--- Проверим фокус на текущем ряде
      int i=m_prev_item_index_focus;
      bool condition=m_mouse.X()>m_items[i].X() && m_mouse.X()<m_items[i].X2() &&
                     m_mouse.Y()>m_items[i].Y() && m_mouse.Y()<m_items[i].Y2();
      //--- Если переход на другой пункт
      if(!condition)
        {
         //--- Сбросить цвет предыдущего пункта
         m_items[i].BackColor(m_item_color);
         m_items[i].Color(m_item_text_color);
         m_prev_item_index_focus=WRONG_VALUE;
         //--- Проверка фокуса на текущем пункте
         CheckItemFocus();
        }
     }
  }

В обработчике событий CListView::OnEvent() вызов метода CListView::ChangeItemsColor() осуществляется только если курсор мыши находится в области элемента. Как только курсор вышел из области элемента, устанавливаются цвета по умолчанию и сбрасывается значение индекса пункта. Ниже показана сокращённая версия обработчика событий. 

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CListView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события перемещения курсора
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Выйти, если элемент скрыт
      //--- Выйти, если номера подокон не совпадают
      //--- Проверка фокуса над элементами
      //--- Если это выпадающий список и кнопка мыши нажата
      //--- Смещаем список, если управление ползунком полосы прокрутки в действии

      //--- Сбросить цвета элемента, если не в фокусе
      if(!CElement::MouseFocus())
        {
         //--- Если уже есть пункт в фокусе
         if(m_prev_item_index_focus!=WRONG_VALUE)
           {
            //--- Сбросить цвета списка
            ResetColors();
            m_prev_item_index_focus=WRONG_VALUE;
           }
         return;
        }
      //--- Изменяет цвет строк списка при наведении
      ChangeItemsColor();
      return;
     }
  }

Такой же принцип реализован в классах CTable, CCalendar и CTreeView, но с некоторыми отличиями, учитывающими особенности каждого элемента.

6. При нажатии на кнопку типа CIconButton в двойном режиме (когда кнопка не отжимается после нажатия) показывается другая картинка, если она установлена. Картинку для нажатой кнопки можно установить с помощью методов CIconButton::IconFilePressedOn() и CIconButton::IconFilePressedOff()

//+------------------------------------------------------------------+
//| Класс для создания кнопки с картинкой                            |
//+------------------------------------------------------------------+
class CIconButton : public CElement
  {
private:
   //--- Ярлыки кнопки в активном, заблокированном и нажатом состоянии
   string            m_icon_file_on;
   string            m_icon_file_off;
   string            m_icon_file_pressed_on;
   string            m_icon_file_pressed_off;

   //---
public:
   //--- Установка ярлыков для кнопки в нажатом, активном и заблокированном состояниях
   void              IconFileOn(const string file_path);
   void              IconFileOff(const string file_path);
   void              IconFilePressedOn(const string file_path);
   void              IconFilePressedOff(const string file_path);

  };
//+------------------------------------------------------------------+
//| Установка картинки для нажатого состояния "Включено"             |
//+------------------------------------------------------------------+
void CIconButton::IconFilePressedOn(const string file_path)
  {
//--- Выйти, если у кнопки отключен режим "Два состояния"
   if(!m_two_state)
      return;
//--- Сохранить путь к картинке
   m_icon_file_pressed_on=file_path;
//--- Сразу установить, если кнопка нажата
   if(m_button.State())
      m_icon.BmpFileOn("::"+file_path);
  }
//+------------------------------------------------------------------+
//| Установка картинки для нажатого состояния "Отключено"            |
//+------------------------------------------------------------------+
void CIconButton::IconFilePressedOff(const string file_path)
  {
//--- Выйти, если у кнопки отключен режим "Два состояния"
   if(!m_two_state)
      return;
//--- Сохранить путь к картинке
   m_icon_file_pressed_off=file_path;
//--- Сразу установить, если кнопка нажата
   if(m_button.State())
      m_icon.BmpFileOff("::"+file_path);
  }

7. Добавлена возможность программно выделять ряд в таблице (CTable). Для этого воспользуйтесь методом CTable::SelectRow(). Указание индекса уже выделенного ряда снимает выделение. 

//+------------------------------------------------------------------+
//| Класс для создания таблицы из полей ввода                        |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
public:
   //--- Выделение указанного ряда таблицы
   void              SelectRow(const uint row_index);
  };
//+------------------------------------------------------------------+
//| Выделение указанного ряда таблицы                                |
//+------------------------------------------------------------------+
void CTable::SelectRow(const uint row_index)
  {
//--- Корректировка в случае выхода из диапазона
   uint index=(row_index>=(uint)m_rows_total)? m_rows_total-1 : row_index;
//--- Если этот ряд уже выделен, снимем выделение
   bool is_selected=(index==m_selected_item);
//--- Сохраним индекс ряда
   m_selected_item=(is_selected)? WRONG_VALUE : (int)index;
//--- Сохраним строку ячейки
   m_selected_item_text=(is_selected)? "" : m_vcolumns[0].m_vrows[index];
//--- Сформировать строку с параметрами ячейки
   string cell_params=string(0)+"_"+string(index)+"_"+m_vcolumns[0].m_vrows[index];
//--- Сброс фокуса
   m_prev_item_index_focus=WRONG_VALUE;
//--- Обновляет таблицу
   UpdateTable();
//--- Подсветка выделенного ряда
   HighlightSelectedItem();
  }

8. Устранена недоработка в отображении элементов выделенной вкладки элемента типа CIconTabs. Проблема возникала при открытии и разворачивании формы с этим типом вкладок. 

 

Приложение для теста элемента

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

1. Первая вкладка:

Для всех элементов, кроме текстовой метки, установим ярлык. У элементов «Прогресс-бар» и «Текстовое поле ввода» через определённые временные интервалы будем изменять текстовое описание элемента, чтобы продемонстрировать, что такая возможность теперь доступна. В список комбо-бокса поместим названия всех шрифтов из класса CFonts. Событийную модель тестового MQL-приложения настроим таким образом, чтобы выбор шрифта из списка комбо-бокса отражался на текстовой метке. Точно так же свяжем с текстовой меткой числовое поле ввода для изменения размера шрифта и выбор цвета в палитре. 

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

//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Событие выбора в списке
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Если идентификаторы элементов совпадают
      if(lparam==m_combobox1.Id())
        {
         //--- Изменить шрифт
         m_text_label1.LabelFont(m_combobox1.ButtonText());
        }
      //---
      return;
     }
//--- Событие нажатия на кнопках поля ввода
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC ||
      id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      //--- Если идентификаторы элементов совпадают
      if(lparam==m_spin_edit1.Id())
        {
         //--- Изменить размер шрифта
         m_text_label1.LabelFontSize(int(m_spin_edit1.GetValue()));
        }
      //---
      return;
     }
//--- Событие изменения цвета посредством цветовой палитры
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      //--- Если идентификаторы элементов совпадают
      if(lparam==m_spin_edit1.Id())
        {
         //--- Изменить размер шрифта
         m_text_label1.LabelFontSize(int(m_spin_edit1.GetValue()));
        }
      //---
      return;
     }
//--- Событие изменения цвета посредством цветовой палитры
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_COLOR)
     {
      //--- Если идентификаторы элементов совпадают
      if(lparam==m_color_picker.Id())
        {
         //--- Если ответ от первой кнопки
         if(sparam==m_color_button1.LabelText())
           {
            //--- Изменить цвет объекта
            m_text_label1.LabelColor(m_color_button1.CurrentColor());
            return;
           }
        }
      return;
     }
//--- Событие нажатия на кнопке
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Если нажали на первую кнопку для вызова цветовой палитры
      if(sparam==m_color_button1.LabelText())
        {
         //--- Передать указатель кнопки, что автоматически откроет окно с цветовой палитрой
         m_color_picker.ColorButtonPointer(m_color_button1);
         return;
        }
      //---
      return;
     }
  }

На скриншоте ниже показано, как в итоге можно настроить отображение текста посредством графического интерфейса, созданного разрабатываемой библиотекой:

Рис. 8. Группа элементов на первой вкладке. 

Рис. 8. Группа элементов на первой вкладке.


2. На второй вкладке поместим только один элемент – «Слайдер картинок» (класс CPicturesSlider). Добавим в группу только три картинки по умолчанию, чтобы вы могли быстро протестировать этот элемент самостоятельно. Для корректной работы элемента подготовьте картинки одинакового размера.

Рис. 9. Элемент «Слайдер картинок» на второй вкладке. 

Рис. 9. Элемент «Слайдер картинок» на второй вкладке.


Для программного переключения картинок воспользуйтесь методом CPicturesSlider::SelectPicture().


3. На третьей вкладке будет таблица типа CTable. Для программного выделения ряда воспользуйтесь методом CTable::SelectRow(). 

 Рис. 10. Элемент «Таблица» на третьей вкладке.

Рис. 10. Элемент «Таблица» на третьей вкладке.


4. На четвёртой вкладке расположим три элемента: (1) календарь, (2) выпадающий календарь и (3) кнопку с двумя разными картинками для состояний нажата/отжата.

Рис. 11. Группа элементов на четвёртой вкладке. 

Рис. 11. Группа элементов на четвёртой вкладке.

 

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


Заключение

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

 Рис. 12. Структура библиотеки на текущей стадии разработки.

Рис. 12. Структура библиотеки на текущей стадии разработки.


В следующей версии библиотека будет расширена дополнительными элементами управления. Также мы разовьем и дополним новыми возможностями уже существующие элементы.

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