English 中文 Español Deutsch 日本語 Português
Графические интерфейсы V: Элемент "Список" (Глава 2)

Графические интерфейсы V: Элемент "Список" (Глава 2)

MetaTrader 5Примеры | 6 мая 2016, 14:04
3 050 4
Anatoli Kazharski
Anatoli Kazharski

Содержание

 


Введение

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

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

 


Элемент «Список»

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

Собирать список будем из нескольких объектов-примитивов и подключаемого элемента. Перечислим их.

  1. Фон списка.
  2. Массив пунктов списка.
  3. Элемент «Вертикальная полоса прокрутки».


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

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

 

Далее рассмотрим разработку класса для создания элемента «Список».

 


Разработка класса для создания элемента

Для создания элемента и внедрения его в разрабатываемую библиотеку нужно создать файл с классом элемента (CListView) — в нашем случае это ListView.mqh — и подключить его к файлу WndContainer.mqh:

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

Класс CListView,  так же, как и другие классы элементов управления, которые были рассмотрены ранее в других статьях этой серии, имеет стандартный набор методов. Для использования в этом элементе полосы прокрутки файл Scrolls.mqh должен быть подключен к файлу ListView.mqh

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

Понадобятся методы для установки свойств объектов-примитивов (из которых будет собираться список) до их создания.

  • Высота пунктов списка.
  • Цвет рамки фона списка.
  • Цвета фона пунктов в разных состояниях.
  • Цвета текста пунктов в разных состояниях.

Значения по умолчанию перечисленных выше свойств задаются в конструкторе класса. 

class CListView : public CElement
  {
private:
   //--- Свойства фона списка
   int               m_area_zorder;
   color             m_area_border_color;
   //--- Свойства пунктов списка
   int               m_item_zorder;
   int               m_item_y_size;
   color             m_item_color;
   color             m_item_color_hover;
   color             m_item_color_selected;
   color             m_item_text_color;
   color             m_item_text_color_hover;
   color             m_item_text_color_selected;
   //---
public:
   //--- Высота пункта
   void              ItemYSize(const int y_size)                         { m_item_y_size=y_size;             }
   //--- Цвет рамки фона
   void              AreaBorderColor(const color clr)                    { m_area_border_color=clr;          }
   //--- Цвета пунктов списка в разных состояниях
   void              ItemColor(const color clr)                          { m_item_color=clr;                 }
   void              ItemColorHover(const color clr)                     { m_item_color_hover=clr;           }
   void              ItemColorSelected(const color clr)                  { m_item_color_selected=clr;        }
   void              ItemTextColor(const color clr)                      { m_item_text_color=clr;            }
   void              ItemTextColorHover(const color clr)                 { m_item_text_color_hover=clr;      }
   void              ItemTextColorSelected(const color clr)              { m_item_text_color_selected=clr;   }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_item_y_size(18),
                             m_area_border_color(C'235,235,235'),
                             m_item_color(clrWhite),
                             m_item_color_hover(C'240,240,240'),
                             m_item_color_selected(C'51,153,255'),
                             m_item_text_color(clrBlack),
                             m_item_text_color_hover(clrBlack),
                             m_item_text_color_selected(clrWhite)
  {
//--- Установим приоритеты на нажатие левой кнопки мыши
   m_area_zorder =1;
   m_item_zorder =2;
  }

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

class CListView : public CElement
  {
private:
   //--- Объекты для создания списка
   CRectLabel        m_area;
   CEdit             m_items[];
   CScrollV          m_scrollv;
   //---
public:
   //--- Методы для создания списка
   bool              CreateListView(const long chart_id,const int window,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateList(void);
   bool              CreateScrollV(void);
  };

Размер списка и его видимой части по умолчанию равен двум элементам, так как не имеет смысла создавать список, состоящий только из одного пункта. Для установки размеров списка и его видимой части создадим методы CListView::ListSize() и CListView::VisibleListSize() с проверкой на количество пунктов не менее двух.  

class CListView : public CElement
  {
private:
   //--- Массив значений списка
   string            m_value_items[];
   //--- Размер списка и его видимой части
   int               m_items_total;
   int               m_visible_items_total;
   //---
public:
   //--- Возвращает (1) размер списка и (2) видимой его части
   int               ItemsTotal(void)                              const { return(m_items_total);            }
   int               VisibleItemsTotal(void)                       const { return(m_visible_items_total);    }
   //--- Установка (1) размера списка и (2) видимой его части
   void              ListSize(const int items_total);
   void              VisibleListSize(const int visible_items_total);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_items_total(2),
                             m_visible_items_total(2)
  {
//--- Установим размер списка и его видимой части
   ListSize(m_items_total);
   VisibleListSize(m_visible_items_total);
  }
//+------------------------------------------------------------------+
//| Устанавливает размер списка                                      |
//+------------------------------------------------------------------+
void CListView::ListSize(const int items_total)
  {
//--- Не имеет смысла делать список менее двух пунктов
   m_items_total=(items_total<2) ? 2 : items_total;
   ::ArrayResize(m_value_items,m_items_total);
  }
//+------------------------------------------------------------------+
//| Устанавливает размер видимой части списка                        |
//+------------------------------------------------------------------+
void CListView::VisibleListSize(const int visible_items_total)
  {
//--- Не имеет смысла делать список менее двух пунктов
   m_visible_items_total=(visible_items_total<2) ? 2 : visible_items_total;
   ::ArrayResize(m_items,m_visible_items_total);
  }

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

class CListView : public CElement
  {
private:
   //--- (1) Индекс и (2) текст выбранного пункта
   int               m_selected_item_index;
   string            m_selected_item_text;
   //---
public:
   //--- Возвращает/cохраняет (1) индекс и (2) текст выделенного пункта в списке
   void              SelectedItemIndex(const int index);
   int               SelectedItemIndex(void)                       const { return(m_selected_item_index);    }
   void              SelectedItemText(const string text)                 { m_selected_item_text=text;        }
   string            SelectedItemText(void)                        const { return(m_selected_item_text);     }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_selected_item_index(0),
                             m_selected_item_text("")
  {
//--- ...
  }
//+------------------------------------------------------------------+
//| Сохранение индекса                                               |
//+------------------------------------------------------------------+
void CListView::SelectedItemIndex(const int index)
  {
//--- Корректировка в случае выхода из диапазона
   m_selected_item_index=(index>=m_items_total)? m_items_total-1 : (index<0)? 0 : index;
  }

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

class CListView : public CElement
  {
public:
   //--- Подсветка выбранного пункта
   void              HighlightSelectedItem(void);
  };
//+------------------------------------------------------------------+
//| Выделяет выбранный пункт                                         |
//+------------------------------------------------------------------+
void CListView::HighlightSelectedItem(void)
  {
//--- Выйдем, если полоса прокрутки в активном режиме
   if(m_scrollv.ScrollState())
      return;
//--- Получим текущую позицию ползунка полосы прокрутки
   int v=m_scrollv.CurrentPos();
//--- Идём в цикле по видимой части списка
   for(int r=0; r<m_visible_items_total; r++)
     {
      //--- Если внутри диапазона списка
      if(v>=0 && v<m_items_total)
        {
         //--- Изменение цвета фона и цвета текста
         m_items[r].BackColor((m_selected_item_index==v) ? m_item_color_selected : m_item_color);
         m_items[r].Color((m_selected_item_index==v) ? m_item_text_color_selected : m_item_text_color);
         //--- Увеличим счётчик
         v++;
        }
     }
  }

В момент создания пунктов списка в методе CListView::CreateList(), координаты и ширина для них рассчитываются так, чтобы не заслонять рамку фона списка. Ширина пунктов рассчитывается с учётом того, будет ли в списке полоса прокрутки. Все пункты после первого в списке устанавливаются один на один с наслоением, равным одному пикселю. Это нужно, чтобы исключить зазоры в два пикселя, которые будут видны при выделении (подсветке) пунктов, когда на них наводится курсор мыши. Учитываем это при расчёте высоты фона списка и полосы прокрутки. После создания всех элементов в конце метода производится подсветка выделенного пункта и сохраняется его текст

//+------------------------------------------------------------------+
//| Создаёт пункты списка                                            |
//+------------------------------------------------------------------+
bool CListView::CreateList(void)
  {
//--- Координаты
   int x =CElement::X()+1;
   int y =0;
//--- Расчёт ширины пунктов списка
   int w=(m_items_total>m_visible_items_total) ? CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-2;
//---
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- Формирование имени объекта
      string name=CElement::ProgramName()+"_listview_edit_"+(string)i+"__"+(string)CElement::Id();
      //--- Расчёт координаты Y
      y=(i>0) ? y+m_item_y_size-1 : CElement::Y()+1;
      //--- Создание объекта
      if(!m_items[i].Create(m_chart_id,name,m_subwin,x,y,w,m_item_y_size))
         return(false);
      //--- Установка свойств
      m_items[i].Description(m_value_items[i]);
      m_items[i].TextAlign(m_align_mode);
      m_items[i].Font(FONT);
      m_items[i].FontSize(FONT_SIZE);
      m_items[i].Color(m_item_text_color);
      m_items[i].BackColor(m_item_color);
      m_items[i].BorderColor(m_item_color);
      m_items[i].Corner(m_corner);
      m_items[i].Anchor(m_anchor);
      m_items[i].Selectable(false);
      m_items[i].Z_Order(m_item_zorder);
      m_items[i].ReadOnly(true);
      m_items[i].Tooltip("\n");
      //--- Координаты
      m_items[i].X(x);
      m_items[i].Y(y);
      //--- Размеры
      m_items[i].XSize(w);
      m_items[i].YSize(m_item_y_size);
      //--- Отступы от крайней точки панели
      m_items[i].XGap(x-m_wnd.X());
      m_items[i].YGap(y-m_wnd.Y());
      //--- Сохраним указатель объекта
      CElement::AddToArray(m_items[i]);
     }
//--- Подсветка выделенного элемента
   HighlightSelectedItem();
//--- Сохраним текст выделенного пункта
   m_selected_item_text=m_value_items[m_selected_item_index];
   return(true);
  }

При создании полосы прокрутки в методе CListView::CreateScrollV() в самом начале проверяется соотношение количества пунктов всего списка и в его видимой части. Если окажется, что общее количество пунктов меньше либо равно количеству пунктов в видимой части, то идти дальше не имеет смысла, и программа выходит из метода. Далее (1) сохраняется указатель на форму, (2) рассчитываются координаты и (3) устанавливаются свойства. Идентификатор элемента для полосы прокрутки должен быть таким же, как и у элемента, составной частью которого он является. Режимы выпадающего элемента тоже должны совпадать.

//+------------------------------------------------------------------+
//| Создаёт вертикальный скролл                                      |
//+------------------------------------------------------------------+
bool CListView::CreateScrollV(void)
  {
//--- Если количество пунктов больше, чем размер списка, то
//    установим вертикальный скроллинг
   if(m_items_total<=m_visible_items_total)
      return(true);
//--- Сохранить указатель формы
   m_scrollv.WindowPointer(m_wnd);
//--- Координаты
   int x=CElement::X()+m_area.X_Size()-m_scrollv.ScrollWidth();
   int y=CElement::Y();
//--- Установим свойства
   m_scrollv.Id(CElement::Id());
   m_scrollv.XSize(m_scrollv.ScrollWidth());
   m_scrollv.YSize(CElement::YSize());
   m_scrollv.AreaBorderColor(m_area_border_color);
   m_scrollv.IsDropdown(CElement::IsDropdown());
//--- Создание полосы прокрутки
   if(!m_scrollv.CreateScroll(m_chart_id,m_subwin,x,y,m_items_total,m_visible_items_total))
      return(false);
//---
   return(true);
  }

Размер списка по оси Y рассчитывается в главном (публичном) методе создания списка CListView::CreateListView(). Как уже упоминалось ранее, при расчёте размера нужно учитывать, что пункты списка наслаиваются друг на друга в один пиксель. Также помним о том, что массив пунктов должен содержаться строго внутри фона списка, чтобы не заслонять его рамку. 

//+------------------------------------------------------------------+
//| Создаёт список                                                   |
//+------------------------------------------------------------------+
bool CListView::CreateListView(const long chart_id,const int window,const int x,const int y)
  {
//--- Выйти, если нет указателя на форму
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Перед созданием списка классу нужно передать "
              "указатель на форму: CListView::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Инициализация переменных
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =window;
   m_x        =x;
   m_y        =y;
   m_y_size   =m_item_y_size*m_visible_items_total-(m_visible_items_total-1)+2;
//--- Отступы от крайней точки
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Создание кнопки
   if(!CreateArea())
      return(false);
   if(!CreateList())
      return(false);
   if(!CreateScrollV())
      return(false);
//--- Скрыть элемент, если окно диалоговое или оно минимизировано
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

Дадим возможность пользователю библиотеки самому решать, нужна ли ему подсветка пунктов при наведении на них курсора мыши. По умолчанию будет установлен режим, когда подсветка отключена. В качестве дополнительного настраиваемого свойства создадим метод для установки выравнивания текста: (1) по левому краю, (2) по правому краю, (3) по центру. По умолчанию текст будет выравниваться по левому краю. 

class CListView : public CElement
  {
private:
   //--- Режим подсветки при наведении курсора
   bool              m_lights_hover;
   //--- Режим выравнивания текста в списке
   ENUM_ALIGN_MODE   m_align_mode;
   //---
public:
   //--- (1) Режим подсветки пунктов при наведении, (2) режим выравнивания текста
   void              LightsHover(const bool state)                       { m_lights_hover=state;             }
   void              TextAlign(const ENUM_ALIGN_MODE align_mode)         { m_align_mode=align_mode;          }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_lights_hover(false),
                             m_align_mode(ALIGN_LEFT)
  {
//--- ...
  }

Перед созданием списка нужно инициализировать массив данных, которые в нём должны содержаться. Для этого создадим метод CListView::ValueToList(), в котором будет производиться проверка на размер массива и корректировка индекса в случае выхода из диапазона.  

class CListView : public CElement
  {
public:
   //--- Установка значения в список по указанному индексу ряда
   void              ValueToList(const int item_index,const string value);
  };
//+------------------------------------------------------------------+
//| Сохраняет переданное значение в списке по указанному индексу     |
//+------------------------------------------------------------------+
void CListView::ValueToList(const int item_index,const string value)
  {
   int array_size=::ArraySize(m_value_items);
//--- Если нет ни одного пункта в контекстном меню, сообщить об этом
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, "
              "когда в списке есть хотя бы один пункт!");
     }
//--- Корректировка в случае выхода из диапазона
   int i=(item_index>=array_size)? array_size-1 : (item_index <0)? 0 : item_index;
//--- Сохраним значение в списке
   m_value_items[i]=value;
  }

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



Тест установки списка

Поскольку файл ListView.mqh уже подключен к библиотеке, то класс элемента «Список» (CListView) уже доступен для использования в пользовательском классе. Но перед тем, как протестировать установку списка, нужно внести некоторые дополнения в класс CWndContainer. Элемент «Список» — составной, поэтому нужно обеспечить добавление указателя полосы прокрутки в базу элементов. 

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

class CWndContainer
  {
private:
   //--- Сохраняет указатели на объекты списка в базу
   bool              AddListViewElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Сохраняет указатели на объекты списка в базу                     |
//+------------------------------------------------------------------+
bool CWndContainer::AddListViewElements(const int window_index,CElement &object)
  {
//--- Выйдем, если это не список
   if(object.ClassName()!="CListView")
      return(false);
//--- Получим указатель на список
   CListView *lv=::GetPointer(object);
//--- Увеличение массива элементов
   int size=::ArraySize(m_wnd[window_index].m_elements);
   ::ArrayResize(m_wnd[window_index].m_elements,size+1);
//--- Получим указатель полосы прокрутки
   CScrollV *sv=lv.GetScrollVPointer();
//--- Сохраним элемент в базу
   m_wnd[window_index].m_elements[size]=sv;
   return(true);
  }

Вызов метода CWndContainer::AddListViewElements() осуществляется в главном публичном методе для добавления элементов в базу: 

//+------------------------------------------------------------------+
//| Добавляет указатель в массив элементов                           |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Если в базе нет форм для элементов управления
//--- Если запрос на несуществующую форму
//--- Добавим в общий массив элементов
//--- Добавим объекты элемента в общий массив объектов
//--- Запомним во всех формах id последнего элемента
//--- Увеличим счётчик идентификаторов элементов

//--- Сохраняет указатели на объекты контекстного меню в базу
//--- Сохраняет указатели на объекты главного меню в базу
//--- Сохраняет указатели на объекты сдвоенной кнопки в базу
//--- Сохраняет указатели на объекты всплывающей подсказки в базу
//--- Сохраняет указатели на объекты списка в базу
   if(AddListViewElements(window_index,object))
      return;
  }

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

class CProgram : public CWndEvents
  {
private:
   //--- Список
   CListView         m_listview1;
   //--- 
private:
   //--- Список
#define LISTVIEW1_GAP_X       (2)
#define LISTVIEW1_GAP_Y       (43)
   bool              CreateListView1(void);
  };

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

//+------------------------------------------------------------------+
//| Создаёт список 1                                                 |
//+------------------------------------------------------------------+
bool CProgram::CreateListView1(void)
  {
//--- Размер списка
#define ITEMS_TOTAL1 20
//--- Сохраним указатель на окно
   m_listview1.WindowPointer(m_window1);
//--- Координаты
   int x=m_window1.X()+LISTVIEW1_GAP_X;
   int y=m_window1.Y()+LISTVIEW1_GAP_Y;
//--- Установим свойства перед созданием
   m_listview1.XSize(100);
   m_listview1.LightsHover(true);
   m_listview1.ListSize(ITEMS_TOTAL1);
   m_listview1.VisibleListSize(10);
   m_listview1.AreaBorderColor(clrDarkGray);
   m_listview1.SelectedItemIndex(5);
//--- Получим указатель полосы прокрутки
   CScrollV *sv=m_listview1.GetScrollVPointer();
//--- Свойства скролла
   sv.ThumbBorderColor(C'190,190,190');
   sv.ThumbBorderColorHover(C'180,180,180');
   sv.ThumbBorderColorPressed(C'160,160,160');
//--- Заполнение списка данными
   for(int r=0; r<ITEMS_TOTAL1; r++)
      m_listview1.ValueToList(r,"SYMBOL "+string(r));
//--- Создадим список
   if(!m_listview1.CreateListView(m_chart_id,m_subwin,x,y))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_listview1);
   return(true);
  }

Вызов метода создания списка нужно осуществлять в главном методе создания графического интерфейса: 

//+------------------------------------------------------------------+
//| Создаёт торговую панель                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Создание формы 1 для элементов управления
//--- Создание элементов управления:
//    Главное меню
//--- Контекстные меню
//--- Создание статусной строки
//--- Список
   if(!CreateListView1())
      return(false);
//--- Перерисовка графика
   m_chart.Redraw();
   return(true);
  }

Теперь уже можно скомпилировать код и загрузить программу на график. Если всё сделано правильно, то Вы увидите результат, как на скриншоте ниже:

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

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

 

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

 


Методы для управления элементом

Для начала создадим метод, который обеспечит изменение цвета пунктов при наведении на них курсора мыши. Следом нам понадобится метод, который позволит восстановить цвета по умолчанию.

class CListView : public CElement
  {
public:
   //--- (1) Сброс цвета пунктов списка, (2) изменение цвета пунктов списка при наведении
   void              ResetItemsColor(void);
   void              ChangeItemsColor(const int x,const int y);
  };

В методе CListView::ResetItemsColor() осуществляется сброс цвета всех пунктов, кроме выделенного. В начале метода определяется позиция, в которой находится ползунок полосы прокрутки. Это значение получается в переменную, которая и далее в цикле будет использоваться в качестве счетчика для определения выделенного пункта.

//+------------------------------------------------------------------+
//| Сброс цвета пунктов списка                                       |
//+------------------------------------------------------------------+
void CListView::ResetItemsColor(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)
         continue;
      //--- Установка цвета (фон, текст)
      m_items[i].BackColor(m_item_color);
      m_items[i].Color(m_item_text_color);
     }
  }

В начале метода CListView::ChangeItemsColor() нужно пройти несколько проверок. Программа выйдет из метода в следующих случаях:

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

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

//+------------------------------------------------------------------+
//| Изменение цвета строки списка при наведении курсора              |
//+------------------------------------------------------------------+
void CListView::ChangeItemsColor(const int x,const int y)
  {
//--- Выйдем, если отключена подсветка при наведении курсора или прокрутка списка активна
   if(!m_lights_hover || m_scrollv.ScrollState())
      return;
//--- Выйдем, если элемент не выпадающий и форма заблокирована
   if(!CElement::IsDropdown() && m_wnd.IsLocked())
      return;
//--- Получим текущую позицию ползунка полосы прокрутки
   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)
         continue;
      //--- Если курсор над этим пунктом, то подсветим его
      if(x>m_items[i].X() && x<m_items[i].X2() && y>m_items[i].Y() && y<m_items[i].Y2())
        {
         m_items[i].BackColor(m_item_color_hover);
         m_items[i].Color(m_item_text_color_hover);
        }
      //--- Если курсор не над этим пунктом, то сделаем соответствующий этому состоянию цвет
      else
        {
         m_items[i].BackColor(m_item_color);
         m_items[i].Color(m_item_text_color);
        }
     }
  }

Теперь нужно задействовать метод CListView::ChangeItemsColor() в обработчике событий класса CListView:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CListView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Список скрыт
      if(!CElement::IsVisible())
         return;
      //--- Координаты
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Изменяет цвет строк списка при наведении
      ChangeItemsColor(x,y);
      return;
     }
  }

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

Рис. 3. Тест изменения цвета пунктов списка при наведении курсора мыши. 

Рис. 3. Тест изменения цвета пунктов списка при наведении курсора мыши.

Для смещения списка относительно ползунка полосы прокрутки напишем метод CListView::ShiftList(). Здесь метод тоже начинается с сохранения в переменную текущей позиции ползунка полосы прокрутки. Далее в цикле она, как и в предыдущем методе, будет служить как счётчик в цикле для определения выделенного пункта в списке, а также для смещения данных (см. листинг кода ниже). 

class CListView : public CElement
  {
public:
   //--- Смещение списка
   void              ShiftList(void);
  };
//+------------------------------------------------------------------+
//| Сдвигает список относительно полосы прокрутки                    |
//+------------------------------------------------------------------+
void CListView::ShiftList(void)
  {
//--- Получим текущую позицию ползунка полосы прокрутки
   int v=m_scrollv.CurrentPos();
//--- Идём в цикле по видимой части списка
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- Если внутри диапазона списка
      if(v>=0 && v<m_items_total)
        {
         //--- Смещение текста, цвета фона и цвета текста
         m_items[i].Description(m_value_items[v]);
         m_items[i].BackColor((m_selected_item_index==v) ? m_item_color_selected : m_item_color);
         m_items[i].Color((m_selected_item_index==v) ? m_item_text_color_selected : m_item_text_color);
         //--- Увеличим счётчик
         v++;
        }
     }
  }

Вызывать метод CListView::ShiftList() нужно в обработчике CListView::OnEvent() при условии, что метод полосы прокрутки CScrollV::ScrollBarControl() вернёт значение true. То есть, это будет означать, что управление ползунком полосы прокрутки приведено в действие.

class CListView : public CElement
  {
private:
   //--- Состояние левой кнопки мыши (зажата/отжата)
   bool              m_mouse_state;
  };
//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CListView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Список скрыт
      if(!CElement::IsVisible())
         return;
      //--- Координаты и состояние левой кнопки мыши
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Проверка фокуса над списком
      CElement::MouseFocus(x>CElement::X() && x<CElement::X2() && 
                           y>CElement::Y() && y<CElement::Y2());
      //--- Смещаем список, если управление ползунком полосы прокрутки в действии
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state))
         ShiftList();
      //--- Изменяет цвет строк списка при наведении
      ChangeItemsColor(x,y);
      return;
     }
  }

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

 Рис. 4. Управление списком с помощью ползунка полосы прокрутки.

Рис. 4. Управление списком с помощью ползунка полосы прокрутки.

 

Необходим метод для отслеживания нажатия на одном из пунктов списка. Создадим такой приватный метод и назовём его CListView::OnClickListItem(). Также понадобится приватный метод CListView::IdFromObjectName() для извлечения идентификатора элемента из имени объекта, который уже неоднократно рассматривался в классах других элементов разрабатываемой библиотеки.

Кроме этого, потребуется ещё уникальный идентификатор события нажатия на пункте списка (ON_CLICK_LIST_ITEM). Добавим его в файл Defines.mqh

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CLICK_LIST_ITEM        (16) // Выбор пункта в списке

В самом начале метода CListView::OnClickListItem() стоят проверки на имя и идентификатор объекта, на котором было осуществлено нажатие. Затем локальной переменной присваивается текущая позиция ползунка полосы прокрутки, с помощью которой далее, используя её как счётчик, в цикле определяется индекс и текст пункта. В конце метода отправляется сообщение с (1) идентификатором события ON_CLICK_LIST_ITEM, (2) идентификатором элемента и (3) текстом текущего выделенного пункта. 

class CListView : public CElement
  {
private:
   //--- Обработка нажатия на пункте списка
   bool              OnClickListItem(const string clicked_object);
   //--- Получение идентификатора из имени пункта списка
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Обработка нажатия на пункте списка                               |
//+------------------------------------------------------------------+
bool CListView::OnClickListItem(const string clicked_object)
  {
//--- Выйдем, если нажатие было не на пункте меню
   if(::StringFind(clicked_object,CElement::ProgramName()+"_listview_edit_",0)<0)
      return(false);
//--- Получим идентификатор и индекс из имени объекта
   int id=IdFromObjectName(clicked_object);
//--- Выйти, если идентификатор не совпадает
   if(id!=CElement::Id())
      return(false);
//--- Получим текущую позицию ползунка полосы прокрутки
   int v=m_scrollv.CurrentPos();
//--- Пройдёмся по видимой части списка
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- Если выбран этот пункт в списке
      if(m_items[i].Name()==clicked_object)
        {
         m_selected_item_index =v;
         m_selected_item_text  =m_value_items[v];
        }
      //--- Если внутри диапазона списка
      if(v>=0 && v<m_items_total)
         //--- Увеличим счётчик
         v++;
     }
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElement::Id(),0,m_selected_item_text);
   return(true);
  }

Теперь есть все необходимые методы для управления списком, и в обработчике событий списка CListView::OnEvent() осталось разместить код, как показано в листинге ниже. При нажатии на один из пунктов списка вызывается метод CListView::HighlightSelectedItem(). Здесь же отслеживается нажатие на кнопках полосы прокрутки, и, если было нажатие на одной из её кнопок, то осуществляется смещение списка относительно текущей позиции ползунка. 

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CListView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка нажатия на объектах
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Если было нажатие на элементе списка
      if(OnClickListItem(sparam))
        {
         //--- Подсветка строки
         HighlightSelectedItem();
         return;
        }
      //--- Если было нажатие на кнопках полосы прокрутки списка
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam))
        {
         //--- Сдвигает список относительно полосы прокрутки
         ShiftList();
         return;
        }
     }
  }


 

Ускоренная перемотка списка

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

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

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Задержка перед включением перемотки счётчика (миллисекунды)
#define SPIN_DELAY_MSC (-450)

Ниже представлен листинг с кодом метода CListView::FastSwitching(). Ещё нужно объявить поле m_timer_counter, которое будет использоваться как счётчик таймера. В самом начале метода CListView::FastSwitching() проверяется фокус на списке. Если фокуса нет, то программа выходит из метода. Затем, если кнопка мыши отжата, то счётчику присваивается значение задержки (в нашем случае это -450 ms), и на этом всё заканчивается. Если же кнопка мыши зажата, то значение счётчика увеличивается на установленный в библиотеке шаг таймера (в данном случае это 16 ms). Далее идёт условие, исходя из которого следует, что пока значение счётчика не станет равно либо больше нуля, программа дальше пройти не сможет. Как только условие исполняется, то далее проверяется состояние кнопок полосы прокрутки. В зависимости от того, какая кнопка зажата, вызывается метод для имитации нажатия на кнопке, а затем смещается список в соответствии с текущим положением ползунка полосы прокрутки. 

class CListView : public CElement
  {
private:
   //--- Счётчик таймера для перемотки списка
   int               m_timer_counter;
private:
   //--- Ускоренная перемотка списка
   void              FastSwitching(void);
  };
//+------------------------------------------------------------------+
//| Ускоренная перемотка полосы прокрутки                            |
//+------------------------------------------------------------------+
void CListView::FastSwitching(void)
  {
//--- Выйдем, если нет фокуса на списке
   if(!CElement::MouseFocus())
      return;
//--- Вернём счётчик к первоначальному значению, если кнопка мыши отжата
   if(!m_mouse_state)
      m_timer_counter=SPIN_DELAY_MSC;
//--- Если же кнопка мыши нажата
   else
     {
      //--- Увеличим счётчик на установленный интервал
      m_timer_counter+=TIMER_STEP_MSC;
      //--- Выйдем, если меньше нуля
      if(m_timer_counter<0)
         return;
      //--- Если прокрутка вверх
      if(m_scrollv.ScrollIncState())
         m_scrollv.OnClickScrollInc(m_scrollv.ScrollIncName());
      //--- Если прокрутка вниз
      else if(m_scrollv.ScrollDecState())
         m_scrollv.OnClickScrollDec(m_scrollv.ScrollDecName());
      //--- Смещает список
      ShiftList();
     }
  }

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

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

Все методы для управления списком готовы. Теперь всё это можно протестировать. Добавим ещё два списка к тому, что уже сделали ранее:

Рис. 5. Тест трёх списков в графическом интерфейсе. 

Рис. 5. Тест трёх списков в графическом интерфейсе.

