Графические интерфейсы VII: Элементы "Вкладки" (Глава 2)
Содержание
- Введение
- Элемент "Вкладки"
- Разработка класса для создания элемента "Вкладки"
- Тест элемента "Вкладки"
- Заключение
Введение
Более подробно о том, для чего предназначена эта библиотека, можно прочитать в самой первой статье: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). В конце статей каждой части представлен список глав со ссылками. Там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.
В первой главе седьмой части были представлены три класса элементов управления для создания таблиц: таблица из текстовых меток (CLabelsTable), таблица из полей ввода (CTable) и нарисованная таблица (CCanvasTable). В этой статье (второй главе) рассмотрим такой элемент интерфейса, как «Вкладки». Будут представлены два класса этого элемента – простой и с расширенными возможностями.
Элемент "Вкладки"
Вкладки предназначены для управления показом предопределённых наборов элементов графического интерфейса. В многофункциональных приложениях довольно часто в ограниченном пространстве, выделенном для графического интерфейса, нужно уместить большое количество элементов управления. С помощью вкладок их можно сгруппировать по категориям и включать показ нужной в текущий момент группы, что делает интерфейс намного удобнее и понятнее для конечного пользователя. Внешне вкладки выглядят как группа кнопок с надписью (названием группы элементов), при этом выделенной (активной) может быть только одна из них.
Перечислим все составные части этого элемента.
- Фон или область для размещения группы элементов управления
- Вкладки
Рис. 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. Режим позиционирования вкладок — «Сверху».
Рис. 3. Режим позиционирования вкладок — «Снизу».
Рис. 4. Режим позиционирования вкладок — «Слева».
Рис. 5. Режим позиционирования вкладок — «Справа».
Теперь протестируем, как это работает с группами элементов управления, прикреплённых к каждой вкладке. Для этого создадим отдельную копию того же самого эксперта и удалим из него внешние параметры. Здесь вкладки будут позиционироваться в верхней части (TABS_TOP) рабочей области.
- К первой вкладке присоединим таблицу из текстовых меток.
- Ко второй вкладке присоединим таблицу из полей ввода.
- К третьей — нарисованную таблицу.
- К четвёртой вкладке — группу элементов управления, в которой будет:
- четыре чекбокса;
- четыре чекбокса с полями ввода;
- четыре комбо-бокса с чекбоксами;
- одна разделительная линия.
В пользовательском классе (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. Элементы управления первой вкладки.
Рис. 7. Элементы управления второй вкладки.
Рис. 8. Элементы управления третьей вкладки.
Рис. 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. Элемент «Вкладки с картинками».
Код этого эксперта тоже можно загрузить к себе на компьютер в приложенных к статье файлах.
Заключение
На текущем этапе разработки библиотеки для создания графических интерфейсов её схема выглядит так:
Рис. 11. Структура библиотеки на текущей стадии разработки.
В седьмой части серии о создании графических интерфейсов в торговых терминалах MetaTrader были рассмотрены такие элементы управления, как таблицы и вкладки. Для создания таблиц представлено три класса кода (CLabelsTable, CTable и CCanvasTable), а для создания вкладок – два класса (CTabs и CIconTabs).
В следующей (восьмой) части серии мы разберем следующие элементы управления.
- Статический и выпадающий календарь.
- Древовидный список.
- Файловый навигатор.
Ниже вы можете загрузить к себе на компьютер весь материал седьмой части серии, чтобы у Вас сразу была возможность протестировать, как всё это работает. Если у вас возникают вопросы по использованию материала, предоставленного в этих файлах, то Вы можете обратиться к подробному описанию процесса разработки библиотеки в одной из статей в представленном списке ниже или задать вопрос в комментариях к статье.
Список статей (глав) седьмой части:
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Супер. Жаль, что пока таблицы и списки - лишь таблицы и списки. Пока практичестки служат лишь визуальным оформлением данных без какого-либо интерактива.
...
По большей части из перечисленного всё это есть.
Выбирая в списках (CListView) пункт генерируется событие, по которому можно определить, в каком именно элементе был осуществлён выбор и какое текущее значение выбрано.
Тоже самое в таблицах (CTable). При выделении ряда таблицы генерируется событие, в параметрах которого содержится индекс ряда (параметр dparam), по которому можно потом получить значения в ячейках этого ряда с помощью метода CTable::GetValue():
//---
А в режиме редактирования ячеек таблицы генерируется событие, в параметрах которого (параметр sparam) можно получить строку с номером столбца, с номером ряда и текущим значением в редактируемой ячейке, в формате "column_row_text".
Хотелось бы, конечно брать, и выделять нужные столбцы/строки списков/таблиц, и проводить с ними некие манипуляции - например: есть список, поставили чекбоксы, или вообще целиком выделили нужные строки, и вывели в отдельном окне общие данные по выбранным строкам... Вот нечто такое было бы супер :)
Для списков с чек-боксами и радио-кнопками сделаю потом отдельные классы.
Толь, что-то никак не могу победить... Есть два окна. Из первого - из главного меню открываем второе окно, в котором есть 4-ре вкладки с иконками. К каждой вкладке привязана своя таблица.
Проблема вот в чём: при первом открытии окна всегда видна самая последняя построенная таблица, и не важно, что перед открытием окна вкладка переключается программно на нужную - она выделена как и полагается, но таблица на ней не та, которая к ней привязана, а самая последняя.
Сделал пример. Картинка первого открытия окна:
Скриншоты торговой платформы MetaTrader
EURUSD, D1, 2016.11.03
MetaQuotes Software Corp., MetaTrader 5, Demo
Как видно, выделена вкладка 1, но таблица видна та, которая привязана к Tab 4 - в самой таблице в заголовках столбцов это написано.
Далее, если переключать вкладки, то таблицы помещаются туда, куда и положено, выделил вторую вкладку мышкой:
Скриншоты торговой платформы MetaTrader
EURUSD, D1, 2016.11.03
MetaQuotes Software Corp., MetaTrader 5, Demo
Вернулся на первую:
Скриншоты торговой платформы MetaTrader
EURUSD, D1, 2016.11.03
MetaQuotes Software Corp., MetaTrader 5, Demo
Что не так делаю? Или это баг?
Файлы в прицепе. Индикатор.
...
Что не так делаю? Или это баг?
С вкладками типа 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();
}
}
С вкладками типа 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();
}
}