
Таблицы в парадигме MVC на MQL5: Интегрируем компонент Model в компонент View
Содержание
- Введение
- Дорабатываем классы библиотеки
- Класс ячейки таблицы (View)
- Класс строки таблицы (View)
- Класс заголовка столбца таблицы (View)
- Класс заголовка таблицы (View)
- Класс таблицы (View)
- Тестируем результат
- Заключение
Введение
В статье "Классы таблицы и заголовка на базе модели таблицы в MQL5: Применение концепции MVC" мы завершили создание класса модели таблиц (в концепции MVC — компонент Model). Далее занимались разработкой библиотеки простых элементов управления, которые позволяют создавать на их основе совершенно разные по назначению и сложности элементы управления. В частности — компонент View для создания элемента управления TableView.
Данная статья будет посвящена реализации взаимодействия компонента Model с компонентом View. Иными словами — сегодня мы свяжем воедино табличные данные с их графическим представлением в едином элементе управления.
Элемент управления будет создан на базе класса объекта Панель (Panel), и состоять из нескольких элементов:
- Panel — основа, подложка, к которой прикреплены заголовок таблицы и область данных таблицы;
- Panel — заголовок таблицы, состоящий из ряда элементов — заголовков столбцов, созданных на базе класса объекта Button;
- Container — контейнер табличных данных с возможностью прокрутки содержимого;
- Panel — панель для расположения на ней строк таблицы (прикреплена к контейнеру (п.3) и, при выходе за пределы контейнера, прокручивается при помощи полос прокрутки контейнера);
- Panel — строка таблицы — панель для рисования на ней ячеек таблицы, прикреплённая к панели (п.4); таких объектов создаётся по количеству строк таблицы;
- Класс ячейки таблицы — новый класс, позволяющий рисовать в указанных координатах на указанном холсте (CCanvas). Объект такого класса прикрепляется к объекту строки таблицы (п.5), канвасы объекта строки таблицы указываются как элементы для рисования, и ячейка таблицы рисуется на этой панели в указанных координатах. Область каждой ячейки таблицы задаётся в объекте строки таблицы (п.5) экземпляром объекта класса CBound, и к этому объекту прикрепляется объект класса ячейки таблицы.
Такой алгоритм построения строк и ячеек таблицы, где каждая строка распределена на горизонтальные области, к которым прикрепляются объекты класса ячеек, позволяет легко сортировать и менять положение ячеек простым переназначением ячеек на нужные области строки.
Исходя из написанного выше, понимаем, что нам необходим новый класс объекта, в который будет передаваться указатель на элемент управления, и на его канвасе будет происходить рисование. На текущий момент все объекты библиотеки при их создании оператором new создают собственные объекты канвасов (фона и переднего плана). Нам же нужен такой объект, который будет давать возможность установки и получения своего размера и установки в него указателя на элемент управления, на котором будет производиться рисование.
Класс CBound даёт возможность установки и получения размеров созданного объекта, и он находится в составе базового объекта всех элементов управления. В этом же объекте создаются и канвасы фона и переднего плана. Значит, нам нужно создать ещё один промежуточный объект, который будет наследоваться от базового, будет иметь в своём составе класс CBound для установки и получения размеров. От этого объекта будет наследоваться класс, в котором создаются объекты CCanvas. Таким образом, от этого промежуточного класса можно унаследовать тот класс, в который мы будем передавать указатель на элемент управления, на котором будем рисовать.
Давайте доработаем файлы уже созданных классов, а уже затем напишем новые.
Дорабатываем классы библиотеки
Все файлы разрабатываемой библиотеки расположены по адресу \MQL5\Indicators\Tables\Controls\. Если их ещё нет, то получить предыдущую версию всех файлов можно из прошлой статьи. Также для проекта потребуется файл классов таблиц (компонент Model), получить который можно из статьи, где мы завершили их разработку. Файл Tables.mqh необходимо сохранить в папку \MQL5\Indicators\Tables\. Дорабатываться будут файлы Base.mqh и Control.mqh.
В самом начале файла Tables.mqh в разделе макросов напишем идентификатор файла:
//+------------------------------------------------------------------+ //| Макросы | //+------------------------------------------------------------------+ #define __TABLES__ // Идентификатор данного файла #define MARKER_START_DATA -1 // Маркер начала данных в файле #define MAX_STRING_LENGTH 128 // Максимальная длина строки в ячейке #define CELL_WIDTH_IN_CHARS 19 // Ширина ячейки таблицы в символах
Это необходимо для того, чтобы макроподстановка MARKER_START_DATA могла быть объявлена в двух разных включаемых файлах для их раздельной компиляции.
Откроем файл Base.mqh. Подключим к нему файл классов модели таблицы и пропишем форвард-декларацию новых классов:
//+------------------------------------------------------------------+ //| Base.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Canvas\Canvas.mqh> // Класс СБ CCanvas #include <Arrays\List.mqh> // Класс СБ CList #include "..\Tables.mqh" //--- Форвард-декларация классов элементов управления class CBoundedObj; // Базовый класс, хранящий размеры объекта class CCanvasBase; // Базовый класс холста графических элементов class CCounter; // Класс счётчика задержки class CAutoRepeat; // Класс автоповтора событий class CImagePainter; // Класс рисования изображений class CVisualHint; // Класс подсказки class CLabel; // Класс текстовой метки class CButton; // Класс простой кнопки class CButtonTriggered; // Класс двухпозиционной кнопки class CButtonArrowUp; // Класс кнопки со стрелкой вверх class CButtonArrowDown; // Класс кнопки со стрелкой вниз class CButtonArrowLeft; // Класс кнопки со стрелкой влево class CButtonArrowRight; // Класс кнопки со стрелкой вправо class CCheckBox; // Класс элемента управления CheckBox class CRadioButton; // Класс элемента управления RadioButton class CScrollBarThumbH; // Класс ползунка горизонтальной полосы прокрутки class CScrollBarThumbV; // Класс ползунка вертикальной полосы прокрутки class CScrollBarH; // Класс горизонтальной полосы прокрутки class CScrollBarV; // Класс вертикальной полосы прокрутки class CTableRowView; // Класс визуального представления строки таблицы class CPanel; // Класс элемента управления Panel class CGroupBox; // Класс элемента управления GroupBox class CContainer; // Класс элемента управления Container //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+
В разделе макроподстановок декларацию макроса MARKER_START_DATAобрамим проверкой его существования:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define clrNULL 0x00FFFFFF // Прозрачный цвет для CCanvas #ifndef __TABLES__ #define MARKER_START_DATA -1 // Маркер начала данных в файле #endif #define DEF_FONTNAME "Calibri" // Шрифт по умолчанию #define DEF_FONTSIZE 10 // Размер шрифта по умолчанию #define DEF_EDGE_THICKNESS 3 // Толщина зоны для захвата границы/угла
Теперь объявление одной и той же макроподстановки в двух разных файлах не будет приводить к ошибкам компиляции — как по отдельности, так и совместно двух файлов.
В перечисление типов графических элементов добавим новые типы, и установим новый максимальный тип активного элемента:
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE // Перечисление типов графических элементов { ELEMENT_TYPE_BASE = 0x10000, // Базовый объект графических элементов ELEMENT_TYPE_COLOR, // Объект цвета ELEMENT_TYPE_COLORS_ELEMENT, // Объект цветов элемента графического объекта ELEMENT_TYPE_RECTANGLE_AREA, // Прямоугольная область элемента ELEMENT_TYPE_IMAGE_PAINTER, // Объект для рисования изображений ELEMENT_TYPE_COUNTER, // Объект счётчика ELEMENT_TYPE_AUTOREPEAT_CONTROL, // Объект автоповтора событий ELEMENT_TYPE_BOUNDED_BASE, // Базовый объект размеров графических элементов ELEMENT_TYPE_CANVAS_BASE, // Базовый объект холста графических элементов ELEMENT_TYPE_ELEMENT_BASE, // Базовый объект графических элементов ELEMENT_TYPE_HINT, // Подсказка ELEMENT_TYPE_LABEL, // Текстовая метка ELEMENT_TYPE_BUTTON, // Простая кнопка ELEMENT_TYPE_BUTTON_TRIGGERED, // Двухпозиционная кнопка ELEMENT_TYPE_BUTTON_ARROW_UP, // Кнопка со стрелкой вверх ELEMENT_TYPE_BUTTON_ARROW_DOWN, // Кнопка со стрелкой вниз ELEMENT_TYPE_BUTTON_ARROW_LEFT, // Кнопка со стрелкой влево ELEMENT_TYPE_BUTTON_ARROW_RIGHT, // Кнопка со стрелкой вправо ELEMENT_TYPE_CHECKBOX, // Элемент управления CheckBox ELEMENT_TYPE_RADIOBUTTON, // Элемент управления RadioButton ELEMENT_TYPE_SCROLLBAR_THUMB_H, // Ползунок горизонтальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_THUMB_V, // Ползунок вертикальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_H, // Элемент управления ScrollBarHorisontal ELEMENT_TYPE_SCROLLBAR_V, // Элемент управления ScrollBarVertical ELEMENT_TYPE_TABLE_CELL, // Ячейка таблицы (View) ELEMENT_TYPE_TABLE_ROW, // Строка таблицы (View) ELEMENT_TYPE_TABLE_COLUMN_CAPTION, // Заголовок столбца таблицы (View) ELEMENT_TYPE_TABLE_HEADER, // Заголовок таблицы (View) ELEMENT_TYPE_TABLE, // Таблица (View) ELEMENT_TYPE_PANEL, // Элемент управления Panel ELEMENT_TYPE_GROUPBOX, // Элемент управления GroupBox ELEMENT_TYPE_CONTAINER, // Элемент управления Container }; #define ACTIVE_ELEMENT_MIN ELEMENT_TYPE_LABEL // Минимальное значение списка активных элементов #define ACTIVE_ELEMENT_MAX ELEMENT_TYPE_TABLE_HEADER // Максимальное значение списка активных элементов
Добавим новые возвращаемые значения в функцию, возвращающую короткое имя элемента по типу:
//+------------------------------------------------------------------+ //| Возвращает короткое имя элемента по типу | //+------------------------------------------------------------------+ string ElementShortName(const ENUM_ELEMENT_TYPE type) { switch(type) { case ELEMENT_TYPE_ELEMENT_BASE : return "BASE"; // Базовый объект графических элементов case ELEMENT_TYPE_HINT : return "HNT"; // Подсказка case ELEMENT_TYPE_LABEL : return "LBL"; // Текстовая метка case ELEMENT_TYPE_BUTTON : return "SBTN"; // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : return "TBTN"; // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : return "BTARU"; // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : return "BTARD"; // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : return "BTARL"; // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT : return "BTARR"; // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : return "CHKB"; // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : return "RBTN"; // Элемент управления RadioButton case ELEMENT_TYPE_SCROLLBAR_THUMB_H : return "THMBH"; // Ползунок горизонтальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_THUMB_V : return "THMBV"; // Ползунок вертикальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_H : return "SCBH"; // Элемент управления ScrollBarHorisontal case ELEMENT_TYPE_SCROLLBAR_V : return "SCBV"; // Элемент управления ScrollBarVertical case ELEMENT_TYPE_TABLE_CELL : return "TCELL"; // Ячейка таблицы (View) case ELEMENT_TYPE_TABLE_ROW : return "TROW"; // Строка таблицы (View) case ELEMENT_TYPE_TABLE_COLUMN_CAPTION : return "TCAPT"; // Заголовок столбца таблицы (View) case ELEMENT_TYPE_TABLE_HEADER : return "THDR"; // Заголовок таблицы (View) case ELEMENT_TYPE_TABLE : return "TABLE"; // Таблица (View) case ELEMENT_TYPE_PANEL : return "PNL"; // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : return "GRBX"; // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : return "CNTR"; // Элемент управления Container default : return "Unknown"; // Unknown } }
В базовом классе графических элементов метод установки идентификатора сделаем виртуальным, так как в новых классах его потребуется переопределить:
//+------------------------------------------------------------------+ //| Базовый класс графических элементов | //+------------------------------------------------------------------+ class CBaseObj : public CObject { protected: int m_id; // Идентифткатор ushort m_name[]; // Наименование public: //--- Устанавливает (1) наименование, (2) идентификатор void SetName(const string name) { ::StringToShortArray(name,this.m_name); } virtual void SetID(const int id) { this.m_id=id; } //--- Возвращает (1) наименование, (2) идентификатор string Name(void) const { return ::ShortArrayToString(this.m_name); } int ID(void) const { return this.m_id; } //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_BASE); } //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); virtual void Print(void); //--- Конструктор/деструктор CBaseObj (void) : m_id(-1) { this.SetName(""); } ~CBaseObj (void) {} };
Каждый объект в своём составе имеет класс прямоугольной области CBound, который возвращает размер элемента управления. Также каждый элемент управления имеет список объектов CBound. Этот список позволяет хранить множество объектов CBound, которые в свою очередь позволяют задать множество различных областей на поверхности графического элемента.
Каждая такая область может быть использована для каких-либо сценариев. Например, можно определить некую зону внутри графического элемента, отслеживать нахождение курсора внутри этой области и реагировать на взаимодействие этой области с курсором мышки. Можно внутрь определённой области поместить некоторый элемент управления для его отображения внутри этой области. Для этого необходимо дать возможность записывать в объект CBound указатель на элемент управления.
Пропишем в классе CBound такую возможность:
//+------------------------------------------------------------------+ //| Класс прямоугольной области | //+------------------------------------------------------------------+ class CBound : public CBaseObj { protected: CBaseObj *m_assigned_obj; // Назначенный на область объект CRect m_bound; // Структура прямоугольной области public: //--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника void ResizeW(const int size) { this.m_bound.Width(size); } void ResizeH(const int size) { this.m_bound.Height(size); } void Resize(const int w,const int h) { this.m_bound.Width(w); this.m_bound.Height(h); } //--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника void SetX(const int x) { this.m_bound.left=x; } void SetY(const int y) { this.m_bound.top=y; } void SetXY(const int x,const int y) { this.m_bound.LeftTop(x,y); } //--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения void Move(const int x,const int y) { this.m_bound.Move(x,y); } void Shift(const int dx,const int dy) { this.m_bound.Shift(dx,dy); } //--- Возвращает координаты, размеры и границы объекта int X(void) const { return this.m_bound.left; } int Y(void) const { return this.m_bound.top; } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } int Right(void) const { return this.m_bound.right-(this.m_bound.Width() >0 ? 1 : 0);} int Bottom(void) const { return this.m_bound.bottom-(this.m_bound.Height()>0 ? 1 : 0);} //--- Возвращает флаг нахождения курсора внутри области bool Contains(const int x,const int y) const { return this.m_bound.Contains(x,y); } //--- (1) Назначает, (2) возвращает указатель на назначенный элемент void AssignObject(CBaseObj *obj) { this.m_assigned_obj=obj; } CBaseObj *GetAssignedObj(void) { return this.m_assigned_obj; } //--- Возвращает описание объекта virtual string Description(void); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_RECTANGLE_AREA); } //--- Конструкторы/деструктор CBound(void) { ::ZeroMemory(this.m_bound); } CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); } ~CBound(void) { ::ZeroMemory(this.m_bound); } };
На данный момент объект прямоугольной области CBound объявлен в классе CCanvasBase, в котором создаются два объекта CCanvas для рисования фона и переднего плана графического элемента. То есть все графические элементы имеют в своём составе два канваса. Но нам необходимо создать класс, который будет позволять указать в нём тот элемент управления, на канвасах которого нужно рисовать. При этом не создавать собственных канвасов, и при этом не выбиваться из общей концепции создания графических элементов.
Значит, нам необходимо перенести из класса CCanvasBase объект класса CBound в другой — родительский класс, который не будет создавать объекты канваса, и от которого будет наследоваться класс CCanvasBase. А в классе CCanvasBase не объявлять экземпляры двух CCanvas, а объявить указатели на создаваемые канвасы и сделать метод, который будет создавать два объекта канваса. Необходимо также объявить флаг, указывающий, что графический элемент управляет или не управляет объектами канвасов фона и переднего плана. Это требуется для управления удалением созданных объектов канвасов.
В файле Base.mqh сразу после класса CAutoRepeat, перед классом CCanvasBase напишем новый класс CBoundedObj, наследуемый от CBaseObj, в который просто перенесём все методы работы с объектом CBound из класса CCanvasBase:
//+------------------------------------------------------------------+ //| Базовый класс, хранящий размеры объекта | //+------------------------------------------------------------------+ class CBoundedObj : public CBaseObj { protected: CBound m_bound; // Границы объекта bool m_canvas_owner; // Флаг владения канвасами public: //--- Возвращает координаты, размеры и границы объекта int X(void) const { return this.m_bound.X(); } int Y(void) const { return this.m_bound.Y(); } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } int Right(void) const { return this.m_bound.Right(); } int Bottom(void) const { return this.m_bound.Bottom(); } //--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника void BoundResizeW(const int size) { this.m_bound.ResizeW(size); } void BoundResizeH(const int size) { this.m_bound.ResizeH(size); } void BoundResize(const int w,const int h) { this.m_bound.Resize(w,h); } //--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника void BoundSetX(const int x) { this.m_bound.SetX(x); } void BoundSetY(const int y) { this.m_bound.SetY(y); } void BoundSetXY(const int x,const int y) { this.m_bound.SetXY(x,y); } //--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения void BoundMove(const int x,const int y) { this.m_bound.Move(x,y); } void BoundShift(const int dx,const int dy) { this.m_bound.Shift(dx,dy); } //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта //virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_BOUNDED_BASE); } CBoundedObj (void) : m_canvas_owner(true) {} CBoundedObj (const string user_name,const int id,const int x,const int y,const int w,const int h); ~CBoundedObj (void){} }; //+------------------------------------------------------------------+ //| CBoundedObj::Конструктор | //+------------------------------------------------------------------+ CBoundedObj::CBoundedObj(const string user_name,const int id,const int x,const int y,const int w,const int h) : m_canvas_owner(true) { //--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y this.m_bound.SetName(user_name); this.m_bound.SetID(id); this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); } //+------------------------------------------------------------------+ //| CBoundedObj::Сохранение в файл | //+------------------------------------------------------------------+ bool CBoundedObj::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем флаг владения канвасами if(::FileWriteInteger(file_handle,this.m_canvas_owner,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем размеры return this.m_bound.Save(file_handle); } //+------------------------------------------------------------------+ //| CBoundedObj::Загрузка из файла | //+------------------------------------------------------------------+ bool CBoundedObj::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CBaseObj::Load(file_handle)) return false; //--- Загружаем флаг владения канвасами this.m_canvas_owner=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем размеры return this.m_bound.Load(file_handle); }
Представленный класс просто наследует свойства базового класса CBaseObj, позволяет указывать границы объекта (класс CBound) и имеет флаг управления канвасами.
Базовый класс холста графических элементов теперь должен быть унаследован от нового CBoundedObj, а не от класса CBaseObj, как это было ранее. А всё, что было связано с работой с объектом класса CBound для установки и возврата размеров элемента, всё уже перенесено в новый класс CBoundedObj и удалено из класса CCanvasBase. Теперь объекты канвасов не объявлены как экземпляры класса, а объявлены как указатели на создаваемые объекты CCanvas:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ class CCanvasBase : public CBoundedObj { private: bool m_chart_mouse_wheel_flag; // Флаг отправки сообщений о прокрутке колёсика мышки bool m_chart_mouse_move_flag; // Флаг отправки сообщений о перемещениях курсора мышки bool m_chart_object_create_flag; // Флаг отправки сообщений о событии создания графического объекта bool m_chart_mouse_scroll_flag; // Флаг прокрутки графика левой кнопкой и колёсиком мышки bool m_chart_context_menu_flag; // Флаг доступа к контекстному меню по нажатию правой клавиши мышки bool m_chart_crosshair_tool_flag; // Флаг доступа к инструменту "Перекрестие" по нажатию средней клавиши мышки bool m_flags_state; // Состояние флагов прокрутки графика колёсиком, контекстного меню и перекрестия //--- Установка запретов для графика (прокрутка колёсиком, контекстное меню и перекрестие) void SetFlags(const bool flag); protected: CCanvas *m_background; // Канвас для рисования фона CCanvas *m_foreground; // Канвас для рисования переднего плана CCanvasBase *m_container; // Родительский объект-контейнер CColorElement m_color_background; // Объект управления цветом фона CColorElement m_color_foreground; // Объект управления цветом переднего плана CColorElement m_color_border; // Объект управления цветом рамки
Методы для получения координат и размеров графических объектов канвасов сделаны публичными, а методы для изменения размеров канвасов — виртуальными:
//--- Возврат координат, границ и размеров графического объекта public: int ObjectX(void) const { return this.m_obj_x; } int ObjectY(void) const { return this.m_obj_y; } int ObjectWidth(void) const { return this.m_background.Width(); } int ObjectHeight(void) const { return this.m_background.Height(); } int ObjectRight(void) const { return this.ObjectX()+this.ObjectWidth()-1; } int ObjectBottom(void) const { return this.ObjectY()+this.ObjectHeight()-1; } //--- Изменяет (1) ширину, (2) высоту, (3) размер графического объекта protected: virtual bool ObjectResizeW(const int size); virtual bool ObjectResizeH(const int size); bool ObjectResize(const int w,const int h); //--- Устанавливает координату (1) X, (2) Y, (3) обе координаты графического объекта virtual bool ObjectSetX(const int x); virtual bool ObjectSetY(const int y); bool ObjectSetXY(const int x,const int y) { return(this.ObjectSetX(x) && this.ObjectSetY(y)); }
В защищённой секции класса объявлен метод для создания канвасов:
//--- Устанавливает указатель на родительский объект-контейнер void SetContainerObj(CCanvasBase *obj); protected: //--- Создаёт канвасы фона и переднего плана bool CreateCanvasObjects(void); //--- Создаёт OBJ_BITMAP_LABEL bool Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h); public: //--- (1) Устанавливает, (2) возвращает состояние void SetState(ENUM_ELEMENT_STATE state) { this.m_state=state; this.ColorsToDefault(); } ENUM_ELEMENT_STATE State(void) const { return this.m_state; }
Метод, устанавливающий флаг разрешения обрезки элемента по границам контейнера, сделан виртуальным:
//--- Устанавливает объекту флаг (1) перемещаемости, (2) главного объекта, (3) возможности изменения размеров //--- обрезки по границам контейнера void SetMovable(const bool flag) { this.m_movable=flag; } void SetAsMain(void) { this.m_main=true; } virtual void SetResizable(const bool flag) { this.m_resizable=flag; } void SetAutorepeat(const bool flag) { this.m_autorepeat_flag=flag; } void SetScrollable(const bool flag) { this.m_scroll_flag=flag; } virtual void SetTrimmered(const bool flag) { this.m_trim_flag=flag; }
В конструкторе класса теперь вызывается метод для создания объектов канвасов фона и переднего плана:
//+------------------------------------------------------------------+ //| CCanvasBase::Конструктор | //+------------------------------------------------------------------+ CCanvasBase::CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_movable(false), m_resizable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false), m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0), m_state(0), m_cursor_delta_x(0), m_cursor_delta_y(0) { //--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y //--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика this.m_chart_id=this.CorrectChartID(chart_id); //--- Если не удалось создать канвасы - уходим if(!this.CreateCanvasObjects()) return; //--- Если графический ресурс и графический объект созданы if(this.Create(this.m_chart_id,this.m_wnd,object_name,x,y,w,h)) { //--- Очищаем канвасы фона и переднего плана и устанавливаем начальные значения координат, //--- наименования графических объектов и свойства текста, рисуемого на переднем плане this.Clear(false); this.m_obj_x=x; this.m_obj_y=y; this.m_color_background.SetName("Background"); this.m_color_foreground.SetName("Foreground"); this.m_color_border.SetName("Border"); this.m_foreground.FontSet(DEF_FONTNAME,-DEF_FONTSIZE*10,FW_MEDIUM); this.m_bound.SetName("Perimeter"); //--- Запоминаем разрешения для мышки и инструментов графика this.Init(); } }
Метод, создающий канвасы фона и переднего плана:
//+------------------------------------------------------------------+ //| CCanvasBase::Создаёт канвасы фона и переднего плана | //+------------------------------------------------------------------+ bool CCanvasBase::CreateCanvasObjects(void) { //--- Если оба канваса уже созданы, или класс не управляет канвасами - возвращаем true if((this.m_background!=NULL && this.m_foreground!=NULL) || !this.m_canvas_owner) return true; //--- Создаём канвас фона this.m_background=new CCanvas(); if(this.m_background==NULL) { ::PrintFormat("%s: Error! Failed to create background canvas",__FUNCTION__); return false; } //--- Создаём канвас переднего плана this.m_foreground=new CCanvas(); if(this.m_foreground==NULL) { ::PrintFormat("%s: Error! Failed to create foreground canvas",__FUNCTION__); return false; } //--- Всё успешно return true; }
В методе, уничтожающем объект, теперь проверяем флаг управления канвасами:
//+------------------------------------------------------------------+ //| CCanvasBase::Уничтожает объект | //+------------------------------------------------------------------+ void CCanvasBase::Destroy(void) { if(this.m_canvas_owner) { this.m_background.Destroy(); this.m_foreground.Destroy(); delete this.m_background; delete this.m_foreground; this.m_background=NULL; this.m_foreground=NULL; } }
Канвасы удаляются только в случае, если объект управляет канвасами. Если же объекту переданы для рисования канвасы другого элемента управления, то он не будет их удалять — они должны быть удалены только тем объектом, которому принадлежат.
В методе инициализации класса по умолчанию для объекта устанавливается флаг управления канвасами:
//+------------------------------------------------------------------+ //| CCanvasBase::Инициализация класса | //+------------------------------------------------------------------+ void CCanvasBase::Init(void) { //--- Запоминаем разрешения для мышки и инструментов графика this.m_chart_mouse_wheel_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL); this.m_chart_mouse_move_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE); this.m_chart_object_create_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE); this.m_chart_mouse_scroll_flag = ::ChartGetInteger(this.m_chart_id, CHART_MOUSE_SCROLL); this.m_chart_context_menu_flag = ::ChartGetInteger(this.m_chart_id, CHART_CONTEXT_MENU); this.m_chart_crosshair_tool_flag= ::ChartGetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL); //--- Устанавливаем разрешения для мышки и графика ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, true); //--- Инициализируем цвета объекта по умолчанию this.InitColors(); //--- Инициализируем миллисекундный таймер ::EventSetMillisecondTimer(16); //--- Флаг владения канвасами this.m_canvas_owner=true; }
Только для тех объектов, у которых нет собственных канвасов, и которые будут наследоваться от класса CBoundedObj, для них нужно будет этот флаг сбрасывать в false.
Теперь откроем файл Controls.mqh и внесём в него доработки.
В разделе макроподстановок напишем две новые, определяющие высоту строки и заголовка таблицы по умолчанию:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define DEF_LABEL_W 50 // Ширина текстовой метки по умолчанию #define DEF_LABEL_H 16 // Высота текстовой метки по умолчанию #define DEF_BUTTON_W 60 // Ширина кнопки по умолчанию #define DEF_BUTTON_H 16 // Высота кнопки по умолчанию #define DEF_TABLE_ROW_H 16 // Высота строки таблицы по умолчанию #define DEF_TABLE_HEADER_H 20 // Высота заголовка таблицы по умолчанию #define DEF_PANEL_W 80 // Ширина панели по умолчанию #define DEF_PANEL_H 80 // Высота панели по умолчанию #define DEF_PANEL_MIN_W 60 // Минимальная ширина панели #define DEF_PANEL_MIN_H 60 // Минимальная высота панели #define DEF_SCROLLBAR_TH 13 // Толщина полосы прокрутки по умолчанию #define DEF_THUMB_MIN_SIZE 8 // Минимальная толщина ползунка полосы прокрутки #define DEF_AUTOREPEAT_DELAY 500 // Задержка перед запуском автоповтора #define DEF_AUTOREPEAT_INTERVAL 100 // Частота автоповторов
В файле есть класс связанного списка объектов CListObj для хранения списка графических элементов. Его мы скопировали в этот файл из файла классов модели таблицы. Если оставить наименование класса тем же, то будет конфликт имён, а объекты, хранящиеся в этих двух списках в разных файлах совершенно разные. Значит, здесь мы оставим класс без изменения, но переименуем его. Это будет класс связанного списка графических элементов:
//+------------------------------------------------------------------+ //| Класс связанного списка графических элементов | //+------------------------------------------------------------------+ class CListElm : public CList { protected: ENUM_ELEMENT_TYPE m_element_type; // Тип создаваемого объекта в CreateElement() public: //--- Установка типа элемента void SetElementType(const ENUM_ELEMENT_TYPE type) { this.m_element_type=type; } //--- Виртуальный метод (1) загрузки списка из файла, (2) создания элемента списка virtual bool Load(const int file_handle); virtual CObject *CreateElement(void); };
По всему файлу произведём замену строки "CListObj" на строку "CListElm".
В методе создания элемента списка впишем новые типы графических элементов:
//+------------------------------------------------------------------+ //| Метод создания элемента списка | //+------------------------------------------------------------------+ CObject *CListElm::CreateElement(void) { //--- В зависимости от типа объекта в m_element_type, создаём новый объект switch(this.m_element_type) { case ELEMENT_TYPE_BASE : return new CBaseObj(); // Базовый объект графических элементов case ELEMENT_TYPE_COLOR : return new CColor(); // Объект цвета case ELEMENT_TYPE_COLORS_ELEMENT : return new CColorElement(); // Объект цветов элемента графического объекта case ELEMENT_TYPE_RECTANGLE_AREA : return new CBound(); // Прямоугольная область элемента case ELEMENT_TYPE_IMAGE_PAINTER : return new CImagePainter(); // Объект для рисования изображений case ELEMENT_TYPE_CANVAS_BASE : return new CCanvasBase(); // Базовый объект холста графических элементов case ELEMENT_TYPE_ELEMENT_BASE : return new CElementBase(); // Базовый объект графических элементов case ELEMENT_TYPE_HINT : return new CVisualHint(); // Подсказка case ELEMENT_TYPE_LABEL : return new CLabel(); // Текстовая метка case ELEMENT_TYPE_BUTTON : return new CButton(); // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : return new CButtonTriggered(); // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : return new CButtonArrowUp(); // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : return new CButtonArrowDown(); // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : return new CButtonArrowLeft(); // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT : return new CButtonArrowRight(); // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : return new CCheckBox(); // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : return new CRadioButton(); // Элемент управления RadioButton case ELEMENT_TYPE_TABLE_CELL : return new CTableCellView(); // Ячейка таблицы (View) case ELEMENT_TYPE_TABLE_ROW : return new CTableRowView(); // Строка таблицы (View) case ELEMENT_TYPE_TABLE_COLUMN_CAPTION : return new CColumnCaptionView(); // Заголовок столбца таблицы (View) case ELEMENT_TYPE_TABLE_HEADER : return new CTableHeaderView(); // Заголовок таблицы (View) case ELEMENT_TYPE_TABLE : return new CTableView(); // Таблица (View) case ELEMENT_TYPE_PANEL : return new CPanel(); // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : return new CGroupBox(); // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : return new CContainer(); // Элемент управления GroupBox default : return NULL; } }
В базовом классе графического элемента добавим метод, возвращающий указатель на объект:
public: //--- Возвращает себя CElementBase *GetObject(void) { return &this; } //--- Возвращает указатель на (1) класс рисования, (2) список подсказок CImagePainter *Painter(void) { return &this.m_painter; } CListElm *GetListHints(void) { return &this.m_list_hints; }
В классе текстовой метки метод, выводящий текст на канвас, сделаем виртуальным:
//--- Устанавливает координату (1) X, (2) Y текста void SetTextShiftH(const int x) { this.ClearText(); this.m_text_x=x; } void SetTextShiftV(const int y) { this.ClearText(); this.m_text_y=y; } //--- Выводит текст virtual void DrawText(const int dx, const int dy, const string text, const bool chart_redraw); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw);
В классе панели CPanel доработаем методы изменения размеров:
//+------------------------------------------------------------------+ //| CPanel::Изменяет ширину объекта | //+------------------------------------------------------------------+ bool CPanel::ResizeW(const int w) { if(!this.ObjectResizeW(w)) return false; this.BoundResizeW(w); this.SetImageSize(w,this.Height()); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } //--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер, //--- проверяем отношение размеров текущего элемента относительно размеров контейнера //--- для отображения полос прокрутки в контейнере при необходимости CContainer *base=this.GetContainer(); if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER) base.CheckElementSizes(&this); //--- В цикле по присоединённым элементам обрезаем каждый элемент по границам контейнера int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.ObjectTrim(); } //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CPanel::Изменяет высоту объекта | //+------------------------------------------------------------------+ bool CPanel::ResizeH(const int h) { if(!this.ObjectResizeH(h)) return false; this.BoundResizeH(h); this.SetImageSize(this.Width(),h); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } //--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер, //--- проверяем отношение размеров текущего элемента относительно размеров контейнера //--- для отображения полос прокрутки в контейнере при необходимости CContainer *base=this.GetContainer(); if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER) base.CheckElementSizes(&this); //--- В цикле по присоединённым элементам обрезаем каждый элемент по границам контейнера int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.ObjectTrim(); } //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CPanel::Изменяет размеры объекта | //+------------------------------------------------------------------+ bool CPanel::Resize(const int w,const int h) { if(!this.ObjectResize(w,h)) return false; this.BoundResize(w,h); this.SetImageSize(w,h); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } //--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер, //--- проверяем отношение размеров текущего элемента относительно размеров контейнера //--- для отображения полос прокрутки в контейнере при необходимости CContainer *base=this.GetContainer(); if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER) base.CheckElementSizes(&this); //--- В цикле по присоединённым элементам обрезаем каждый элемент по границам контейнера int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.ObjectTrim(); } //--- Всё успешно return true; }
Суть доработки методов заключается в том, что если элемент находится в составе контейнера, им если у элемента были изменены размеры, то необходимо проверить новые размеры элемента относительно контейнера. Если элемент стал по размерам больше границ контейнера, то элемент обрезается по размерам контейнера (это ранее уже было реализовано), но при этом у контейнера ещё должны появиться полосы прокрутки. Вот эту проверку при помощи метода объекта-контейнера CheckElementSizes() мы и добавили в методы изменения размеров графического элемента.
В методе рисования внешнего вида объекта панели в классе CPanel, рисуем рамку только, если хотя бы одна из четырёх сторон имеет ненулевое значение параметра BorderWidth:
//+------------------------------------------------------------------+ //| CPanel::Рисует внешний вид | //+------------------------------------------------------------------+ void CPanel::Draw(const bool chart_redraw) { //--- Заливаем объект цветом фона this.Fill(this.BackColor(),false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Задаём цвет для тёмной и светлой линий и рисуем рамку панели color clr_dark =(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(),-20,-20,-20)); color clr_light=(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(), 6, 6, 6)); if(this.BorderWidthBottom()+this.BorderWidthLeft()+this.BorderWidthRight()+this.BorderWidthTop()!=0) this.m_painter.FrameGroupElements(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()), this.m_painter.Width(),this.m_painter.Height(),this.Text(), this.ForeColor(),clr_dark,clr_light,this.AlphaFG(),true); //--- Обновляем канвас фона без перерисовки графика this.m_background.Update(false); //--- Рисуем элементы списка for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL && elm.Type()!=ELEMENT_TYPE_SCROLLBAR_H && elm.Type()!=ELEMENT_TYPE_SCROLLBAR_V) elm.Draw(false); } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
В методе создания и добавления нового элемента в список в классе объекта панели добавим создание новых элементов управления:
//+------------------------------------------------------------------+ //| CPanel::Создаёт и добавляет новый элемент в список | //+------------------------------------------------------------------+ CElementBase *CPanel::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h) { //--- Создаём имя графического объекта int elm_total=this.m_list_elm.Total(); string obj_name=this.NameFG()+"_"+ElementShortName(type)+(string)elm_total; //--- Рассчитываем координаты int x=this.X()+dx; int y=this.Y()+dy; //--- В зависимости от типа объекта, создаём новый объект CElementBase *element=NULL; switch(type) { case ELEMENT_TYPE_LABEL : element = new CLabel(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Текстовая метка case ELEMENT_TYPE_BUTTON : element = new CButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : element = new CButtonTriggered(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : element = new CButtonArrowUp(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : element = new CButtonArrowDown(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : element = new CButtonArrowLeft(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT : element = new CButtonArrowRight(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : element = new CCheckBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : element = new CRadioButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления RadioButton case ELEMENT_TYPE_SCROLLBAR_THUMB_H : element = new CScrollBarThumbH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Полоса прокрутки горизонтального ScrollBar case ELEMENT_TYPE_SCROLLBAR_THUMB_V : element = new CScrollBarThumbV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Полоса прокрутки вертикального ScrollBar case ELEMENT_TYPE_SCROLLBAR_H : element = new CScrollBarH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления горизонтальный ScrollBar case ELEMENT_TYPE_SCROLLBAR_V : element = new CScrollBarV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления вертикальный ScrollBar case ELEMENT_TYPE_TABLE_ROW : element = new CTableRowView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления строки таблицы case ELEMENT_TYPE_TABLE_COLUMN_CAPTION : element = new CColumnCaptionView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления заголовка столбца таблицы case ELEMENT_TYPE_TABLE_HEADER : element = new CTableHeaderView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления заголовка таблицы case ELEMENT_TYPE_TABLE : element = new CTableView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления таблицы case ELEMENT_TYPE_PANEL : element = new CPanel(obj_name,"",this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : element = new CGroupBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : element = new CContainer(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления Container default : element = NULL; } //--- Если новый элемент не создан - сообщаем об этом и возвращаем NULL if(element==NULL) { ::PrintFormat("%s: Error. Failed to create graphic element %s",__FUNCTION__,ElementDescription(type)); return NULL; } //--- Устанавливаем идентификатор, имя, контейнер и z-order элемента element.SetID(elm_total); element.SetName(user_name); element.SetContainerObj(&this); element.ObjectSetZOrder(this.ObjectZOrder()+1); //--- Если созданный элемент не добавлен в список - сообщаем об этом, удаляем созданный элемент и возвращаем NULL if(!this.AddNewElement(element)) { ::PrintFormat("%s: Error. Failed to add %s element with ID %d to list",__FUNCTION__,ElementDescription(type),element.ID()); delete element; return NULL; } //--- Получаем родительский элемент, к которому привязаны дочерние CElementBase *elm=this.GetContainer(); //--- Если родительский элемент имеет тип "Контейнер", значит, у него есть полосы прокрутки if(elm!=NULL && elm.Type()==ELEMENT_TYPE_CONTAINER) { //--- Преобразуем CElementBase в CContainer CContainer *container_obj=elm; //--- Если горизонтальная полоса прокрутки видима, if(container_obj.ScrollBarHorzIsVisible()) { //--- получаем указатель на горизонтальный скроллбар и переносим его на передний план CScrollBarH *sbh=container_obj.GetScrollBarH(); if(sbh!=NULL) sbh.BringToTop(false); } //--- Если вертикальная полоса прокрутки видима, if(container_obj.ScrollBarVertIsVisible()) { //--- получаем указатель на вертикальный скроллбар и переносим его на передний план CScrollBarV *sbv=container_obj.GetScrollBarV(); if(sbv!=NULL) sbv.BringToTop(false); } } //--- Возвращаем указатель на созданный и присоединённый элемент return element; }
В методе, помещающем объект на передний план, добавим обрезку прикреплённых объектов по размерам контейнера:
//+------------------------------------------------------------------+ //| CPanel::Помещает объект на передний план | //+------------------------------------------------------------------+ void CPanel::BringToTop(const bool chart_redraw) { //--- Помещаем панель на передний план CCanvasBase::BringToTop(false); //--- Помещаем на передний план прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) { if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_H || elm.Type()==ELEMENT_TYPE_SCROLLBAR_V) continue; elm.BringToTop(false); elm.ObjectTrim(); } } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
В классе горизонтальной полосы прокрутки есть флаг, указывающий на разрешение обрезки элемента по границам контейнера. Объект полосы прокрутки состоит из нескольких объектов, и для каждого из них нужно устанавливать одинаковое значение флага. Сделаем всё в одном методе. Переопределим в классе метод, устанавливающий флаг обрезки по границам контейнера:
//+------------------------------------------------------------------+ //| Класс горизонтальной полосы прокрутки | //+------------------------------------------------------------------+ class CScrollBarH : public CPanel { protected: CButtonArrowLeft *m_butt_left; // Кнопка со стрелкой влево CButtonArrowRight*m_butt_right; // Кнопка со стрелкой вправо CScrollBarThumbH *m_thumb; // Ползунок скроллбара public: //--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок CButtonArrowLeft *GetButtonLeft(void) { return this.m_butt_left; } CButtonArrowRight*GetButtonRight(void) { return this.m_butt_right; } CScrollBarThumbH *GetThumb(void) { return this.m_thumb; } //--- (1) Устанавливает, (2) возвращает флаг обновления графика void SetChartRedrawFlag(const bool flag) { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag); } bool ChartRedrawFlag(void) const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false); } //--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка int TrackLength(void) const; int TrackBegin(void) const; int ThumbPosition(void) const; //--- Устанавливает позицию ползунка bool SetThumbPosition(const int pos) const { return(this.m_thumb!=NULL ? this.m_thumb.MoveX(pos) : false); } //--- Изменяет размер ползунка bool SetThumbSize(const uint size) const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeW(size) : false); } //--- Изменяет ширину объекта virtual bool ResizeW(const int size); //--- Устанавливает флаг видимости в контейнере virtual void SetVisibleInContainer(const bool flag); //--- Устанавливает флаг обрезки по границам контейнера virtual void SetTrimmered(const bool flag); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_SCROLLBAR_H); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчик прокрутки колёсика (Wheel) virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Конструкторы/деструктор CScrollBarH(void); CScrollBarH(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CScrollBarH(void) {} };
Реализация метода:
//+------------------------------------------------------------------+ //| CScrollBarH::Устанавливает флаг обрезки по границам контейнера | //+------------------------------------------------------------------+ void CScrollBarH::SetTrimmered(const bool flag) { this.m_trim_flag=flag; if(this.m_butt_left!=NULL) this.m_butt_left.SetTrimmered(flag); if(this.m_butt_right!=NULL) this.m_butt_right.SetTrimmered(flag); if(this.m_thumb!=NULL) this.m_thumb.SetTrimmered(flag); }
Для каждого из объектов, составляющих полосу прокрутки, устанавливается переданный в метод флаг.
В методе инициализации объекта теперь не нужно для каждого из элементов устанавливать флаг обрезки по границам контейнера, а достаточно в конце метода вызвать метод SetTrimmered():
//+------------------------------------------------------------------+ //| CScrollBarH::Инициализация | //+------------------------------------------------------------------+ void CScrollBarH::Init(void) { //--- Инициализация родительского класса CPanel::Init(); //--- Фон - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки и текст this.SetBorderWidth(0); this.SetText(""); //--- Создаём кнопки прокрутки int w=this.Height(); int h=this.Height(); this.m_butt_left = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_LEFT, "","ButtL",0,0,w,h); this.m_butt_right= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_RIGHT,"","ButtR",this.Width()-w,0,w,h); if(this.m_butt_left==NULL || this.m_butt_right==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета и вид кнопки со стрелкой влево this.m_butt_left.SetImageBound(1,1,w-2,h-4); this.m_butt_left.InitBackColors(this.m_butt_left.BackColorFocused()); this.m_butt_left.ColorsToDefault(); this.m_butt_left.InitBorderColors(this.BorderColor(),this.m_butt_left.BackColorFocused(),this.m_butt_left.BackColorPressed(),this.m_butt_left.BackColorBlocked()); this.m_butt_left.ColorsToDefault(); //--- Настраиваем цвета и вид кнопки со стрелкой вправо this.m_butt_right.SetImageBound(1,1,w-2,h-4); this.m_butt_right.InitBackColors(this.m_butt_right.BackColorFocused()); this.m_butt_right.ColorsToDefault(); this.m_butt_right.InitBorderColors(this.BorderColor(),this.m_butt_right.BackColorFocused(),this.m_butt_right.BackColorPressed(),this.m_butt_right.BackColorBlocked()); this.m_butt_right.ColorsToDefault(); //--- Создаём ползунок int tsz=this.Width()-w*2; this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_H,"","ThumbH",w,1,tsz-w*4,h-2); if(this.m_thumb==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused()); this.m_thumb.ColorsToDefault(); this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked()); this.m_thumb.ColorsToDefault(); this.m_thumb.SetMovable(true); //--- Запрещаем самостоятельную перерисовку графика this.m_thumb.SetChartRedrawFlag(false); //--- Изначально в контейнере не отображается и не обрезается по его границам this.SetVisibleInContainer(false); this.SetTrimmered(false); }
Точно такие же доработки сделаны и для класса вертикальной полосы прокрутки. Они полностью идентичны рассмотренным, и повторяться не будем.
В классе объекта контейнера метод CheckElementSizes сделаем публичным:
//+------------------------------------------------------------------+ //| Класс Контейнер | //+------------------------------------------------------------------+ class CContainer : public CPanel { private: bool m_visible_scrollbar_h; // Флаг видимости горизонтальной полосы прокрутки bool m_visible_scrollbar_v; // Флаг видимости вертикальной полосы прокрутки int m_init_border_size_top; // Изначальный размер рамки сверху int m_init_border_size_bottom; // Изначальный размер рамки снизу int m_init_border_size_left; // Изначальный размер рамки слева int m_init_border_size_right; // Изначальный размер рамки справа //--- Возвращает тип элемента, отправившего событие ENUM_ELEMENT_TYPE GetEventElementType(const string name); protected: CScrollBarH *m_scrollbar_h; // Указатель на горизонтальную полосу прокрутки CScrollBarV *m_scrollbar_v; // Указатель на вертикальную полосу прокрутки //--- Обработчик перетаскивания граней и углов элемента virtual void ResizeActionDragHandler(const int x, const int y); public: //--- Проверяет размеры элемента для отображения полос прокрутки void CheckElementSizes(CElementBase *element); protected: //--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека горизонтального скроллбара int ThumbSizeHorz(void); int TrackLengthHorz(void) const { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.TrackLength() : 0); } int TrackEffectiveLengthHorz(void) { return(this.TrackLengthHorz()-this.ThumbSizeHorz()); }
Этот метод вызывается из привязанных к контейнеру элементов, и для доступа к нему должен быть публичным.
Основные доработки классов библиотеки мы рассмотрели. За рамками остались некоторые незначительные доработки и исправления. Полные коды можно посмотреть в прилагаемых к статье файлах.
Модель таблицы состоит из ячейки таблицы, строки таблицы и списка строк, являющихся в итоге таблицей. В дополнение к перечисленному для таблицы ещё есть заголовок, состоящий из заголовков столбцов таблицы.
- Ячейка таблицы — это отдельный элемент, хранящий номер строки, номер столбца и содержание;
- Строка таблицы — это список ячеек таблицы, имеет свой номер;
- Таблица — это список строк, может иметь заголовок, представляющий из себя список заголовков столбцов.
Примерно ту же структуру будет иметь компонент View для отображения данных модели таблицы. Объект таблицы будет строиться на, например, массиве данных и массиве заголовков этих данных. Затем в созданный объект визуального отображения таблицы (компонент View) будем передавать указатель на модель таблицы (компонент Model) и все данные будут записаны в ячейки таблицы.
На данном этапе будет создана простая статичная таблица без возможности управления её отображением курсором мышки.
Класс ячейки таблицы (View)
Таблица состоит из списка строк. Строки в свою очередь состоят из объектов, наделённых двумя канвасами для рисования фона и переднего плана. Ячейки таблицы располагаются горизонтально на своих строках. Нет смысла наделять ячейки собственными канвасами. Можно просто "нарезать" каждую строку на области, где каждая область будет соответствовать расположению своей ячейки. И рисовать данные ячейки на канвасе объекта строки таблицы.
Именно для этих целей мы в самом начале сделали промежуточный класс, в котором есть только размер объекта с его координатами и указатель на элемент управления, на канвасе которого будет производиться рисование. И таким объектом будет ячейка таблицы. В неё будем передавать указатель на объект строки таблицы для рисования на её канвасах.
В файле Controls.mqh напишем новый класс:
//+------------------------------------------------------------------+ //| Класс визуального представления ячейки таблицы | //+------------------------------------------------------------------+ class CTableCellView : public CBoundedObj { protected: CTableCell *m_table_cell_model; // Указатель на модель ячейки CImagePainter *m_painter; // Указатель на объект рисования CTableRowView *m_element_base; // Указатель на базовый элемент (строка таблицы) CCanvas *m_background; // Указатель на канвас фона CCanvas *m_foreground; // Указатель на канвас переднего плана int m_index; // Индекс в списке ячеек ENUM_ANCHOR_POINT m_text_anchor; // Точка привязки текста (выравнивание в ячейке) int m_text_x; // Координата X текста (смещение относительно левой границы области объекта) int m_text_y; // Координата Y текста (смещение относительно верхней границы области объекта) ushort m_text[]; // Текст //--- Возвращает смещения начальных координат рисования на холсте относительно канваса и координат базового элемента int CanvasOffsetX(void) const { return(this.m_element_base.ObjectX()-this.m_element_base.X()); } int CanvasOffsetY(void) const { return(this.m_element_base.ObjectY()-this.m_element_base.Y()); } //--- Возвращает скорректированную координату точки на холсте с учётом смещения холста относительно базового элемента int AdjX(const int x) const { return(x-this.CanvasOffsetX()); } int AdjY(const int y) const { return(y-this.CanvasOffsetY()); } //--- Возвращает координаты X и Y текста в зависимости от точки привязки bool GetTextCoordsByAnchor(int &x, int&y); public: //--- (1) Устанавливает, (2) возвращает текст ячейки void SetText(const string text) { ::StringToShortArray(text,this.m_text); } string Text(void) const { return ::ShortArrayToString(this.m_text); } //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_index=this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс ячейки void SetIndex(const int index) { this.SetID(index); } int Index(void) const { return this.m_index; } //--- (1) Устанавливает, (2) возвращает смещение текста по оси X void SetTextShiftX(const int shift) { this.m_text_x=shift; } int TextShiftX(void) const { return this.m_text_x; } //--- (1) Устанавливает, (2) возвращает смещение текста по оси Y void SetTextShiftY(const int shift) { this.m_text_y=shift; } int TextShiftY(void) const { return this.m_text_y; } //--- (1) Устанавливает, (2) возвращает точку привязки текста void SetTextAnchor(const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw); int TextAnchor(void) const { return this.m_text_anchor; } //--- Устанавливает точку привязки и смещения текста void SetTextPosition(const ENUM_ANCHOR_POINT anchor,const int shift_x,const int shift_y,const bool cell_redraw,const bool chart_redraw); //--- Назначает базовый элемент (строку таблицы) void RowAssign(CTableRowView *base_element); //--- (1) Назначает, (2) возвращает модель ячейки bool TableCellModelAssign(CTableCell *cell_model,int dx,int dy,int w,int h); CTableCell *GetTableCellModel(void) { return this.m_table_cell_model; } //--- Распечатывает в журнале назначенную модель ячейки void TableCellModelPrint(void); //--- (1) Заливает объект цветом фона, (2) обновляет объект для отображения изменений, (3) рисует внешний вид virtual void Clear(const bool chart_redraw); virtual void Update(const bool chart_redraw); virtual void Draw(const bool chart_redraw); //--- Выводит текст virtual void DrawText(const int dx, const int dy, const string text, const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CBaseObj::Compare(node,mode); } virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_CELL); } //--- Инициализация объекта класса void Init(const string text); //--- Возвращает описание объекта virtual string Description(void); //--- Конструкторы/деструктор CTableCellView(void); CTableCellView(const int id, const string user_name, const string text, const int x, const int y, const int w, const int h); ~CTableCellView (void){} };
Все переменные и методы здесь прокомментированы в коде. Индекс ячейки и её идентификатор здесь это одно и то же. Поэтому и переопределён виртуальный метод SetID(), где индексу и идентификатору устанавливаются одинаковые значения. Давайте рассмотрим реализацию объявленных методов.
В конструкторах класса вызывается метод инициализации объекта, устанавливается идентификатор ячейки и её наименование:
//+------------------------------------------------------------------+ //| CTableCellView::Конструктор по умолчанию. Строит объект в главном| //| окне текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CTableCellView::CTableCellView(void) : CBoundedObj("TableCell",-1,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(-1),m_text_anchor(ANCHOR_LEFT) { //--- Инициализация this.Init(""); this.SetID(-1); this.SetName("TableCell"); } //+------------------------------------------------------------------+ //| CTableCellView::Конструктор параметрический. Строит объект | //| в указанном окне указанного графика с указанными текстом, | //| координатами и размерами | //+------------------------------------------------------------------+ CTableCellView::CTableCellView(const int id, const string user_name, const string text, const int x, const int y, const int w, const int h) : CBoundedObj(user_name,id,x,y,w,h), m_index(-1),m_text_anchor(ANCHOR_LEFT) { //--- Инициализация this.Init(text); this.SetID(id); this.SetName(user_name); }
Метод инициализации объекта класса:
//+------------------------------------------------------------------+ //| CTableCellView::Инициализация | //+------------------------------------------------------------------+ void CTableCellView::Init(const string text) { //--- Класс не управляет канвасами this.m_canvas_owner=false; //--- Текст ячейки this.SetText(text); //--- Смещения текста по умолчанию this.m_text_x=2; this.m_text_y=0; }
Обязательно устанавливаем объекту флаг, что он не управляет канвасами — чтобы при уничтожении объекта он не удалил чужие канвасы.
Метод, возвращающий описание объекта:
//+------------------------------------------------------------------+ //| CTableCellView::Возвращает описание объекта | //+------------------------------------------------------------------+ string CTableCellView::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); return ::StringFormat("%s%s ID %d, X %d, Y %d, W %d, H %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID(),this.X(),this.Y(),this.Width(),this.Height()); }
Метод выводит в журнал строку с наименованием типа объекта, идентификатором, координатами и размерами ячейки.
Метод, назначающий ячейке строку таблицы, канвасы фона и переднего плана:
//+------------------------------------------------------------------+ //| CTableCellView::Назначает строку, канвасы фона и переднего плана | //+------------------------------------------------------------------+ void CTableCellView::RowAssign(CTableRowView *base_element) { if(base_element==NULL) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return; } this.m_element_base=base_element; this.m_background=this.m_element_base.GetBackground(); this.m_foreground=this.m_element_base.GetForeground(); this.m_painter=this.m_element_base.Painter(); }
В метод передаётся указатель на объект строки таблицы (описание класса будет далее) и, если указатель не валидный — сообщаем об этом и возвращаем NULL. Далее в переменные класса записываются указатель на строку таблицы, и из объекта строки таблицы — указатели на канвасы фона и переднего плана, и на объект рисования.
В объекте модели таблицы тоже есть свои строки и ячейки. Модель ячейки таблицы назначается этому классу, и он рисует на канвасах объекта строки данные из модели ячейки.
Метод, назначающий модель ячейки:
//+------------------------------------------------------------------+ //| CTableCellView::Назначает модель ячейки | //+------------------------------------------------------------------+ bool CTableCellView::TableCellModelAssign(CTableCell *cell_model,int dx,int dy,int w,int h) { //--- Если передан невалидный объект модели ячейки - сообщаем об этом и возвращаем false if(cell_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Если базовый элемент (строка таблицы) не назначен - сообщаем об этом и возвращаем false if(this.m_element_base==NULL) { ::PrintFormat("%s: Error. Base element not assigned. Please use RowAssign() method first",__FUNCTION__); return false; } //--- Сохраняем модель ячейки this.m_table_cell_model=cell_model; //--- Устанавливаем координаты и размеры визуального представления ячейки this.BoundSetXY(dx,dy); this.BoundResize(w,h); //--- Устанавливаем размеры области рисования визуального представления ячейки this.m_painter.SetBound(dx,dy,w,h); //--- Всё успешно return true; }
Сначала проверяется валидность переданного указателя на модель ячейки, затем проверяется, что строка таблицы уже назначена этому объекту ячейки. Если что-то из этого не верно, то об этом сообщается в журнале и возвращается false. Далее указатель на модель ячейки сохраняется в переменной и устанавливаются координаты и размеры области ячейки на соответствующей ей строке.
При сортировке строк и столбцов таблицы просто будет переназначаться указатель на объект ячейки, и этот объект, имеющий свои точные координаты в таблице, будет рисовать данные новой назначенной ему ячейки.
Метод, возвращающий координаты X и Y текста в зависимости от точки привязки:
//+------------------------------------------------------------------+ //| CTableCellView::Возвращает координаты X и Y текста | //| в зависимости от точки привязки | //+------------------------------------------------------------------+ bool CTableCellView::GetTextCoordsByAnchor(int &x,int &y) { //--- Получаем размеры текста в ячейке int text_w=0, text_h=0; this.m_foreground.TextSize(this.Text(),text_w,text_h); if(text_w==0 || text_h==0) return false; //--- В зависимости от точки привязки текста в ячейке //--- рассчитываем его начальные координаты (верхний левый угол) switch(this.m_text_anchor) { //--- Точка привязки слева по центру case ANCHOR_LEFT : x=0; y=(this.Height()-text_h)/2; break; //--- Точка привязки в левом нижнем углу case ANCHOR_LEFT_LOWER : x=0; y=this.Height()-text_h; break; //--- Точка привязки снизу по центру case ANCHOR_LOWER : x=(this.Width()-text_w)/2; y=this.Height()-text_h; break; //--- Точка привязки в правом нижнем углу case ANCHOR_RIGHT_LOWER : x=this.Width()-text_w; y=this.Height()-text_h; break; //--- Точка привязки справа по центру case ANCHOR_RIGHT : x=this.Width()-text_w; y=(this.Height()-text_h)/2; break; //--- Точка привязки в правом верхнем углу case ANCHOR_RIGHT_UPPER : x=this.Width()-text_w; y=0; break; //--- Точка привязки сверху по центру case ANCHOR_UPPER : x=(this.Width()-text_w)/2; y=0; break; //--- Точка привязки строго по центру объекта case ANCHOR_CENTER : x=(this.Width()-text_w)/2; y=(this.Height()-text_h)/2; break; //--- Точка привязки в левом верхнем углу //---ANCHOR_LEFT_UPPER default: x=0; y=0; break; } return true; }
В метод передаются по ссылке переменные, в которые будут записаны рассчитанные координаты расположения верхнего левого угла выводимого текста. При неудаче получения размеров текста ячейки метод возвращает false.
После расчёта координат верхнего левого угла, в переменные записываются рассчитанные координаты текста и возвращается true.
Метод, устанавливающий точку привязки текста:
//+------------------------------------------------------------------+ //| CTableCellView::Устанавливает точку привязки текста | //+------------------------------------------------------------------+ void CTableCellView::SetTextAnchor(const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw) { if(this.m_text_anchor==anchor) return; this.m_text_anchor=anchor; if(cell_redraw) this.Draw(chart_redraw); }
Сначала в переменную записывается новая точка привязки, затем, если установлен флаг перерисовки этой ячейки — вызывается метод её рисования с указанным флагом перерисовки графика.
Метод, устанавливающий точку привязки и смещения текста:
//+------------------------------------------------------------------+ //| CTableCellView::Устанавливает точку привязки и смещения текста | //+------------------------------------------------------------------+ void CTableCellView::SetTextPosition(const ENUM_ANCHOR_POINT anchor,const int shift_x,const int shift_y,const bool cell_redraw,const bool chart_redraw) { this.SetTextShiftX(shift_x); this.SetTextShiftY(shift_y); this.SetTextAnchor(anchor,cell_redraw,chart_redraw); }
В этом методе дополнительно к установке точки привязки устанавливаются начальные смещения текста по осям X и Y.
Метод, заливающий объект цветом:
//+------------------------------------------------------------------+ //| CTableCellView::Заливает объект цветом | //+------------------------------------------------------------------+ void CTableCellView::Clear(const bool chart_redraw) { //--- Устанавливаем корректные координаты углов ячейки int x1=this.AdjX(this.m_bound.X()); int y1=this.AdjY(this.m_bound.Y()); int x2=this.AdjX(this.m_bound.Right()); int y2=this.AdjY(this.m_bound.Bottom()); //--- Стираем фон и передний план внутри прямоугольной области расположения ячейки if(this.m_background!=NULL) this.m_background.FillRectangle(x1,y1,x2,y2-1,::ColorToARGB(this.m_element_base.BackColor(),this.m_element_base.AlphaBG())); if(this.m_foreground!=NULL) this.m_foreground.FillRectangle(x1,y1,x2,y2-1,clrNULL); }
Получаем корректные координаты четырёх углов прямоугольной области, а затем заливаем фон цветом фона строки таблицы, а передний план — прозрачным цветом.
Метод, обновляющий объект для отображения изменений:
//+------------------------------------------------------------------+ //| CTableCellView::Обновляет объект для отображения изменений | //+------------------------------------------------------------------+ void CTableCellView::Update(const bool chart_redraw) { if(this.m_background!=NULL) this.m_background.Update(false); if(this.m_foreground!=NULL) this.m_foreground.Update(chart_redraw); }
Если канвасы фона и переднего плана валидны — вызываются их методы обновления с указанным флагом перерисовки графика.
Метод, рисущий внешний вид ячейки:
//+------------------------------------------------------------------+ //| CTableCellView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableCellView::Draw(const bool chart_redraw) { //--- Получаем координаты текста в зависимости от точки привязки int text_x=0, text_y=0; if(!this.GetTextCoordsByAnchor(text_x,text_y)) return; //--- Корректируем координаты текста int x=this.AdjX(this.X()+text_x); int y=this.AdjY(this.Y()+text_y); //--- Устанавливаем координаты разделительной линии int x1=this.AdjX(this.X()); int x2=this.AdjX(this.X()); int y1=this.AdjY(this.Y()); int y2=this.AdjY(this.Bottom()); //--- Выводим текст на канвасе переднего плана без обновления графика this.DrawText(x+this.m_text_x,y+this.m_text_y,this.Text(),false); //--- Если это не крайняя справа ячейка - рисуем у ячейки справа вертикальную разделительную полосу if(this.m_element_base!=NULL && this.Index()<this.m_element_base.CellsTotal()-1) { int line_x=this.AdjX(this.Right()); this.m_background.Line(line_x,y1,line_x,y2,::ColorToARGB(this.m_element_base.BorderColor(),this.m_element_base.AlphaBG())); } //--- Обновляем канвас фона с указанным флагом перерисовки графика this.m_background.Update(chart_redraw); }
Логика метода расписана в комментариях. Корректировка координат необходима для случая, если объект обрезан по границам контейнера — в этом случае начальные координаты рассчитываются не от реальных координат графического объекта канваса, а от координат графического элемента, которые виртуально расположены за пределами границ контейнера.
Разделительные полосы рисуются только с правой стороны ячейки, и в случае, если это не крайняя справа ячейка — она ограничена своим контейнером, и там рисовать дополнительно линию не целесообразно.
Метод, выводящий текст ячейки:
//+------------------------------------------------------------------+ //| CTableCellView::Выводит текст | //+------------------------------------------------------------------+ void CTableCellView::DrawText(const int dx,const int dy,const string text,const bool chart_redraw) { //--- Проверяем базовый элемент if(this.m_element_base==NULL) return; //--- Очищаем ячейку и устанавливаем текст this.Clear(false); this.SetText(text); //--- Выводим установленный текст на канвасе переднего плана this.m_foreground.TextOut(dx,dy,this.Text(),::ColorToARGB(this.m_element_base.ForeColor(),this.m_element_base.AlphaFG())); //--- Если текст выходит за правую границу области ячейки if(this.Right()-dx<this.m_foreground.TextWidth(text)) { //--- Получаем размеры текста "троеточие" int w=0,h=0; this.m_foreground.TextSize("... ",w,h); if(w>0 && h>0) { //--- Стираем текст у правой границы объекта по размеру текста "троеточие" и заменяем троеточием окончание текста метки this.m_foreground.FillRectangle(this.AdjX(this.Right())-w,this.AdjY(this.Y()),this.AdjX(this.Right()),this.AdjY(this.Y())+h,clrNULL); this.m_foreground.TextOut(this.AdjX(this.Right())-w,this.AdjY(dy),"...",::ColorToARGB(this.m_element_base.ForeColor(),this.m_element_base.AlphaFG())); } } //--- Обновляем канвас переднего плана с указанным флагом перерисовки графика this.m_foreground.Update(chart_redraw); }
Логика метода полностью расписана в комментариях к коду. В случае, если выводимый текст выходит за пределы области ячейки, то он обрезается по границам области, и его окончание заменяется троеточием ("..."), указывающим, что не весь текст поместился в размер ячейки по её ширине.
Метод, распечатывающий в журнале назначенную модель строки:
//+------------------------------------------------------------------+ //| CTableCellView::Распечатывает в журнале назначенную модель строки| //+------------------------------------------------------------------+ void CTableCellView::TableCellModelPrint(void) { if(this.m_table_cell_model!=NULL) this.m_table_cell_model.Print(); }
Метод позволяет проконтролировать, какая ячейки компонента Model назначена на ячейку компонента View.
Методы для работы с файлами:
//+------------------------------------------------------------------+ //| CTableCellView::Сохранение в файл | //+------------------------------------------------------------------+ bool CTableCellView::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем номер ячейки if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем точку привязки текста if(::FileWriteInteger(file_handle,this.m_text_anchor,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем координату X текста if(::FileWriteInteger(file_handle,this.m_text_x,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем координату Y текста if(::FileWriteInteger(file_handle,this.m_text_y,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем текст if(::FileWriteArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CTableCellView::Загрузка из файла | //+------------------------------------------------------------------+ bool CTableCellView::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CBaseObj::Load(file_handle)) return false; //--- Загружаем номер ячейки this.m_id=this.m_index=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем точку привязки текста this.m_text_anchor=(ENUM_ANCHOR_POINT)::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем координату X текста this.m_text_x=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем координату Y текста this.m_text_y=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем текст if(::FileReadArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Всё успешно return true; }
Методы позволяют сохранить в файл и загрузить из файла данные ячейки таблицы.
Теперь рассмотрим класс визуального представления строки таблицы.
Класс строки таблицы (View)
Строка таблицы — это объект, унаследованный от класса панели. Имеет список объектов ячеек класса CTableCellView.
Продолжим писать код в файле Controls.mqh:
//+------------------------------------------------------------------+ //| Класс визуального представления строки таблицы | //+------------------------------------------------------------------+ class CTableRowView : public CPanel { protected: CTableCellView m_temp_cell; // Временный объект ячейки для поиска CTableRow *m_table_row_model; // Указатель на модель строки CListElm m_list_cells; // Список ячеек int m_index; // Индекс в списке строк //--- Создаёт и добавляет в список новый объект представления ячейки CTableCellView *InsertNewCellView(const int index,const string text,const int dx,const int dy,const int w,const int h); public: //--- Возвращает (1) список, (2) количество ячеек CListElm *GetListCells(void) { return &this.m_list_cells; } int CellsTotal(void) const { return this.m_list_cells.Total(); } //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_index=this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс строки void SetIndex(const int index) { this.SetID(index); } int Index(void) const { return this.m_index; } //--- (1) Устанавливает, (2) возвращает модель строки bool TableRowModelAssign(CTableRow *row_model); CTableRow *GetTableRowModel(void) { return this.m_table_row_model; } //--- Распечатывает в журнале назначенную модель строки void TableRowModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CLabel::Compare(node,mode);} virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_ROW); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Конструкторы/деструктор CTableRowView(void); CTableRowView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableRowView (void){} };
У строки таблицы так же, как и у ячейки, свойство индекса строки равно её идентификатору. Здесь тоже виртуальный метод установки идентификатора переопределён, и устанавливает одновременно оба свойства — индекс и идентификатор.
В конструкторах класса вызывается метод инициализации объекта:
//+------------------------------------------------------------------+ //| CTableRowView::Конструктор по умолчанию. Строит объект в главном | //| окне текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CTableRowView::CTableRowView(void) : CPanel("TableRow","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(-1) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CTableRowView::Конструктор параметрический. Строит объект в | //| указанном окне указанного графика с указанными текстом, | //| координатами и размерами | //+------------------------------------------------------------------+ CTableRowView::CTableRowView(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,chart_id,wnd,x,y,w,h), m_index(-1) { //--- Инициализация this.Init(); }
Инициализация объекта:
//+------------------------------------------------------------------+ //| CTableRowView::Инициализация | //+------------------------------------------------------------------+ void CTableRowView::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Фон - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки this.SetBorderWidth(1); }
Метод инициализации цветов объекта по умолчанию:
//+------------------------------------------------------------------+ //| CTableRowView::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CTableRowView::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(C'200,200,200',C'200,200,200',C'200,200,200',clrSilver); this.InitBorderColorsAct(C'200,200,200',C'200,200,200',C'200,200,200',clrSilver); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrSilver); this.InitForeColorBlocked(clrSilver); }
Метод, создающий и добавляющий в список новый объект представления ячейки:
//+------------------------------------------------------------------+ //| CTableRowView::Создаёт и добавляет в список | //| новый объект представления ячейки | //+------------------------------------------------------------------+ CTableCellView *CTableRowView::InsertNewCellView(const int index,const string text,const int dx,const int dy,const int w,const int h) { //--- Проверяем есть ли в списке объект с указанным идентификатором и, если да - сообщаем об этом и возвращаем NULL this.m_temp_cell.SetIndex(index); //--- Устанавливаем списку флаг сортировки по идентификатору this.m_list_cells.Sort(ELEMENT_SORT_BY_ID); if(this.m_list_cells.Search(&this.m_temp_cell)!=NULL) { ::PrintFormat("%s: Error. The TableCellView object with index %d is already in the list",__FUNCTION__,index); return NULL; } //--- Создаём имя и идентификатор объекта ячейки string name="TableCellView"+(string)this.Index()+"x"+(string)index; int id=this.m_list_cells.Total(); //--- Создаём новый объект TableCellView; при неудаче - сообщаем об этом и возвращаем NULL CTableCellView *cell_view=new CTableCellView(id,name,""+text,dx,dy,w,h); if(cell_view==NULL) { ::PrintFormat("%s: Error. Failed to create CTableCellView object",__FUNCTION__); return NULL; } //--- Если новый объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем NULL if(this.m_list_cells.Add(cell_view)==-1) { ::PrintFormat("%s: Error. Failed to add CTableCellView object to list",__FUNCTION__); delete cell_view; return NULL; } //--- Назначаем базовый элемент и возвращаем указатель на объект cell_view.RowAssign(this.GetObject()); return cell_view; }
Помимо того, что каждая ячейка назначается на свою область объекта строки таблицы, всё равно объекты, создаваемые посредством оператора new, необходимо хранить в списке, либо отслеживать их самостоятельно и удалять при необходимости.
Проще хранить их в списке — тогда подсистема терминала сама будет отслеживать, когда их нужно удалять. Метод создаёт новый объект представления ячейки и помещает его в список ячеек строки. После создания объекта, ему назначается строка, в которой он расположен, и на канвасах которой созданный объект будет рисовать данные модели ячейки таблицы.
Метод, устанавливающий модель строки:
//+------------------------------------------------------------------+ //| CTableRowView::Устанавливает модель строки | //+------------------------------------------------------------------+ bool CTableRowView::TableRowModelAssign(CTableRow *row_model) { //--- Если передан пустой объект - сообщаем об этом и возвращаем false if(row_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Если в переданной модели строки нет ни одной ячейки - сообщаем об этом и возвращаем false int total=(int)row_model.CellsTotal(); if(total==0) { ::PrintFormat("%s: Error. Row model does not contain any cells",__FUNCTION__); return false; } //--- Сохраняем указатель на переданную модель строки и рассчитываем ширину ячейки this.m_table_row_model=row_model; int cell_w=(int)::round((double)this.Width()/(double)total); //--- В цикле по количеству ячеек в модели строки for(int i=0;i<total;i++) { //--- получаем модель очередной ячейки, CTableCell *cell_model=this.m_table_row_model.GetCell(i); if(cell_model==NULL) return false; //--- рассчитываем координату и создаём имя для области ячейки int x=cell_w*i; string name="CellBound"+(string)this.m_table_row_model.Index()+"x"+(string)i; //--- Создаём новую область ячейки CBound *cell_bound=this.InsertNewBound(name,x,0,cell_w,this.Height()); if(cell_bound==NULL) return false; //--- Создаём новый объект визуального представления ячейки CTableCellView *cell_view=this.InsertNewCellView(i,cell_model.Value(),x,0,cell_w,this.Height()); if(cell_view==NULL) return false; //--- На текущую область ячейки назначаем соответствующий объект визуального представления ячейки cell_bound.AssignObject(cell_view); } //--- Всё успешно return true; }
В метод передаётся указатель на модель строки, и в цикле по количеству ячеек в модели, создаются новые области расположения объектов представления ячеек и сами ячейки (класс CTableCellView). И каждая новая такая ячейка назначается на каждую новую область. В итоге получаем список областей ячеек, на которые назначены указатели на соответствующие ячейки таблицы.
Метод, рисующий внешний вид строки:
//+------------------------------------------------------------------+ //| CTableRowView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableRowView::Draw(const bool chart_redraw) { //--- Заливаем объект цветом фона, рисуем линию строки и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Line(this.AdjX(0),this.AdjY(this.Height()-1),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); //--- Рисуем ячейки строки int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- Получаем область очередной ячейки CBound *cell_bound=this.GetBoundAt(i); if(cell_bound==NULL) continue; //--- Из области ячейки получаем присоединённый объект ячейки CTableCellView *cell_view=cell_bound.GetAssignedObj(); //--- Рисуем визуальное представление ячейки if(cell_view!=NULL) cell_view.Draw(false); } //--- Обновляем канвасы фона и переднего плана с указанным флагом перерисовки графика this.Update(chart_redraw); }
Сначала строка заливается цветом фона и снизу рисуется линия. Затем в цикле по областям ячеек строки получаем очередную область ячейки, из неё получаем указатель на объект ячейки и вызываем его метод рисования.
Метод, распечатывающий в журнале назначенную модель строки:
//+------------------------------------------------------------------+ //| CTableRowView::Распечатывает в журнале назначенную модель строки | //+------------------------------------------------------------------+ void CTableRowView::TableRowModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS) { if(this.m_table_row_model!=NULL) this.m_table_row_model.Print(detail,as_table,cell_width); }
Метод позволяет проконтролировать какая модель строки из модели таблицы назначена на этот объект визуального представления строки.
Методы для работы с файлами:
//+------------------------------------------------------------------+ //| CTableRowView::Сохранение в файл | //+------------------------------------------------------------------+ bool CTableRowView::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CPanel::Save(file_handle)) return false; //--- Сохраняем список ячеек if(!this.m_list_cells.Save(file_handle)) return false; //--- Сохраняем номер строки if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CTableRowView::Загрузка из файла | //+------------------------------------------------------------------+ bool CTableRowView::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CPanel::Load(file_handle)) return false; //--- Загружаем список ячеек if(!this.m_list_cells.Load(file_handle)) return false; //--- Загружаем номер строки this.m_id=this.m_index=(uchar)::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Методы позволяют сохранить в файл и загрузить из файла данные строки таблицы.
У каждой таблицы должен быть заголовок. Он позволяет понимать, какие данные отображаются в столбцах таблицы. Заголовок таблицы — это список заголовков столбцов. Каждому столбцу таблицы соответствует свой заголовок, отображающий тип и название данных, расположенных в столбце.
Класс заголовка столбца таблицы (View)
Заголовок столбца таблицы — это самостоятельный объект, унаследованный от объекта "Кнопка" (CButton), наследующий поведение этого объекта и дополняющий его. В текущей реализации свойства кнопки не будут дополняться — просто поменяем цвета различных состояний. В следующей статье добавим к заголовку индикацию направления сортировки — стрелочки вверх/вниз в правой стороне пространства кнопки и событийную модель для запуска сортировки по нажатию на кнопку.
Продолжим писать код в файле Controls.mqh:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка столбца таблицы | //+------------------------------------------------------------------+ class CColumnCaptionView : public CButton { protected: CColumnCaption *m_column_caption_model; // Указатель на модель заголовка столбца int m_index; // Индекс в списке столбцов public: //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_index=this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс ячейки void SetIndex(const int index) { this.SetID(index); } int Index(void) const { return this.m_index; } //--- (1) Назначает, (2) возвращает модель заголовка столбца bool ColumnCaptionModelAssign(CColumnCaption *caption_model); CColumnCaption *ColumnCaptionModel(void) { return this.m_column_caption_model; } //--- Распечатывает в журнале назначенную модель заголовка столбца void ColumnCaptionModelPrint(void); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CButton::Compare(node,mode); } virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_COLUMN_CAPTION); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void); //--- Возвращает описание объекта virtual string Description(void); //--- Конструкторы/деструктор CColumnCaptionView(void); CColumnCaptionView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CColumnCaptionView (void){} };
Метод имеет индекс, равный идентификатору, как и у двух предыдущих рассмотренных классов. И метод установки идентификатора аналогичен предыдущим классам.
В конструкторах класса вызывается метод инициализации объекта и устанавливается нулевой идентификатор (он меняется после создания объекта):
//+------------------------------------------------------------------+ //| CColumnCaptionView::Конструктор по умолчанию. Строит объект | //| в главном окне текущего графика в координатах 0,0 | //| с размерами по умолчанию | //+------------------------------------------------------------------+ CColumnCaptionView::CColumnCaptionView(void) : CButton("ColumnCaption","Caption",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(0) { //--- Инициализация this.Init("Caption"); this.SetID(0); this.SetName("ColumnCaption"); } //+------------------------------------------------------------------+ //| CColumnCaptionView::Конструктор параметрический. | //| Строит объект в указанном окне указанного графика с | //| указанными текстом, координатами и размерами | //+------------------------------------------------------------------+ CColumnCaptionView::CColumnCaptionView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h) : CButton(object_name,text,chart_id,wnd,x,y,w,h), m_index(0) { //--- Инициализация this.Init(text); this.SetID(0); }
Метод инициализации объекта:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Инициализация | //+------------------------------------------------------------------+ void CColumnCaptionView::Init(const string text) { //--- Смещения текста по умолчанию this.m_text_x=4; this.m_text_y=2; //--- Устанавливаем цвета различных состояний this.InitColors(); }
Метод инициализации цветов объекта по умолчанию:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CColumnCaptionView::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke,this.GetBackColorControl().NewColor(clrWhiteSmoke,-6,-6,-6),clrWhiteSmoke,clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrLightGray,clrLightGray,clrLightGray,clrLightGray); this.InitBorderColorsAct(clrLightGray,clrLightGray,clrLightGray,clrLightGray); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrSilver); }
Метод, рисующий внешний вид заголовка столбца:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Рисует внешний вид | //+------------------------------------------------------------------+ void CColumnCaptionView::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем слева светлую вертикальную линию, справа - тёмную this.Fill(this.BackColor(),false); color clr_dark =this.BorderColor(); // "Тёмный цвет" color clr_light=this.GetBackColorControl().NewColor(this.BorderColor(), 100, 100, 100); // "Светлый цвет" this.m_background.Line(this.AdjX(0),this.AdjY(0),this.AdjX(0),this.AdjY(this.Height()-1),::ColorToARGB(clr_light,this.AlphaBG())); // Линия слева this.m_background.Line(this.AdjX(this.Width()-1),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(clr_dark,this.AlphaBG())); // Линия справа //--- обновляем канвас фона this.m_background.Update(false); //--- Выводим текст заголовка CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Сначала фон заливается установленным цветом, затем рисуются две вертикальные линии — слева светлая, а справа — тёмная. Далее на канвас переднего плана выводится текст заголовка.
Метод, возвращающий описание объекта:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Возвращает описание объекта | //+------------------------------------------------------------------+ string CColumnCaptionView::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); return ::StringFormat("%s%s ID %d, X %d, Y %d, W %d, H %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID(),this.X(),this.Y(),this.Width(),this.Height()); }
Метод возвращает описание типа элемента с идентификатором, координатами и размерами объекта.
Метод, назначающий модель заголовка столбца:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Назначает модель заголовка столбца | //+------------------------------------------------------------------+ bool CColumnCaptionView::ColumnCaptionModelAssign(CColumnCaption *caption_model) { //--- Если передан невалидный объект модели заголовка столбца - сообщаем об этом и возвращаем false if(caption_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Сохраняем модель заголовка столбца this.m_column_caption_model=caption_model; //--- Устанавливаем размеры области рисования визуального представления заголовка столбца this.m_painter.SetBound(0,0,this.Width(),this.Height()); //--- Всё успешно return true; }
В метод передаётся указатель на модель заголовка столбца, который сохраняется в переменную. Далее объекту рисования устанавливаются координаты и размеры области рисования.
Метод, распечатывающий в журнале назначенную модель заголовка столбца:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Распечатывает в журнале | //| назначенную модель заголовка столбца | //+------------------------------------------------------------------+ void CColumnCaptionView::ColumnCaptionModelPrint(void) { if(this.m_column_caption_model!=NULL) this.m_column_caption_model.Print(); }
Позволяет проконтролировать, распечатав в журнале описание модели заголовка столбца, назначенной на объект, визуального представления заголовка.
Методы для работы с файлами:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Сохранение в файл | //+------------------------------------------------------------------+ bool CColumnCaptionView::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CButton::Save(file_handle)) return false; //--- Сохраняем номер заголовка if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CColumnCaptionView::Загрузка из файла | //+------------------------------------------------------------------+ bool CColumnCaptionView::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CButton::Load(file_handle)) return false; //--- Загружаем номер заголовка this.m_id=this.m_index=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Предоставляют возможность сохранения и загрузки параметров заголовка из файла.
Класс заголовка таблицы (View)
Заголовок таблицы — это обычный список объектов заголовков столбцов, основанный на элементе управления Панель (CPanel). Такой объект в итоге предоставляет инструменты управления столбцами таблицы.
В данной реализации это будет обычный статический объект. Весь функционал интерактивного взаимодействия с пользователем будет добавлен позже.
Продолжим писать код в том же файле:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка таблицы | //+------------------------------------------------------------------+ class CTableHeaderView : public CPanel { protected: CColumnCaptionView m_temp_caption; // Временный объект заголовка столбца для поиска CTableHeader *m_table_header_model; // Указатель на модель заголовка таблицы //--- Создаёт и добавляет в список новый объект представления заголовка столбца CColumnCaptionView *InsertNewColumnCaptionView(const string text, const int x, const int y, const int w, const int h); public: //--- (1) Устанавливает, (2) возвращает модель заголовка таблицы bool TableHeaderModelAssign(CTableHeader *header_model); CTableHeader *GetTableHeaderModel(void) { return this.m_table_header_model; } //--- Распечатывает в журнале назначенную модель заголовка таблицы void TableHeaderModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CPanel::Compare(node,mode); } virtual bool Save(const int file_handle) { return CPanel::Save(file_handle); } virtual bool Load(const int file_handle) { return CPanel::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_HEADER); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Конструкторы/деструктор CTableHeaderView(void); CTableHeaderView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableHeaderView (void){} };
Объекту назначается модель заголовка таблицы, и по его содержимому создаются и добавляются в список прикреплённых элементов объекты заголовков столбцов.
В конструкторах класса вызывается метод инициализации объекта:
//+------------------------------------------------------------------+ //| CTableHeaderView::Конструктор по умолчанию. Строит объект в | //| главном окне текущего графика в координатах 0,0 | //| с размерами по умолчанию | //+------------------------------------------------------------------+ CTableHeaderView::CTableHeaderView(void) : CPanel("TableHeader","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CTableHeaderView::Конструктор параметрический. Строит объект в | //| указанном окне указанного графика с указанными текстом, | //| координатами и размерами | //+------------------------------------------------------------------+ CTableHeaderView::CTableHeaderView(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(); }
Метод инициализации объекта:
//+------------------------------------------------------------------+ //| CTableHeaderView::Инициализация | //+------------------------------------------------------------------+ void CTableHeaderView::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Цвет фона - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки this.SetBorderWidth(1); }
Метод инициализации цветов объекта по умолчанию:
//+------------------------------------------------------------------+ //| CTableHeaderView::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CTableHeaderView::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(C'200,200,200',C'200,200,200',C'200,200,200',clrSilver); this.InitBorderColorsAct(C'200,200,200',C'200,200,200',C'200,200,200',clrSilver); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrSilver); this.InitForeColorBlocked(clrSilver); }
Метод, создающий и добавляющий в список новый объект представления заголовка столбца:
//+------------------------------------------------------------------+ //| CTableHeaderView::Создаёт и добавляет в список | //| новый объект представления заголовка столбца | //+------------------------------------------------------------------+ CColumnCaptionView *CTableHeaderView::InsertNewColumnCaptionView(const string text,const int x,const int y,const int w,const int h) { //--- Создаём наименование объекта и возвращаем результат создания нового заголовка столбца string user_name="ColumnCaptionView"+(string)this.m_list_elm.Total(); CColumnCaptionView *caption_view=this.InsertNewElement(ELEMENT_TYPE_TABLE_COLUMN_CAPTION,text,user_name,x,y,w,h); return(caption_view!=NULL ? caption_view : NULL); }
Объект заголовка столбца создаётся стандартным для объектов библиотеки методом InsertNewElement(), размещающего созданные объекты в списке графических элементов объекта.
Метод, устанавливающий модель заголовка:
//+------------------------------------------------------------------+ //| CTableHeaderView::Устанавливает модель заголовка | //+------------------------------------------------------------------+ bool CTableHeaderView::TableHeaderModelAssign(CTableHeader *header_model) { //--- Если передан пустой объект - сообщаем об этом и возвращаем false if(header_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Если в переданной модели заголовка нет ни одного заголовка столбца - сообщаем об этом и возвращаем false int total=(int)header_model.ColumnsTotal(); if(total==0) { ::PrintFormat("%s: Error. Header model does not contain any columns",__FUNCTION__); return false; } //--- Сохраняем указатель на переданную модель заголовка таблицы и рассчитываем ширину каждого заголовка столбца this.m_table_header_model=header_model; int caption_w=(int)::round((double)this.Width()/(double)total); //--- В цикле по количеству заголовков столбцов в модели заголовка таблицы for(int i=0;i<total;i++) { //--- получаем модель очередного заголовка столбца, CColumnCaption *caption_model=this.m_table_header_model.GetColumnCaption(i); if(caption_model==NULL) return false; //--- рассчитываем координату и создаём имя для области заголовка столбца int x=caption_w*i; string name="CaptionBound"+(string)i; //--- Создаём новую область заголовка столбца CBound *caption_bound=this.InsertNewBound(name,x,0,caption_w,this.Height()); if(caption_bound==NULL) return false; //--- Создаём новый объект визуального представления заголовка столбца CColumnCaptionView *caption_view=this.InsertNewColumnCaptionView(caption_model.Value(),x,0,caption_w,this.Height()); if(caption_view==NULL) return false; //--- На текущую область заголовка столбца назначаем соответствующий объект визуального представления заголовка столбца caption_bound.AssignObject(caption_view); } //--- Всё успешно return true; }
В метод передаётся модель заголовка таблицы. В цикле по количеству заголовков столбцов в модели создаём очередную область для размещения заголовка столбца. Создаём объект заголовка столбца и назначаем его на текущую область. По завершении цикла будем иметь список областей с назначенными для них заголовками столбцов.
Метод, рисующий внешний вид заголовка таблицы:
//+------------------------------------------------------------------+ //| CTableHeaderView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableHeaderView::Draw(const bool chart_redraw) { //--- Заливаем объект цветом фона, рисуем линию строки и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Line(this.AdjX(0),this.AdjY(this.Height()-1),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Рисуем заголовки столбцов int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- Получаем область очередного заголовка столбца CBound *cell_bound=this.GetBoundAt(i); if(cell_bound==NULL) continue; //--- Из области заголовка столбца получаем присоединённый объект заголовка столбца CColumnCaptionView *caption_view=cell_bound.GetAssignedObj(); //--- Рисуем визуальное представление заголовка столбца if(caption_view!=NULL) caption_view.Draw(false); } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Сначала вся область заголовка таблицы закрашивается цветом фона, затем рисуется снизу разделительная линия. Далее в цикле по списку областей заголовков получаем очередную область, а из неё — назначенный этой области объект заголовка столбца, метод рисования которого и вызываем.
Метод, распечатывающий в журнале назначенную модель заголовка таблицы:
//+------------------------------------------------------------------+ //| CTableHeaderView::Распечатывает в журнале | //| назначенную модель заголовка таблицы | //+------------------------------------------------------------------+ void CTableHeaderView::TableHeaderModelPrint(const bool detail,const bool as_table=false,const int cell_width=CELL_WIDTH_IN_CHARS) { if(this.m_table_header_model!=NULL) this.m_table_header_model.Print(detail,as_table,cell_width); }
Позволяет распечатать в журнале модель заголовка таблицы, назначенную этому объекту.
Таблица, её визуальное представление — это составной объект, получающий данные о таблице и заголовке из её модели, и рисующий эти данные на различных элементах управления. Это панель, на которой размещён заголовок и контейнер с табличными данными (строками таблицы).
Класс таблицы (View)
Продолжим писать код в файле Controls.mqh:
//+------------------------------------------------------------------+ //| Класс визуального представления таблицы | //+------------------------------------------------------------------+ class CTableView : public CPanel { protected: //--- Получаемые данные таблицы CTable *m_table_obj; // Указатель на объект таблицы (включает модели таблицы и заголовка) CTableModel *m_table_model; // Указатель на модель таблицы (получаем из CTable) CTableHeader *m_header_model; // Указатель на модель заголовка таблицы (получаем из CTable) //--- Данные компонента View CTableHeaderView *m_header_view; // Указатель на заголовок таблицы (View) CPanel *m_table_area; // Панель для размещения строк таблицы CContainer *m_table_area_container; // Контейнер для размещения панели со строками таблицы //--- (1) Устанавливает, (2) возвращает модель таблицы bool TableModelAssign(CTableModel *table_model); CTableModel *GetTableModel(void) { return this.m_table_model; } //--- (1) Устанавливает, (2) возвращает модель заголовка таблицы bool HeaderModelAssign(CTableHeader *header_model); CTableHeader *GetHeaderModel(void) { return this.m_header_model; } //--- Создаёт из модели объект (1) заголовка, (2) таблицы bool CreateHeader(void); bool CreateTable(void); public: //--- (1) Устанавливает, (2) возвращает объект таблицы bool TableObjectAssign(CTable *table_obj); CTable *GetTableObj(void) { return this.m_table_obj; } //--- Распечатывает в журнале назначенную модель (1) таблицы, (2) заголовка, (3) объекта таблицы void TableModelPrint(const bool detail); void HeaderModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS); void TablePrint(const int column_width=CELL_WIDTH_IN_CHARS); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CPanel::Compare(node,mode); } virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); //--- Конструкторы/деструктор CTableView(void); CTableView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableView (void){} };
Класс унаследован от класса объекта панели. Видно три компонента Model и три компонента View. В классе есть указатели на модель таблицы, модель заголовка и таблицу. Из таблицы получаем указатели на табличные данные и заголовок. И есть три указателя на заголовок, панель строк таблицы и контейнер для размещения этой панели.
Смысл в том, что строки таблицы прикрепляются к объекту панели (возможно прикреплять множество элементов), и эта панель прикрепляется к контейнеру (можно прикрепить единственный элемент), где имеются полосы прокрутки, которыми возможно перемещать панель со строками таблицы. Таким образом, визуально элемент выглядит как заголовок таблицы, а ниже располагается прокручиваемая табличка (строки и столбцы, образуемые ячейками).
В конструкторах класса вызываем метод инициализации объекта:
//+------------------------------------------------------------------+ //| CTableView::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CTableView::CTableView(void) : CPanel("TableView","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H), m_table_model(NULL),m_header_model(NULL),m_table_obj(NULL),m_header_view(NULL),m_table_area(NULL),m_table_area_container(NULL) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CTableView::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координатами и размерами | //+------------------------------------------------------------------+ CTableView::CTableView(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,chart_id,wnd,x,y,w,h),m_table_model(NULL),m_header_model(NULL),m_table_obj(NULL),m_header_view(NULL),m_table_area(NULL),m_table_area_container(NULL) { //--- Инициализация this.Init(); }
В методе инициализации на панели объекта создаём все необходимые элементы управления:
//+------------------------------------------------------------------+ //| CTableView::Инициализация | //+------------------------------------------------------------------+ void CTableView::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Ширина рамки this.SetBorderWidth(1); //--- Создаём заголовок таблицы this.m_header_view=this.InsertNewElement(ELEMENT_TYPE_TABLE_HEADER,"","TableHeader",0,0,this.Width(),DEF_TABLE_HEADER_H); if(this.m_header_view==NULL) return; this.m_header_view.SetBorderWidth(1); //--- Создаём контейнер, в котором будет находиться панель строк таблицы this.m_table_area_container=this.InsertNewElement(ELEMENT_TYPE_CONTAINER,"","TableAreaContainer",0,DEF_TABLE_HEADER_H,this.Width(),this.Height()-DEF_TABLE_HEADER_H); if(this.m_table_area_container==NULL) return; this.m_table_area_container.SetBorderWidth(0); this.m_table_area_container.SetScrollable(true); //--- Присоединяем к контейнеру панель для хранения строк таблицы int shift_y=0; this.m_table_area=this.m_table_area_container.InsertNewElement(ELEMENT_TYPE_PANEL,"","TableAreaPanel",0,shift_y,this.m_table_area_container.Width(),this.m_table_area_container.Height()-shift_y); if(m_table_area==NULL) return; this.m_table_area.SetBorderWidth(0); }
Метод, устанавливающий модель таблицы:
//+------------------------------------------------------------------+ //| CTableView::Устанавливает модель таблицы | //+------------------------------------------------------------------+ bool CTableView::TableModelAssign(CTableModel *table_model) { if(table_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } this.m_table_model=table_model; return true; }
Просто передаём в метод указатель на объект модели таблицы и записываем его в переменную, из которой и будем к нему обращаться.
Метод, устанавливающий модель заголовка таблицы:
//+------------------------------------------------------------------+ //| CTableView::Устанавливает модель заголовка таблицы | //+------------------------------------------------------------------+ bool CTableView::HeaderModelAssign(CTableHeader *header_model) { if(header_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } this.m_header_model=header_model; return true; }
Передаём в метод указатель на объект модели заголовка таблицы и записываем его в переменную, из которой и будем к нему обращаться.
Метод, устанавливающий объект таблицы:
//+------------------------------------------------------------------+ //| CTableView::Устанавливает объект таблицы | //+------------------------------------------------------------------+ bool CTableView::TableObjectAssign(CTable *table_obj) { //--- Если передан пустой объект таблицы - сообщаем об этом и возвращаем false if(table_obj==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Сохраняем указатель в переменную this.m_table_obj=table_obj; //--- Записываем результат назначения модели таблицы и модели заголовка bool res=this.TableModelAssign(this.m_table_obj.GetTableModel()); res &=this.HeaderModelAssign(this.m_table_obj.GetTableHeader()); //--- Если не удалось назначить какую-либо модель - возвращаем false if(!res) return false; //--- Записываем результат создания заголовка таблицы из модели и таблицы из модели res=this.CreateHeader(); res&=this.CreateTable(); //--- Возвращаем результат return res; }
В метод передаётся указатель на объект таблицы. Из этого объекта получаем и назначаем модель заголовка и модель таблицы. Далее на данных моделей заголовка и таблицы создаём объекты визуального представления заголовка и таблицы.
Метод, создающий из модели объект таблицы:
//+------------------------------------------------------------------+ //| CTableView::Создаёт из модели объект таблицы | //+------------------------------------------------------------------+ bool CTableView::CreateTable(void) { if(this.m_table_area==NULL) return false; //--- В цикле создаёи и присоединяем к элементу Panel (m_table_area) RowsTotal строк из элементов TableRowView int total=(int)this.m_table_model.RowsTotal(); int y=1; // Смещение по вертикали int table_height=0; // Рассчитываемая высота панели CTableRowView *row=NULL; for(int i=0;i<total;i++) { //--- Создаём и присоединяем к панели объект строки таблицы row=this.m_table_area.InsertNewElement(ELEMENT_TYPE_TABLE_ROW,"","TableRow"+(string)i,0,y+(row!=NULL ? row.Height()*i : 0),this.m_table_area.Width()-1,DEF_TABLE_ROW_H); if(row==NULL) return false; //--- Устанавливаем идентификатор строки row.SetID(i); //--- В зависимости от номера строки (чет/нечет) устанавливаем цвет её фона if(row.ID()%2==0) row.InitBackColorDefault(clrWhite); else row.InitBackColorDefault(clrWhiteSmoke); row.BackColorToDefault(); row.InitBackColorFocused(row.GetBackColorControl().NewColor(row.BackColor(),-4,-4,-4)); //--- Получаем модель строки из объекта таблицы CTableRow *row_model=this.m_table_model.GetRow(i); if(row_model==NULL) return false; //--- Созданному объекту строки таблицы назначаем полученную модель строки row.TableRowModelAssign(row_model); //--- Рассчитываем новое значение высоты панели table_height+=row.Height(); } //--- Возвращаем результат изменения размера панели на рассчитанное в цикле значение return this.m_table_area.ResizeH(table_height+y); }
Логика метода расписана в комментариях к коду. В цикле по количеству строк в модели таблицы создаём новый объект строки таблицы и прикрепляем его к панели, одновременно рассчитывая будущую высоту панели. По завершении цикла и создания всех нужных строк, высота панели изменяется в соответствии с рассчитанным в цикле размером.
Метод, рисующий внешний вид таблицы:
//+------------------------------------------------------------------+ //| CTableView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableView::Draw(const bool chart_redraw) { //--- Рисуем заголовок и строки таблицы this.m_header_view.Draw(false); this.m_table_area_container.Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Сначала рисуем заголовок таблицы, а под ним — строки таблицы в контейнере.
Метод, распечатывающий в журнале назначенную модель таблицы:
//+------------------------------------------------------------------+ //| CTableView::Распечатывает в журнале назначенную модель таблицы | //+------------------------------------------------------------------+ void CTableView::TableModelPrint(const bool detail) { if(this.m_table_model!=NULL) this.m_table_model.Print(detail); }
Позволяет распечатать в журнал назначенную модель таблицы.
Метод, распечатывающий в журнале назначенную модель заголовка таблицы:
//+------------------------------------------------------------------+ //| CTableView::Распечатывает в журнале назначенную модель заголовка | //+------------------------------------------------------------------+ void CTableView::HeaderModelPrint(const bool detail,const bool as_table=false,const int column_width=CELL_WIDTH_IN_CHARS) { if(this.m_header_model!=NULL) this.m_header_model.Print(detail,as_table,column_width); }
Позволяет распечатать в журнал назначенную модель заголовка таблицы.
Метод, распечатывающий в журнале назначенный объект таблицы:
//+------------------------------------------------------------------+ //| CTableView::Распечатывает в журнале назначенный объект таблицы | //+------------------------------------------------------------------+ void CTableView::TablePrint(const int column_width=CELL_WIDTH_IN_CHARS) { if(this.m_table_obj!=NULL) this.m_table_obj.Print(column_width); }
Распечатывает в журнале всю назначенную объекту таблицу — заголовок и данные.
Всё, мы создали все классы для создания визуального представления модели таблиц. Давайте посмотрим, как теперь можно создать простую табличку из массива данных.
Тестируем результат
В каталоге терминала \MQL5\Indicators\Tables\ создадим новый индикатор, не имеющий буферов и строящийся в подокне графика:
//+------------------------------------------------------------------+ //| iTestTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0
Подключим к файлу индикатора библиотеку и объявим глобально указатели на объекты:
//+------------------------------------------------------------------+ //| iTestTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Controls\Controls.mqh" // Библиотека элементов управления CPanel *panel=NULL; // Указатель на графический элемент Panel CTable *table; // Указатель на объект таблицы (Model)
Далее напишем в обработчике OnInit() индикатора такой код:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Создаём графический элемент "Панель" panel=new CPanel("Panel","",0,wnd,100,40,400,192); if(panel==NULL) return INIT_FAILED; //--- Устанавливаем параметры панели panel.SetID(1); // Идентификатор panel.SetAsMain(); // На графике обязательно должен быть один главный элемент panel.SetBorderWidth(1); // Ширина рамки (отступ видимой области на один пиксель с каждой стороны контейнера) panel.SetResizable(false); // Возможность менять размеры перетаскиванием за грани и углы отключена panel.SetName("Main container"); // Наименование //--- Создаём данные для таблицы //--- Объявляем и заполняем массив заголовков столбцов с размерностью 4 string captions[4]={"Column 0","Column 1","Column 2","Column 3"}; //--- Объявляем и заполняем массив данных с размерностью 10x4 //--- Тип массива может быть double, long, datetime, color, string long array[10][4]={{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}, {25, 26, 27, 28}, {29, 30, 31, 32}, {33, 34, 35, 36}, {37, 38, 39, 40}}; //--- Создаём объект таблицы из вышесозданного long-массива array 10x4 и string-массива заголовков столбцов (компонент Model) table=new CTable(array,captions); if(table==NULL) return INIT_FAILED; PrintFormat("The [%s] has been successfully created:",table.Description()); //--- На панели создаём новый элемент - таблицу (компонент View) CTableView *table_view=panel.InsertNewElement(ELEMENT_TYPE_TABLE,"","TableView",4,4,panel.Width()-8,panel.Height()-8); //--- Графическому элементу "Таблица" (View) назначаем объект таблицы (Model) table_view.TableObjectAssign(table); //--- Распечатаем в журнале модель таблицы table_view.TablePrint(); //--- Нарисуем таблицу вместе с панелью panel.Draw(true); //--- Успешно return(INIT_SUCCEEDED); }
Здесь есть три блока кода:
- Создание панели, на которой будет построена таблица;
- Создание табличных данных в двух массивах — таблица и заголовок (компоненты Model);
- Создание таблицы на панели (компонент View).
По сути, для создания таблицы необходимы два шага — построить данные (п.2) и построить таблицу (п.3). Панель нужна лишь для оформления и как основа для графического элемента.
Допишем остальной код индикатора:
//+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Удаляем элемент Панель и уничтожаем таблицу и менеджер общих ресурсов библиотеки delete panel; delete table; CCommonManager::DestroyInstance(); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик OnChartEvent элемента Панель panel.OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+ //| Таймер | //+------------------------------------------------------------------+ void OnTimer(void) { //--- Вызываем обработчик OnTimer элемента Панель panel.OnTimer(); }
Скомпилируем индикатор и запустим его на графике:
В журнале терминала будет сообщение об успешно созданной таблице, выведено описание таблицы и сама таблица с заголовком:
The [Table: Rows total: 10, Columns total: 4] has been successfully created: Table: Rows total: 10, Columns total: 4: | n/n | Column 0 | Column 1 | Column 2 | Column 3 | | 0 | 1 | 2 | 3 | 4 | | 1 | 5 | 6 | 7 | 8 | | 2 | 9 | 10 | 11 | 12 | | 3 | 13 | 14 | 15 | 16 | | 4 | 17 | 18 | 19 | 20 | | 5 | 21 | 22 | 23 | 24 | | 6 | 25 | 26 | 27 | 28 | | 7 | 29 | 30 | 31 | 32 | | 8 | 33 | 34 | 35 | 36 | | 9 | 37 | 38 | 39 | 40 |
На графике мы видим, что заголовки столбцов и строки таблицы активны и реагируют на наведение указателя мышки. Обработку взаимодействия таблицы с пользователем обсудим в следующей статье.
Заключение
Мы научились создавать простые таблицы и отображать их на графике. Но пока это лишь статичная таблица, отображающая единожды полученные данные. Следующая статья будет посвящена оживлению таблиц — изменению и отображению динамических данных и взаимодействию с пользователем для настройки отображения строк и столбцов таблицы. Также в следующей статье сделаем ещё более простое создание таблиц.
Программы, используемые в статье:
# | Имя | Тип | Описание |
---|---|---|---|
1 | Tables.mqh | Библиотека классов | Классы для создания модели таблицы |
2 | Base.mqh | Библиотека классов | Классы для создания базового объекта элементов управления |
3 | Controls.mqh | Библиотека классов | Классы элементов управления |
4 | iTestTable.mq5 | Тестовый индикатор | Индикатор для тестирования работы с элементом управления TableView |
5 | MQL5.zip | Архив | Архив файлов, представленных выше, для распаковки в каталог MQL5 клиентского терминала |
Все созданные файлы прилагаются к статье для самостоятельного изучения. Файл архива можно распаковать в папку терминала, и все файлы будут расположены в нужной папке: \MQL5\Indicators\Tables\.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования