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

Графические интерфейсы VII: Элементы "Вкладки" (Глава 2)

MetaTrader 5Примеры | 14 июня 2016, 12:11
3 098 16
Anatoli Kazharski
Anatoli Kazharski

Содержание


Введение

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

В первой главе седьмой части были представлены три класса элементов управления для создания таблиц: таблица из текстовых меток (CLabelsTable), таблица из полей ввода (CTable) и нарисованная таблица (CCanvasTable). В этой статье (второй главе) рассмотрим такой элемент интерфейса, как «Вкладки». Будут представлены два класса этого элемента – простой и с расширенными возможностями.

 


Элемент "Вкладки"

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

Перечислим все составные части этого элемента.

  1. Фон или область для размещения группы элементов управления
  2. Вкладки

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

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

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

 


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

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

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

В файле Tabs.mqh нужно создать класс CTabs. В этом классе, так же, как и в других классах элементов управления, нужно создать стандартные методы, а также методы для сохранения указателя на форму, к которой этот элемент будет присоединён:

//+------------------------------------------------------------------+
//| Класс для создания вкладок                                       |
//+------------------------------------------------------------------+
class CTabs : public CElement
  {
private:
   //--- Указатель на форму, к которой элемент присоединён
   CWindow          *m_wnd;
   //---
public:
                     CTabs(void);
                    ~CTabs(void);
   //--- (1) Сохраняет указатель формы, (2) возвращает указатели на полосы прокрутки
   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                                                      |
//+------------------------------------------------------------------+
CTabs::CTabs(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTabs::~CTabs(void)
  {
  }

Свойства вкладок можно разделить на уникальные и общие. Перечислим оба списка.

Уникальные свойства

  • Массив указателей на элементы управления, закреплённых за вкладкой
  • Текст
  • Ширина

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

class CTabs : public CElement
  {
private:
   //--- Структура свойств и массивов элементов, закреплённых за каждой вкладкой
   struct TElements
     {
      CElement         *elements[];
      string            m_text;
      int               m_width;
     };
   TElements         m_tab[];
  };

Общие свойства

  • Режим позиционирования вкладок
  • Цвет фона области
  • Размер вкладок по оси Y (высота)
  • Цвета вкладок в разных состояниях
  • Цвет текста вкладок в разных состояниях
  • Цвет рамок вкладок
  • Приоритеты на нажатие левой кнопки мыши

Для установки режима позиционирования вкладок в файл Enums.mqh нужно добавить перечисление ENUM_TABS_POSITION

//+------------------------------------------------------------------+
//| Перечисление позиционирования вкладок                            |
//+------------------------------------------------------------------+
enum ENUM_TABS_POSITION
  {
   TABS_TOP    =0,
   TABS_BOTTOM =1,
   TABS_LEFT   =2,
   TABS_RIGHT  =3
  };

Объявления полей и методов для установки свойств представлены в листинге кода ниже: 

class CTabs : public CElement
  {
private:
   //--- Позиционирование вкладок
   ENUM_TABS_POSITION m_position_mode;
   //--- Цвет фона общей области
   int               m_area_color;
   //--- Размер вкладок оси Y
   int               m_tab_y_size;
   //--- Цвета вкладок в разных состояниях
   color             m_tab_color;
   color             m_tab_color_hover;
   color             m_tab_color_selected;
   color             m_tab_color_array[];
   //--- Цвет текста вкладок в разных состояниях
   color             m_tab_text_color;
   color             m_tab_text_color_selected;
   //--- Цвет рамок вкладок
   color             m_tab_border_color;
   //--- Приоритеты на нажатие левой кнопки мыши
   int               m_zorder;
   int               m_tab_zorder;
   //---
public:
   //--- (1) Устанавливает/получает расположение вкладок (сверху/снизу/слева/справа), (2) устанавливает размер вкладок по оси Y
   void              PositionMode(const ENUM_TABS_POSITION mode)     { m_position_mode=mode;          }
   ENUM_TABS_POSITION PositionMode(void)                       const { return(m_position_mode);       }
   void              TabYSize(const int y_size)                      { m_tab_y_size=y_size;           }
   //--- Цвет (1) общего фона, (2) цвета вкладок в разных состояниях, (3) цвет рамок вкладок
   void              AreaColor(const color clr)                      { m_area_color=clr;              }
   void              TabBackColor(const color clr)                   { m_tab_color=clr;               }
   void              TabBackColorHover(const color clr)              { m_tab_color_hover=clr;         }
   void              TabBackColorSelected(const color clr)           { m_tab_color_selected=clr;      }
   void              TabBorderColor(const color clr)                 { m_tab_border_color=clr;        }
   //--- Цвет текста вкладок в разных состояниях
   void              TabTextColor(const color clr)                   { m_tab_text_color=clr;          }
   void              TabTextColorSelected(const color clr)           { m_tab_text_color_selected=clr; }
  };

Перед созданием элемента нужно сначала добавить нужное количество вкладок с указанием отображаемого текста и ширины. Для этого напишем метод CTabs::AddTab(). По умолчанию аргументы метода имеют значения «» (пустая строка) и 50 (ширина)

class CTabs : public CElement
  {
public:
   //--- Добавляет вкладку
   void              AddTab(const string tab_text="",const int tab_width=50);
  };
//+------------------------------------------------------------------+
//| Добавляет вкладку                                                |
//+------------------------------------------------------------------+
void CTabs::AddTab(const string tab_text,const int tab_width)
  {
//--- Установить размер массивам вкладок
   int array_size=::ArraySize(m_tabs);
   ::ArrayResize(m_tabs,array_size+1);
   ::ArrayResize(m_tab,array_size+1);
//--- Сохранить переданные свойства
   m_tab[array_size].m_text  =tab_text;
   m_tab[array_size].m_width =tab_width;
//--- Сохраним количество вкладок
   m_tabs_total=array_size+1;
  }

Если нужно, чтобы после загрузки MQL-приложения на график была выделена сразу какая-то определённая вкладка, то перед созданием элемента нужно указать её индекс с помощью метода CTabs::SelectedTab(). Также понадобится приватный метод CTabs::CheckTabIndex() для проверки и корректировки индекса выделенной вкладки на случай выхода из диапазона. 

class CTabs : public CElement
  {
private:
   //--- Индекс выделенной вкладки
   int               m_selected_tab;
   //---
public:
   //--- (1) Сохраняет и (2) возвращает индекс выделенной вкладки
   void              SelectedTab(const int index)                    { m_selected_tab=index;          }
   int               SelectedTab(void)                         const { return(m_selected_tab);        }
   //---
private:
   //--- Проверка индекса выделенной вкладки
   void              CheckTabIndex(void);
  };
//+------------------------------------------------------------------+
//| Проверка индекса выделенной вкладки                              |
//+------------------------------------------------------------------+
void CTabs::CheckTabIndex(void)
  {
//--- Проверка на выход из диапазона
   int array_size=::ArraySize(m_tab);
   if(m_selected_tab<0)
      m_selected_tab=0;
   if(m_selected_tab>=array_size)
      m_selected_tab=array_size-1;
  }

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

class CTabs : public CElement
  {
private:
   //--- Объекты для создания элемента
   CRectLabel        m_main_area;
   CRectLabel        m_tabs_area;
   CEdit             m_tabs[];
   //---
public:
   //--- Методы для создания вкладок
   bool              CreateTabs(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateMainArea(void);
   bool              CreateTabsArea(void);
   bool              CreateButtons(void);
  };

Если перед установкой элемента не добавить ни одной вкладки, то при вызове публичного метода CTabs::CreateTabs() создание графического интерфейса будет остановлено, а в журнале появится сообщение с подсказкой: 

//--- Если нет ни одной вкладки в группе, сообщить об этом
   if(m_tabs_total<1)
     {
      ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, "
              "когда в группе есть хотя бы одна вкладка! Воспользуйтесь методом CTabs::AddTab()");
      return(false);
     }

В зависимости от того, какой режим позиционирования вкладок выбран, определение и расчёт координат составных объектов элемента тоже будет отличаться. Для этих расчётов понадобится метод CTabs::SumWidthTabs(), который возвращает общую ширину всех вкладок. В режиме позиционирования вкладок слева (TABS_LEFT) и справа (TABS_RIGHT) будет возвращаться ширина первой вкладки. Для режимов сверху (TABS_TOP) и снизу (TABS_BOTTOM) ширина всех вкладок суммируется. 

class CTabs : public CElement
  {
private:
   //--- Позиционирование вкладок
   ENUM_TABS_POSITION m_position_mode;
   //---
private:
   //--- Ширина всех вкладок
   int               SumWidthTabs(void);
  };
//+------------------------------------------------------------------+
//| Общая ширина всех вкладок                                        |
//+------------------------------------------------------------------+
int CTabs::SumWidthTabs(void)
  {
   int width=0;
//--- Если позиционирование вкладок справа или слева, вернуть ширину первой вкладки
   if(m_position_mode==TABS_LEFT || m_position_mode==TABS_RIGHT)
      return(m_tab[0].m_width);
//--- Суммируем ширину всех вкладок
   for(int i=0; i<m_tabs_total; i++)
      width=width+m_tab[i].m_width;
//--- С учётом наслоения на один пиксель
   width=width-(m_tabs_total-1);
   return(width);
  }

Метод CTabs::CreateMainArea() предназначен для создания области, в которой размещаются группы элементов управления. Расчёт координат и размеров объекта в нем выглядит так (сокращённая версия метода): 

//+------------------------------------------------------------------+
//| Создаёт фон общей области                                        |
//+------------------------------------------------------------------+
bool CTabs::CreateMainArea(void)
  {
//--- Формирование имени объекта
   string name=CElement::ProgramName()+"_tabs_main_area_"+(string)CElement::Id();
//--- Координаты
   int x=0;
   int y=0;
//--- Размеры
   int x_size=0;
   int y_size=0;
//--- Расчёт координат и размеров относительно позиционирования вкладок
   switch(m_position_mode)
     {
      case TABS_TOP :
         x      =CElement::X();
         y      =CElement::Y()+m_tab_y_size-1;
         x_size =CElement::XSize();
         y_size =CElement::YSize()-m_tab_y_size;
         break;
      case TABS_BOTTOM :
         x      =CElement::X();
         y      =CElement::Y();
         x_size =CElement::XSize();
         y_size =CElement::YSize()-m_tab_y_size;
         break;
      case TABS_RIGHT :
         x      =CElement::X();
         y      =CElement::Y();
         x_size =CElement::XSize()-SumWidthTabs()+1;
         y_size =CElement::YSize();
         break;
      case TABS_LEFT :
         x      =CElement::X()+SumWidthTabs()-1;
         y      =CElement::Y();
         x_size =CElement::XSize()-SumWidthTabs()+1;
         y_size =CElement::YSize();
         break;
     }
//--- Создание объекта
   if(!m_main_area.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- Установка свойств
//--- Отступы от крайней точки
//--- Сохраним размеры
//--- Сохраним координаты
//--- Сохраним указатель объекта
//...
   return(true);
  }

Ниже показан расчёт координат и размеров фона для вкладок в зависимости от установленного режима позиционирования в методе CTabs::CreateTabsArea(): 

//+------------------------------------------------------------------+
//| Создаёт фон для вкладок                                          |
//+------------------------------------------------------------------+
bool CTabs::CreateTabsArea(void)
  {
//--- Формирование имени объекта
   string name=CElement::ProgramName()+"_tabs_area_"+(string)CElement::Id();
//--- Координаты
   int x=CElement::X();
   int y=CElement::Y();
//--- Размеры
   int x_size=SumWidthTabs();
   int y_size=0;
//--- Расчёт размеров относительно позиционирования вкладок
   if(m_position_mode==TABS_TOP || m_position_mode==TABS_BOTTOM)
     {
      y_size=m_tab_y_size;
     }
   else
     {
      y_size=m_tab_y_size*m_tabs_total-(m_tabs_total-1);
     }
//--- Скорректировать координаты для позиционирования вкладок снизу и справа
   if(m_position_mode==TABS_BOTTOM)
     {
      y=CElement::Y2()-m_tab_y_size-1;
     }
   else if(m_position_mode==TABS_RIGHT)
     {
      x=CElement::X2()-x_size;
     }
//--- Создание объекта
   if(!m_tabs_area.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- Установка свойств
//--- Отступы от крайней точки
//--- Сохраним размеры
//--- Сохраним координаты
//--- Сохраним указатель объекта
//...
   return(true);
  }

В методе CTabs::CreateButtons() для создания вкладок нужен только расчёт координат. Ширина устанавливается в пользовательском классе приложения до создания элемента. В ином случае используется значение (ширина) по умолчанию. В листинге кода ниже показана сокращённая версия метода: 

//+------------------------------------------------------------------+
//| Создаёт вкладки                                                  |
//+------------------------------------------------------------------+
bool CTabs::CreateButtons(void)
  {
//--- Координаты
   int x =CElement::X();
   int y =CElement::Y();
//--- Расчёт координат относительно позиционирования вкладок
   if(m_position_mode==TABS_BOTTOM)
      y=CElement::Y2()-m_tab_y_size-1;
   else if(m_position_mode==TABS_RIGHT)
      x=CElement::X2()-SumWidthTabs();
//--- Проверка индекса выделенной вкладки
   CheckTabIndex();
//--- Создание вкладок
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- Формирование имени объекта
      string name=CElement::ProgramName()+"_tabs_edit_"+(string)i+"__"+(string)CElement::Id();
      //--- Расчёт координат относительно позиционирования вкладок для каждой вкладки отдельно
      if(m_position_mode==TABS_TOP || m_position_mode==TABS_BOTTOM)
         x=(i>0) ? x+m_tab[i-1].m_width-1 : CElement::X();
      else
         y=(i>0) ? y+m_tab_y_size-1 : CElement::Y();
      //--- Создание объекта
      if(!m_tabs[i].Create(m_chart_id,name,m_subwin,x,y,m_tab[i].m_width,m_tab_y_size))
         return(false);
      //--- Установка свойств
      //--- Отступы от крайней точки панели
      //--- Координаты
      //--- Размеры
      //--- Инициализация массива градиента
      //--- Сохраним указатель объекта
     }
//---
   return(true);
  }

Для закрепления какого-либо элемента управления за определённой вкладкой напишем метод CTabs::AddToElementsArray(). У него есть два аргумента: (1) индекс вкладки, к которой нужно прикрепить элемент управления и (2) ссылка на элемент управления, указатель которого затем нужно сохранить в массив элементов вкладки

class CTabs : public CElement
  {
public:
   //--- Добавляет элемент в массив вкладки
   void              AddToElementsArray(const int tab_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Добавляет элемент в массив указанной вкладки                     |
//+------------------------------------------------------------------+
void CTabs::AddToElementsArray(const int tab_index,CElement &object)
  {
//--- Проверка на выход из диапазона
   int array_size=::ArraySize(m_tab);
   if(array_size<1 || tab_index<0 || tab_index>=array_size)
      return;
//--- Добавим указатель переданного элемента в массив указанной вкладки
   int size=::ArraySize(m_tab[tab_index].elements);
   ::ArrayResize(m_tab[tab_index].elements,size+1);
   m_tab[tab_index].elements[size]=::GetPointer(object);
  }

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

class CTabs : public CElement
  {
public:
   //--- Показать элементы только выделенной вкладки
   void              ShowTabElements(void);
  };
//+------------------------------------------------------------------+
//| Показывает элементы только выделенной вкладки                    |
//+------------------------------------------------------------------+
void CTabs::ShowTabElements(void)
  {
//--- Выйти, если вкладки скрыты
   if(!CElement::IsVisible())
      return;
//--- Проверка индекса выделенной вкладки
   CheckTabIndex();
//---
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- Получим количество элементов, присоединённых к вкладке
      int tab_elements_total=::ArraySize(m_tab[i].elements);
      //--- Если выделена эта вкладка
      if(i==m_selected_tab)
        {
         //--- Показать элементы вкладки
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Show();
        }
      //--- Скрыть элементы неактивных вкладок
      else
        {
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Hide();
        }
     }
  }

Для обработки нажатия на вкладке будет использоваться метод CTabs::OnClickTab(). Сначала программе нужно пройти через две проверки: (1) по имени объекта, на котором было осуществлено нажатие и (2) по идентификатору элемента, который с помощью метода CTabs::IdFromObjectName() извлекается из имени объекта. Если проверки пройдены, то далее в цикле (1) находим вкладку, на которой было нажатие, (2) сохраняем её индекс и (3) устанавливаем соответствующие цвета. В самом конце с помощью метода CTabs::ShowTabElements() делаются видимыми элементы управления только активной вкладки. 

class CTabs : public CElement
  {
private:
   //--- Обработка нажатия на вкладке
   bool              OnClickTab(const string pressed_object);
   //--- Получение идентификатора из имени объекта
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Нажатие на вкладку в группе                                      |
//+------------------------------------------------------------------+
bool CTabs::OnClickTab(const string clicked_object)
  {
//--- Выйдем, если нажатие было не на ячейке таблицы
   if(::StringFind(clicked_object,CElement::ProgramName()+"_tabs_edit_",0)<0)
      return(false);
//--- Получим идентификатор из имени объекта
   int id=IdFromObjectName(clicked_object);
//--- Выйти, если идентификатор не совпадает
   if(id!=CElement::Id())
      return(false);
//---
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- Если выбрана эта вкладка
      if(m_tabs[i].Name()==clicked_object)
        {
         //--- Сохранить индекс выделенной вкладки
         SelectedTab(i);
         //--- Установить цвета
         m_tabs[i].Color(m_tab_text_color_selected);
         m_tabs[i].BackColor(m_tab_color_selected);
        }
      else
        {
         //--- Установить цвета для неактивных вкладок
         m_tabs[i].Color(m_tab_text_color);
         m_tabs[i].BackColor(m_tab_color);
        }
     }
//--- Показать элементы только выделенной вкладки
   ShowTabElements();
   return(true);
  }

Код главного обработчика событий элемента CTabs::OnEvent() в этом случае будет выглядеть так, как показано в листинге ниже: 

//+------------------------------------------------------------------+
//| Обработчик события графика                                       |
//+------------------------------------------------------------------+
void CTabs::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;
      for(int i=0; i<m_tabs_total; i++)
         m_tabs[i].MouseFocus(x>m_tabs[i].X() && x<m_tabs[i].X2() && y>m_tabs[i].Y() && y<m_tabs[i].Y2());
      //---
      return;
     }
//--- Обработка события нажатия левой кнопки мыши на объекте
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Нажатие на вкладке
      if(OnClickTab(sparam))
         return;
     }
  }

Мы рассмотрели все методы класса CTabs. Теперь протестируем, как это всё работает. 

 


Тест элемента "Вкладки"

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

//--- Внешние параметры эксперта
input ENUM_TABS_POSITION TabsPosition =TABS_TOP; // Tabs Position
input                int TabsHeight   =20;       // Tabs Height

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

class CProgram : public CWndEvents
  {
private:
   //--- Вкладки
   CTabs             m_tabs;
   //---
private:
   //--- Вкладки
#define TABS1_GAP_X           (4)
#define TABS1_GAP_Y           (45)
   bool              CreateTabs(void);
  };

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

//+------------------------------------------------------------------+
//| Создаёт область с вкладками                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs(void)
  {
#define TABS1_TOTAL 4
//--- Передать объект панели
   m_tabs.WindowPointer(m_window1);
//--- Координаты
   int x=m_window1.X()+TABS1_GAP_X;
   int y=m_window1.Y()+TABS1_GAP_Y;
//--- Массивы с текстом и шириной для вкладок
   string tabs_text[]={"Tab 1","Tab 2","Tab 3","Tab 4"};
   int tabs_width[]={90,90,90,90};
//--- Установим свойства перед созданием
   m_tabs.XSize(596);
   m_tabs.YSize(243);
   m_tabs.TabYSize(TabsHeight);
   m_tabs.PositionMode(TabsPosition);
   m_tabs.SelectedTab((m_tabs.SelectedTab()==WRONG_VALUE) ? 1 : m_tabs.SelectedTab());
   m_tabs.AreaColor(clrWhite);
   m_tabs.TabBackColor(C'225,225,225');
   m_tabs.TabBackColorHover(C'240,240,240');
   m_tabs.TabBackColorSelected(clrWhite);
   m_tabs.TabBorderColor(clrSilver);
   m_tabs.TabTextColor(clrGray);
   m_tabs.TabTextColorSelected(clrBlack);
//--- Добавим вкладки с указанными свойствами
   for(int i=0; i<TABS1_TOTAL; i++)
      m_tabs.AddTab(tabs_text[i],tabs_width[i]);
//--- Создадим элемент управления
   if(!m_tabs.CreateTabs(m_chart_id,m_subwin,x,y))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_tabs);
   return(true);
  }

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

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

Скомпилируем программу и загрузим её на график. Во внешних параметрах последовательно будем изменять режимы позиционирования вкладок (см. скриншоты ниже):

 Рис. 2. Режим позиционирования вкладок - «Сверху».

Рис. 2. Режим позиционирования вкладок — «Сверху».

 Рис. 3. Режим позиционирования вкладок - «Снизу».

Рис. 3. Режим позиционирования вкладок — «Снизу».

 Рис. 4. Режим позиционирования вкладок - «Слева».

Рис. 4. Режим позиционирования вкладок — «Слева».

 Рис. 5. Режим позиционирования вкладок - «Справа».

Рис. 5. Режим позиционирования вкладок — «Справа».


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

  1. К первой вкладке присоединим таблицу из текстовых меток. 
  2. Ко второй вкладке присоединим таблицу из полей ввода. 
  3. К третьей — нарисованную таблицу.
  4. К четвёртой вкладке — группу элементов управления, в которой будет:

    • четыре чекбокса;
    • четыре чекбокса с полями ввода;
    • четыре комбо-бокса с чекбоксами;
    • одна разделительная линия. 

В пользовательском классе (CProgram) тестового приложения объявляем (1) экземпляры классов этих элементов, (2) методы для их создания и (3) отступы от крайней точки формы  (см. листинг кода ниже): 

class CProgram : public CWndEvents
  {
private:
   //--- Таблица из текстовых меток
   CLabelsTable      m_labels_table;
   //--- Таблица из полей ввода
   CTable            m_table;
   //--- Нарисованная таблица
   CCanvasTable      m_canvas_table;
   //--- Чекбоксы
   CCheckBox         m_checkbox1;
   CCheckBox         m_checkbox2;
   CCheckBox         m_checkbox3;
   CCheckBox         m_checkbox4;
   //--- Чекбоксы с полем ввода
   CCheckBoxEdit     m_checkboxedit1;
   CCheckBoxEdit     m_checkboxedit2;
   CCheckBoxEdit     m_checkboxedit3;
   CCheckBoxEdit     m_checkboxedit4;
   //--- Комбо-боксы с чекбоксами
   CCheckComboBox    m_checkcombobox1;
   CCheckComboBox    m_checkcombobox2;
   CCheckComboBox    m_checkcombobox3;
   CCheckComboBox    m_checkcombobox4;
   //--- Разделительная линия
   CSeparateLine     m_sep_line;
   //---
private:
   //--- Таблица из текстовых меток
#define TABLE1_GAP_X          (5)
#define TABLE1_GAP_Y          (65)
   bool              CreateLabelsTable(void);
   //--- Таблица из полей ввода
#define TABLE2_GAP_X          (5)
#define TABLE2_GAP_Y          (65)
   bool              CreateTable(void);
   //--- Нарисованная таблица
#define TABLE3_GAP_X          (5)
#define TABLE3_GAP_Y          (65)
   bool              CreateCanvasTable(void);
   //--- Разделительная линия
#define SEP_LINE_GAP_X        (300)
#define SEP_LINE_GAP_Y        (70)
   bool              CreateSepLine(void);
   //--- Чекбоксы
#define CHECKBOX1_GAP_X       (18)
#define CHECKBOX1_GAP_Y       (75)
   bool              CreateCheckBox1(const string text);
#define CHECKBOX2_GAP_X       (18)
#define CHECKBOX2_GAP_Y       (175)
   bool              CreateCheckBox2(const string text);
#define CHECKBOX3_GAP_X       (315)
#define CHECKBOX3_GAP_Y       (75)
   bool              CreateCheckBox3(const string text);
#define CHECKBOX4_GAP_X       (315)
#define CHECKBOX4_GAP_Y       (175)
   bool              CreateCheckBox4(const string text);
   //--- Чекбоксы с полем ввода
#define CHECKBOXEDIT1_GAP_X   (40)
#define CHECKBOXEDIT1_GAP_Y   (105)
   bool              CreateCheckBoxEdit1(const string text);
#define CHECKBOXEDIT2_GAP_X   (40)
#define CHECKBOXEDIT2_GAP_Y   (135)
   bool              CreateCheckBoxEdit2(const string text);
#define CHECKBOXEDIT3_GAP_X   (337)
#define CHECKBOXEDIT3_GAP_Y   (105)
   bool              CreateCheckBoxEdit3(const string text);
#define CHECKBOXEDIT4_GAP_X   (337)
#define CHECKBOXEDIT4_GAP_Y   (135)
   bool              CreateCheckBoxEdit4(const string text);
   //--- Комбо-боксы с чекбоксами
#define CHECKCOMBOBOX1_GAP_X  (40)
#define CHECKCOMBOBOX1_GAP_Y  (205)
   bool              CreateCheckComboBox1(const string text);
#define CHECKCOMBOBOX2_GAP_X  (40)
#define CHECKCOMBOBOX2_GAP_Y  (235)
   bool              CreateCheckComboBox2(const string text);
#define CHECKCOMBOBOX3_GAP_X  (337)
#define CHECKCOMBOBOX3_GAP_Y  (205)
   bool              CreateCheckComboBox3(const string text);
#define CHECKCOMBOBOX4_GAP_X  (337)
#define CHECKCOMBOBOX4_GAP_Y  (235)
   bool              CreateCheckComboBox4(const string text);
  };

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

//+------------------------------------------------------------------+
//| Создаёт разделительную линию                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateSepLine(void)
  {
//--- Сохраним указатель на окно
   m_sep_line.WindowPointer(m_window1);
//--- Закрепить за 4-ой вкладкой первой группы вкладок
   m_tabs.AddToElementsArray(3,m_sep_line);
//--- Координаты  
   int x=m_window1.X()+SEP_LINE_GAP_X;
   int y=m_window1.Y()+SEP_LINE_GAP_Y;
//--- Размеры
   int x_size=2;
   int y_size=210;
//--- Установим свойства перед созданием
   m_sep_line.DarkColor(C'213,223,229');
   m_sep_line.LightColor(clrWhite);
   m_sep_line.TypeSepLine(V_SEP_LINE);
//--- Создание элемента
   if(!m_sep_line.CreateSeparateLine(m_chart_id,m_subwin,0,x,y,x_size,y_size))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_sep_line);
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Создаёт экспертную панель                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Создание формы 1 для элементов управления
//--- Создание элементов управления:
//--- Главное меню
//--- Контекстные меню
//--- Статусная строка
//--- Вкладки
//...
//--- Таблица из текстовых меток
   if(!CreateLabelsTable())
      return(false);
//--- Таблица из полей ввода
   if(!CreateTable())
      return(false);
//--- Создание нарисованной таблицы
   if(!CreateCanvasTable())
      return(false);
//--- Разделительная линия
   if(!CreateSepLine())
      return(false);
//--- Чекбоксы
   if(!CreateCheckBox1("Checkbox 1"))
      return(false);
   if(!CreateCheckBox2("Checkbox 2"))
      return(false);
   if(!CreateCheckBox3("Checkbox 3"))
      return(false);
   if(!CreateCheckBox4("Checkbox 4"))
      return(false);
//--- Чекбоксы с полем ввода
   if(!CreateCheckBoxEdit1("Checkbox Edit 1:"))
      return(false);
   if(!CreateCheckBoxEdit2("Checkbox Edit 2:"))
      return(false);
   if(!CreateCheckBoxEdit3("Checkbox Edit 3:"))
      return(false);
   if(!CreateCheckBoxEdit4("Checkbox Edit 4:"))
      return(false);
//--- Комбо-боксы с чекбоксами
   if(!CreateCheckComboBox1("CheckCombobox 1:"))
      return(false);
   if(!CreateCheckComboBox2("CheckCombobox 2:"))
      return(false);
   if(!CreateCheckComboBox3("CheckCombobox 3:"))
      return(false);
   if(!CreateCheckComboBox4("CheckCombobox 4:"))
      return(false);
//--- Показать элементы только активной вкладки
   m_tabs.ShowTabElements();
//--- Перерисовка графика
   m_chart.Redraw();
   return(true);
  }

В итоге должен получиться результат как на скриншотах ниже:

 Рис. 6. Элементы управления первой вкладки.

Рис. 6. Элементы управления первой вкладки.

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

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

 Рис. 8. Элементы управления третьей вкладки.

Рис. 8. Элементы управления третьей вкладки.

 Рис. 9. Элементы управления четвёртой вкладки.

Рис. 9. Элементы управления четвёртой вкладки.


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

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

//+------------------------------------------------------------------+
//| Класс для создания вкладок с картинками                          |
//+------------------------------------------------------------------+
class CIconTabs : public CElement
  {
private:
   //--- Отступы ярлыка
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- Отступы текстовой метки
   int               m_label_x_gap;
   int               m_label_y_gap;
   //---
public:
   //--- Отступы ярлыка
   void              IconXGap(const int x_gap)                       { m_icon_x_gap=x_gap;            }
   void              IconYGap(const int y_gap)                       { m_icon_y_gap=y_gap;            }
   //--- Отступы текстовой метки
   void              LabelXGap(const int x_gap)                      { m_label_x_gap=x_gap;           }
   void              LabelYGap(const int y_gap)                      { m_label_y_gap=y_gap;           }
  };

Пример того, как выглядит элемент «Вкладки с картинками», показан на скриншоте ниже:

 Рис. 10. Элемент «Вкладки с картинками».

Рис. 10. Элемент «Вкладки с картинками».

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

 

 


Заключение

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

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

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

В седьмой части серии о создании графических интерфейсов в торговых терминалах MetaTrader были рассмотрены такие элементы управления, как таблицы и вкладки. Для создания таблиц представлено три класса кода (CLabelsTable, CTable и CCanvasTable), а для создания вкладок – два класса (CTabs и CIconTabs). 

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

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

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (16)
Anatoli Kazharski
Anatoli Kazharski | 12 июл. 2016 в 13:03
Artyom Trishkin:

Супер. Жаль, что пока таблицы и списки - лишь таблицы и списки. Пока практичестки служат лишь визуальным оформлением данных без какого-либо интерактива.

...

По большей части из перечисленного всё это есть.

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

Тоже самое в таблицах (CTable). При выделении ряда таблицы генерируется событие, в параметрах которого содержится индекс ряда (параметр dparam), по которому можно потом получить значения в ячейках этого ряда с помощью метода CTable::GetValue():

//+------------------------------------------------------------------+
//| Возвращает значение по указанным индексам                        | 
//+------------------------------------------------------------------+
string CTable::GetValue(const int column_index,const int row_index)
  {
//--- Проверка на выход из диапазона столбцов
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return("");
//--- Проверка на выход из диапазона рядов
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return("");
//--- Вернуть значение
   return(m_vcolumns[column_index].m_vrows[row_index]);
  }

//---

А в режиме редактирования ячеек таблицы генерируется событие, в параметрах которого (параметр sparam) можно получить строку с номером столбца, с номером ряда и текущим значением в редактируемой ячейке, в формате "column_row_text".

Artyom Trishkin:

Хотелось бы, конечно брать, и  выделять нужные столбцы/строки списков/таблиц, и проводить с ними некие манипуляции - например: есть список, поставили чекбоксы, или вообще целиком выделили нужные строки, и вывели в отдельном окне общие данные по выбранным строкам... Вот нечто такое было бы супер :) 

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

Artyom Trishkin
Artyom Trishkin | 3 нояб. 2016 в 20:20

Толь, что-то никак не могу победить... Есть два окна. Из первого - из главного меню открываем второе окно, в котором есть 4-ре вкладки с иконками. К каждой вкладке привязана своя таблица.

Проблема вот в чём: при первом открытии окна всегда видна самая последняя построенная таблица, и не важно, что перед открытием окна вкладка переключается программно на нужную - она выделена как и полагается, но таблица на ней не та, которая к ней привязана, а самая последняя.

Сделал пример. Картинка первого открытия окна:

Как видно, выделена вкладка 1, но таблица видна та, которая привязана к Tab 4 - в самой таблице в заголовках столбцов это написано.

Далее, если переключать вкладки, то таблицы помещаются туда, куда и положено, выделил вторую вкладку мышкой:

Вернулся на первую:

Что не так делаю? Или это баг?

Файлы в прицепе. Индикатор.

Anatoli Kazharski
Anatoli Kazharski | 4 нояб. 2016 в 08:27
Artyom Trishkin:

...

Что не так делаю? Или это баг?

С вкладками типа CTabs работает правильно. А для вкладок CIconTabs пока не делал персональные массивы в движке библиотеки и автоматический показ содержимого выделенной вкладки при открытии диалогового окна. В следующем обновлении будет исправлено.

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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
     {
      if(lparam==m_window2.Id())
         m_icon_tabs.ShowTabElements();
     }
  }
Artyom Trishkin
Artyom Trishkin | 4 нояб. 2016 в 19:42
Anatoli Kazharski:

С вкладками типа CTabs работает правильно. А для вкладок CIconTabs пока не делал персональные массивы в движке библиотеки и автоматический показ содержимого выделенной вкладки при открытии диалогового окна. В следующем обновлении будет исправлено.

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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
     {
      if(lparam==m_window2.Id())
         m_icon_tabs.ShowTabElements();
     }
  }
Спасибо. Так работает. Теперь буду по две перекрывающих друг дружку таблицы в каждую вкладку пихать - глянем-с ;)
Francuz
Francuz | 18 апр. 2019 в 12:09
При компиляции выдаёт ошибки вида:
'return' - cannot convert from const pointer to nonconst pointer        SplitButton.mqh 90      65
Лечится удалением Const в соответствующих строках.
С чего начать при создании торгового робота для Московской биржи MOEX С чего начать при создании торгового робота для Московской биржи MOEX
Многие трейдеры на Московской бирже хотели бы автоматизировать свои торговые алгоритмы, но не знают с чего начать. Язык MQL5 предлагает не только огромный набор торговых функций, но и готовые классы, которые максимально облегчают первые шаги в алготрейдинге.
Доработка тестера стратегий для оптимизации индикаторов на примерах тренда и флета Доработка тестера стратегий для оптимизации индикаторов на примерах тренда и флета
При торговле по различным стратегиям зачастую требуется определить, трендовый сейчас рынок или флетовый. С этой целью разрабатывается множество индикаторов. Но как определить, справится ли индикатор с поставленной задачей? Как выяснить средний диапазон состояний флета и тренда для определения наших стопов и целей? В настоящей статье предлагается использовать для этого тестер стратегий, тем самым продемонстрировав, что он годится не только для оптимизации роботов под определенные нужды. В качестве тестового индикатора используем давно известный нам ADX.
Графические интерфейсы VIII: Элемент "Календарь" (Глава 1) Графические интерфейсы VIII: Элемент "Календарь" (Глава 1)
В восьмой части серии о создании графических интерфейсов в среде торговых терминалов MetaTrader мы рассмотрим сложные составные элементы управления: календари, древовидный список, файловый навигатор. Каждому из этих элементов будут посвящены отдельные статьи, поскольку материал довольно объемный. Итак, в первой главе этой части описывается элемент "Календарь" и его расширенная версия — "Выпадающий календарь".
Как копировать сигналы с помощью советника по своим правилам? Как копировать сигналы с помощью советника по своим правилам?
При подписке на сигналы может возникнуть такая ситуация: у Вашего торгового счёта кредитное плечо 1:100, провайдер имеет кредитное плечо 1:500 и торгует минимальным лотом, а Ваши торговые балансы практически равны — при этом коэффициент копирования будет от 10% до 15%. Эта статья расскажет, как в таком случае увеличить коэффициент копирования.