В обработчике событий пользовательского класса (CProgram) будем принимать сообщения от списков: 

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Событие нажатия на пункте списка
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      if(lparam==m_listview1.Id())
         ::Print(__FUNCTION__," > Это сообщение от первого списка > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      else if(lparam==m_listview2.Id())
         ::Print(__FUNCTION__," > Это сообщение от второго списка > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      else if(lparam==m_listview3.Id())
         ::Print(__FUNCTION__," > Это сообщение от третьего списка > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
  }

При нажатии на пунктах списков в журнал будут выводиться сообщения, как показано ниже: 

2016.01.16 13:02:00.085 TestLibrary (GBPUSD,D1) CProgram::OnEvent > Это сообщение от первого списка > id: 1016; lparam: 7; dparam: 0.0; sparam: SYMBOL 11
2016.01.16 13:01:59.056 TestLibrary (GBPUSD,D1) CProgram::OnEvent > Это сообщение от третьего списка > id: 1016; lparam: 9; dparam: 0.0; sparam: SYMBOL 12
2016.01.16 13:01:58.479 TestLibrary (GBPUSD,D1) CProgram::OnEvent > Это сообщение от второго списка > id: 1016; lparam: 8; dparam: 0.0; sparam: SYMBOL 9
2016.01.16 13:01:57.868 TestLibrary (GBPUSD,D1) CProgram::OnEvent > Это сообщение от третьего списка > id: 1016; lparam: 9; dparam: 0.0; sparam: SYMBOL 19
2016.01.16 13:01:56.854 TestLibrary (GBPUSD,D1) CProgram::OnEvent > Это сообщение от второго списка > id: 1016; lparam: 8; dparam: 0.0; sparam: SYMBOL 4
2016.01.16 13:01:56.136 TestLibrary (GBPUSD,D1) CProgram::OnEvent > Это сообщение от первого списка > id: 1016; lparam: 7; dparam: 0.0; sparam: SYMBOL 9
2016.01.16 13:01:55.433 TestLibrary (GBPUSD,D1) CProgram::OnEvent > Это сообщение от первого списка > id: 1016; lparam: 7; dparam: 0.0; sparam: SYMBOL 14

 


Заключение

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

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Alexander Bereznyak
Alexander Bereznyak | 6 мая 2016 в 14:55
надо добавить две кнопки, это поля выше и ниже движка прокрутки
Реter Konow
Реter Konow | 6 мая 2016 в 15:14
Как всегда безупречно!
Anatoli Kazharski
Anatoli Kazharski | 6 мая 2016 в 15:59
Alexander Bereznyak:
надо добавить две кнопки, это поля выше и ниже движка прокрутки

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

Реter Konow:
Как всегда безупречно!

Спасибо. 

P.S. Но всё же до безупречности ещё далеко. ;) 

Alexander Bereznyak
Alexander Bereznyak | 6 мая 2016 в 21:31
Anatoli Kazharski:

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

да, именно эти кнопки
Создание бота для Telegram на языке MQL5 Создание бота для Telegram на языке MQL5
Эта статья — пошаговое руководство по созданию бота для Telegram на языке MQL5. Данный материал будет интересен тем, кто хочет связать торгового робота со своим мобильным устройством. В статье приведены примеры ботов, выполняющие рассылку торговых сигналов, поиск информации на сайте, присылающие информацию о состоянии торгового счета, котировки и скриншоты графиков на ваш смартфон.
Как написать для Маркета индикатор любых нестандартных графиков Как написать для Маркета индикатор любых нестандартных графиков
С помощью оффлайновых графиков, программирования на языке MQL4 и небольшого желания вы можете получить графики любого типа: "Крестики-Нолики", "Ренко", "Каги", "Range bars", эквиобъемные и т.п. В этой статье мы покажем, как это сделать без использования DLL, и поэтому такие индикаторы "два-в-одном" вы можете публиковать и приобретать в Маркете.
Графические интерфейсы V: Элемент "Комбинированный список" (Глава 3) Графические интерфейсы V: Элемент "Комбинированный список" (Глава 3)
В первых двух главах пятой части серии о графических интерфейсах были разработаны классы для создания полосы прокрутки и списка. В этой главе рассмотрим класс для создания такого элемента управления, как «Комбинированный список». Это тоже составной элемент, в числе частей которого есть элементы, рассмотренные в первых двух главах пятой части.
Универсальный торговый эксперт: работа с отложенными ордерами и поддержка хеджинга (часть 5) Универсальный торговый эксперт: работа с отложенными ордерами и поддержка хеджинга (часть 5)
Эта статья продолжает знакомить читателей с торговым движком CStrategy. По многочисленным просьбам пользователей в торговый движок были добавлены функции по работе с отложенными ордерами. Также последние версии MetaTrader 5 стали поддерживать счета с хеджингом. Теперь CStrategy поддерживает и их. В статье дается подробное описание алгоритмов по работе с отложенными ордерами и принципов работы CStrategy на хеджируемых типах счетов.