Содержание
- Концепция
- Доработка классов библиотеки
- Класс TabHeader — заголовок вкладки объекта TabControl
- Класс TabControl — продолжаем разработку
- Тестирование
- Что дальше
Концепция
В прошлой статье, при разработке WinForms-объекта TabControl, мы столкнулись с ограничением на длину имени графического элемента, что не дало в полной мере работать над созданием объекта — имя каждого дочернего графического элемента, входящего в состав родительского, включало в себя отсылку к своему родительскому элементу в иерархии цепочки всех связанных графических элементов управления, и имя каждого последующего объекта в этой цепочке было длиннее имени предыдущего объекта. В итоге мы упирались в ограничение на длину имени графического ресурса в 63 символа. Сегодня мы сделаем иной алгоритм именования графических элементов, свободный от описанного недостатка: каждый новый объект одного и того же типа будет содержать в своём имени имя программы, наименование типа графического элемента, и количество уже имеющихся элементов такого типа, созданных в программе при построении элементов GUI.
Например, при создании элементов GUI для тестовой программы этой статьи у нас получился такой список графических элементов
(видна только первая часть из всех построенных элементов, но и этого достаточно для понимания принятой концепции):
Таким образом, теперь у нас не будет никаких ограничений на вложенность объектов при построении элементов управления — вместо отображения иерархии в имени графического элемента, мы будем просто использовать номер элемента с указанием имени программы и типа элемента управления.
Да, это не даёт по имени графического элемента понять его примерное расположение в иерархии цепочек связанных объектов, но зато снимает ограничение на длину имени, а чтобы можно было как-то понять, что это за объект, мы добавим к строковым свойствам графических элементов ещё одно свойство — описание графического элемента. И это закроет вопрос о понимании назначения графического элемента и то, как к нему можно будет обратиться в своей программе. Например, создав графический элемент кнопку-переключатель, мы вписываем в его описание что-то типа "кнопка для переключения направления торговли", и прямо по этому описанию мы сможем в программе обратиться к этому элементу управления, что гораздо лучше, чем обращаться по "расплывчатому" имени, типа "MyProgram_Elm00_Elm01_Elm00" как это было раньше...
Кроме создания нового алгоритма именования графических элементов, сегодня продолжим разработку элементауправления TabControl. Создадим для него объект TabHeader, описывающий заголовок вкладки. Этот объект должен будет уметь работать в группе с другими такими же объектами — заголовками других объектов-вкладок. При выборе этого элемента он должен уметь немного увеличиваться в размерах, и при этом должен учитывать расположение набора заголовков всех вкладок на элементе управления TabControl — сверху, снизу, слева или справа, и в зависимости от их расположения, рисовать рамку только в нужном месте объекта. Например, если объект-заголовок вкладки расположен сверху элемента управления TabControl, то рамка, очерчивающая заголовок вкладки, должна рисоваться только с трёх сторон — слева, сверху и справа. Нижняя же сторона заголовка вкладки будет соприкасаться с полем вкладки, на котором должны размещаться объекты этой вкладки. И место соприкосновения не должно иметь нарисованной границы — чтобы заголовок вкладки и поле вкладки были одним целым — без видимого разделения.
Сегодня сделаем описанную обработку очерчивания границ в зависимости от места расположения заголовков вкладок только для объектов-заголовков вкладок, а рисованием границ поля вкладки и расположением в нём других элементов управления займёмся уже в следующей статье.
Доработка классов библиотеки
Некоторые элементы управления для своей работы используют уже имеющиеся элементы управления, например, ListBox использует для рисования своей коллекции (Items) элементы управления Button, но с небольшой доработкой функционала. Для его реализации нужно создать новый объект-наследник от элемента Button и добавить требуемый функционал. И вот такой, и некоторые другие такие же объекты, желательно вынести в отдельную категорию вспомогательных объектов, которые будем размещать не в папках категорий элементов управления, а в их корневом каталоге.
В файле \MQL5\Include\DoEasy\Defines.mqh в перечислении типов графических элементов добавим новый тип объекта-контейнера TabControl и добавим два вспомогательных элемента управления ListBoxItem и TabHeader в новой категории:
//+------------------------------------------------------------------+ //| Список типов графических элементов | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Стандартный графический объект GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Расширенный стандартный графический объект GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Объект тени GRAPH_ELEMENT_TYPE_ELEMENT, // Элемент GRAPH_ELEMENT_TYPE_FORM, // Форма GRAPH_ELEMENT_TYPE_WINDOW, // Окно //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Подложка объекта-панели GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base //--- Ниже нужно вписывать типы объектов "контейнер" GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms базовый объект-контейнер GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Windows Forms TabControl //--- Ниже нужно вписывать типы объектов "стандартный элемент управления" GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms базовый стандартный элемент управления GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Базовый объект-список Windows Forms элементов GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // Windows Forms ListBox GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Windows Forms ButtonListBox //--- Вспомогательные элементы WinForms-объектов GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // Windows Forms ListBoxItem GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader }; //+------------------------------------------------------------------+
В перечислении строковых свойств графического элемента на канвасе добавим новое свойство — описание графического элемента,
и увеличим общее количество строковых свойств с 3 до 4:
//+------------------------------------------------------------------+ //| Строковые свойства графического элемента на канвасе | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_STRING { CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), // Имя объекта-графического элемента CANV_ELEMENT_PROP_NAME_RES, // Имя графического ресурса CANV_ELEMENT_PROP_TEXT, // Текст графического элемента CANV_ELEMENT_PROP_DESCRIPTION, // Описание графического элемента }; #define CANV_ELEMENT_PROP_STRING_TOTAL (4) // Общее количество строковых свойств //+------------------------------------------------------------------+
В списке возможных критериев сортировки графических элементов на канвасе в самом конце добавим сортировку по новому свойству:
//+------------------------------------------------------------------+ //| Возможные критерии сортировки графических элементов на канвасе | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Сортировка по целочисленным свойствам SORT_BY_CANV_ELEMENT_ID = 0, // Сортировать по идентификатору элемента SORT_BY_CANV_ELEMENT_TYPE, // Сортировать по типу графического элемента //---... //---... SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, // Сортировать по местоположению вкладок внутри элемента управления SORT_BY_CANV_ELEMENT_ALIGNMENT, // Сортировать по местоположению объекта внутри элемента управления //--- Сортировка по вещественным свойствам //--- Сортировка по строковым свойствам SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Сортировать по имени объекта-элемента SORT_BY_CANV_ELEMENT_NAME_RES, // Сортировать по имени графического ресурса SORT_BY_CANV_ELEMENT_TEXT, // Сортировать по тексту графического элемента SORT_BY_CANV_ELEMENT_DESCRIPTION, // Сортировать по описанию графического элемента }; //+------------------------------------------------------------------+
Теперь мы сможем выбирать, сортировать и фильтровать графические элементы по новому свойству.
В файле \MQL5\Include\DoEasy\Data.mqh добавим индексы новых сообщений, удалим индекс ненужного сообщения:
//--- WinForms-стандартные MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Базовый стандартный элемент управления WinForms MSG_GRAPH_ELEMENT_TYPE_WF_LABEL, // Элемент управления Label MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Элемент управления CheckBox MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Элемент управления RadioButton MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON, // Элемент управления Button MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Базовый объект-список Windows Forms элементов MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // Элемент управления ListBox MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // Объект коллекции элемента управления ListBox MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Элемент управления CheckedListBox MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Элемент управления ButtonListBox MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Заголовок вкладки MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE, // Элемент управления TabPage MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Элемент управления TabControl MSG_GRAPH_OBJ_BELONG_PROGRAM, // Графический объект принадлежит программе MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Графический объект не принадлежит программе
...
//--- Строковые свойства графических элементов MSG_CANV_ELEMENT_PROP_NAME_OBJ, // Имя объекта-графического элемента MSG_CANV_ELEMENT_PROP_NAME_RES, // Имя графического ресурса MSG_CANV_ELEMENT_PROP_TEXT, // Текст графического элемента MSG_CANV_ELEMENT_PROP_DESCRIPTION, // Описание графического элемента }; //+------------------------------------------------------------------+
и впишем тексты новых сообщений, соответствующие вновь добавленным индексам. Текст удалённого индекса, соответственно, тоже удалим:
//--- WinForms-стандартные {"Базовый стандартный элемент управления WinForms","Basic Standard WinForms Control"}, {"Элемент управления \"Label\"","Control element \"Label\""}, {"Элемент управления \"CheckBox\"","Control element \"CheckBox\""}, {"Элемент управления \"RadioButton\"","Control element \"RadioButton\""}, {"Элемент управления \"Button\"","Control element \"Button\""}, {"Базовый объект-список Windows Forms элементов","Basic Windows Forms List Object"}, {"Элемент управления \"ListBox\"","Control element \"ListBox\""}, {"Объект коллекции элемента управления ListBox","Collection object of the ListBox control"}, {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""}, {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
...
//--- Строковые свойства графических элементов {"Имя объекта-графического элемента","The name of the graphic element object"}, {"Имя графического ресурса","Image resource name"}, {"Текст графического элемента","Text of the graphic element"}, {"Описание графического элемента","Description of the graphic element"}, }; //+---------------------------------------------------------------------+
Чтобы нам можно было получить описание типа графического элемента в приемлемом виде для последующего использования в библиотеке, в файле сервисных функций \MQL5\Include\DoEasy\Services\DELib.mqh создадим функцию, которая из имени константы перечисления типов графических элементов создаст и вернёт описание типа графического элемента:
//+------------------------------------------------------------------+ //| Возвращает тип графического объекта как string | //+------------------------------------------------------------------+ string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type) { ushort array[]; int total=StringToShortArray(StringSubstr(::EnumToString(type),18),array); for(int i=0;i<total-1;i++) { if(array[i]==95) { i+=1; continue; } else array[i]+=0x20; } string txt=ShortArrayToString(array); StringReplace(txt,"_Wf_Base","WFBase"); StringReplace(txt,"_Wf_",""); StringReplace(txt,"_Obj",""); StringReplace(txt,"_",""); StringReplace(txt,"Groupbox","GroupBox"); return txt; } //+------------------------------------------------------------------+
Алгоритм здесь такой: в функцию передаём требуемый для получения описания тип графического элемента и далее, в строке
int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);
получаем количество символов подстроки, выделенной из наименования константы перечисления типа.
Разберём на примере константы GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX.
Преобразуем константу перечисления в текст "GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX":
EnumToString(type)
Из полученного текста "GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX" выделяем строку "_WF_CHECKED_LIST_BOX", начиная с символа 18:
StringSubstr(EnumToString(type),18)
и полученную строку "_WF_CHECKED_LIST_BOX" посимвольно копируем в ushort-массив, получая при этом количество скопированных символов:
int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);
В итоге у нас в массиве array[] содержатся коды каждого символа строки "_WF_CHECKED_LIST_BOX".
Теперь нам нужно после каждого знака "_" оставить прописную (заглавную) букву, а все остальные сделать строчными (маленькими).
Это делаем в цикле по массиву символов:
for(int i=0;i<total-1;i++) { if(array[i]==95) { i+=1; continue; } else array[i]+=0x20; }
Первый символ строки и, соответственно, и в массиве, у нас "_", и как только мы встречаем код этого символа (95) в массиве, нам нужно установить индекс цикла на следующий символ за ним.
В строке "_WF_CHECKED_LIST_BOX" это будут отмеченные цветом символы.
После установки индекса цикла на следующий код символа в массиве, сразу же идём на следующую итерацию, тем самым пропуская символ, который нужно оставить без изменений. И попадаем на оператор else, где прибавляем к коду символа в массиве значение 32, что сделает этот символ строчным.
Таким образом, в массиве после всего цикла будут содержаться коды символов строки "_Wf_Checked_List_Box", которые мы преобразуем в строку:
string txt=ShortArrayToString(array);
И далее мы просто заменяем в полученной строке указанные вхождения строк в нужные нам и возвращаем итоговую строку:
StringReplace(txt,"_Wf_Base","WFBase"); StringReplace(txt,"_Wf_",""); StringReplace(txt,"_Obj",""); StringReplace(txt,"_",""); StringReplace(txt,"Groupbox","GroupBox"); return txt;
Для получения имени файла из типа графического элемента будем использовать эту новую функцию.
В файле базового графического объекта \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh в методе, возвращающем описание типа графического элемента, добавим новый тип и удалим ненужный:
//+------------------------------------------------------------------+ //| Возвращает описание типа графического элемента | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Контейнеры type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : //--- Стандартные элементы управления type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : "Unknown" ); } //+------------------------------------------------------------------+
Нам, для получения имени объекта, необходима вышерассмотренная функция, которая возвратит нам наименование создаваемого графического элемента по его типу. Но этого недостаточно для создания полного имени объекта. К полученной из функции строке нам нужно добавить количество уже имеющихся графических элементов этого типа на графике символа и его подокне, на которых строится этот объект.
В файле класса объекта-графического элемента \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, в его защищённой секции, объявим два метода — возвращающий количество графических элементов по типу и создающий и возвращающий имя графического элемента по его типу:
//--- Создаёт (1) структуру объекта, (2) объект из структуры virtual bool ObjectToStruct(void); virtual void StructToObject(void); //--- Копирует массив цветов в указанный массив цветов фона void CopyArraysColors(color &array_dst[],const color &array_src[],const string source); //--- Возвращает количество графических элементов по типу int GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const; //--- Создаёт и возвращает имя графического элемента по его типу string CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type); private:
Реализацию методов рассмотрим чуть позже.
В приватной секции добавим в структуру объекта новые поля:
private: int m_shift_coord_x; // Смещение координаты X относительно базового объекта int m_shift_coord_y; // Смещение координаты Y относительно базового объекта struct SData { //--- Целочисленные свойства объекта int id; // Идентификатор элемента int type; // Тип графического элемента //---... //---... bool button_toggle; // Флаг "Переключатель" элемента управления, имеющего кнопку bool button_state; // Состояние элемента управления "Переключатель", имеющего кнопку bool button_group_flag; // Флаг группы кнопки bool multicolumn; // Горизонтальное отображение столбцов в элементе управления ListBox int column_width; // Ширина каждого столбца элемента управления ListBox bool tab_multiline; // Несколько рядов вкладок в элементе управления TabControl int tab_alignment; // Местоположение вкладок внутри элемента управления int alignment; // Местоположение объекта внутри элемента управления //--- Вещественные свойства объекта //--- Строковые свойства объекта uchar name_obj[64]; // Имя объекта-графического элемента uchar name_res[64]; // Имя графического ресурса uchar text[256]; // Текст графического элемента uchar descript[256]; // Описание графического элемента }; SData m_struct_obj; // Структура объекта uchar m_uchar_array[]; // uchar-массив структуры объекта
они нам потребуются для правильного сохранения объекта в файл и чтения из него. Это новые свойства графического элемента, которые мы добавили сегодня, либо ранее, но здесь забыли прописать.
Из объявления метода, создающего новый графический элемент, удалим формальный параметр, в котором в метод передавалось имя создаваемого объекта:
//--- Создаёт элемент bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const bool redraw=false);
Теперь имя объекта будет не передаваться в метод, а создаваться в нём, исходя из типа создаваемого объекта.
Абсолютно во всех ранее написанных нами классах объектов-графических элементов, в формальных параметрах всех их конструкторов, уже заменены переменные "name" на переменные "descript". Для примера, здесь, в этом файле, это:
protected: //--- Защищённый конструктор CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h); public: //--- (1) Устанавливает, (2) возвращает смещение координаты X относительно базового объекта void SetCoordXRelative(const int value) { this.m_shift_coord_x=value; } int CoordXRelative(void) const { return this.m_shift_coord_x; } //--- (1) Устанавливает, (2) возвращает смещение координаты Y относительно базового объекта void SetCoordYRelative(const int value) { this.m_shift_coord_y=value; } int CoordYRelative(void) const { return this.m_shift_coord_y; } //--- Обработчик событий virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Параметрический конструктор CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false);
Теперь мы не передаём имя создаваемого объекта в конструктор класса. Теперь библиотека сама будет создавать новое имя новому объекту, исходя из его типа. Поэтому мы, вместо передачи в конструктор имени объекта, будем передавать его описание, которое сможем назначить сами, чтобы затем по этому описанию обратиться к созданному объекту. Все подобные изменения уже внесены во все классы всех WinForms-объектов, и далее их рассматривать здесь не будем — с ними можно ознакомиться в прилагаемых к статье файлах.
Точно так же доработке подверглись и установка типа графического элемента. Ранее во всех конструкторах мы дважды прописывали тип элемента в каждом конструкторе каждого класса WinForms-объектов — сначала тип записывали в базовый объект графических элементов библиотеки (в его переменную m_type_element):
void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type; }
а затем, второй строкой, прописывали этот же тип в свойства объекта.
Упростим это, создав публичный метод для записи (и возврата) типа объекта сразу в оба значения — в переменную и в свойство:
//--- Устанавливает смещение (1) левого, (2) верхнего, (3) правого, (4) нижнего края активной зоны относительно элемента, //--- (5) все смещения краёв активной зоны относительно элемента, (6) непрозрачность void SetActiveAreaLeftShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); } void SetActiveAreaRightShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); } void SetActiveAreaTopShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); } void SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); } void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetOpacity(const uchar value,const bool redraw=false); //--- (1) Устанавливает, (2) возвращает тип графического элемента void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { CGBaseObj::SetTypeElement(type); this.SetProperty(CANV_ELEMENT_PROP_TYPE,type); } ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE); } //--- Устанавливает основной цвет фона
Теперь в каждом конструкторе каждого класса WinForms-объекта вместо двух строк установки одного свойства в разные родительские классы будем вписывать одну строку с вызовом этого метода для установки свойства — он запишет его в оба родительских класса.
Добавим два метода для возврата и установки описания графического элемента в его свойства:
//--- Группа графического объекта virtual int Group(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP); } virtual void SetGroup(const int value) { CGBaseObj::SetGroup(value); this.SetProperty(CANV_ELEMENT_PROP_GROUP,value); } //--- Описание графического элемента string Description(void) const { return this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION); } void SetDescription(const string descr) { this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descr); } //+------------------------------------------------------------------+ //| Методы получения растровых данных | //+------------------------------------------------------------------+
В обоих конструкторах класса впишем метод для установки типа графического элемента, вызовем метод для создания имени элемента по его типу, и впишем установку новых свойств графического элемента:
//+------------------------------------------------------------------+ //| Параметрический конструктор | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(colour,true); this.SetOpacity(opacity); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Имя графического ресурса this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Идентификатор графика //---... //---... this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,CANV_ELEMENT_CHEK_STATE_UNCHECKED); // Состояние элемента управления, имеющего флажок проверки this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,true); // Автоматическое изменение состояния флажка при его выборе //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,false); // Флаг "Переключатель" элемента управления, имеющего кнопку this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,false); // Состояние элемента управления "Переключатель", имеющего кнопку this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,false); // Флаг группы кнопки this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,false); // Горизонтальное отображение столбцов в элементе управления ListBox this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,0); // Ширина каждого столбца элемента управления ListBox this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,false); // Несколько рядов вкладок в элементе управления TabControl this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Местоположение вкладок внутри элемента управления this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Местоположение объекта внутри элемента управления this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Текст графического элемента this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Описание графического элемента } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+
В методе, создающем структуру объекта, впишем сохранение в полях структуры новых свойств:
//+------------------------------------------------------------------+ //| Создаёт структуру объекта | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Сохранение целочисленных свойств this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Идентификатор элемента this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Тип графического элемента //---... //---... this.m_struct_obj.button_toggle=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE); // Флаг "Переключатель" элемента управления, имеющего кнопку this.m_struct_obj.button_state=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); // Состояние элемента управления "Переключатель", имеющего кнопку this.m_struct_obj.button_group_flag=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP); // Флаг группы кнопки this.m_struct_obj.multicolumn=(bool)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN); // Горизонтальное отображение столбцов в элементе управления ListBox this.m_struct_obj.column_width=(int)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH); // Ширина каждого столбца элемента управления ListBox this.m_struct_obj.tab_multiline=(bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); // Несколько рядов вкладок в элементе управления TabControl this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); // Местоположение вкладок внутри элемента управления this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT); // Местоположение объекта внутри элемента управления //--- Сохранение вещественных свойств //--- Сохранение строковых свойств ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); // Имя объекта-графического элемента ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); // Имя графического ресурса ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); // Текст графического элемента ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Описание графического элемента //--- Сохранение структуры в uchar-массив ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
В методе, создающем объект из структуры, впишем из новых полей структуры значения в новые свойства объекта:
//+------------------------------------------------------------------+ //| Создаёт объект из структуры | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Сохранение целочисленных свойств this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Идентификатор элемента this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Тип графического элемента //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,this.m_struct_obj.button_toggle); // Флаг "Переключатель" элемента управления, имеющего кнопку this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,this.m_struct_obj.button_state); // Состояние элемента управления "Переключатель", имеющего кнопку this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,this.m_struct_obj.button_group_flag); // Флаг группы кнопки this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,this.m_struct_obj.multicolumn); // Горизонтальное отображение столбцов в элементе управления ListBox this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,this.m_struct_obj.column_width); // Ширина каждого столбца элемента управления ListBox this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,this.m_struct_obj.tab_multiline); // Несколько рядов вкладок в элементе управления TabControl this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment); // Местоположение вкладок внутри элемента управления this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment); // Местоположение объекта внутри элемента управления //--- Сохранение вещественных свойств //--- Сохранение строковых свойств this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); // Имя объекта-графического элемента this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); // Имя графического ресурса this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); // Текст графического элемента this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Описание графического элемента } //+------------------------------------------------------------------+
В методе, создающем графический объект-элемент, теперь мы не передаём в него имя создаваемого объекта, которое отправляли в качестве параметра в метод создания графического ресурса, привязанного к объекту чарта класса CCanvas, а передаём в него ранее уже установленное имя объекта:
//+------------------------------------------------------------------+ //| Создаёт графический объект-элемент | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Идентификатор графика const int wnd_num, // Подокно графика const int x, // Координата X const int y, // Координата Y const int w, // Ширина const int h, // Высота const bool redraw=false) // Флаг необходимости перерисовки { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,this.m_name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); return true; } CMessage::ToLog(DFUN_ERR_LINE,::GetLastError(),true); return false; } //+------------------------------------------------------------------+
Метод, возвращающий количество графических элементов по типу:
//+------------------------------------------------------------------+ //| Возвращает количество графических элементов по типу | //+------------------------------------------------------------------+ int CGCnvElement::GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const { //--- Объявляем переменную с количеством графических объектов и //--- получаем общее количество графических объектов на графике и подокне, где создаётся графический элемент int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow()); //--- Создаём имя графического объекта по его типу string name=TypeGraphElementAsString(type); //--- В цикле по всем объектам графика и подокна for(int i=0;i<total;i++) { //--- получаем имя очередного объекта string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow()); //--- если в имени объекта нет установленного префикса имён графических объектов библиотеки, идём далее - этот объект не наш if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE) continue; //--- Если в имени выбранного в цикле графического объекта есть подстрока с созданным именем объекта по его типу - //--- значит есть графический объект такого типа - увеличиваем счётчик объектов этого типа if(::StringFind(name_obj,name)>0) n++; } //--- Возвращаем количество найденных объектов по их типу return n; } //+------------------------------------------------------------------+
Каждая строка метода подробно прокомментирована и логика метода должна быть понятна. Вкратце: нам нужно узнать сколько графических объектов указанного типа есть на графике и подокне, где нужно создать ещё один элемент этого типа. Имя по типу мы уже создать можем — функцию уже написали для этого выше. Сразу создаём имя объекта по его типу, а затем в цикле по всем графическим объектам на графике и его подокне ищем объект, в имя которого входит подстрока с созданным именем графического объекта по его типу. Если такое имя найдено — значит объект этого типа уже есть и нужно увеличить счётчик. В итоге, по завершениб цикла, мы имеем количество найденных объектов с нужным нам типом — возвращаем его.
Метод, создающий и возвращающий имя графического элемента по его типу:
//+------------------------------------------------------------------+ //| Создаёт и возвращает имя графического элемента по его типу | //+------------------------------------------------------------------+ string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type) { return this.NamePrefix()+TypeGraphElementAsString(type)+(string)this.GetNumGraphElements(type); } //+------------------------------------------------------------------+
В метод передаётся тип объекта, для которого нужно создать имя.
Далее к префиксу графических объектов библиотеки ("Имя_программы"+"_") добавляем имя объекта по его типу и количество объектов такого типа.
Полученный результат возвращаем из метода.
Оба этих метода используют вызов метода для получения имени графического объекта по его типу. А значит — их можно оптимизировать, убрав один вызов дважды вызываемого метода. Это сделаем в следующей статье (помним о принципе "от простого к сложному").
Чтобы мы могли правильно указать для создаваемого объекта его тип, нам нужно понять каким образом этот тип "доходит" до класса-графического элемента CGCnvElement, который является одним из родительских классов для WinForms-объектов, и в котором создаются эти объекты. Нам нужно до этого класса "донести" тип создаваемого графического элемента. Сейчас во всех конструкторах наследуемых от него классов явно указывается тот тип, к классу которого принадлежит класс-наследник. Таким образом мы всегда будем создавать только тот тип, который указан в классе, расположенном в иерархии наследования слудеющим после класса CGCnvElement. А это класс объекта-формы.
Как же нам отправить тип создаваемого объекта, расположенного в иерархии наследования далеко от класса CForm? Ответ очевиден: у каждого такого класса должен быть ещё один конструктор, в котором не указывается однозначно тип создаваемого объекта (как это делается сейчас), а передаётся в родительский класс через переменную конструктора в его списке инициализации. И такой конструктор должен быть защищённым — чтобы он мог работать только в наследуемых классах, а доступ извне к нему был запрещён. И вот такие конструкторы уже созданы для каждого WinForms-объекта, и при наследовании друг от друга тип дочернего класса передаётся в класс родительского. И так происходит по всей цепочке иерархии объектов вплоть до объекта CGCnvElement, в котором будет создан нужный графический элемент с тем типом, который "дошёл" по всей цепочке наследования до своего родителя.
И не забываем, что во всех файлах всех классов WInForms-объектов формальные переменные "name" уже переименованы в "descript" в конструкторах и методах создания графических элементов. Сообщаю об этом ещё раз — чтобы более к этому вопросу не возвращаться и не описывать уже сделанные одинаковые изменения для каждого WinForms-объекта из всех имеющихся.
В файле объекта-формы \MQL5\Include\DoEasy\Objects\Graph\Form.mqh удалим объявление более не нужного метода, возвращающего имя зависимого объекта (все имена теперь создаются автоматически в классе CGCnvElement):
//--- Создаёт объект для тени void CreateShadowObj(const color colour,const uchar opacity); //--- Возвращает имя зависимого объекта string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Обновляет координаты привязанных объектов virtual bool MoveDependentObj(const int x,const int y,const bool redraw=false);
Также удалим и код реализации этого метода из листинга класса.
Перед всеми конструкторами класса объявим новый защищённый конструктор:
//--- Обработчик последнего события мышки virtual void OnMouseEventPostProcessing(void); protected: //--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна CForm(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Конструкторы
В нём, в отличие от других конструкторов, есть формальный параметр, через который указывается тип создаваемого объекта.
За пределами тела класса напишем его реализацию:
//+------------------------------------------------------------------+ //| Защищённый конструктор с указанием типа объекта, | //| идентификатора чарта и подокна | //+------------------------------------------------------------------+ CForm::CForm(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CGCnvElement(type,chart_id,subwindow,descript,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
Всё отличие от публичных конструкторов в том, что в списке инициализации этого конструктора в родительский класс, в его конструктор, передаётся тип создаваемого объекта, который в свою очередь передаётся в этот конструктор из наследуемого класса.
Таким образом, создав для каждого WinForms-объекта подобный защищённый конструктор, мы создаём цепочку, по которой от любого класса-наследника его тип передаётся до родительского класса CGCnvElement, в котором и создаётся объект с типом, полученным по всей цепочке иерархии наследуемых объектов.
Из метода, создающего новый графический объект, удалим строку с созданием имени зависимого объекта и при создании новых объектов передаём в них не имя, а параметр "descript" (описание), передаваемых в формальных параметрах метода:
//+------------------------------------------------------------------+ //| Создаёт новый графический объект | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; //--- В зависимости от типа создаваемого объекта switch(type) { //--- создаём объект-графический элемент case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; //--- создаём объект-форму case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(type,this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
Такие изменения внесены во все аналогичные методы других классов WinForms-объектов, и здесь далее мы их рассматривать не будем — всё есть в прикреплённых к статье файлах.
В методе, создающем новый присоединённый элемент, и добавляющем его в список присоединённых объектов, вместо строк создания имени графического объекта
//--- Создаём имя графического элемента string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns;
впишем создание текста описания объекта по умолчанию, и этот текст передаём в метод создания нового графического объекта:
//+------------------------------------------------------------------+ //| Создаёт новый присоединённый элемент | //| и добавляет его в список присоединённых объектов | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- Если тип создаваемого графического элемента меньше, чем "элемент" - сообщаем об этом и возвращаем false if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- Задаём номер элемента в списке int num=this.m_list_elements.Total(); //--- Создаём описание графического элемента по умолчанию string descript=TypeGraphElementAsString(element_type); //--- Получаем экранные координаты объекта относительно системы координат базового объекта int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Создаём новый графический элемент CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return NULL; //--- и добавляем его в список привязанных графических элементов //---... //---... //---... return obj; } //+------------------------------------------------------------------+
В методе, создающем объект тени, впишем вместо имени объекта его описание по умолчанию:
//+------------------------------------------------------------------+ //| Создаёт объект тени | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- Если флаг тени выключен, или объект тени уже существует - уходим if(!this.m_shadow || this.m_shadow_obj!=NULL) return; //---... //---... //---... //--- Создаём новый объект тени и записываем указатель на него в переменную this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.NameObj()+"Shadow",x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } this.m_list_tmp.Add(this.m_shadow_obj); //--- Устанавливаем свойства созданному объекту-тени //---... //---... //---... //--- Объект-форму перемещаем на передний план this.BringToTop(); } //+------------------------------------------------------------------+
Во всех методах, где мы автоматически создаём описание объекта по умолчанию, затем всегда можно поменять это описание из своей программы при помощи метода SetDescription().
В файле класса базового WinForms-объекта \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh объявим защищённый конструктор и удалим один из публичных — он не нужен здесь:
protected: //--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Конструкторы CWinFormBase(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { this.m_type=OBJECT_DE_TYPE_GWF_BASE; } //--- (1) Устанавливает, (2) возвращает цвет текста по умолчанию всех объектов на панели
Реализация защищённого конструктора практически полностью повторяет реализацию публичного параметрического:
//+------------------------------------------------------------------+ //| Защищённый конструктор с указанием типа объекта, | //| идентификатора чарта и подокна | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(type,chart_id,subwindow,descript,x,y,w,h) { //--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Инициализируем все переменные this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
Разница между ними лишь в том, что тут мы в списке инициализации в конструктор родительского класса передаём тип, указанный в формальных параметрах конструктора. Строка, записывающая тип объекта в свойства теперь выполнена вызовом нового метода. И точно такая же строка теперь вписана и в публичный конструктор:
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Инициализируем все переменные
вместо двух, ранее в нём записанных, и делающих то же самое:
//--- Установим тип графического элемента и тип объекта библиотеки как базовый WinForms-объект CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Инициализируем все переменные
Аналогичные доработки внесны во все классы всех WinForms-объектов, и далее уже рассматриваться не будут.
В методе, возвращающем описание строкового свойства элемента, впишем возврат описания нового свойства:
//+------------------------------------------------------------------+ //| Возвращает описание строкового свойства элемента | //+------------------------------------------------------------------+ string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_STRING property,bool only_prop=false) { return ( property==CANV_ELEMENT_PROP_NAME_OBJ ? CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_OBJ)+": \""+this.GetProperty(property)+"\"" : property==CANV_ELEMENT_PROP_NAME_RES ? CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_RES)+": \""+this.GetProperty(property)+"\"" : property==CANV_ELEMENT_PROP_TEXT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TEXT)+": \""+this.GetProperty(property)+"\"" : property==CANV_ELEMENT_PROP_DESCRIPTION ? CMessage::Text(MSG_CANV_ELEMENT_PROP_DESCRIPTION)+": \""+this.GetProperty(property)+"\"" : "" ); } //+------------------------------------------------------------------+
Все вышеозвученные доработки сделаны в классах WinForms-объектов в файлах:
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh,
В файле объекта-кнопки \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh помимо таких же общих для всех WinForms-объектов доработок, сделаем метод для установки состояния виртуальным, так как в наследуемых классах его потребуется переопределить:
bool Toggle(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE); } //--- (1) Устанавливает, (2) возвращает состояние элемента управления "Переключатель" virtual void SetState(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag); if(this.State()) { this.SetBackgroundColor(this.BackgroundStateOnColor(),false); this.SetForeColor(this.ForeStateOnColor(),false); this.UnpressOtherAll(); } else { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); } } bool State(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); }
В классе объекта ListBox у нас для вывода строк используются объекты-кнопки. Но для вывода текста на кнопках с условием, что текст всегда прижат к левому краю кнопки при его выравниванию по левому краю, нам нужно внести параметр, указывающий на сколько символов нужно сдвинуть текст вправо.
У объекта-кнопки нет такого свойства. Поэтому создадим вспомогательный объект ListBoxItem.
В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\ создадим новый файл ListBoxItem.mqh класса CListBoxItem. Класс должен быть унаследован от класса объекта-кнопки, и его файл должен быть подключен к файлу создаваемого класса:
//+------------------------------------------------------------------+ //| ListBoxItem.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" #property strict // Нужно для mql4 //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| Класс объекта Label элементов управления WForms | //+------------------------------------------------------------------+ class CListBoxItem : public CButton { }
В приватной секции класса объявим переменную для хранения количества символов, на которые необходимо сместить текст, и строковую переменную, в которой будет содержаться строка смещения. В защищённой секции класса объявим защищённый конструктор, а в публичной — методы для работы с переменными класса и параметрический конструктор:
//+------------------------------------------------------------------+ //| Класс объекта Label элементов управления WForms | //+------------------------------------------------------------------+ class CListBoxItem : public CButton { private: uchar m_text_shift; // Смещение текста элемента вправо от левого края в символах string m_shift_space; // Строка смещения protected: //--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Возвращает смещение текста элемента вправо от левого края в символах uchar TextShift(void) const { return this.m_text_shift; } //--- (1) Создаёт, (2) возвращает строку, состоящую из количества символов смещения void SetTextShiftSpace(const uchar value); string GetTextShiftSpace(void) const { return this.m_shift_space; } //--- Устанавливает текст элемента virtual void SetText(const string text); //--- Конструктор CListBoxItem(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Конструкторы класса:
//+------------------------------------------------------------------+ //| Защищённый конструктор с указанием типа объекта, | //| идентификатора чарта и подокна | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { //--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+
Оба конструктора практически идентичны, за исключением того, что в защищённом есть формальный параметр, в который передаётся тип объекта, и этот тип передаётся в конструктор родительского класса, тогда как в параметрическом конструкторе тип объекта задаётся именно как ListBoxItem. Сейчас у нас так устроены все WinForms-объекты библиотеки. В каждом конструкторе задаётся смещение текста вправо на один символ.
Метод, создающий строку, состоящую из количества символов смещения:
//+------------------------------------------------------------------+ //| Создаёт строку, состоящую из количества символов смещения | //+------------------------------------------------------------------+ void CListBoxItem::SetTextShiftSpace(const uchar value) { this.m_text_shift=value; this.m_shift_space=""; switch(this.TextAlign()) { case ANCHOR_LEFT : case ANCHOR_LEFT_LOWER : case ANCHOR_LEFT_UPPER : for(int i=0;i<(int)this.m_text_shift;i++) this.m_shift_space+=" "; break; default: break; } } //+------------------------------------------------------------------+
В метод передаётся количество символов, на которые необходимо сместить строку. Затем устанавливается начальное значение строки смещения. В зависимости от выравнивания текста, и если оно от левого края, то в цикле по переданному количеству символов смещения прибавляем к строке смещения один знак пробела на каждой итерации цикла. По окончании цикла строка будет содержать требуемое количество знаков пробела.
Метод, устанавливающий текст элемента:
//+------------------------------------------------------------------+ //| Устанавливает текст элемента | //+------------------------------------------------------------------+ void CListBoxItem::SetText(const string text) { this.SetProperty(CANV_ELEMENT_PROP_TEXT,this.GetTextShiftSpace()+text); } //+------------------------------------------------------------------+
Здесь в свойство объекта вписывается текст объекта, к которому слева прибавлено количество пробелов, установленное методом SetTextShiftSpace(), рассмотренного нами выше.
В классе объекта ListBox в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ListBox.mqh, в его приватной секции, добавим переменную, хранящую смещение текста объектов-коллекции этого элемента (класса объекта ListBoxItem, рассмотренного нами выше), в публичной секции объявим методы для работы с новой переменной, а в защищённой секции объявим защищённый конструктор:
//+------------------------------------------------------------------+ //| Класс объекта ListBox элементов управления WForms | //+------------------------------------------------------------------+ class CListBox : public CElementsListBox { private: uchar m_text_shift; // Смещение текста элементов ListBoxItem вправо от левого края в символах //--- Создаёт новый графический объект virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); public: //--- (1) Устанавливает, (2) возвращает смещение текста элемента вправо от левого края в символах void SetTextShift(const uchar value); uchar TextShift(void) const { return this.m_text_shift; } //--- Создаёт список из указанного количества строк (объектов Label) void CreateList(const int line_count,const int new_column_width=0,const bool autosize=true); protected: //--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна CListBox(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Конструктор CListBox(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Конструкторы сделаны и доработаны идентично остальным, уже рассмотренным объектм других классов, и тут повторяться не будем.
Доработаем метод, создающий список из указанного количества строк (ListBoxItem).
Теперь вместо объекта класса CButton, будем работать с новым классом CListBoxItem, соответственно, и создавать будем объект этого класса. После создания объекта сразу зададим смещение его текста и установим текст созданного элемента коллекции по умолчанию:
//+------------------------------------------------------------------+ //| Создаёт список из указанного количества строк (ListBoxItem) | //+------------------------------------------------------------------+ void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true) { //--- Создаём указатель на объект CListBoxItem CListBoxItem *obj=NULL; //--- Рассчитываем ширину создаваемого объекта в зависимости от указанной ширины столбца int width=(new_column_width>0 ? new_column_width : this.Width()-this.BorderSizeLeft()-this.BorderSizeRight()); //--- Создаём указанное количество объектов ListBoxItem CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,count,0,0,width,15,new_column_width,autosize); //--- В цикле по созданному количеству объектов for(int i=0;i<this.ElementsTotal();i++) { //--- Получаем созданный объект из списка по индексу цикла obj=this.GetElement(i); //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему if(obj==NULL) { ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)); continue; } //--- Устанавливаем выравнивание текста слева по центру obj.SetTextAlign(ANCHOR_LEFT); obj.SetTextShiftSpace(this.TextShift()); //--- Устанавливаем текст объекта obj.SetFontSize(8); obj.SetText(TypeGraphElementAsString(obj.TypeGraphElement())+string(i+1)); //--- Устанавливаем цвета фона, текста и рамки obj.SetBackgroundStateOnColor(clrDodgerBlue,true); obj.SetBackgroundStateOnColorMouseOver(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-5)); obj.SetBackgroundStateOnColorMouseDown(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-10)); obj.SetForeStateOnColor(this.BackgroundColor(),true); obj.SetForeStateOnColorMouseOver(obj.ChangeColorLightness(obj.ForeStateOnColor(),-5)); obj.SetForeStateOnColorMouseDown(obj.ChangeColorLightness(obj.ForeStateOnColor(),-10)); obj.SetBorderColor(obj.BackgroundColor(),true); obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown()); obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver()); //--- Ставим флаги кнопки-переключателя и групповой кнопки obj.SetToggleFlag(true); obj.SetGroupButtonFlag(true); } //--- Если в метод передан флаг автоматического изменения размеров базового объекта - //--- устанавливаем режим автоизменения размера как "увиличить и уменьшить" if(autosize) this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); } //+------------------------------------------------------------------+
Метод, устанавливающий смещение текста элемента вправо от левого края в символах:
//+------------------------------------------------------------------+ //| Устанавливает смещение текста элемента | //| вправо от левого края в символах | //+------------------------------------------------------------------+ void CListBox::SetTextShift(const uchar value) { this.m_text_shift=value; for(int i=0;i<this.ElementsTotal();i++) { CListBoxItem *obj=this.GetElement(i); if(obj==NULL || obj.TextShift()==value) continue; obj.SetTextShiftSpace(value); obj.SetText(obj.Text()); obj.Update(false); } } //+------------------------------------------------------------------+
Здесь: записываем количество символов смещения, переданное в метод, в переменную, затем в цикле по списку привязанных объектов получаем очередной объект и устанавливаем для него текст со смещением при помощи метода объекта SetText(), рассмотренного нами выше при обсуждении методов класса CListBoxItem.
В классе базового объекта-контейнера в файле MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh удалим лишний конструктор:
CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false,false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); } //--- Деструктор ~CContainer(); }; //+------------------------------------------------------------------+
В защищённой секции объявим защищённый конструктор:
protected: //--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна CContainer(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Конструктор CContainer(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h);
Реализация защищённого конструктора идентична всем, ранее добавленным защищённым конструкторам в других классах.
В методе, устанавливающим параметры присоединённому объекту, добавим обработку объекта ListBoxItem такую же, как и для объектов Button и TabHeader:
//--- Для WinForms-объектов "Label", "CheckBox", "RadioButton" case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- устанавливаем цвет текста объекта в зависимости от переданного в метод: //--- либо цвет текста контейнера, либо переданный в метод. //--- Цвет рамки устанавливаем равным цвету текста //--- Цвет фона устанавливаем прозрачным obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- Для WinForms-объекта "Button", "TabHeader", "ListBoxItem" case GRAPH_ELEMENT_TYPE_WF_BUTTON : case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод: //--- цвет фона устанавливаем в зависимости от переданного в метод: //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод. //--- Цвет рамки устанавливаем равным цвету текста obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- Для WinForms-объекта "ListBox", "CheckedListBox", "ButtonListBox" case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод: //--- цвет фона устанавливаем в зависимости от переданного в метод: //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод. //--- Цвет рамки устанавливаем равным цвету текста obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break;
Обработку теперь отсутствующего объекта TabPage удалим из метода:
break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод: //--- цвет фона устанавливаем в зависимости от переданного в метод: //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод. //--- Цвет рамки устанавливаем равным цвету текста obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR : colour,true); obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); obj.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); obj.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); obj.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); obj.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY); obj.SetBorderSizeAll(1); obj.SetBorderStyle(FRAME_STYLE_NONE); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL :
К файлу класса базового объекта-списка элементов управления \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh подключим файл класса CListBoxItem:
//+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\ListBoxItem.mqh" //+------------------------------------------------------------------+ //| Класс базового объекта списка элементов управления WForms | //+------------------------------------------------------------------+
Теперь класс коллекции списка элементов управления будет доступен в других объектах библиотеки.
Так же, как и в остальных объектах, объявим защищённый конструктор:
protected: //--- Создаёт указанное количество указанных WinForms-объектов void CreateElements(ENUM_GRAPH_ELEMENT_TYPE element_type, const int count, const int x, const int y, const int w, const int h, uint new_column_width=0, const bool autosize=true); //--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна CElementsListBox(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Конструктор
Реализация защищённого конструктора и доработка параметрического идентичны остальным WinForms-объектам.
Класс TabHeader — заголовок вкладки объекта TabControl
Заголовок вкладки элемента управления TabControl сделаем на основе объекта-кнопки. Заголовок вкладки так же, как и кнопка-переключатель, должен быть либо включенным (вкладка выбрана), либо выключенным (выбрана другая вкладка), а все заголовки должны работать в группе — так же, как и групповые кнопки — если одна вкладка выбрана, то остальные должны быть не выбранными. При этом нельзя сделать так, чтобы все вкладки были не выбраны — одна должна всегда оставаться выбранной.
Это всё могут делать кнопки. Но кнопки не могут изменять свои размеры в зависимости от состояния. А заголовки вкладок должны. Выбранная вкладка имеет заголовок чуть больше, чем не выбранная. При этом есть ещё одно: заголовки вкладок могут размещаться в элементе управления с четырёх сторон — сверху, снизу, слева и справа. Соответственно, и рамка должна быть не прорисована с той стороны, которая соприкасается с полем вкладки. Значит, нужно для кнопки сделать новый метод её перерисовки — где рамка будет рисоваться в соответствии с расположением заголовка на элементе управления, и новый метод обработки нажатия — где кнопка будет увеличиваться в размерах и смещаться на новые координаты так, чтобы она всегда оставалась прижатой к полю вкладки.
В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\ создадим новый файл TabHeader.mqh класса TabHeader. Класс должен быть унаследован от класса объекта-кнопки, а его файл должен быть подключен к файлу создаваемого класса:
//+------------------------------------------------------------------+ //| TabHeader.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" #property strict // Нужно для mql4 //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| Класс объекта TabHeader элемента управления WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { }
В приватной, защищённой и публичной секциях класса объявим переменные и методы для работы класса:
//+------------------------------------------------------------------+ //| Класс объекта TabHeader элемента управления WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { private: int m_width_off; // Ширина объекта в состоянии "не выбран" int m_height_off; // Высота объекта в состоянии "не выбран" int m_width_on; // Ширина объекта в состоянии "выбран" int m_height_on; // Высота объекта в состоянии "выбран" int m_col; // Номер колонки заголовка int m_row; // Номер строки заголовка //--- Устанавливает ширину, высоту и смещение элемента в зависимости от состояния void SetWH(void); //--- Подстраивает размер и расположение элемента в зависимости от состояния void WHProcessStateOn(void); void WHProcessStateOff(void); //--- Русует рамку элемента в зависимости от расположения void DrawFrame(void); protected: //--- Обработчик события Курсор в пределах активной области, отжата кнопка мышки (левая) virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); public: //---Устанавливает размеры элемента управления в состоянии (1) "не выбран", (2) "выбран" bool SetSizeOff(void) { return(CGCnvElement::SetWidth(this.m_width_off) && CGCnvElement::SetHeight(this.m_height_off) ? true : false); } bool SetSizeOn(void) { return(CGCnvElement::SetWidth(this.m_width_on) && CGCnvElement::SetHeight(this.m_height_on) ? true : false); } //--- Устанавливает размеры элемента управления void SetWidthOff(const int value) { this.m_width_off=value; } void SetHeightOff(const int value) { this.m_height_off=value; } void SetWidthOn(const int value) { this.m_width_on=value; } void SetHeightOn(const int value) { this.m_height_on=value; } //--- Возвращает размеры элемента управления int WidthOff(void) const { return this.m_width_off; } int HeightOff(void) const { return this.m_height_off;} int WidthOn(void) const { return this.m_width_on; } int HeightOn(void) const { return this.m_height_on; } //--- (1) Устанавливает, (2) возвращает номер строки заголовка вкладки void SetRow(const int value) { this.m_row=value; } int Row(void) const { return this.m_row; } //--- (1) Устанавливает, (2) возвращает номер колонки заголовка вкладки void SetColumn(const int value) { this.m_col=value; } int Column(void) const { return this.m_col; } //--- Устанавливает местоположение вкладки void SetTabLocation(const int index,const int row,const int col) { this.SetRow(row); this.SetColumn(col); } //--- (1) Устанавливает, (2) возвращает местоположение объекта на элементе управления void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) this.SetBorderSize(1,1,1,0); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) this.SetBorderSize(1,0,1,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) this.SetBorderSize(1,1,0,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) this.SetBorderSize(0,1,1,1); } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- Устанавливает состояние элемента управления virtual void SetState(const bool flag); //--- Очищает элемент с заполнением его цветом и непрозрачностью virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Очищает элемент заливкой градиентом virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Обработчик последнего события мышки virtual void OnMouseEventPostProcessing(void); protected: //--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Конструктор CTabHeader(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Из описаний переменных и методов должно быть понятно их назначение. Рассмотрим их подробнее.
Конструкторы класса — защищённый и параметрический. Реализация практически идентична, а логика точно такая же, как и у остальных объектов библиотеки: защищённый конструктор передаёт указанный тип в конструктор родительского класса, тогда как параметрический конструктор передаёт родительскому свой тип объекта:
//+------------------------------------------------------------------+ //| Защищённый конструктор с указанием типа объекта, | //| идентификатора чарта и подокна | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetState(false); } //+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetState(false); } //+------------------------------------------------------------------+
В теле конструктора устанавливаются значения по умолчанию для цветов объекта в разных состояниях. Сразу же устанавливаются флаги кнопи-переключателя и кнопки, работающей в группе. Для нажатого состояния размер объекта устанавливается увеличенным в каждую сторону, кроме прилегающей к полю вкладки, на два пикселя.
Метод, устанавливающий состояние элемента управления:
//+------------------------------------------------------------------+ //| Устанавливает состояние элемента управления | //+------------------------------------------------------------------+ void CTabHeader::SetState(const bool flag) { bool state=this.State(); CButton::SetState(flag); if(state!=flag) this.SetWH(); } //+------------------------------------------------------------------+
Сначала сохраняем текущее состояние, затем вызываем метод родительского класса для установки состояния, и далее, если прошлое состояние было не тем, какое установлено сейчас, то вызываем метод изменения размера заголовка вкладки.
Метод, устанавливающий ширину, высоту и смещение элемента в зависимости от его состояния:
//+------------------------------------------------------------------+ //| Устанавливает ширину, высоту и смещение элемента | //| в зависимости от его состояния | //+------------------------------------------------------------------+ void CTabHeader::SetWH(void) { if(this.State()) this.WHProcessStateOn(); else this.WHProcessStateOff(); } //+------------------------------------------------------------------+
Если состояние "включено", то вызываем метод изменения размера и расположения для состояния "включено", иначе — вызываем метод изменения размера и расположения для состояния "выключено".
Метод, подстраивающий размер и расположение элемента в состоянии "выбран" в зависимости от его расположения:
//+------------------------------------------------------------------+ //| Подстраивает размер и расположение элемента | //| в состоянии "выбран" в зависимости от его расположения | //+------------------------------------------------------------------+ void CTabHeader::WHProcessStateOn(void) { //--- Еси не удалось установить новый размер - уходим if(!this.SetSizeOn()) return; //--- В зависимости от расположения заголовка switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- смещаем его в нужное место и устанавливаем новые относительные координаты if(this.Move(this.CoordX()-2,this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()-2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- смещаем его в нужное место и устанавливаем новые относительные координаты if(this.Move(this.CoordX()-2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()); } break; default: break; } this.Update(false); } //+------------------------------------------------------------------+
Логика метода прокомментирована в коде. В зависимости от того, где расположен заголовок (пока обрабатываются только два положения — сверху и снизу), изменяются размеры заголовка и он смещается на новые координаты так, чтобы та его грань, которая соприкасается с полем вкладки, оставалась на месте. Визуально это будет расширением объекта на два пикселя в каждую сторону, кроме стороны, прилегающей к полю.
Метод, подстраивающий размер и расположение элемента в состоянии "не выбран" в зависимости от его расположения:
//+------------------------------------------------------------------+ //| Подстраивает размер и расположение элемента | //| в состоянии "не выбран" в зависимости от его расположения | //+------------------------------------------------------------------+ void CTabHeader::WHProcessStateOff(void) { //--- Еси не удалось установить новый размер - уходим if(!this.SetSizeOff()) return; //--- В зависимости от расположения заголовка switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- смещаем его в нужное место и устанавливаем новые относительные координаты if(this.Move(this.CoordX()+2,this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()+2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- смещаем его в нужное место и устанавливаем новые относительные координаты if(this.Move(this.CoordX()+2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()); } break; default: break; } this.Update(false); } //+------------------------------------------------------------------+
В зависимости от того, где расположен заголовок (сейчас пока обрабатываются только два положения — сверху и снизу), изменяются размеры заголовка и он смещается на новые координаты так, чтобы та его грань, которая соприкасается с полем вкладки, оставалась на месте. Визуально это будет уменьшением объекта на два пикселя с каждой стороны, кроме прилегающей к полю.
Метод, рисующий рамку элемента в зависимости от расположения:
//+------------------------------------------------------------------+ //| Рисует рамку элемента в зависимости от расположения | //+------------------------------------------------------------------+ void CTabHeader::DrawFrame(void) { //--- Устанавливаемначальные координаты int x1=0; int x2=this.Width()-1; int y1=0; int y2=this.Height()-1; //--- В зависимости от расположения заголовка рисуем рамку так, //--- чтобы прилегающая к полю грань рисуемой рамки выходила за пределы объекта //--- таким образом визуально грань не будет нарисована на прилегающейстороне switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : this.DrawRectangle(x1,y1,x2,y2+1,this.BorderColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.DrawRectangle(x1,y1-1,x2,y2,this.BorderColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : this.DrawRectangle(x1,y1,x2+1,y2,this.BorderColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.DrawRectangle(x1-1,y1,x2,y2,this.BorderColor(),this.Opacity()); break; default: break; } } //+------------------------------------------------------------------+
Логика метода прокомментирована в коде. Если рисовать прямоугольник на объекте так, что координаты одной из сторон выходят за пределы объекта, то на этой стороне ничего нарисовано не будет. Вот это мы тут и используем — там, где заголовок должен прилегать к полю вкладки — там мы заведомо указываем координаты, выходящие за пределы объекта, и на этой стороне рамка не рисуется.
Метод, очищающий элемент с заполнением его цветом и непрозрачностью:
//+------------------------------------------------------------------+ //| Очищает элемент с заполнением его цветом и непрозрачностью | //+------------------------------------------------------------------+ void CTabHeader::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Закрашиваем элемент с указанным цветом и флагом необходимости перерисовки CGCnvElement::Erase(colour,opacity,redraw); //--- Если у объекта есть рамка - рисуем её if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFrame(); //--- Обновляем элемент с указанным флагом необходимости перерисовки this.Update(redraw); } //+------------------------------------------------------------------+
Метод виртуальный, переопределяет метод родительского объекта, где рисуется рамка в пределах объекта. Здесь же мы вызываем вышенаписанный метод для отрисовки рамки только с трёх сторон объекта.
Метод, очищающий элемент заливкой градиентом:
//+------------------------------------------------------------------+ //| Очищает элемент заливкой градиентом | //+------------------------------------------------------------------+ void CTabHeader::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Закрашиваем элемент с указанным массивом цветов и флагом необходимости перерисовки CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- Если у объекта есть рамка - рисуем её if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFrame(); //--- Обновляем элемент с указанным флагом необходимости перерисовки this.Update(redraw); } //+------------------------------------------------------------------+
Метод идентичен вышерассмотренному, но заливает фон градиентным цветом из массива цветов, переданного в метод.
Обработчик события Курсор в пределах активной области, отжата кнопка мышки (левая):
//+------------------------------------------------------------------+ //| Обработчик события Курсор в пределах активной области, | //| отжата кнопка мышки (левая) | //+------------------------------------------------------------------+ void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Если кнопка мышки отпущена за пределами элемента - это отказ от взаимодействия с элементом if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- Если это простая кнопка - устанавливаем изначальный цвет фона и текста if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- Если это кнопка-переключатель - устанавливаем изначальный цвет фона и текста в зависимости от того нажата кнопка или нет else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Устанавливаем изначальный цвет рамки this.SetBorderColor(this.BorderColorInit(),false); //--- Выводим тестовое сообщение в журнал Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- Если кнопка мышки отпущена в пределах элемента - это щелчок по элементу управления else { //--- Если это простая кнопка - устанавливаем цвет фона и текста для состояния "Курсор мышки над активной зоной" if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- Если это кнопка-переключатель - else { //--- если кнопка не работает в группе - устанавливаем её состояние на противоположное, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- иначе - если кнопка ещё не нажата - устанавливаем её в нажатое состояние else if(!this.State()) this.SetState(true); //--- устанавливаем цвет фона и текста для состояния "Курсор мышки над активной зоной" в зависимости от того нажата кнопка или нет this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); } //--- Выводим тестовое сообщение в журнал Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Устанавливаем цвет рамки для состояния "Курсор мышки над активной зоной" this.SetBorderColor(this.BorderColorMouseOver(),false); } //--- Перерисовываем объект this.Redraw(false); } //+------------------------------------------------------------------+
Обработчик последнего события мышки:
//+------------------------------------------------------------------+ //| Обработчик последнего события мышки | //+------------------------------------------------------------------+ void CTabHeader::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- Курсор за пределами формы, кнопки мышки не нажаты //--- Курсор за пределами формы, нажата кнопка мышки (любая) //--- Курсор за пределами формы, прокручивается колёсико мышки case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false); this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- Курсор в пределах формы, кнопки мышки не нажаты //--- Курсор в пределах формы, нажата кнопка мышки (любая) //--- Курсор в пределах формы, прокручивается колёсико мышки //--- Курсор в пределах активной области, кнопки мышки не нажаты //--- Курсор в пределах активной области, нажата кнопка мышки (любая) //--- Курсор в пределах активной области, прокручивается колёсико мышки //--- Курсор в пределах активной области, отжата кнопка мышки (левая) //--- Курсор в пределах области прокрутки окна, кнопки мышки не нажаты //--- Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая) //--- Курсор в пределах области прокрутки окна, прокручивается колёсико мышки case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //---MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Оба метода идентичны методам родительского класса.
Сюда перенесены для возможного их переопределения при дальнейшей разработке объекта TabControl.
Класс TabControl — продолжаем разработку
В прошлой статье мы начали разработку элемента управления TabControl, но столкнулись с ограничением длины имён создаваемых графических объектов. После создания нового алгоритма именования графических элементов библиотеки,
продолжим работу в фале \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh.
Подключим к файлу элемента управления файл класса объекта-заголовка вкладки:
//+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "..\TabHeader.mqh" //+------------------------------------------------------------------+
В приватной секции класса объявим два метода для установки состояния указанной по индексу вкладки:
private: int m_item_width; // Фиксированная ширина заголовков вкладок int m_item_height; // Фиксированная высота заголовков вкладок //--- Создаёт новый графический объект virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Устанавливает вкладку выбранной void SetSelected(const int index); //--- Устанавливает вкладку не выбранной void SetUnselected(const int index); public:
Публичный метод CreateTabPage() переименуем в CreateTabPages(), сделаем его с возвращаемым типом bool и с иным набором формальных параметров, а также добавим два метода, возвращающие указатели на заголовок и поле вкладки по индексу:
public: //--- Создаёт указанное количество вкладок bool CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text=""); //--- Возвращает указатель на (1) заголовок, (2) поле вкладки CTabHeader *GetHeader(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); } CContainer *GetField(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index); }
Метод SetAlignment() сделаем таким, чтобы он не просто устанавливал значение в свойство объекта, но и устанавливал его для всех заголовков вкладок, созданных в элементе управления:
//--- (1) Устанавливает, (2) возвращает местоположение вкладок на элементе управления void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); CArrayObj *list=this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetAlignment(alignment); } }
Сначала устанавливаем значение в свойства объекта, затем получаем список всех созданных заголовков вкладок, и в цикле по полученному списку для каждого объекта устанавливаем такое же значение.
Объявим ещё три публичных метода:
//--- Устанавливает фиксированный размер вкладки void SetItemSize(const int w,const int h) { if(this.ItemWidth()!=w) this.SetItemWidth(w); if(this.ItemHeight()!=h) this.SetItemHeight(h); } //--- Устанавливает вкладку выбранной/не выбранной void Select(const int index,const bool flag); //--- Устанавливает текст заголовка (1) указанной вкладки, (2) по индексу void SetHeaderText(CTabHeader *header,const string text); void SetHeaderText(const int index,const string text); //--- Конструктор
В конструкторе класса устанавлиаем все цвета прозрачными кроме цвета текста, и вносим изменения, какие сделали со всеми WinForms-объектами:
//+------------------------------------------------------------------+ //| Конструктор с указанием идентификатора чарта и подокна | //+------------------------------------------------------------------+ CTabControl::CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetOpacity(0,true); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetBackgroundColorMouseDown(CLR_CANV_NULL); this.SetBackgroundColorMouseOver(CLR_CANV_NULL); this.SetBorderColor(CLR_CANV_NULL,true); this.SetBorderColorMouseDown(CLR_CANV_NULL); this.SetBorderColorMouseOver(CLR_CANV_NULL); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetItemSize(58,18); } //+------------------------------------------------------------------+
Этот объект должен служить контейнером для создаваемых внутри него вкладок и управлять ими, поэтому он полностью прозрачный, но оставлена возможность выводить на него текст.
Метод, создающий указанное количество вкладок:
//+------------------------------------------------------------------+ //| Создаёт указанное количество вкладок | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Рассчитываем размеры и начальные координаты заголовка вкладки int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- В цикле по количеству вкладок CTabHeader *header=NULL; for(int i=0;i<total;i++) { //--- В зависимости от расположения заголовков вкладок устанавливаем их начальные координаты int header_x=2; int header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) header_y=this.Height()-h; //--- Устанавливаем текущую координату X header_x=(header==NULL ? header_x : header.RightEdgeRelative()); //--- Создаём объект TabHeader if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,w,h,clrNONE,255,this.Active(),false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); header.SetBorderStyle(FRAME_STYLE_SIMPLE); header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); header.SetAlignment(this.Alignment()); if(header_text!="" && header_text!=NULL) this.SetHeaderText(header,header_text+string(i+1)); //--- В зависимости от расположения заголовков вкладок устанавливаем начальные координаты полей вкладок int field_x=0; int field_y=0; int field_h=this.Height()-header.Height(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) field_y=header.BottomEdgeRelative(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) field_y=0; //--- Создаём объект Container (поле вкладки) CContainer *field=NULL; if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,field_x,field_y,this.Width(),field_h,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER),string(i+1)); return false; } field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i); if(field==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER),string(i+1)); return false; } field.SetBorderSizeAll(1); field.SetBorderStyle(FRAME_STYLE_SIMPLE); field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); field.SetForeColor(CLR_DEF_FORE_COLOR,true); field.Hide(); //--- } this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
Вкладки у нас состоят из двух объектов — заголовка вкладки и поля вкладки. Заголовок (кнопка) служит для выбора активной вкладки, а поле — для размещения на нём других элементов управления. Кнопки всегда должны отображаться, а поля вкладок — всегда скрываться, кроме активной.
Здесь мы в цикле сначала создаём кнопки в качестве заголовков вкладок и устанавливаем им значения по умолчанию. На этой же итерации цикла после создания кнопки, создаём контейнер для поля вкладки и тоже устанавливаем ему значения по умолчанию. Координаты и размеры полей вкладок зависят и рассчитываются, исходя из места расположения заголовков в элементе управления. По окончании цикла создания объектов-вкладок, делаем выбранной указанной во входных параметрах вкладку.
Метод, устанавливающий вкладку выбранной:
//+------------------------------------------------------------------+ //| Устанавливает вкладку выбранной | //+------------------------------------------------------------------+ void CTabControl::SetSelected(const int index) { CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index); if(header==NULL || field==NULL) return; field.Show(); field.BringToTop(); header.SetState(true); header.BringToTop(); } //+------------------------------------------------------------------+
Получаем указатели на заголовок и поле вкладки по индексу.
Отображаем поле и выводим его на передний план.
Устанавливаем выбранным заголовок и вводим его на передний план.
Метод, устанавливающий вкладку не выбранной:
//+------------------------------------------------------------------+ //| Устанавливает вкладку не выбранной | //+------------------------------------------------------------------+ void CTabControl::SetUnselected(const int index) { CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index); if(header==NULL || field==NULL) return; field.Hide(); header.SetState(false); } //+------------------------------------------------------------------+
Получаем указатели на заголовок и поле вкладки по индексу.
Скрываем поле и устанавливаем заголовок как не выбранный.
Метод, устанавливающий вкладку выбранной/не выбранной:
//+------------------------------------------------------------------+ //| Устанавливает вкладку выбранной/не выбранной | //+------------------------------------------------------------------+ void CTabControl::Select(const int index,const bool flag) { if(flag) this.SetSelected(index); else this.SetUnselected(index); } //+------------------------------------------------------------------+
В метод передаётся индекс вкладки и флаг, и в зависимости от значения флага, вызываем один из двух вышерассмотренных методов.
Метод, устанавливающий текст заголовка указанной вкладки:
//+------------------------------------------------------------------+ //| Устанавливает текст заголовка указанной вкладки | //+------------------------------------------------------------------+ void CTabControl::SetHeaderText(CTabHeader *header,const string text) { if(header==NULL) return; header.SetText(text); } //+------------------------------------------------------------------+
В метод передаётся указатель на объект, в который устананавливается текст, переданный в метод.
Метод, устанавливающий текст заголовка вкладки по индексу:
//+------------------------------------------------------------------+ //| Устанавливает текст заголовка вкладки по индексу | //+------------------------------------------------------------------+ void CTabControl::SetHeaderText(const int index,const string text) { CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); this.SetHeaderText(header,text); } //+------------------------------------------------------------------+
В метод передаётся индекс заголовка вкладки, в котором нужно установить текст.
По индексу получаем объект с типом заголовка вкладки и
вызываем вышерассмотренный метод установки текста в указанный объект.
В методе, создающем новый графический объект, добавим создание объекта ListBoxItem и удалим блок создания теперь отсутствующего объекта TabPage:
//+------------------------------------------------------------------+ //| Создаёт новый графический объект | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Теперь в каждом блоке создания объекта передаём в метод описание объекта, а не его имя.
В классах объекта Panel в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh
и объекта GroupBox в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh
сделаны аналогичные доработки, какие мы делали для WinForms-объектов: добавлен новый защищённый конструктор, удалён один ненужный параметрический конструктор и доработан виртуальный метод CreateNewGObject() точно так же, как мы только что рассмотрели выше.
На сегодня это все необходимые доработки.
Тестирование
Для тестирования возьмём советник из прошлой сатьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part114\ под новым именем TstDE114.mq5.
Для отображения описаний констант перечисления на двух разных языках в настройках советника, создадим дополнительное перечисление для английского языка компиляции и русского, и в настройках добавим новую переменную с типом этого перечисления, которая будет указывать с какой стороны — сверху или снизу (право-лево пока не работают) будут размещаться заголовки вкладок:
//--- enumerations by compilation language #ifdef COMPILE_EN enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Grow AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Grow and Shrink }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // None BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Simple BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Flat BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Embossed (bevel) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Embossed (stamp) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Indeterminate }; enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Top ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Left ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Только увеличение AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Увеличение и уменьшение }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // Нет рамки BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Простая рамка BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Плоская рамка BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Рельефная (выпуклая) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Рельефная (вдавленная) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Не установлен CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Установлен CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Неопределённый }; enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Сверху ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Снизу ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Слева ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Справа }; #endif //--- input parameters sinput bool InpMovable = true; // Panel Movable flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Panel Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Panel Autosize mode sinput ENUM_BORDER_STYLE InpFrameStyle = BORDER_STYLE_SIMPLE; // Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_CENTER; // Label text align sinput ENUM_INPUT_YES_NO InpTextAutoSize = INPUT_NO; // Label autosize sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT; // Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT; // Check label text align sinput ENUM_CHEK_STATE InpCheckState = CHEK_STATE_UNCHECKED; // Check flag state sinput ENUM_INPUT_YES_NO InpCheckAutoSize = INPUT_YES; // CheckBox autosize sinput ENUM_BORDER_STYLE InpCheckFrameStyle = BORDER_STYLE_NONE; // CheckBox border style sinput ENUM_ANCHOR_POINT InpButtonTextAlign = ANCHOR_CENTER; // Button text align sinput ENUM_INPUT_YES_NO InpButtonAutoSize = INPUT_YES; // Button autosize sinput ENUM_AUTO_SIZE_MODE InpButtonAutoSizeMode= AUTO_SIZE_MODE_GROW; // Button Autosize mode sinput ENUM_BORDER_STYLE InpButtonFrameStyle = BORDER_STYLE_NONE; // Button border style sinput bool InpButtonToggle = false; // Button toggle flag //sinput bool InpListBoxMColumn = false; // ListBox MultiColumn flag sinput bool InpButtListMSelect = false; // ButtonListBox Button MultiSelect flag sinput ENUM_ELEMENT_ALIGNMENT InpHeaderAlignment = ELEMENT_ALIGNMENT_TOP; // TabHeader Alignment //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
В обработчике OnInit() в блоке создания графических объектов закомментируем блок создания объекта TabControl из прошлой статьи (там мы его разместим позже) и код создания объекта ListBox (его мы будем размещать во вкладки будущего объекта TabControl). А после блока кода для создания объекта ButtonListBox разместим блок кода для создания объекта TabControl c тремя вкладками, первая из которых будет выбрана изначально:
//--- Если прикреплённый объект GroupBox создан if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- получим указатель на объект GroupBox по его индексу в списке прикреплённых объектов с типом GroupBox gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- установим тип рамки "вдавленная рамка", цвет рамки как цвет фона основной панели, //--- а цвет текста - затемнённый на 1 цвет фона последней прикреплённой панели gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Создадим объект TabControl // gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false); // //--- получим указатель на объект TabControl по его индексу в списке прикреплённых объектов с типом TabControl // CTabControl *tctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); // if(tctrl!=NULL) // { // //--- получим указатель на объект Container по его индексу в списке прикреплённых объектов с типом Container // CContainer *page=tctrl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,0); // if(page!=NULL) // { // // Здесь будем создавать объекты, прикреплённые к указанной вкладке объекта TabControl // // К сожалению, в текущем состоянии создания имён графических объектов библиотеки // // дальнейшее их создание упирается в предел количества символов в имени ресурса в классе CCanvas // } // // } ///* //--- Создадим объект CheckedListBox gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false); //--- получим указатель на объект CheckedListBox по его индексу в списке прикреплённых объектов с типом CheckBox CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0); //--- Если CheckedListBox создан и указатель на него получен if(clbox!=NULL) { clbox.SetMultiColumn(true); clbox.SetColumnWidth(0); clbox.CreateCheckBox(4,66); } //--- Создадим объект ButtonListBox gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,clbox.BottomEdgeRelative()+6,160,30,clrNONE,255,true,false); //--- получим указатель на объект ButtonListBox по его индексу в списке прикреплённых объектов с типом Button CButtonListBox *blbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0); //--- Если ButtonListBox создан и указатель на него получен if(blbox!=NULL) { blbox.SetMultiColumn(true); blbox.SetColumnWidth(0); blbox.CreateButton(4,66,16); blbox.SetMultiSelect(InpButtListMSelect); blbox.SetToggle(InpButtonToggle); for(int i=0;i<blbox.ElementsTotal();i++) { blbox.SetButtonGroup(i,(i % 2==0 ? blbox.Group()+1 : blbox.Group()+2)); blbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false)); } } int lbx=6; int lby=blbox.BottomEdgeRelative()+6; int lbw=180; //--- Создадим объект TabControl gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,lbx,lby,lbw,78,clrNONE,255,true,false); //--- получим указатель на объект TabControl по его индексу в списке прикреплённых объектов с типом TabControl CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); //--- Если TabControl создан и указатель на него получен if(tab_ctrl!=NULL) { //--- Установим расположение заголовков вкладок на элементе и текст вкладок tab_ctrl.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tab_ctrl.CreateTabPages(3,0,56,16,TextByLanguage("Вкладка","TabPage")); } //--- Создадим объект ListBox //int lbx=4; //int lby=blbox.BottomEdgeRelative()+6; //int lbw=146; //if(!InpListBoxMColumn) // { // lbx=blbox.RightEdgeRelative()+6; // lby=14; // lbw=100; // } //gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,lbx,lby,lbw,60,clrNONE,255,true,false); ////--- получим указатель на объект ListBox по его индексу в списке прикреплённых объектов с типом ListBox //CListBox *lbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0); ////--- Если ListBox создан и указатель на него получен //if(lbox!=NULL) // { // lbox.SetMultiColumn(true); // lbox.CreateList(8,68); // } //*/ } }
Скомпилируем советник и запустим его на графике:
Вот теперь вкладки ожили по сравнению с советником из прошлой статьи. Заголовки вкладок могут располагаться как сверху, так и снизу контейнера, но заметны и недоработки: если другие элементы вполне сносно себя ведут при взаимодействии с мышкой, то заголовки вкладок заметно "моргают". С этим разберёмся. И ещё: под конец я указал курсором на линию между заголовком и полем вкладки. Её там быть не должно. Но это в статье оговаривалось — будем делать в следующей статье так, чтобы вкладка — заголовок и поле были одним целым.
Что дальше
В следующей статье продолжим развитие элемента управления TabControl.
*Статьи этой серии:
DoEasy. Элементы управления (Часть 1): Первые шаги
DoEasy. Элементы управления (Часть 2): Продолжаем работу над классом CPanel
DoEasy. Элементы управления (Часть 3): Создание привязанных элементов управления
DoEasy. Элементы управления (Часть 4): Элемент управления "Панель", параметры Padding и Dock
DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize
DoEasy. Элементы управления (Часть 6): Элемент управления "Панель", автоизменение размеров контейнера под внутреннее содержимое
DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"
DoEasy. Элементы управления (Часть 8): Базовые WinForms-объекты по категориям, элементы управления "GroupBox" и "CheckBox
DoEasy. Элементы управления (Часть 9): Реорганизация методов WinForms-объектов, элементы управления "RadioButton" и "Button"
DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс
DoEasy. Элементы управления (Часть 11): WinForms-объекты — группы, WinForms-объект CheckedListBox
DoEasy. Элементы управления (Часть 12): Базовый объект-список, WinForms-объекты ListBox и ButtonListBox
DoEasy. Элементы управления (Часть 13): Оптимизация взаимодействия WinForms-объектов с мышкой, начало разработки WinForms-объекта TabControl