Графические интерфейсы V: Вертикальная и горизонтальная полоса прокрутки (Глава 1)

Anatoli Kazharski | 22 апреля, 2016

Содержание



Введение

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

 


Элемент «Полоса прокрутки»

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

Собирать полосу прокрутки мы будем из пяти графических объектов.

  1. Основной фон.
  2. Фон области перемещения ползунка.
  3. Две кнопки для смещения массивов данных на один шаг.
  4. Ползунок для быстрого перемещения массивов данных.

 Рис. 1. Составные части элемента управления «Полоса прокрутки».

Рис. 1. Составные части элемента управления «Полоса прокрутки».

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

Базовым классом для них станет (CScroll), в котором должны содержаться общие для каждого типа поля и методы. Все три класса поместим в один файл Scrolls.mqh. Схема для элемента управления «Полоса прокрутки» тогда будет выглядеть так:

 Рис. 2. Схема элемента управления «Полоса прокрутки».

Рис. 2. Схема элемента управления «Полоса прокрутки».


Далее рассмотрим процесс разработки базового класса этого элемента управления (CScroll). 

 


Базовый класс элемента

Элемент управления «Полоса прокрутки» не является самостоятельным элементом графического интерфейса. Это вспомогательный элемент, и его нужно будет подключать к другим элементам, для которых может понадобиться перемещение данных в рабочей области. Поэтому файл Scrolls.mqh не нужно подключать напрямую к файлу WndContainer.mqh. Классы полосы прокрутки будут доступны в библиотеке через подключение к файлам с классами других элементов.

Создайте класс CScroll в файле Scrolls.mqh со стандартными для всех элементов интерфейса методами:

//+------------------------------------------------------------------+
//|                                                      Scrolls.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Базовый класс для создания полосы прокрутки                      |
//+------------------------------------------------------------------+
class CScroll : public CElement
  {
protected:
   //--- Указатель на форму, к которой элемент присоединён
   CWindow          *m_wnd;
   //---
public:
   //--- Сохраняет указатель формы
   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) {}
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CScroll::CScroll(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CScroll::~CScroll(void)
  {
  }

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

//+------------------------------------------------------------------+
//| Класс для управления вертикальной полосой прокрутки              |
//+------------------------------------------------------------------+
class CScrollV : public CScroll
  {
public:
                     CScrollV(void);
                    ~CScrollV(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CScrollV::CScrollV(void)
  {
//--- Сохраним имя класса элемента в базовом классе
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CScrollV::~CScrollV(void)
  {
  }
//+------------------------------------------------------------------+
//| Класс для управления горизонтальной полосой прокрутки            |
//+------------------------------------------------------------------+
class CScrollH : public CScroll
  {
public:
                     CScrollH(void);
                    ~CScrollH(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CScrollH::CScrollH(void)
  {
//--- Сохраним имя класса элемента в базовом классе
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CScrollH::~CScrollH(void)
  {
  }

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

Следует уточнить, что шириной полосы прокрутки (а также других её объектов) для вертикального типа будет размер по оси X, а для горизонтального типа — размер по оси Y. По умолчанию ширина будет равна 15 пикселям. Картинки для кнопок по умолчанию подобраны именно под этот размер. Они расположены внутри общего фона элемента с отступом 1 пиксель, чтобы не заслонять рамку фона, и их размер равен 13x13 пикселей. Поэтому если нам понадобится изменить ширину полосы прокрутки, то нужно будет переопределить картинки для кнопок. Для этих целей создадим соответствующие методы.

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

class CScroll : public CElement
  {
protected:
   //--- Свойства общей площади полосы прокрутки
   int               m_area_width;
   int               m_area_length;
   color             m_area_color;
   color             m_area_border_color;
   //--- Свойства фона под ползунком
   int               m_bg_length;
   color             m_bg_border_color;
   //--- Картинки для кнопок
   string            m_inc_file_on;
   string            m_inc_file_off;
   string            m_dec_file_on;
   string            m_dec_file_off;
   //--- Цвета ползунка в разных состояниях
   color             m_thumb_color;
   color             m_thumb_color_hover;
   color             m_thumb_color_pressed;
   color             m_thumb_border_color;
   color             m_thumb_border_color_hover;
   color             m_thumb_border_color_pressed;
   //--- (1) Ширина ползунка, (2) длина ползунка и (3) его минимальная длина
   int               m_thumb_width;
   int               m_thumb_length;
   int               m_thumb_min_length;
   //--- (1) Размер шага ползунка и (2) кол-во шагов
   double            m_thumb_step_size;
   double            m_thumb_steps_total;
   //--- Приоритеты на нажатие левой кнопкой мыши
   int               m_area_zorder;
   int               m_bg_zorder;
   int               m_arrow_zorder;
   int               m_thumb_zorder;
   //---
public:
   //--- Ширина ползунка
   void              ScrollWidth(const int width)             { m_area_width=width;               }
   int               ScrollWidth(void)                  const { return(m_area_width);             }
   //--- (1) Цвет фона, (2) рамки фона и (3) внутренней рамки фона
   void              AreaColor(const color clr)               { m_area_color=clr;                 }
   void              AreaBorderColor(const color clr)         { m_area_border_color=clr;          }
   void              BgBorderColor(const color clr)           { m_bg_border_color=clr;            }
   //--- Установка картинок для кнопок
   void              IncFileOn(const string file_path)        { m_inc_file_on=file_path;          }
   void              IncFileOff(const string file_path)       { m_inc_file_off=file_path;         }
   void              DecFileOn(const string file_path)        { m_dec_file_on=file_path;          }
   void              DecFileOff(const string file_path)       { m_dec_file_off=file_path;         }
   //--- (1) Цвета фона ползунка и (2) рамки фона ползунка
   void              ThumbColor(const color clr)              { m_thumb_border_color=clr;         }
   void              ThumbColorHover(const color clr)         { m_thumb_border_color_hover=clr;   }
   void              ThumbColorPressed(const color clr)       { m_thumb_border_color_pressed=clr; }
   void              ThumbBorderColor(const color clr)        { m_thumb_border_color=clr;         }
   void              ThumbBorderColorHover(const color clr)   { m_thumb_border_color_hover=clr;   }
   void              ThumbBorderColorPressed(const color clr) { m_thumb_border_color_pressed=clr; }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CScroll::CScroll(void) : m_area_width(15),
                         m_area_length(0),
                         m_inc_file_on(""),
                         m_inc_file_off(""),
                         m_dec_file_on(""),
                         m_dec_file_off(""),
                         m_thumb_width(0),
                         m_thumb_length(0),
                         m_thumb_min_length(15),
                         m_area_color(C'210,210,210'),
                         m_area_border_color(C'240,240,240'),
                         m_bg_border_color(C'210,210,210'),
                         m_thumb_color(C'190,190,190'),
                         m_thumb_color_hover(C'180,180,180'),
                         m_thumb_color_pressed(C'160,160,160'),
                         m_thumb_border_color(C'170,170,170'),
                         m_thumb_border_color_hover(C'160,160,160'),
                         m_thumb_border_color_pressed(C'140,140,140')
  {
//--- Установим приоритеты на нажатие левой кнопки мыши
   m_area_zorder  =8;
   m_bg_zorder    =9;
   m_arrow_zorder =10;
   m_thumb_zorder =11;
  }

Рассмотрим методы создания элемента «Полоса прокрутки». Каждая его часть создаётся отдельным приватным методом. В главном методе в качестве последних двух параметров нужно передать размер списка и размер видимой части списка. Эти параметры затем будут участвовать в расчёте количества шагов для ползунка полосы прокрутки. 

class CScroll : public CElement
  {
protected:
   //--- Объекты для создания полосы прокрутки
   CRectLabel        m_area;
   CRectLabel        m_bg;
   CBmpLabel         m_inc;
   CBmpLabel         m_dec;
   CRectLabel        m_thumb;
   //---
public:
   //--- Методы для создания полосы прокрутки
   bool              CreateScroll(const long chart_id,const int subwin,const int x,const int y,const int items_total,const int visible_items_total);
   //---
private:
   bool              CreateArea(void);
   bool              CreateBg(void);
   bool              CreateInc(void);
   bool              CreateDec(void);
   bool              CreateThumb(void);
  };

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

Также от типа полосы прокрутки зависят расчёты и определения значений таких параметров, как: (1) координаты, (2) длина области перемещения ползунка и (3) размеры. Эта часть выделена синим маркером в листинге кода ниже. 

//+------------------------------------------------------------------+
//| Создаёт фон скролла                                              |
//+------------------------------------------------------------------+
bool CScroll::CreateBg(void)
  {
//--- Формирование имени объекта
   string name      ="";
   string name_part =(CElement::ClassName()=="CScrollV")? "_scrollv_bg_" : "_scrollh_bg_";
//--- Если индекс не задан
   if(CElement::Index()==WRONG_VALUE)
      name=CElement::ProgramName()+name_part+(string)CElement::Id();
//--- Если индекс задан
   else
      name=CElement::ProgramName()+name_part+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Координаты
   int x=0;
   int y=0;
//--- Размеры
   int x_size=0;
   int y_size=0;
//--- Установка свойств с учётом типа скролла
   if(CElement::ClassName()=="CScrollV")
     {
      m_bg_length =CElement::YSize()-(m_thumb_width*2)-2;
      x           =CElement::X()+1;
      y           =CElement::Y()+m_thumb_width+1;
      x_size      =m_thumb_width;
      y_size      =m_bg_length;
     }
   else
     {
      m_bg_length =CElement::XSize()-(m_thumb_width*2)-2;
      x           =CElement::X()+m_thumb_width+1;
      y           =CElement::Y()+1;
      x_size      =m_bg_length;
      y_size      =m_thumb_width;
     }
//--- Создание объекта
   if(!m_bg.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- Установка свойств
   m_bg.BackColor(m_area_color);
   m_bg.Color(m_bg_border_color);
   m_bg.BorderType(BORDER_FLAT);
   m_bg.Corner(m_corner);
   m_bg.Selectable(false);
   m_bg.Z_Order(m_bg_zorder);
   m_bg.Tooltip("\n");
//--- Сохраним координаты
   m_bg.X(x);
   m_bg.Y(y);
//--- Сохраним отступы
   m_bg.XGap(x-m_wnd.X());
   m_bg.YGap(y-m_wnd.Y());
//--- Сохраним размеры
   m_bg.XSize(x_size);
   m_bg.YSize(y_size);
//--- Сохраним указатель объекта
   CElement::AddToArray(m_bg);
   return(true);
  }

При создании графического объекта, который будет выступать в качестве ползунка полосы прокрутки, понадобится метод для расчёта его длины. Этот параметр (длина) зависит от количества пунктов в списке и от количества пунктов в видимой его части. Создадим такой метод и назовём его CScroll::CalculateThumbSize(). 

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

Подробнее с кодом метода CScroll::CalculateThumbSize() можно ознакомиться в листинге ниже:

class CScroll : public CElement
  {
protected:

public:
   //--- Расчёт длины ползунка полосы прокрутки
   bool              CalculateThumbSize(void);
  };
//+------------------------------------------------------------------+
//| Расчёт размера полосы прокрутки                                  |
//+------------------------------------------------------------------+
bool CScroll::CalculateThumbSize(void)
  {
//--- Расчёт не нужен, если длина области для перемещения ползунка меньше, чем минимальная длина ползунка
   if(m_bg_length<m_thumb_min_length)
      return(false);
//--- Рассчитаем размер шага ползунка
   m_thumb_step_size=(double)(m_bg_length-m_thumb_min_length)/m_thumb_steps_total;
//--- Размер шага не может быть меньше 1
   m_thumb_step_size=(m_thumb_step_size<1)? 1 : m_thumb_step_size;
//--- Рассчитаем размер рабочей области для перемещения ползунка
   double work_area=m_thumb_step_size*m_thumb_steps_total;
//--- Если размер рабочей области меньше размера всей области, получим размер ползунка, иначе установим минимальный размер
   double thumb_size=(work_area<m_bg_length)? m_bg_length-work_area+m_thumb_step_size : m_thumb_min_length;
//--- Проверка размера ползунка с учётом приведения типа
   m_thumb_length=((int)thumb_size<m_thumb_min_length)? m_thumb_min_length :(int)thumb_size;
   return(true);
  }

Вызывать метод CScroll::CalculateThumbSize() нужно в методе CScroll::CreateThumb() до создания объекта. В дальнейшем будет показан ещё один случай, когда длину полосы прокрутки нужно рассчитывать в процессе использования элемента управления. Рассматривать его будем в процессе разработки элемента управления, где это будет необходимо.

//+------------------------------------------------------------------+
//| Создаёт ползунок полосы прокрутки                                |
//+------------------------------------------------------------------+
bool CScroll::CreateThumb(void)
  {
//--- Формирование имени объекта  
   string name      ="";
   string name_part =(CElement::ClassName()=="CScrollV")? "_scrollv_thumb_" : "_scrollh_thumb_";
//--- Если индекс не задан
   if(CElement::Index()==WRONG_VALUE)
      name=CElement::ProgramName()+name_part+(string)CElement::Id();
//--- Если индекс задан
   else
      name=CElement::ProgramName()+name_part+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Координаты
   int x=0;
   int y=0;
//--- Размеры
   int x_size=0;
   int y_size=0;
//--- Рассчитаем размер полосы прокрутки
   if(!CalculateThumbSize())
      return(true);
//--- Установка свойства с учётом типа скролла
   if(CElement::ClassName()=="CScrollV")
     {
      x      =(m_thumb.X()>0) ? m_thumb.X() : m_x+1;
      y      =(m_thumb.Y()>0) ? m_thumb.Y() : m_y+m_thumb_width+1;
      x_size =m_thumb_width;
      y_size =m_thumb_length;
     }
   else
     {
      x      =(m_thumb.X()>0) ? m_thumb.X() : m_x+m_thumb_width+1;
      y      =(m_thumb.Y()>0) ? m_thumb.Y() : m_y+1;
      x_size =m_thumb_length;
      y_size =m_thumb_width;
     }
//--- Создание объекта
   if(!m_thumb.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- Установка свойств
   m_thumb.BackColor(m_thumb_color);
   m_thumb.Color(m_thumb_border_color);
   m_thumb.BorderType(BORDER_FLAT);
   m_thumb.Corner(m_corner);
   m_thumb.Selectable(false);
   m_thumb.Z_Order(m_thumb_zorder);
   m_thumb.Tooltip("\n");
//--- Сохраним координаты
   m_thumb.X(x);
   m_thumb.Y(y);
//--- Сохраним отступы
   m_thumb.XGap(x-m_wnd.X());
   m_thumb.YGap(y-m_wnd.Y());
//--- Сохраним размеры
   m_thumb.XSize(x_size);
   m_thumb.YSize(y_size);
//--- Сохраним указатель объекта
   CElement::AddToArray(m_thumb);
   return(true);
  }

Другие методы создания объектов полосы прокрутки предлагаются для самостоятельного изучения в приложенном к статье файле.

В главном (публичном) методе создания полосы прокрутки добавим проверку на тип класса. В базовом классе элемента (CScroll) не будет сохраняться имя класса. Поэтому при попытке создать полосу прокрутки, используя базовый класс, создание графического интерфейса будет прервано, и в журнал распечатается подсказка о том, что нужно использовать производные классы типа CScrollV или CScrollH. Обратите также внимание на то, как инициализируются некоторые параметры элемента. Такой подход позволяет максимально автоматизировать все расчёты, избавив пользователей библиотеки от рутины, предоставляя возможность указать минимальное количество свойств при создании элементов управления с полосой прокрутки. 

//+------------------------------------------------------------------+
//| Создаёт полосу прокрутки                                         |
//+------------------------------------------------------------------+
bool CScroll::CreateScroll(const long chart_id,const int subwin,const int x,const int y,const int items_total,const int visible_items_total)
  {
//--- Выйти, если нет указателя на форму
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Перед созданием скролла классу нужно передать "
              "указатель на форму: CScroll::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Выйти, если производится попытка использовать базовый класс полосы прокрутки
   if(CElement::ClassName()=="")
     {
      ::Print(__FUNCTION__," > Используйте производные классы полосы прокрутки (CScrollV или CScrollH).");
      return(false);
     }
//--- Инициализация переменных
   m_chart_id          =chart_id;
   m_subwin            =subwin;
   m_x                 =x;
   m_y                 =y;
   m_area_width        =(CElement::ClassName()=="CScrollV")? CElement::XSize() : CElement::YSize();
   m_area_length       =(CElement::ClassName()=="CScrollV")? CElement::YSize() : CElement::XSize();
   m_thumb_width       =m_area_width-2;
   m_thumb_steps_total =items_total-visible_items_total+1;
//--- Отступы от крайней точки
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Создание кнопки
   if(!CreateArea())
      return(false);
   if(!CreateBg())
      return(false);
   if(!CreateInc())
      return(false);
   if(!CreateDec())
      return(false);
   if(!CreateThumb())
      return(false);
//--- Скрыть элемент, если окно диалоговое или оно минимизировано
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

Для определения состояния левой кнопки мыши относительно ползунка полосы прокрутки создадим перечисление ENUM_THUMB_MOUSE_STATE в файле Enums.mqh.

//+------------------------------------------------------------------+
//| Перечисление состояний левой кнопки мыши для полосы прокрутки    |
//+------------------------------------------------------------------+
enum ENUM_THUMB_MOUSE_STATE
  {
   THUMB_NOT_PRESSED     =0,
   THUMB_PRESSED_OUTSIDE =1,
   THUMB_PRESSED_INSIDE  =2
  };

Кроме этого, нужно создать и соответствующие поля и методы. Метод CScroll::CheckMouseButtonState() нужен для определения того, над какой областью была зажата левая кнопка мыши. Метод CScroll::ZeroThumbVariables() используется для обнуления переменных, связанных с перемещением ползунка и вызывается в методе CScroll::CheckMouseButtonState() по условию, когда левая кнопка мыши отжата.

Обратите внимание также на то, что когда полоса прокрутки переводится в режим перемещения ползунка, то форма блокируется, и это при том, что делается это только тогда, когда элемент не является выпадающим. Нужно это для того, чтобы исключить режимы подсветки других элементов при наведении курсора в момент, когда ползунок полосы прокрутки находится в режиме перемещения, а курсор вышел за его границы. Когда режим перемещения ползунка отключен, то форму нужно разблокировать в методе CScroll::ZeroThumbVariables()

class CScroll : public CElement
  {
protected:
   //--- Переменные связанные с перемещением ползунка
   bool              m_scroll_state;
   int               m_thumb_size_fixing;
   int               m_thumb_point_fixing;
   //--- Для определения области зажатия левой кнопки мыши
   ENUM_THUMB_MOUSE_STATE m_clamping_area_mouse;
   //---
public:
   //--- Определяет область зажатия левой кнопки мыши
   void              CheckMouseButtonState(const bool mouse_state);
   //--- Обнуление переменных
   void              ZeroThumbVariables(void);
  };
//+------------------------------------------------------------------+
//| Определяет область зажатия левой кнопки мыши                     |
//+------------------------------------------------------------------+
void CScroll::CheckMouseButtonState(const bool mouse_state)
  {
//--- Определим состояние кнопки мыши:
//    Если отжата
   if(!mouse_state)
     {
      //--- Обнулим переменные
      ZeroThumbVariables();
      return;
     }
//--- Если кнопка нажата
   if(mouse_state)
     {
      //--- Выйдем, если кнопка уже нажата в какой-либо области
      if(m_clamping_area_mouse!=THUMB_NOT_PRESSED)
         return;
      //--- Вне области ползунка полосы прокрутки
      if(!m_thumb.MouseFocus())
         m_clamping_area_mouse=THUMB_PRESSED_OUTSIDE;
      //--- В области ползунка полосы прокрутки
      else
        {
         m_clamping_area_mouse=THUMB_PRESSED_INSIDE;
         //--- Если элемент не выпадающий
         if(!CElement::IsDropdown())
           {
            //--- Заблокируем форму и запомним идентификатор активного элемента
            m_wnd.IsLocked(true);
            m_wnd.IdActivatedElement(CElement::Id());
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Обнуление переменных связанных с перемещением ползунка           |
//+------------------------------------------------------------------+
void CScroll::ZeroThumbVariables(void)
  {
//--- Если элемент не выпадающий
   if(!CElement::IsDropdown())
     {
      //--- Разблокируем форму и сбросим идентификатор активного элемента
      m_wnd.IsLocked(false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
     }
   m_thumb_size_fixing   =0;
   m_clamping_area_mouse =THUMB_NOT_PRESSED;
  }

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

class CScroll : public CElement
  {
public:
   //--- Изменение цвета объектов полосы прокрутки
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Изменяет цвет объектов полосы прокрутки списка                   |
//+------------------------------------------------------------------+
void CScroll::ChangeObjectsColor(void)
  {
//--- Выйти, если форма заблокирована и идентификатор активного в текущий момент элемента отличается
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return;
//--- Цвет кнопок полосы прокрутки списка
   if(!m_scroll_state)
     {
      m_inc.State(m_inc.MouseFocus());
      m_dec.State(m_dec.MouseFocus());
     }
//--- Если курсор в зоне полосы прокрутки
   if(m_thumb.MouseFocus())
     {
      //--- Если левая кнопка мыши отжата
      if(m_clamping_area_mouse==THUMB_NOT_PRESSED)
        {
         m_scroll_state=false;
         m_thumb.BackColor(m_thumb_color_hover);
         m_thumb.Color(m_thumb_border_color_hover);
        }
      //--- Левая кнопка мыши нажата на ползунке
      else if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE)
        {
         m_scroll_state=true;
         m_thumb.BackColor(m_thumb_color_pressed);
         m_thumb.Color(m_thumb_border_color_pressed);
        }
     }
//--- Если курсор вне зоны полосы прокрутки
   else
     {
      //--- Левая кнопка мыши отжата
      if(m_clamping_area_mouse==THUMB_NOT_PRESSED)
        {
         m_scroll_state=false;
         m_thumb.BackColor(m_thumb_color);
         m_thumb.Color(m_thumb_border_color);
        }
     }
  }

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

class CScroll : public CElement
  {
protected:
   //--- Определение состояния перемещения
   bool              m_scroll_state;
   //--- Текущая позиция ползунка
   int               m_current_pos;
   //---
public:
   //--- Имена объектов кнопок
   string            ScrollIncName(void)                const { return(m_inc.Name());             }
   string            ScrollDecName(void)                const { return(m_dec.Name());             }
   //--- Состояние кнопок
   bool              ScrollIncState(void)               const { return(m_inc.State());            }
   bool              ScrollDecState(void)               const { return(m_dec.State());            }
   //--- Состояние полосы прокрутки
   void              ScrollState(const bool scroll_state)     { m_scroll_state=scroll_state;      }
   bool              ScrollState(void)                  const { return(m_scroll_state);           }
   //--- Текущая позиция ползунка
   void              CurrentPos(const int pos)                { m_current_pos=pos;                }
   int               CurrentPos(void)                   const { return(m_current_pos);            }
  };

Разработка базового класса полосы прокрутки завершена. Далее мы займёмся наполнением производных классов этого элемента управления. 

 


Производные классы элемента

Если базовый класс полосы прокрутки наполнен общими методами для создания объектов, настройки параметров и получения их значений, то производные классы предназначены для управления этим элементом. Ранее мы уже создали производные классы CScrollV и CScrollH в файле Scrolls.mqh. Рассмотрим методы только одного из них — вертикального типа (CScrollV). Во втором (CScrollH) будет всё то же самое, но с поправкой на то, что это горизонтальный тип, а именно: если в классе вертикальной полосы прокрутки ведётся работа с координатой Y, то в классе горизонтального типа этого элемента ведётся работа с координатой X.

Сначала рассмотрим код методов CScrollV::OnDragThumb() и CScrollV::UpdateThumb(), предназначенных для перемещения ползунка полосы прокрутки. Объявить их нужно в приватной секции класса, поскольку использоваться они будут только внутри класса CScrollV.

//+------------------------------------------------------------------+
//| Класс для управления вертикальной полосой прокрутки              |
//+------------------------------------------------------------------+
class CScrollV : public CScroll
  {
private:
   //--- Перемещение ползунка
   void              OnDragThumb(const int y);
   //--- Обновление положения ползунка
   void              UpdateThumb(const int new_y_point);
  };

Метод CScrollV::OnDragThumb() нужен для определения того, что производится попытка перемещения ползунка пользователем. Если левая кнопка мыши была зажата над ползунком полосы прокрутки и в таком состоянии состоялось смещение курсора по оси Y, то, значит, процесс перемещения начался, и нужно сместить объект. Обновление координат ползунка производится с помощью метода CScrollV::UpdateThumb().  

//+------------------------------------------------------------------+
//| Перемещение ползунка                                             |
//+------------------------------------------------------------------+
void CScrollV::OnDragThumb(const int y)
  {
//--- Для определения новой Y координаты
   int new_y_point=0;
//--- Если полоса прокрутки неактивна, ...
   if(!CScroll::ScrollState())
     {
      //--- ...обнулим вспомогательные переменные для перемещения ползунка
      CScroll::m_thumb_size_fixing  =0;
      CScroll::m_thumb_point_fixing =0;
      return;
     }
//--- Если точка фиксации нулевая, то запомним текущую координаты курсора
   if(CScroll::m_thumb_point_fixing==0)
      CScroll::m_thumb_point_fixing=y;
//--- Если значение расстояния от крайней точки ползунка до текущей координаты курсора нулевое, рассчитаем его
   if(CScroll::m_thumb_size_fixing==0)
      CScroll::m_thumb_size_fixing=m_thumb.Y()-y;
//--- Если в нажатом состоянии прошли порог вниз
   if(y-CScroll::m_thumb_point_fixing>0)
     {
      //--- Рассчитаем координату Y
      new_y_point=y+CScroll::m_thumb_size_fixing;
      //--- Обновим положение ползунка
      UpdateThumb(new_y_point);
      return;
     }
//--- Если в нажатом состоянии прошли порог вверх
   if(y-CScroll::m_thumb_point_fixing<0)
     {
      //--- Рассчитаем координату Y
      new_y_point=y-::fabs(CScroll::m_thumb_size_fixing);
      //--- Обновим положение ползунка
      UpdateThumb(new_y_point);
      return;
     }
  }

В листинге ниже можно подробнее изучить код метода CScrollV::UpdateThumb(). В него передаётся рассчитанная в методе CScrollV::OnDragThumb() координата Y. Здесь она проверяется на корректность. Если окажется, что мы выходим за пределы рабочей области, предназначенной для перемещения ползунка, то значения корректируются. Только после этого осуществляется обновление координат и их сохранение в полях базовых классов. 

//+------------------------------------------------------------------+
//| Обновление положения ползунка                                    |
//+------------------------------------------------------------------+
void CScrollV::UpdateThumb(const int new_y_point)
  {
   int y=new_y_point;
//--- Обнуление точки фиксации
   CScroll::m_thumb_point_fixing=0;
//--- Проверка на выход из рабочей области вниз и корректировка значений
   if(new_y_point>m_bg.Y2()-CScroll::m_thumb_length)
     {
      y=m_bg.Y2()-CScroll::m_thumb_length;
      CScroll::CurrentPos(int(CScroll::m_thumb_steps_total));
     }
//--- Проверка на выход из рабочей области вверх и корректировка значений
   if(new_y_point<=m_bg.Y())
     {
      y=m_bg.Y();
      CScroll::CurrentPos(0);
     }
//--- Обновим координаты и отступы
   m_thumb.Y(y);
   m_thumb.Y_Distance(y);
   m_thumb.YGap(m_thumb.Y()-(CElement::Y()-CElement::YGap()));
  }

Нам понадобится ещё один приватный метод — для корректировки текущей позиции ползунка относительно его координаты Y. Назовём его CScrollV::CalculateThumbPos(), с его кодом можно ознакомиться в листинге ниже. 

class CScrollV : public CScroll
  {
private:
   //--- Корректирует номер позиции ползунка
   void              CalculateThumbPos(void);
  };
//+------------------------------------------------------------------+
//| Корректирует номер позиции ползунка                              |
//+------------------------------------------------------------------+
void CScrollV::CalculateThumbPos(void)
  {
//--- Выйти, если шаг равен нулю
   if(CScroll::m_thumb_step_size==0)
      return;
//--- Корректирует номер позиции полосы прокрутки
   CScroll::CurrentPos(int((m_thumb.Y()-m_bg.Y())/CScroll::m_thumb_step_size));
//--- Проверка на выход из рабочей области вниз/вверх
   if(m_thumb.Y2()>=m_bg.Y2()-1)
      CScroll::CurrentPos(int(CScroll::m_thumb_steps_total-1));
   if(m_thumb.Y()<m_bg.Y())
      CScroll::CurrentPos(0);
  }

Теперь создадим публичный метод CScrollV::ScrollBarControl() для управления ползунком, в котором будут вызываться все приватные методы, представленные выше. Его нужно будет вызывать в обработчиках событий OnEvent() тех элементов, где будет использоваться полоса прокрутки. К подробному примеру, как это будет работать, вернёмся во второй главе, в которой будет рассматриваться класс для создания элемента управления «Список». 

Ниже представлен код метода CScrollV::ScrollBarControl(). В качестве аргументов ему передаются координаты курсора и состояние левой кнопки мыши. В самом начале проверяется фокус над ползунком полосы прокрутки. Затем определяется, над какой областью было произведено зажатие левой кнопки мыши. В зависимости от местоположения курсора и состояния левой кнопки, изменяется цвет ползунка. После этого, если управление передано полосе прокрутки, ползунок смещается по новой рассчитанной координате Y, и уже относительно нее рассчитывается номер позиции в списке. 

class CScrollV : public CScroll
  {
public:
   //--- Управление ползунком
   bool              ScrollBarControl(const int x,const int y,const bool mouse_state);
  };
//+------------------------------------------------------------------+
//| Управление ползунком                                             |
//+------------------------------------------------------------------+
bool CScrollV::ScrollBarControl(const int x,const int y,const bool mouse_state)
  {
//--- Проверка фокуса над ползунком
   m_thumb.MouseFocus(x>m_thumb.X() && x<m_thumb.X2() && 
                      y>m_thumb.Y() && y<m_thumb.Y2());
//--- Проверим и запомним состояние кнопки мыши
   CScroll::CheckMouseButtonState(mouse_state);
//--- Изменим цвет ползунка
   CScroll::ChangeObjectsColor();
//--- Если управление передано полосе прокрутки, определим положение ползунка
   if(CScroll::ScrollState())
     {
      //--- Перемещение ползунка
      OnDragThumb(y);
      //--- Изменяет номер позиции ползунка
      CalculateThumbPos();
      return(true);
     }
//---
   return(false);
  }

Если метод CScrollV::CalculateThumbPos() преобразует координату Y в номер позиции в списке, то, помимо прочих, нам понадобится метод, который делает обратное преобразование, то есть, для расчёта координаты Y относительно текущей позиции ползунка. Здесь также стоят проверки на выход из рабочей области и корректировка в случае выхода. В самом конце метода координаты и отступы объекта обновляются. 

class CScrollV : public CScroll
  {
public:
   //--- Расчёт координаты Y ползунка
   void              CalculateThumbY(void);
  };
//+------------------------------------------------------------------+
//| Расчёт координаты Y ползунка полосы прокрутки                    |
//+------------------------------------------------------------------+
void CScrollV::CalculateThumbY(void)
  {
//--- Определим текущую координату Y ползунка
   int scroll_thumb_y=int(m_bg.Y()+(CScroll::CurrentPos()*CScroll::m_thumb_step_size));
//--- Если выходим за пределы рабочей области вверх
   if(scroll_thumb_y<=m_bg.Y())
      scroll_thumb_y=m_bg.Y();
//--- Если выходим за пределы рабочей области вниз
   if(scroll_thumb_y+CScroll::m_thumb_length>=m_bg.Y2() || 
      CScroll::CurrentPos()>=CScroll::m_thumb_steps_total-1)
     {
      scroll_thumb_y=int(m_bg.Y2()-CScroll::m_thumb_length);
     }
//--- Обновим координату и отступ по оси Y
   m_thumb.Y(scroll_thumb_y);
   m_thumb.Y_Distance(scroll_thumb_y);
   m_thumb.YGap(m_thumb.Y()-m_wnd.Y());
  }

Вызываться метод CScrollV::CalculateThumbY() будет в методах обработки нажатия на кнопках полосы прокрутки (см. листинг кода ниже). В методы CScrollV::OnClickScrollInc() и CScrollV::OnClickScrollDec() в качестве единственного аргумента передаётся имя объекта. В самом начале проверяются следующие параметры.

Если все проверки пройдены, то далее производится смещение позиции на один шаг с учётом недопущения выхода из рабочего диапазона. После этого с помощью метода CScrollV::CalculateThumbY() рассчитывается и обновляется в объекте координата Y и отступы от крайней точки формы. Кнопка после нажатия должна оставаться в состоянии «включено».

class CScrollV : public CScroll
  {
public:
   //--- Обработка нажатия на кнопках полосы прокрутки
   bool              OnClickScrollInc(const string clicked_object);
   bool              OnClickScrollDec(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Обработка нажатия на кнопке вверх/влево                          |
//+------------------------------------------------------------------+
bool CScrollV::OnClickScrollInc(const string clicked_object)
  {
//--- Выйдем, если нажатие было не на этот объект или скролл сейчас активен или кол-во шагов не определено
   if(m_inc.Name()!=clicked_object || CScroll::ScrollState() || CScroll::m_thumb_steps_total<1)
      return(false);
//--- Уменьшим номер позиции полосы прокрутки
   if(CScroll::CurrentPos()>0)
      CScroll::m_current_pos--;
//--- Расчёт координаты Y полосы прокрутки
   CalculateThumbY();
//--- Установим состояние On
   m_inc.State(true);
   return(true);
  }
//+------------------------------------------------------------------+
//| Обработка нажатия на кнопке вниз/вправо                          |
//+------------------------------------------------------------------+
bool CScrollV::OnClickScrollDec(const string clicked_object)
  {
//--- Выйдем, если нажатие было не на этот объект или скролл сейчас активен или кол-во шагов не определено
   if(m_dec.Name()!=clicked_object || CScroll::ScrollState() || CScroll::m_thumb_steps_total<1)
      return(false);
//--- Увеличим номер позиции полосы прокрутки
   if(CScroll::CurrentPos()<CScroll::m_thumb_steps_total-1)
      CScroll::m_current_pos++;
//--- Расчёт координаты Y полосы прокрутки
   CalculateThumbY();
//--- Установим состояние On
   m_dec.State(true);
   return(true);
  }

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

 


Заключение

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

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

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