Таблицы в парадигме MVC на MQL5: Таблица корреляции символов
Содержание
Введение
В предыдущей статье, посвящённой созданию таблиц в рамках концепции MVC, мы расширили функционал таблиц, добавив возможность настройки ширины столбцов, указания типов отображаемых данных и сортировки данных по столбцам. Эти улучшения сделали таблицы более гибкими и удобными для работы с различными наборами данных. Однако для некоторых задач, требующих представления данных с чёткой привязкой к строкам, необходим дополнительный элемент интерфейса — вертикальный заголовок, отображающий заголовки строк.
Сегодня мы не будем делать некие абстрактные примеры каких-то таблиц, а сделаем практический пример индикатора для создания таблицы симметричной корреляции выбранных в настройках символов, где вертикальный и горизонтальный заголовки будут отображать названия символов, а сама таблица — значения корреляции между ними. Для этого добавим к таблице вертикальный заголовок, который будет содержать в общем случае какие-либо подписи для строк, например, их порядковые номера или другие идентификаторы, а в случае с выбранным примером — наименования символов. Наличие двух заголовков таблицы позволит легче ориентироваться в данных, особенно при работе с таблицами, где строки имеют важное смысловое значение.
Для реализации этой функциональности мы доработаем существующие классы, добавив поддержку вертикального заголовка, а также обеспечим корректное взаимодействие между компонентами в рамках парадигмы MVC. В результате мы получим инструмент, который не только улучшит визуальное представление данных, но и упростит их анализ.
Дорабатываем классы библиотеки
Все файлы проекта расположены по адресу \MQL5\Indicators\Tables\. Файл классов модели таблиц (Tables.mqh), вместе с файлом тестового индикатора (iCorrelationTable.mq5), располагается в папке \MQL5\Indicators\Tables\.
Файлы графической библиотеки (Base.mqh и Controls.mqh) расположены в подпапке \MQL5\Indicators\Tables\Controls\. Все необходимые для работы файлы можно загрузить одним архивом из прeдыдущей статьи.
Доработаем базовый класс графической библиотеки \MQL5\Indicators\Tables\Controls\Base.mqh.
Впишем форвард-декларацию новых классов и перечисления новых типов объектов:
//+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #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 CTableCellView; // Класс визуального представления ячейки таблицы class CTableRowView; // Класс визуального представления строки таблицы class CCaptionView; // Класс базового объекта визуального представления заголовка class CColumnCaptionView; // Класс визуального представления заголовка столбца таблицы class CRowCaptionView; // Класс визуального представления заголовка строки таблицы class CTableHeaderView; // Класс визуального представления заголовка таблицы class CTableRowsHeaderView; // Класс визуального представления заголовка строк таблицы class CTableView; // Класс визуального представления таблицы class CTableControl; // Класс управления таблицами class CPanel; // Класс элемента управления Panel class CGroupBox; // Класс элемента управления GroupBox class CContainer; // Класс элемента управления Container //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #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, // Ячейка таблицы (View) ELEMENT_TYPE_TABLE_ROW_VIEW, // Строка таблицы (View) ELEMENT_TYPE_TABLE_CAPTION_VIEW, // Базовый объект заголовка (View) ELEMENT_TYPE_TABLE_COLUMN_CAPTION_VIEW,// Заголовок столбца таблицы (View) ELEMENT_TYPE_TABLE_ROW_CAPTION_VIEW, // Заголовок строки таблицы (View) ELEMENT_TYPE_TABLE_HEADER_VIEW, // Заголовок таблицы (View) ELEMENT_TYPE_TABLE_ROWS_HEADER_VIEW, // Заголовок строк таблицы (View) ELEMENT_TYPE_TABLE_VIEW, // Таблица (View) ELEMENT_TYPE_TABLE_CONTROL_VIEW, // Элемент управления таблицами (View) ELEMENT_TYPE_PANEL, // Элемент управления Panel ELEMENT_TYPE_GROUPBOX, // Элемент управления GroupBox ELEMENT_TYPE_CONTAINER, // Элемент управления Container };
В функции, возвращающей короткое имя элемента по типу, впишем новые имена для новых элементов:
//+------------------------------------------------------------------+ //| Возвращает короткое имя элемента по типу | //+------------------------------------------------------------------+ 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_VIEW : return "TCELL"; // Ячейка таблицы (View) case ELEMENT_TYPE_TABLE_ROW_VIEW : return "TROW"; // Строка таблицы (View) case ELEMENT_TYPE_TABLE_CAPTION_VIEW : return "TCAPT"; // Базовый объект заголовка (View) case ELEMENT_TYPE_TABLE_COLUMN_CAPTION_VIEW : return "TCCAPT"; // Заголовок столбца таблицы (View) case ELEMENT_TYPE_TABLE_ROW_CAPTION_VIEW : return "TRCAPT"; // Заголовок строки таблицы (View) case ELEMENT_TYPE_TABLE_HEADER_VIEW : return "TCHDR"; // Заголовок таблицы (View) case ELEMENT_TYPE_TABLE_ROWS_HEADER_VIEW : return "TRHDR"; // Заголовок строк таблицы (View) case ELEMENT_TYPE_TABLE_VIEW : return "TABLE"; // Таблица (View) case ELEMENT_TYPE_TABLE_CONTROL_VIEW : return "TBLCTRL"; // Элемент управления таблицами (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 } }
В классе цветов элемента графического объекта CColorElement объявим новый метод, возвращающий интерполированный цвет между тремя цветами в зависимости от значения коэффициента:
//+------------------------------------------------------------------+ //| Класс цветов элемента графического объекта | //+------------------------------------------------------------------+ class CColorElement : public CBaseObj { protected: CColor m_current; // Текущий цвет. Может быть одним из нижеследующих CColor m_default; // Цвет обычного состояния CColor m_focused; // Цвет при наведении курсора CColor m_pressed; // Цвет при нажатии CColor m_blocked; // Цвет заблокированного элемента //--- Преобразует RGB в color color RGBToColor(const double r,const double g,const double b) const; //--- Записывает в переменные значения компонентов RGB void ColorToRGB(const color clr,double &r,double &g,double &b); //--- Возвращает составляющую цвета (1) Red, (2) Green, (3) Blue double GetR(const color clr) { return clr&0xFF; } double GetG(const color clr) { return(clr>>8)&0xFF; } double GetB(const color clr) { return(clr>>16)&0xFF; } public: //--- Возвращает новый цвет color NewColor(color base_color, int shift_red, int shift_green, int shift_blue); //--- Возвращает интерполированный цвет между тремя цветами в зависимости от значения коэффициента (от -1 до +1) color InterpolateColorByCoeff(const color color1, const color color2, const color color3, const double coeff); //--- Инициализация класса void Init(void); //--- Инициализация цветов различных состояний bool InitDefault(const color clr) { return this.m_default.SetColor(clr); } bool InitFocused(const color clr) { return this.m_focused.SetColor(clr); } bool InitPressed(const color clr) { return this.m_pressed.SetColor(clr); } bool InitBlocked(const color clr) { return this.m_blocked.SetColor(clr); } //--- Установка цветов для всех состояний void InitColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked); void InitColors(const color clr); //--- Возврат цветов различных состояний color GetCurrent(void) const { return this.m_current.Get(); } color GetDefault(void) const { return this.m_default.Get(); } color GetFocused(void) const { return this.m_focused.Get(); } color GetPressed(void) const { return this.m_pressed.Get(); } color GetBlocked(void) const { return this.m_blocked.Get(); } //--- Устанавливает один из списка цветов как текущий bool SetCurrentAs(const ENUM_COLOR_STATE color_state); //--- Возвращает описание объекта virtual string Description(void); //--- Виртуальные методы (1) сохранения в файл, (2) загрузки из файла, (3) тип объекта virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_COLORS_ELEMENT); } //--- Конструкторы/деструктор CColorElement(void); CColorElement(const color clr); CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked); ~CColorElement(void) {} };
За пределами тела класса напишем его реализацию:
//+------------------------------------------------------------------+ //| Возвращает интерполированный цвет между тремя цветами | //| в зависимости от значения коэффициента (от -1 до +1) | //+------------------------------------------------------------------+ color CColorElement::InterpolateColorByCoeff(const color color1,const color color2,const color color3,const double coeff) { //--- Ограничиваем значение коэффициента double val=::fmax(-1.0,::fmin(1.0,coeff)); //--- Переменные для получения компонент RGB для каждого цвета double r1, g1, b1, r2, g2, b2; double r, g, b, t; //--- Интерполяция между начальным и средним цветом if(val<0.0) { this.ColorToRGB(color1,r1,g1,b1); this.ColorToRGB(color2,r2,g2,b2); t=(val+1.0)/1.0; r=r1+(r2-r1)*t; g=g1+(g2-g1)*t; b=b1+(b2-b1)*t; } //--- Интерполяция между средним и конечным цветом else { this.ColorToRGB(color3,r1,g1,b1); this.ColorToRGB(color2,r2,g2,b2); t=val/1.0; r=r2+(r1-r2)*t; g=g2+(g1-g2)*t; b=b2+(b1-b2)*t; } //--- Возвращаем рассчитанный цвет return this.RGBToColor(r,g,b); }
Метод вычисляет интерполированный цвет на основе трёх заданных цветов ( color1 , color2 , color3 ) и коэффициента coeff . Первый цвет — это цвет при значении коэффициента -1. Второй цвет — цвет при значении коэффициента 0, последний цвет — это цвет при значении коэффициента +1. Так как значения корреляции между символами колеблются в диапазоне от -1 до +1, то этот метод позволит плавно рассчитать значение цвета ячейки по значению коэффициента (корреляции символов) и вернёт рассчитанный цвет.
В обработчике событий базового класса CCanvasBase впишем контроль размера подокна графика:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик событий | //+------------------------------------------------------------------+ void CCanvasBase::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Если в момент запуска терминала с индикатором высота подокна ещё не определена, //--- скорректируем дистанцию между верхней рамкой подокна индикатора и верхней рамкой главного окна if(this.m_wnd>0 && this.m_wnd_y==0) this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); //--- Событие изменения графика //... //...
В ситуации, когда терминал запускается с установленным в подокне индикатором, размер подокна может быть ещё не получен в момент инициализации классов, и значение дистанции между верхней рамкой подокна индикатора и верхней границей главного окна может быть ещё нулевым. Это не даёт правильно отслеживать расположение курсора (его координаты считываются с главного окна графика, а не с подокна). Добавление указанного блока кода исправляет эту ситуацию.
Теперь создадим новые классы для вертикального заголовка таблицы в файле \MQL5\Indicators\Tables\Controls\Controls.mqh.
В разделе макроподстановок определим минимальную ширину заголовка строк таблицы, а в разделе перечислений добавим новое перечисление, определяющее режимы подсветки строк/ячеек таблицы:
//+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Base.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_TABLE_ROWS_HEADER_W 24 // Минимальная ширина заголовков строк таблицы #define DEF_TABLE_COLUMN_MIN_W 12 // Минимальная ширина колонки таблицы #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 // Частота автоповторов #define DEF_HINT_NAME_TOOLTIP "HintTooltip" // Наименование подсказки "тултип" #define DEF_HINT_NAME_HORZ "HintHORZ" // Наименование подсказки "Двойная горизонтальная стрелка" #define DEF_HINT_NAME_VERT "HintVERT" // Наименование подсказки "Двойная вертикальная стрелка" #define DEF_HINT_NAME_NWSE "HintNWSE" // Наименование подсказки "Двойная стрелка сверху-лево" --- низ-право (NorthWest-SouthEast) #define DEF_HINT_NAME_NESW "HintNESW" // Наименование подсказки "Двойная стрелка снизу-лево" --- верх-право (NorthEast-SouthWest) #define DEF_HINT_NAME_SHIFT_HORZ "HintShiftHORZ" // Наименование подсказки "Стрелка горизонтального смещения" #define DEF_HINT_NAME_SHIFT_VERT "HintShiftVERT" // Наименование подсказки "Стрелка вертикального смещения" //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_SORT_BY // Сравниваемые свойства { ELEMENT_SORT_BY_ID = BASE_SORT_BY_ID, // Сравнение по идентификатору элемента ELEMENT_SORT_BY_NAME = BASE_SORT_BY_NAME, // Сравнение по наименованию элемента ELEMENT_SORT_BY_X = BASE_SORT_BY_X, // Сравнение по координате X элемента ELEMENT_SORT_BY_Y = BASE_SORT_BY_Y, // Сравнение по координате Y элемента ELEMENT_SORT_BY_WIDTH= BASE_SORT_BY_WIDTH, // Сравнение по ширине элемента ELEMENT_SORT_BY_HEIGHT= BASE_SORT_BY_HEIGHT, // Сравнение по высоте элемента ELEMENT_SORT_BY_ZORDER= BASE_SORT_BY_ZORDER, // Сравнение по Z-order элемента ELEMENT_SORT_BY_TEXT, // Сравнение по тексту элемента ELEMENT_SORT_BY_COLOR_BG, // Сравнение по цвету фона элемента ELEMENT_SORT_BY_ALPHA_BG, // Сравнение по прозрачности фона элемента ELEMENT_SORT_BY_COLOR_FG, // Сравнение по цвету переднего плана элемента ELEMENT_SORT_BY_ALPHA_FG, // Сравнение по прозрачности переднего плана элемента ELEMENT_SORT_BY_STATE, // Сравнение по состоянию элемента ELEMENT_SORT_BY_GROUP, // Сравнение по группе элемента }; enum ENUM_TABLE_SORT_MODE // Режимы сортировки столбцов таблиц { TABLE_SORT_MODE_NONE, // Сортировка отсутствует TABLE_SORT_MODE_ASC, // Сортировка по возрастанию TABLE_SORT_MODE_DESC, // Сортировка по убыванию }; enum ENUM_HINT_TYPE // Типы подсказок { HINT_TYPE_TOOLTIP, // Тултип HINT_TYPE_ARROW_HORZ, // Двойная горизонтальная стрелка HINT_TYPE_ARROW_VERT, // Двойная вертикальная стрелка HINT_TYPE_ARROW_NWSE, // Двойная стрелка сверху-лево --- низ-право (NorthWest-SouthEast) HINT_TYPE_ARROW_NESW, // Двойная стрелка снизу-лево --- верх-право (NorthEast-SouthWest) HINT_TYPE_ARROW_SHIFT_HORZ, // Стрелка горизонтального смещения HINT_TYPE_ARROW_SHIFT_VERT, // Стрелка вертикального смещения }; enum ENUM_ROWS_HIGHLIGHT_MODE // Режимы подсветки строк/ячеек таблицы { ROWS_HIGHLIGHT_MODE_CELLS, // Подсвечивать отдельные ячейки (режим ячеек) ROWS_HIGHLIGHT_MODE_ROW, // Подсвечивать всю строку целиком (режим строки) };
Если с минимальной шириной заголовка всё понятно, то насчёт режима подсветки строк или ячеек стоит уточнить: строки таблицы представляют собой графические элементы с типом "Панель". У этого элемента есть собственный обработчик наведения на него курсора мышки: по умолчанию цвет элемента становится чуть светлее. У строки таблицы есть свой список ячеек, которые расположены на строке. Перечисление указывает, как обрабатывать событие наведения курсора на строку — либо вся строка полностью становится чуть светлее, либо каждая ячейка может подсвечиваться при помощи изменения собственного цвета.
В данный момент полностью работает только ранее реализованный первый вариант. Для второго варианта обработчик просто отключен. Но именно второй вариант будет применяться к строкам таблицы, где каждая ячейка будет окрашиваться в свой собственный цвет, зависящий от значения корреляции символов этой ячейки. Чтобы избежать ненужных морганий строки, мы и будем использовать второй вариант, который попросту ничего не делает с цветом. В последующем этот вариант может быть доработан, где у каждой ячейки будет собственный обработчик наведения на неё курсора.
Сейчас у нас есть только один заголовок у таблицы — горизонтальный, на котором расположены заголовки столбцов. Всё это организовано двумя классами:
- CColumnCaptionView — заголовок столбца,
- CTableHeaderView — горизонтальный заголовок таблицы, в которм в виде списка содержатся объекты заголовков столбцов.
Сейчас нам необходимо реорганизовать эту структуру, так как теперь у нас будет ещё и вертикальный заголовок таблицы. Он точно так же, как и горизонтальный заголовок, будет содержать в себе список объектов заголовков строк.
Значит, нам нужен будет ещё один класс заголовка — для заголовка строки, и класс вертикального заголовка. Соответственно, структура классов будет более разветвлённой — мы сделаем один общий абстрактный класс заголовка с общими свойствами всех заголовков — столбцов и строк, а в наследуемых классах будут собственные свойства и методы, присущие конкретному заголовку — столбца или строки:
- CCaptionView — базовый объект заголовка
- CColumnCaptionView — класс объекта заголовка столбца, унаследованный от CCaptionView,
- CRowCaptionView — класс объекта заголовка строки, унаследованный от CCaptionView.
- CTableHeaderView — горизонтальный заголовок таблицы, содержит список заголовков столбцов CColumnCaptionView,
- CTableRowsHeaderView — вертикальный заголовок таблицы, содержит список заголовков строк CRowCaptionView.
В методе создания элемента списка класса 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_VIEW : return new CTableCellView(); // Ячейка таблицы (View) case ELEMENT_TYPE_TABLE_ROW_VIEW : return new CTableRowView(); // Строка таблицы (View) case ELEMENT_TYPE_TABLE_CAPTION_VIEW : return new CCaptionView(); // Базовый объект заголовка (View) case ELEMENT_TYPE_TABLE_COLUMN_CAPTION_VIEW : return new CColumnCaptionView(); // Заголовок столбца таблицы (View) case ELEMENT_TYPE_TABLE_ROW_CAPTION_VIEW : return new CRowCaptionView(); // Заголовок строки таблицы (View) case ELEMENT_TYPE_TABLE_HEADER_VIEW : return new CTableHeaderView(); // Заголовок таблицы (View) case ELEMENT_TYPE_TABLE_ROWS_HEADER_VIEW : return new CTableRowsHeaderView();// Вертикальный заголовок таблицы (View) case ELEMENT_TYPE_TABLE_VIEW : 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; } }
Если посмотреть на таблицу Excel, то на пересечении горизонтального и вертикального заголовков в левом верхнем углу таблицы увидим значок в виде треугольника в правом нижнем углу области пересечения двух заголовков:

Сделаем такой же.
Все рисунки у нас рисуются специальным классом для рисования CImagePainter.
Объявим в классе методы для рисования треугольников, расположенных по всем углам прямоугольной области:
//+------------------------------------------------------------------+ //| Класс рисования изображений | //+------------------------------------------------------------------+ class CImagePainter : public CBaseObj { protected: CCanvas *m_canvas; // Указатель на канвас, где рисуем CBound m_bound; // Координаты и границы изображения uchar m_alpha; // Прозрачность //--- Проверяет валидность холста и корректность размеров bool CheckBound(const string source); public: //--- (1) Назначает канвас для рисования, (2) устанавливает, (3) возвращает прозрачность void CanvasAssign(CCanvas *canvas) { this.m_canvas=canvas; } void SetAlpha(const uchar value) { this.m_alpha=value; } uchar Alpha(void) const { return this.m_alpha; } //--- (1) Устанавливает координаты, (2) изменяет размеры области void SetXY(const int x,const int y) { this.m_bound.SetXY(x,y); } void SetSize(const int w,const int h) { this.m_bound.Resize(w,h); } //--- Устанавливает координаты и размеры области void SetBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.SetSize(w,h); } //--- Возвращает границы и размеры рисунка int X(void) const { return this.m_bound.X(); } int Y(void) const { return this.m_bound.Y(); } int Right(void) const { return this.m_bound.Right(); } int Bottom(void) const { return this.m_bound.Bottom(); } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } //--- Очищает область bool Clear(const int x,const int y,const int w,const int h,const bool update=true); //--- Рисует закрашенную стрелку (1) вверх, (2) вниз, (3) влево, (4) вправо bool ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) горизонтальную 17х7, (2) вертикальную 7х17 двойную стрелку bool ArrowHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowVert(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует диагональную (1) сверху-слева --- вниз-вправо, (2) снизу-слева --- вверх-вправо 17х17 двойную стрелку bool ArrowNWSE(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowNESW(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует стрелку смещения 18x18 по (1) горизонтали, (2) вертикали bool ArrowShiftHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowShiftVert(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) отмеченный, (2) неотмеченный CheckBox bool CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) отмеченный, (2) неотмеченный RadioButton bool CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует рамку группы элементов bool FrameGroupElements(const int x,const int y,const int w,const int h,const string text, const color clr_text,const color clr_dark,const color clr_light, const uchar alpha,const bool update=true); //--- Рисует закрашенный треугольник в (1) левом-верхнем, (2) левом-нижнем, (3) правом-верхнем, (4) правом-нижнем углу bool TriangleLT(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool TriangleLB(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool TriangleRT(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool TriangleRB(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Виртуальные методы (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_IMAGE_PAINTER); } //--- Конструкторы/деструктор CImagePainter(void) : m_canvas(NULL) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas) : m_canvas(canvas) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas,const int id,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); } CImagePainter(CCanvas *canvas,const int id,const int dx,const int dy,const int w,const int h,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(dx,dy,w,h); } ~CImagePainter(void) {} };
За пределами тела класса напишем их реализацию:
//+------------------------------------------------------------------+ //| Рисует закрашенный треугольник в левом-верхнем углу | //+------------------------------------------------------------------+ bool CImagePainter::TriangleLT(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound(__FUNCTION__)) return false; //--- Координаты фигуры int x1=x; int y1=y+h; int x2=x1; int y2=y; int x3=x2+w; int y3=y2; //--- Рисуем треугольник this.m_canvas.FillTriangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+ //| Рисует закрашенный треугольник в левом-нижнем углу | //+------------------------------------------------------------------+ bool CImagePainter::TriangleLB(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound(__FUNCTION__)) return false; //--- Координаты фигуры int x1=x; int y1=y; int x2=x1+w; int y2=y1+h; int x3=x1; int y3=y2; //--- Рисуем треугольник this.m_canvas.FillTriangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+ //| Рисует закрашенный треугольник в правом-верхнем углу | //+------------------------------------------------------------------+ bool CImagePainter::TriangleRT(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound(__FUNCTION__)) return false; //--- Координаты фигуры int x1=x; int y1=y; int x2=x1+w; int y2=y; int x3=x2; int y3=y2+h; //--- Рисуем треугольник this.m_canvas.FillTriangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+ //| Рисует закрашенный треугольник в правом-нижнем углу | //+------------------------------------------------------------------+ bool CImagePainter::TriangleRB(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound(__FUNCTION__)) return false; //--- Координаты фигуры int x1=x+w; int y1=y; int x2=x1; int y2=y+h; int x3=x; int y3=y2; //--- Рисуем треугольник this.m_canvas.FillTriangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; }
В каждый из методов передаются координаты прямоугольника, внутри которого нужно нарисовать закрашенный треугольник с указанным цветом. Методы рассчитывают все координаты рисуемого треугольника и вызывают метод рисования закрашенного треугольника класса CCanvas. Если в метод передан установленный флаг обновления канваса, то он обновляется.
В класс объекта панели добавим метод, возвращающий количество созданных в объекте областей:
//+------------------------------------------------------------------+ //| Класс панели | //+------------------------------------------------------------------+ class CPanel : public CLabel { private: CElementBase m_temp_elm; // Временный объект для поиска элементов CBound m_temp_bound; // Временный объект для поиска областей protected: CListElm m_list_elm; // Список прикреплённых элементов CListElm m_list_bounds; // Список областей //--- Добавляет новый элемент в список bool AddNewElement(CElementBase *element); public: //--- Возвращает указатель на список (1) прикреплённых элементов, (2) областей CListElm *GetListAttachedElements(void) { return &this.m_list_elm; } CListElm *GetListBounds(void) { return &this.m_list_bounds; } //--- Возвращает прикреплённый элемент по (1) индексу в списке, (2) идентификатору, (3) назначенному имени объекта CElementBase *GetAttachedElementAt(const uint index) { return this.m_list_elm.GetNodeAtIndex(index); } CElementBase *GetAttachedElementByID(const int id); CElementBase *GetAttachedElementByName(const string name); //--- Возвращает количество (1) областей, (2) присоединённых элементов, int BoundsTotal(void) const { return this.m_list_bounds.Total(); } int AttachedElementsTotal(void) const { return this.m_list_elm.Total(); } //--- Возвращает область по (1) индексу в списке, (2) идентификатору, (3) назначенному имени области CBound *GetBoundAt(const uint index) { return this.m_list_bounds.GetNodeAtIndex(index); } CBound *GetBoundByID(const int id); CBound *GetBoundByName(const string name);
Не на каждую созданную область может быть назначен какой-либо графический элемент, поэтому, метод, возвращающий количество присоединённых элементов, некорректно использовать для получения количества областей. Новый метод возвращает именно количество созданных областей в элементе.
В методе, создающем и добавляющем новый элемент в список, добавим создание новых элементов управления:
//+------------------------------------------------------------------+ //| 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_VIEW : element = new CTableRowView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления строки таблицы case ELEMENT_TYPE_TABLE_CAPTION_VIEW : element = new CCaptionView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Базовый объект заголовка (View) case ELEMENT_TYPE_TABLE_COLUMN_CAPTION_VIEW : element = new CColumnCaptionView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления заголовка столбца таблицы case ELEMENT_TYPE_TABLE_ROW_CAPTION_VIEW : element = new CRowCaptionView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления заголовка строки таблицы case ELEMENT_TYPE_TABLE_HEADER_VIEW : element = new CTableHeaderView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Объект визуального представления заголовка таблицы case ELEMENT_TYPE_TABLE_ROWS_HEADER_VIEW : element = new CTableRowsHeaderView(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h);break; // Объект визуального представления заголовка строк таблицы case ELEMENT_TYPE_TABLE_VIEW : 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; }
Теперь эти элементы можно будет создавать из объекта панели и прикреплять к списку присоединённых элементов.
В классе объекта-контейнера CContainer оптимизируем метод, смещающий содержимое по горизонтали на указанное значение:
//+-------------------------------------------------------------------+ //|CContainer::Смещает содержимое по горизонтали на указанное значение| //+-------------------------------------------------------------------+ bool CContainer::ContentShiftHorz(const int value) { //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElement(); if(elm==NULL) return false; //--- Рассчитываем величину смещения по положению ползунка int content_offset=this.CalculateContentOffsetHorz(value); //--- Для элемента CTableView получаем заголовок таблицы bool res=true; CElementBase *elm_container=elm.GetContainer(); CTableHeaderView *table_header=NULL; if(elm_container!=NULL && ::StringFind(elm.Name(),"Table")==0) { CElementBase *obj=elm_container.GetContainer(); if(obj!=NULL && obj.Type()==ELEMENT_TYPE_TABLE_VIEW) { CTableView *table_view=obj; table_header=table_view.GetHeader(); //--- Сдвигаем заголовок if(table_header!=NULL) res &=table_header.MoveX(this.X()-content_offset); } } //--- Возвращаем результат сдвига содержимого на рассчитанную величину res &=elm.MoveX(this.X()-content_offset); return res; }
Здесь мы просто избавились от одного лишнего условия. Доработки минимальны.
Теперь в методе, смещающем содержимое по вертикали, тоже добавим смещение вертикального заголовка:
//+------------------------------------------------------------------+ //| CContainer::Смещает содержимое по вертикали на указанное значение| //+------------------------------------------------------------------+ bool CContainer::ContentShiftVert(const int value) { //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElement(); if(elm==NULL) return false; //--- Рассчитываем величину смещения по положению ползунка int content_offset=this.CalculateContentOffsetVert(value); //--- Для элемента CTableView получаем вертикальный заголовок таблицы bool res=true; CElementBase *elm_container=elm.GetContainer(); CTableRowsHeaderView *table_header=NULL; if(elm_container!=NULL && ::StringFind(elm.Name(),"Table")==0) { CElementBase *obj=elm_container.GetContainer(); if(obj!=NULL && obj.Type()==ELEMENT_TYPE_TABLE_VIEW) { CTableView *table_view=obj; table_header=table_view.GetRowsHeader(); //--- Сдвигаем заголовок if(table_header!=NULL) res &=table_header.MoveY(this.Y()-content_offset); } } //--- Возвращаем результат сдвига содержимого на рассчитанную величину res &=elm.MoveY(this.Y()-content_offset); return res; }
Ячейка таблицы теперь должна иметь возможность не наследовать свой цвет фона от строки, в которой она расположена, а иметь свою собственную настройку этого цвета.
В классе объекта ячейки объявим переменную для хранения цвета фона и методы для установки и получения этого цвета:
//+------------------------------------------------------------------+ //| Класс визуального представления ячейки таблицы | //+------------------------------------------------------------------+ 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[]; // Текст color m_fore_color; // Цвет переднего плана color m_back_color; // Цвет заднего плана //--- Возвращает смещения начальных координат рисования на холсте относительно канваса и координат базового элемента 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, int &dir_x, int dir_y); //--- Возвращает указатель на контейнер панели строк таблицы CContainer *GetRowsPanelContainer(void); public: //--- Возвращает указатель на назначенный канвас (1) фона, (2) переднего плана CCanvas *GetBackground(void) { return this.m_background; } CCanvas *GetForeground(void) { return this.m_foreground; } //--- Получение границ родительского объекта-контейнера int ContainerLimitLeft(void) const { return(this.m_element_base==NULL ? this.X() : this.m_element_base.LimitLeft()); } int ContainerLimitRight(void) const { return(this.m_element_base==NULL ? this.Right() : this.m_element_base.LimitRight()); } int ContainerLimitTop(void) const { return(this.m_element_base==NULL ? this.Y() : this.m_element_base.LimitTop()); } int ContainerLimitBottom(void) const { return(this.m_element_base==NULL ? this.Bottom() : this.m_element_base.LimitBottom()); } //--- Возвращает флаг того, что объект расположен за пределами своего контейнера virtual bool IsOutOfContainer(void); //--- (1) Устанавливает, (2) возвращает текст ячейки void SetText(const string text) { ::StringToShortArray(text,this.m_text); } string Text(void) const { return ::ShortArrayToString(this.m_text); } //--- (1) Устанавливает, (2) возвращает цвет текста ячейки void SetForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Устанавливает, (2) возвращает цвет фона ячейки void SetBackColor(const color clr) { this.m_back_color=clr; } color BackColor(void) const { return this.m_back_color; } //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс ячейки void SetIndex(const int index) { this.m_index=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_VIEW); } //--- Инициализация объекта класса 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){} };
В методе, назначающем строку ячейке, канвасы фона и переднего плана, инициализируем цвет фона ячейки как цвет фона строки, на которой расположена ячейка:
//+------------------------------------------------------------------+ //| 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(); this.m_fore_color=this.m_element_base.ForeColor(); this.m_back_color=this.m_element_base.BackColor(); }
По умолчанию цвет фона ячейки будет таким же, как и цвет фона строки.
В методе, рисующем ячейку, добавим рисование фона ячейки:
//+------------------------------------------------------------------+ //| CTableCellView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableCellView::Draw(const bool chart_redraw) { //--- Если ячейка за пределами контейнера строк таблицы - уходим if(this.IsOutOfContainer()) return; //--- Получаем координаты текста и направление смещения в зависимости от точки привязки int text_x=0, text_y=0; int dir_horz=0, dir_vert=0; if(!this.GetTextCoordsByAnchor(text_x,text_y,dir_horz,dir_vert)) return; //--- Корректируем координаты текста int x=this.AdjX(this.X()+text_x); int y=this.AdjY(this.Y()+text_y); //--- Устанавливаем координаты разделительной линии int x1=this.AdjX(this.X()); int y1=this.AdjY(this.Y()); int x2=this.AdjX(this.X()); int y2=this.AdjY(this.Bottom()); //--- Выводим текст на канвасе переднего плана с учётом направления смещения без обновления графика this.DrawText(x+this.m_text_x*dir_horz,y+this.m_text_y*dir_vert,this.Text(),false); //--- Устанавливаем координаты прямоугольной заливки x1=this.AdjX(this.X()); y1=this.AdjY(this.Y()); x2=this.AdjX(this.Right()); y2=this.AdjY(this.Bottom()-1); this.m_background.FillRectangle(x1,y1,x2,y2,::ColorToARGB(this.BackColor(),this.m_element_base.AlphaBG())); //--- Если это не крайняя справа ячейка - рисуем у ячейки справа вертикальную разделительную полосу 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); }
Выделенный цветом блок кода безусловно рисует закрашенный прямоугольник по размеру всей ячейки. В последующем здесь добавим контроль по методу подсветки строки и, если вся строка подсвечивается при наведении курсора, то этот блок кода не должен обрабатываться. Пока, для краткости, просто полностью закрашиваем фон ячейки установленным цветом.
Теперь доработаем класс визуального представления строки таблицы CTableRowView.
Объявим новые переменные, методы и обработчики событий:
//+------------------------------------------------------------------+ //| Класс визуального представления строки таблицы | //+------------------------------------------------------------------+ class CTableRowView : public CPanel { protected: CTableCellView m_temp_cell; // Временный объект ячейки для поиска CTableRow *m_table_row_model; // Указатель на модель строки CListElm m_list_cells; // Список ячеек int m_index; // Индекс в списке строк ENUM_ROWS_HIGHLIGHT_MODE m_highlight_mode; // Режим подсветки строк //--- Создаёт и добавляет в список новый объект представления ячейки CTableCellView *InsertNewCellView(const int index,const string text,const int dx,const int dy,const int w,const int h); //--- Удаляет указанную область строки и ячейку с соответствующим индексом bool BoundCellDelete(const int index); //--- Возвращает визуальное представление (1) таблицы, заголовка (2) столбцов, (3) строк CTableView *GetTableView(void); CTableHeaderView *GetHeaderView(void); CTableRowsHeaderView *GetRowsHeaderView(void); //--- Устанавливает выбранным указанный заголовок (1) столбца, (2) строки void SetColumnCaptionSelected(const uint index); void SetRowCaptionSelected(const uint index); //--- Снимает выделение со всех заголовков (1) столбца, (2) строки void SetAllColumnCaptionsUnselected(const int exclude=-1); void SetAllRowCaptionsUnselected(const int exclude=-1); public: //--- Возвращает (1) список, (2) количество ячеек, (3) ячейку, заголовок (4) столбца, (5) строки CListElm *GetListCells(void) { return &this.m_list_cells; } int CellsTotal(void) const { return this.m_list_cells.Total(); } CTableCellView *GetCellView(const uint index) { return this.m_list_cells.GetNodeAtIndex(index); } CColumnCaptionView *GetColumnCaption(const uint index); CRowCaptionView *GetRowCaption(const uint index); //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс строки void SetIndex(const int index) { this.m_index=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; } //--- Обновляет сторку с обновлённой моделью bool TableRowModelUpdate(CTableRow *row_model); //--- (1) Устанавливает, (2) возвращает режим подсветки строки void SetHighlightMode(const ENUM_ROWS_HIGHLIGHT_MODE mode) { this.m_highlight_mode=mode; } ENUM_ROWS_HIGHLIGHT_MODE HighlightMode(void) const { return this.m_highlight_mode; } //--- Перерассчитывает области ячеек bool RecalculateBounds(CListElm *list_bounds); //--- Распечатывает в журнале назначенную модель строки 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_VIEW); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), virtual void OnFocusEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Конструкторы/деструктор 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){ this.m_list_cells.Clear(); } };
В конструкторах класса инициализируем режим подсветки по умолчанию как "вся строка":
//+------------------------------------------------------------------+ //| CTableRowView::Конструктор по умолчанию. Строит объект в главном | //| окне текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CTableRowView::CTableRowView(void) : CPanel("TableRow","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(-1), m_highlight_mode(ROWS_HIGHLIGHT_MODE_ROW) { //--- Инициализация 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), m_highlight_mode(ROWS_HIGHLIGHT_MODE_ROW) { //--- Инициализация this.Init(); }
Напишем реализацию новых методов.
Метод, возвращающий визуальное представление таблицы:
//+------------------------------------------------------------------+ //| CTableRowView::Возвращает визуальное представление таблицы | //+------------------------------------------------------------------+ CTableView *CTableRowView::GetTableView(void) { CTableView *obj=NULL; //--- Получаем панель со строками таблицы CElementBase *base0=this.GetContainer(); if(base0==NULL) return NULL; //--- Получаем контейнер панели строк таблицы CElementBase *base1=base0.GetContainer(); if(base1==NULL) return NULL; //--- Получаем объект визуального представления таблицы CElementBase *base2=base1.GetContainer(); if(base2!=NULL && base2.Type()==ELEMENT_TYPE_TABLE_VIEW) { obj=base2; return obj; } return NULL; }
Таблица организована следующим образом:
На панели с типом "Таблица" (1) размещён заголовок таблицы и контейнер (2), внутри которого размещена прокручиваемая панель (3), на которой в свою очередь расположены строки таблицы (4), являющиеся текущим рассматриваемым классом.
Для получения объекта таблицы (1), необходимо получить базовый объект-панель (3), к которому прикреплена строка. Далее из панели (3) получаем его базовый объект-контейнер (2), а из контейнера — его базовый объект-таблицу (1).
Метод, возвращающий визуальное представление заголовка столбцов:
//+------------------------------------------------------------------+ //| CTableRowView::Возвращает визуальное представление | //| заголовка столбцов | //+------------------------------------------------------------------+ CTableHeaderView *CTableRowView::GetHeaderView(void) { CTableView *table=this.GetTableView(); return(table!=NULL ? table.GetHeader() : NULL); }
Здесь: получаем объект "таблица" методом, рассмотренным выше, а из него — объект горизонтальный заголовок.
Метод, возвращающий визуальное представление заголовка строк:
//+------------------------------------------------------------------+ //|CTableRowView::Возвращает визуальное представление заголовка строк| //+------------------------------------------------------------------+ CTableRowsHeaderView *CTableRowView::GetRowsHeaderView(void) { CTableView *table=this.GetTableView(); return(table!=NULL ? table.GetRowsHeader() : NULL); }
Получаем объект "таблица", а из него — объект вертикальный заголовок, реализацию которого будем рассматривать ниже.
Метод, возвращающий заголовок столбца:
//+------------------------------------------------------------------+ //| CTableRowView::Возвращает заголовок столбца | //+------------------------------------------------------------------+ CColumnCaptionView *CTableRowView::GetColumnCaption(const uint index) { CTableHeaderView *header=this.GetHeaderView(); return(header!=NULL ? header.GetColumnCaption(index) : NULL); }
Здесь: сначала получаем объект горизонтального заголовка при помощи метода, рассмотренного выше, а из него — указанный по индексу заголовок столбца.
Метод, возвращающий заголовок строки:
//+------------------------------------------------------------------+ //| CTableRowView::Возвращает заголовок строки | //+------------------------------------------------------------------+ CRowCaptionView *CTableRowView::GetRowCaption(const uint index) { CTableRowsHeaderView *header=this.GetRowsHeaderView(); return(header!=NULL ? header.GetRowCaption(index) : NULL); }
При помощи метода, рассмотренного выше, получаем объект вертикального заголовка, реализацию которого рассмотрим ниже, а из него — указанный по индексу заголовок строки.
Метод, устанавливающий выбранным указанный заголовок столбца:
//+------------------------------------------------------------------+ //|CTableRowView::Устанавливает выбранным указанный заголовок столбца| //+------------------------------------------------------------------+ void CTableRowView::SetColumnCaptionSelected(const uint index) { CColumnCaptionView *capt=this.GetColumnCaption(index); if(capt==NULL || capt.State()==ELEMENT_STATE_ACT) return; capt.SetState(ELEMENT_STATE_ACT); capt.GetBackground().FillRectangle(0,capt.Height()-2,capt.Width()-1,capt.Height()-1,ColorToARGB(clrCadetBlue)); capt.GetBackground().Update(false); }
Заголовок столбца может иметь статус выбранного. Например, чтобы выделить заголовок, соответствующий выбранной ячейке таблицы. Метод проверяет, что полученный заголовок таблицы не является уже выбранным, устанавливает флаг выбранного объекта и рисует снизу заголовка тонкую линию выделения.
Метод, устанавливающий выбранным указанный заголовок строки:
//+------------------------------------------------------------------+ //| CTableRowView::Устанавливает выбранным указанный заголовок строки| //+------------------------------------------------------------------+ void CTableRowView::SetRowCaptionSelected(const uint index) { CRowCaptionView *capt=this.GetRowCaption(index); if(capt==NULL || capt.State()==ELEMENT_STATE_ACT) return; capt.SetState(ELEMENT_STATE_ACT); capt.GetBackground().FillRectangle(capt.Width()-2,2,capt.Width()-1,capt.Height()-0,ColorToARGB(clrCadetBlue)); capt.GetBackground().Update(false); }
Логика метода аналогична логике предыдущего, только выделяется правый край заголовка.
Метод, снимающий выделение со всех заголовков столбца:
//+------------------------------------------------------------------+ //| CTableRowView::Снимает выделение со всех заголовков столбца | //+------------------------------------------------------------------+ void CTableRowView::SetAllColumnCaptionsUnselected(const int exclude=-1) { CTableHeaderView *header=this.GetHeaderView(); if(header==NULL) return; int total=header.BoundsTotal(); for(int i=0;i<total;i++) { CColumnCaptionView *capt=this.GetColumnCaption(i); if(capt==NULL || (exclude>-1 && i==exclude)) continue; if(capt.State()!=ELEMENT_STATE_DEF) { capt.SetState(ELEMENT_STATE_DEF); capt.Draw(false); } } }
В метод передаётся индекс заголовка, который нужно оставить выбранным. Со всех остальных выделение снимается. В цикле по количеству заголовков получаем очередной заголовок столбца и проверяем, что это указанный для исключения заголовок. Если индекс цикла совпадает с указанным в exclude, то этот заголовок остаётся выбранным. Со всех остальных заголовков выделение снимается. Если в exclude передано отрицательное значение, то со всех заголовков снимается статус выбранности.
Метод, снимающий выделение со всех заголовков строки:
//+------------------------------------------------------------------+ //| CTableRowView::Снимает выделение со всех заголовков строки | //+------------------------------------------------------------------+ void CTableRowView::SetAllRowCaptionsUnselected(const int exclude=-1) { CTableRowsHeaderView *header=this.GetRowsHeaderView(); if(header==NULL) return; int total=header.BoundsTotal(); for(int i=0;i<total;i++) { CRowCaptionView *capt=this.GetRowCaption(i); if(capt==NULL || (exclude>-1 && capt.ID()==exclude)) continue; if(capt.State()!=ELEMENT_STATE_DEF) { capt.SetState(ELEMENT_STATE_DEF); capt.Draw(false); } } }
Логика метода идентична логике вышерассмотренного метода.
Обработчик наведения курсора:
//+------------------------------------------------------------------+ //| CTableRowView::Обработчик наведения курсора | //+------------------------------------------------------------------+ void CTableRowView::OnFocusEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если обрабатывается целиком строка - вызываем обработчик событий родительского класса if(this.m_highlight_mode==ROWS_HIGHLIGHT_MODE_ROW) { CCanvasBase::OnFocusEvent(id,lparam,dparam,sparam); return; } //--- Получаем координаты курсора int x=int(lparam-this.X()); int y=int(dparam-this.m_wnd_y-this.Y()); //--- В цикле по областям ячеек строки int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- получаем очередную область CBound *bound=this.GetBoundAt(i); if(bound==NULL) continue; //--- Из текущей области получаем назначенный на неё элемент CBaseObj *obj=bound.GetAssignedObj(); CTableCellView *cell=NULL; //--- Если полученный элемент не является ячейкой таблицы - идём дальше if(obj==NULL || obj.Type()!=ELEMENT_TYPE_TABLE_CELL_VIEW) continue; //--- Это ячейка таблицы. Определяем её координаты в таблице (строка/столбец) cell=obj; int row=this.ID(); int col=obj.ID(); //--- Получаем соответствующие заголовки строки и столбца CColumnCaptionView *col_capt=this.GetColumnCaption(col); CRowCaptionView *row_capt=this.GetRowCaption(row); if(col_capt==NULL || row_capt==NULL) continue; //--- Если курсор находится на области ячейки if(bound.Contains(x,y)) { //--- Устанавливаем заголовок столбца и строки выбранными, this.SetColumnCaptionSelected(i); this.SetRowCaptionSelected(this.ID()); //--- со всех заголовков строк, кроме текущего, снимаем флаг выбранного заголовка this.SetAllRowCaptionsUnselected(this.ID()); } //--- Если курсор за пределами области ячейки else { //--- Если заголовок выбран, if(col_capt.State()!=ELEMENT_STATE_DEF) { //--- снимаем с него выделение и перерисовываем объект не выбранным col_capt.SetState(ELEMENT_STATE_DEF); col_capt.Draw(false); } } } }
Логика обработчика расписана в его комментариях. Если подсвечивается полностью вся строка, то вызывается обработчик "объект в фокусе" родительского класса. Иначе — ищется ячейка по её координатам в таблице и, если курсор на неё наведён, то делаются выбранными соответствующие ячейке заголовки строки и столбца. Остальные какие-либо изменения внешнего вида ячейки здесь не реализованы — эта задача на последующие доработки.
Обработчик нажатия на объект:
//+------------------------------------------------------------------+ //| CTableRowView::Обработчик нажатия на объект | //+------------------------------------------------------------------+ void CTableRowView::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если обрабатывается целиком строка - вызываем обработчик событий родительского класса if(this.m_highlight_mode==ROWS_HIGHLIGHT_MODE_ROW) { CCanvasBase::OnPressEvent(id,lparam,dparam,sparam); return; } //--- В цикле по всем областям строки int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- получаем очередную область CBound *bound=this.GetBoundAt(i); if(bound==NULL) continue; //--- Получаем координаты курсора и int x=int(lparam-this.X()); int y=int(dparam-this.m_wnd_y-this.Y()); //--- проверяем, что курсор находится внутри области if(bound.Contains(x,y)) { //--- Получаем из области прикреплённый объект (ячейка) CBaseObj *obj=bound.GetAssignedObj(); if(obj!=NULL) { //--- Записываем адрес ячейки в таблице (строка/столбец) int row=this.ID(); int col=obj.ID(); //--- На основе идентификаторов строки и столбца получаем указатели на соответствующие заголовки CRowCaptionView *row_capt=this.GetRowCaption(row); CColumnCaptionView *col_capt=this.GetColumnCaption(col); if(row_capt==NULL || col_capt==NULL) return; //--- Создаём текстовое значение для пользовательского события из имени строки и текстов заголовков string sprm=obj.Name()+";"+row_capt.Text()+";"+col_capt.Text(); //--- Посылаем пользовательское событие щелчка по объекту с координатами строки и столбца и текстом ::EventChartCustom(this.m_chart_id,CHARTEVENT_OBJECT_CLICK,row,col,sprm); } } } }
Логика метода расписана в его комментариях. Если обрабатывается полностью вся строка, то вызывается обработчик "объект в фокусе" родительского класса. Иначе — ищется ячейка по её координатам в таблице и, если курсор на неё наведён, то создаём строку из текстов заголовков строки и столбца (имя символа в контексте данной статьи) с разделителем ";" и отсылается пользовательское событие щелчка по объекту с указанием индексов строки (lparam) и столбца (dparam) и созданной строкой из текстов заголовков в sparam. Далее это событие можно получить в программе и определить по какой ячейке был щелчок, чтобы обработать это событие.
В методах работы с файлами сохраняем/загружаем значение режима подсветки:
//+------------------------------------------------------------------+ //| 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; //--- Сохраняем режим подсветки if(::FileWriteInteger(file_handle,this.m_highlight_mode,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_index=(int)::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем режим подсветки this.m_highlight_mode=(ENUM_ROWS_HIGHLIGHT_MODE)::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Так как теперь будет два типа заголовков — заголовок столбца и заголовок строки таблицы, то ранее созданный и используемый класс заголовка столбца таблицы, нужно разделить на два класса: класс абстрактного заголовка и наследуемые от него классы заголовка столбца и заголовка строки таблицы. В абстрактный класс перенесём общие свойства и функциональность любого заголовка, а в наследуемых классах уже будет уточнение функционала относительно того, какой именно это заголовок.
Абстрактный класс визуального представления заголовка:
//+------------------------------------------------------------------+ //| Абстрактный класс визуального представления заголовка | //+------------------------------------------------------------------+ class CCaptionView : public CButton { protected: CBound *m_bound_node; // Указатель на область заголовка int m_index; // Индекс в списке строк public: //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс строки void SetIndex(const int index) { this.m_index=index; } int Index(void) const { return this.m_index; } //--- (1) Назначает, (2) возвращает область заголовка, которой назначен объект void AssignBoundNode(CBound *bound) { this.m_bound_node=bound; } CBound *GetBoundNode(void) { return this.m_bound_node; } //--- Рисует (1) внешний вид, (2) стрелку направления сортировки 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_CAPTION_VIEW); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void); //--- Возвращает описание объекта virtual string Description(void); //--- Конструкторы/деструктор CCaptionView(void); CCaptionView(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); ~CCaptionView (void){} }; //+------------------------------------------------------------------+ //| CCaptionView::Конструктор по умолчанию. Строит объект | //| в главном окне текущего графика в координатах 0,0 | //| с размерами по умолчанию | //+------------------------------------------------------------------+ CCaptionView::CCaptionView(void) : CButton("Caption","Caption",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(0) { //--- Инициализация this.Init("Caption"); this.SetID(0); this.SetIndex(-1); this.SetName("Caption"); } //+------------------------------------------------------------------+ //| CCaptionView::Конструктор параметрический. | //| Строит объект в указанном окне указанного графика с | //| указанными текстом, координатами и размерами | //+------------------------------------------------------------------+ CCaptionView::CCaptionView(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); this.SetIndex(-1); } //+------------------------------------------------------------------+ //| CCaptionView::Инициализация | //+------------------------------------------------------------------+ void CCaptionView::Init(const string text) { //--- Смещения текста по умолчанию this.m_text_x=4; this.m_text_y=2; //--- Устанавливаем цвета различных состояний this.InitColors(); //--- Возможно изменять размеры this.SetResizable(false); this.SetMovable(false); this.SetImageBound(this.ObjectWidth()-14,4,8,11); } //+------------------------------------------------------------------+ //| CCaptionView::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CCaptionView::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(C'230,230,230',C'159,213,183',this.GetBackColorControl().NewColor(C'159,213,183',-6,-6,-6),clrSilver); this.InitBackColorsAct(C'230,230,230',C'159,213,183',this.GetBackColorControl().NewColor(C'159,213,183',-6,-6,-6),clrSilver); 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); } //+------------------------------------------------------------------+ //| CCaptionView::Рисует внешний вид | //+------------------------------------------------------------------+ void CCaptionView::Draw(const bool chart_redraw) { //--- Если объект за пределами своего контейнера - уходим if(this.IsOutOfContainer()) return; //--- Заливаем объект цветом фона, рисуем слева светлую вертикальную линию, справа - тёмную 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); } //+------------------------------------------------------------------+ //| CCaptionView::Возвращает описание объекта | //+------------------------------------------------------------------+ string CCaptionView::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()); } //+------------------------------------------------------------------+ //| CCaptionView::Сохранение в файл | //+------------------------------------------------------------------+ bool CCaptionView::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; } //+------------------------------------------------------------------+ //| CCaptionView::Загрузка из файла | //+------------------------------------------------------------------+ bool CCaptionView::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CButton::Load(file_handle)) return false; //--- Загружаем номер заголовка this.m_index=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
В этот класс перенесены все свойства и методы, абсолютно одинаково используемые в любом из наследуемых классов заголовков.
Теперь ранее созданный класс заголовка столбца таблицы, унаследован от абстрактного класса заголовка и имеет усечённый вид, так как многие методы перенесены в родительский абстрактный класс заголовка.
Рассмотрим класс целиком:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка столбца таблицы | //+------------------------------------------------------------------+ class CColumnCaptionView : public CCaptionView { protected: CColumnCaption *m_column_caption_model; // Указатель на модель заголовка столбца ENUM_TABLE_SORT_MODE m_sort_mode; // Режим сортировки столбца таблицы bool m_sortable; // Флаг управления сортировкой //--- Добавляет в список объекты-подсказки со стрелками virtual bool AddHintsArrowed(void); //--- Отображает курсор изменения размеров virtual bool ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y); public: //--- (1) Назначает, (2) возвращает модель заголовка столбца bool ColumnCaptionModelAssign(CColumnCaption *caption_model); CColumnCaption *ColumnCaptionModel(void) { return this.m_column_caption_model; } //--- Распечатывает в журнале назначенную модель заголовка столбца void ColumnCaptionModelPrint(void); //--- (1) Устанавливает, (2) возвращает флаг возможности сортировки void SetSortableFlag(const bool flag) { this.m_sortable=flag; this.SetSortMode(flag ? TABLE_SORT_MODE_ASC : TABLE_SORT_MODE_NONE); } bool IsSortabe(void) const { return this.m_sortable; } //--- (1) Устанавливает, (2) возвращает режим сортировки void SetSortMode(const ENUM_TABLE_SORT_MODE mode) { this.m_sort_mode=mode; } ENUM_TABLE_SORT_MODE SortMode(void) const { return this.m_sort_mode; } //--- Устанавливает противоположное направление сортировки void SetSortModeReverse(void); //--- Рисует (1) внешний вид, (2) стрелку направления сортировки virtual void Draw(const bool chart_redraw); protected: void DrawSortModeArrow(void); public: //--- Обработчик изменения размеров элемента по правой стороне virtual bool ResizeZoneRightHandler(const int x, const int y); //--- Обработчики изменения размеров элемента по сторонам и углам virtual bool ResizeZoneLeftHandler(const int x, const int y) { return false; } virtual bool ResizeZoneTopHandler(const int x, const int y) { return false; } virtual bool ResizeZoneBottomHandler(const int x, const int y) { return false; } virtual bool ResizeZoneLeftTopHandler(const int x, const int y) { return false; } virtual bool ResizeZoneRightTopHandler(const int x, const int y) { return false; } virtual bool ResizeZoneLeftBottomHandler(const int x, const int y) { return false; } virtual bool ResizeZoneRightBottomHandler(const int x, const int y){ return false; } //--- Изменяет ширину объекта virtual bool ResizeW(const int w); //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Виртуальные методы (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_VIEW);} //--- Инициализация (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) : CCaptionView("ColumnCaption","Caption",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H),m_sort_mode(TABLE_SORT_MODE_NONE),m_sortable(true) { //--- Инициализация this.Init("Caption"); this.SetID(0); this.SetIndex(-1); 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) : CCaptionView(object_name,text,chart_id,wnd,x,y,w,h),m_sort_mode(TABLE_SORT_MODE_NONE),m_sortable(true) { //--- Инициализация this.Init(text); this.SetID(0); this.SetIndex(-1); } //+------------------------------------------------------------------+ //| CColumnCaptionView::Инициализация | //+------------------------------------------------------------------+ void CColumnCaptionView::Init(const string text) { //--- Инициализация родительского объекта CCaptionView::Init(text); //--- Возможно изменять размеры this.SetResizable(true); this.SetMovable(false); } //+------------------------------------------------------------------+ //| CColumnCaptionView::Рисует внешний вид | //+------------------------------------------------------------------+ void CColumnCaptionView::Draw(const bool chart_redraw) { //--- Если объект за пределами своего контейнера - уходим if(this.IsOutOfContainer()) return; //--- Заливаем объект цветом фона, рисуем слева светлую вертикальную линию, справа - тёмную this.Fill(this.BackColor(),false); color clr_dark =this.BorderColor(); // "Тёмный цвет" color clr_light=this.GetBackColorControl().NewColor(this.BorderColor(), 20, 20, 20); // "Светлый цвет" 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())); // Линия справа //--- Выводим текст заголовка CLabel::Draw(false); //--- Рисуем стрелки направления сортировки this.DrawSortModeArrow(); //--- обновляем канвас фона this.m_background.Update(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| CColumnCaptionView::Рисует стрелку направления сортировки | //+------------------------------------------------------------------+ void CColumnCaptionView::DrawSortModeArrow(void) { //--- Задаём цвет стрелки для обычного и заблокированного состояний объекта color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); switch(this.m_sort_mode) { //--- Сортировка по возрастанию case TABLE_SORT_MODE_ASC : //--- Очищаем область рисунка и рисуем стрелку вниз 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); this.m_painter.ArrowDown(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); break; //--- Сортировка по убыванию case TABLE_SORT_MODE_DESC : //--- Очищаем область рисунка и рисуем стрелку вверх 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); this.m_painter.ArrowUp(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); break; //--- Нет сортировки default : //--- Очищаем область рисунка 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); break; } } //+------------------------------------------------------------------+ //| CColumnCaptionView::Разворачивает направление сортировки | //+------------------------------------------------------------------+ void CColumnCaptionView::SetSortModeReverse(void) { switch(this.m_sort_mode) { case TABLE_SORT_MODE_ASC : this.m_sort_mode=TABLE_SORT_MODE_DESC; break; case TABLE_SORT_MODE_DESC : this.m_sort_mode=TABLE_SORT_MODE_ASC; break; default : break; } } //+------------------------------------------------------------------+ //| CColumnCaptionView::Возвращает описание объекта | //+------------------------------------------------------------------+ string CColumnCaptionView::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); string sort=(this.SortMode()==TABLE_SORT_MODE_ASC ? "ascending" : this.SortMode()==TABLE_SORT_MODE_DESC ? "descending" : "none"); return ::StringFormat("%s%s ID %d, X %d, Y %d, W %d, H %d, sort %s",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID(),this.X(),this.Y(),this.Width(),this.Height(),sort); } //+------------------------------------------------------------------+ //| 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::AddHintsArrowed(void) { //--- Создаём подсказку стрелки горизонтального смещения CVisualHint *hint=this.CreateAndAddNewHint(HINT_TYPE_ARROW_SHIFT_HORZ,DEF_HINT_NAME_SHIFT_HORZ,18,18); if(hint==NULL) return false; //--- Устанавливаем размер области изображения подсказки hint.SetImageBound(0,0,hint.Width(),hint.Height()); //--- скрываем подсказку и рисуем внешний вид hint.Hide(false); hint.Draw(false); //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CColumnCaptionView::Отображает курсор изменения размеров | //+------------------------------------------------------------------+ bool CColumnCaptionView::ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y) { CVisualHint *hint=NULL; // Указатель на подсказку int hint_shift_x=0; // Смещение подсказки по X int hint_shift_y=0; // Смещение подсказки по Y //--- В зависимости от расположения курсора на границах элемента //--- указываем смещения подсказки относительно координат курсора, //--- отображаем на графике требуемую подсказку и получаем указатель на этот объект if(edge!=CURSOR_REGION_RIGHT) return false; hint_shift_x=-8; hint_shift_y=-12; this.ShowHintArrowed(HINT_TYPE_ARROW_SHIFT_HORZ,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint(DEF_HINT_NAME_SHIFT_HORZ); //--- Возвращаем результат корректировки положения подсказки относительно курсора return(hint!=NULL ? hint.Move(x+hint_shift_x,y+hint_shift_y) : false); } //+------------------------------------------------------------------+ //| CColumnCaptionView::Обработчик изменения размеров за правую грань| //+------------------------------------------------------------------+ bool CColumnCaptionView::ResizeZoneRightHandler(const int x,const int y) { //--- Рассчитываем и устанавливаем новую ширину элемента int width=::fmax(x-this.X()+1,DEF_TABLE_COLUMN_MIN_W); if(!this.ResizeW(width)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint(DEF_HINT_NAME_SHIFT_HORZ); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=-8; int shift_y=-12; CTableHeaderView *header=this.m_container; if(header==NULL) return false; bool res=header.RecalculateBounds(this.GetBoundNode(),this.Width()); res &=hint.Move(x+shift_x,y+shift_y); if(res) ::ChartRedraw(this.m_chart_id); return res; } //+------------------------------------------------------------------+ //| CColumnCaptionView::Изменяет ширину объекта | //+------------------------------------------------------------------+ bool CColumnCaptionView::ResizeW(const int w) { if(!CCanvasBase::ResizeW(w)) return 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); //--- Устанавливаем новую область рисунка this.SetImageBound(this.Width()-14,4,8,11); return true; } //+------------------------------------------------------------------+ //| CColumnCaptionView::Обработчик событий нажатий кнопок мышки | //+------------------------------------------------------------------+ void CColumnCaptionView::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если кнопка мышки отпущена в области перетаскивания правой грани элемента - уходим if(this.ResizeRegion()==CURSOR_REGION_RIGHT) return; //--- Меняем стрелку направления сортировки на обратную и вызываем обработчик щелчка мышки if(this.m_sortable) this.SetSortModeReverse(); CCanvasBase::OnPressEvent(id,lparam,dparam,sparam); ::EventChartCustom(this.m_chart_id,CHARTEVENT_OBJECT_CLICK,this.ID(),-(10000+this.SortMode()),this.NameFG()); } //+------------------------------------------------------------------+ //| 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; //--- Сохраняем направление сортировки if(::FileWriteInteger(file_handle,this.m_sort_mode,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем флаг управления сортировкой if(::FileWriteInteger(file_handle,this.m_sortable,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_index=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем направление сортировки this.m_sort_mode=(ENUM_TABLE_SORT_MODE)::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем флаг управления сортировкой this.m_sortable=(bool)::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
В класс добавлен флаг управления сортировкой и методы управления этим флагом:
class CColumnCaptionView : public CCaptionView { protected: CColumnCaption *m_column_caption_model; // Указатель на модель заголовка столбца ENUM_TABLE_SORT_MODE m_sort_mode; // Режим сортировки столбца таблицы bool m_sortable; // Флаг управления сортировкой //--- Добавляет в список объекты-подсказки со стрелками virtual bool AddHintsArrowed(void); //--- Отображает курсор изменения размеров virtual bool ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y); public: //--- (1) Назначает, (2) возвращает модель заголовка столбца bool ColumnCaptionModelAssign(CColumnCaption *caption_model); CColumnCaption *ColumnCaptionModel(void) { return this.m_column_caption_model; } //--- Распечатывает в журнале назначенную модель заголовка столбца void ColumnCaptionModelPrint(void); //--- (1) Устанавливает, (2) возвращает флаг возможности сортировки void SetSortableFlag(const bool flag) { this.m_sortable=flag; this.SetSortMode(flag ? TABLE_SORT_MODE_ASC : TABLE_SORT_MODE_NONE); } bool IsSortabe(void) const { return this.m_sortable; } //--- (1) Устанавливает, (2) возвращает режим сортировки
Теперь мы можем устанавливать для столбца возможность сортировки таблицы по значениям его ячеек.
В конструкторах класса его значение по умолчанию установлено в true:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Конструктор по умолчанию. Строит объект | //| в главном окне текущего графика в координатах 0,0 | //| с размерами по умолчанию | //+------------------------------------------------------------------+ CColumnCaptionView::CColumnCaptionView(void) : CCaptionView("ColumnCaption","Caption",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H),m_sort_mode(TABLE_SORT_MODE_NONE),m_sortable(true) { //--- Инициализация this.Init("Caption"); this.SetID(0); this.SetIndex(-1); 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) : CCaptionView(object_name,text,chart_id,wnd,x,y,w,h),m_sort_mode(TABLE_SORT_MODE_NONE),m_sortable(true) { //--- Инициализация this.Init(text); this.SetID(0); this.SetIndex(-1); }
В обработчике событий нажатий кнопок мышки теперь проверяется флаг управления сортировкой и отправляется пользовательское событие щелчка по объекту:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Обработчик событий нажатий кнопок мышки | //+------------------------------------------------------------------+ void CColumnCaptionView::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если кнопка мышки отпущена в области перетаскивания правой грани элемента - уходим if(this.ResizeRegion()==CURSOR_REGION_RIGHT) return; //--- Меняем стрелку направления сортировки на обратную и вызываем обработчик щелчка мышки if(this.m_sortable) this.SetSortModeReverse(); CCanvasBase::OnPressEvent(id,lparam,dparam,sparam); ::EventChartCustom(this.m_chart_id,CHARTEVENT_OBJECT_CLICK,this.ID(),-(10000+this.SortMode()),this.NameFG()); }
В пользовательском событии в lparam указываем идентификатор заголовка, а в dparam — отрицательное значение режима сортировки, увеличенное на 10000, чтобы точно знать, что это не координата курсора. В sparam указывается имя объекта (наименование канваса переднего плана). Всё это позволит в программе получить событие щелчка по заголовку и определить его параметры и режим сортировки, установленный для него.
В методах работы с файлами сохраняем/загружаем флаг управления сортировкой:
//+------------------------------------------------------------------+ //| 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; //--- Сохраняем направление сортировки if(::FileWriteInteger(file_handle,this.m_sort_mode,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем флаг управления сортировкой if(::FileWriteInteger(file_handle,this.m_sortable,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_index=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем направление сортировки this.m_sort_mode=(ENUM_TABLE_SORT_MODE)::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем флаг управления сортировкой this.m_sortable=(bool)::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
На основании этого доработанного класса создадим новый класс заголовка строки, также унаследованного от абстрактного класса заголовка:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка строки таблицы | //+------------------------------------------------------------------+ class CRowCaptionView : public CCaptionView { protected: public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); public: //--- Виртуальные методы (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_ROW_CAPTION_VIEW);} //--- Инициализация объекта класса void Init(const string text); //--- Возвращает описание объекта virtual string Description(void); //--- Конструкторы/деструктор CRowCaptionView(void); CRowCaptionView(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); ~CRowCaptionView (void){} }; //+------------------------------------------------------------------+ //| CRowCaptionView::Конструктор по умолчанию. Строит объект | //| в главном окне текущего графика в координатах 0,0 | //| с размерами по умолчанию | //+------------------------------------------------------------------+ CRowCaptionView::CRowCaptionView(void) : CCaptionView("RowCaption","Caption",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H) { //--- Инициализация this.Init("Caption"); this.SetID(0); this.SetIndex(-1); this.SetName("RowCaption"); this.SetTextShiftH(8); } //+------------------------------------------------------------------+ //| CRowCaptionView::Конструктор параметрический. | //| Строит объект в указанном окне указанного графика с | //| указанными текстом, координатами и размерами | //+------------------------------------------------------------------+ CRowCaptionView::CRowCaptionView(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) : CCaptionView(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(text); this.SetID(0); this.SetIndex(-1); this.SetTextShiftH(8); } //+------------------------------------------------------------------+ //| CRowCaptionView::Инициализация | //+------------------------------------------------------------------+ void CRowCaptionView::Init(const string text) { //--- Инициализация родительского объекта CCaptionView::Init(text); //--- Размеры не изменяемые this.SetResizable(false); this.SetMovable(false); } //+------------------------------------------------------------------+ //| CRowCaptionView::Рисует внешний вид | //+------------------------------------------------------------------+ void CRowCaptionView::Draw(const bool chart_redraw) { //--- Если объект за пределами своего контейнера - уходим if(this.IsOutOfContainer()) return; //--- Заливаем объект цветом фона, рисуем слева светлую вертикальную линию, справа - тёмную this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(2),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); //--- обновляем канвас фона this.m_background.Update(false); //--- Выводим текст заголовка CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| CRowCaptionView::Возвращает описание объекта | //+------------------------------------------------------------------+ string CRowCaptionView::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()); } //+------------------------------------------------------------------+ //| CRowCaptionView::Сохранение в файл | //+------------------------------------------------------------------+ bool CRowCaptionView::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; } //+------------------------------------------------------------------+ //| CRowCaptionView::Загрузка из файла | //+------------------------------------------------------------------+ bool CRowCaptionView::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CButton::Load(file_handle)) return false; //--- Загружаем номер заголовка this.m_index=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Этот класс немного проще класса заголовка столбца, так как здесь не требуется никакой сортировки, вывода подсказок для изменения размеров, равно, как и не реализовано собственно изменение вертикальных размеров строки при изменении вертикального размера заголовка, как это сделано у заголовка столбца при изменении его горизонтальных размеров. Здесь для краткости всё это убрано, но вполне может быть реализовано впоследствии.
Доработаем класс визуального представления заголовка таблицы для установки флага возможности сортировки таблицы.
Добавим новые переменные и методы для работы с флагом сортировки:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка таблицы | //+------------------------------------------------------------------+ class CTableHeaderView : public CPanel { protected: CColumnCaptionView m_temp_caption; // Временный объект заголовка столбца для поиска CTableHeader *m_table_header_model; // Указатель на модель заголовка таблицы bool m_sortable; // Флаг управления сортировкой //--- Создаёт и добавляет в список новый объект представления заголовка столбца 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; } //--- Перерассчитывает области заголовков bool RecalculateBounds(CBound *bound,int new_width); //--- Распечатывает в журнале назначенную модель заголовка таблицы 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) возвращает флаг возможности сортировки void SetSortableFlag(const bool flag); bool IsSortabe(void) const { return this.m_sortable; } //--- Устанавливает заголовку столбца флаг сортировки void SetSortedColumnCaption(const uint index); //--- Получает заголовок столбца (1) по индексу, (2) с флагом сортировки CColumnCaptionView *GetColumnCaption(const uint index); CColumnCaptionView *GetSortedColumnCaption(void); //--- Возвращает индекс заголовка столбца с флагом сортировки int IndexSortedColumnCaption(void); //--- Виртуальные методы (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_VIEW); } //--- Обработчик пользовательского события элемента при щелчке на области объекта virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam); //--- Инициализация (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),m_sortable(true) { //--- Инициализация 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),m_sortable(true) { //--- Инициализация this.Init(); }
В методе установки модели заголовка теперь контролируем этот флаг:
//+------------------------------------------------------------------+ //| 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)::fmax(::round((double)this.Width()/(double)total),DEF_TABLE_COLUMN_MIN_W); //--- В цикле по количеству заголовков столбцов в модели заголовка таблицы 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; caption_bound.SetID(i); //--- Создаём новый объект визуального представления заголовка столбца CColumnCaptionView *caption_view=this.InsertNewColumnCaptionView(caption_model.Value(),x,0,caption_w,this.Height()); if(caption_view==NULL) return false; caption_view.SetIndex(i); //--- На текущую область заголовка столбца назначаем соответствующий объект визуального представления заголовка столбца caption_bound.AssignObject(caption_view); caption_view.AssignBoundNode(caption_bound); //--- Для самого первого заголовка устанавливаем флаг сортировки по возрастанию if(i==0 && caption_view.IsSortabe()) caption_view.SetSortMode(TABLE_SORT_MODE_ASC); } //--- Всё успешно return true; }
Метод, устанавливающий флаг возможности сортировки:
//+------------------------------------------------------------------+ //| CTableHeaderView::Устанавливает флаг возможности сортировки | //+------------------------------------------------------------------+ void CTableHeaderView::SetSortableFlag(const bool flag) { //--- Записываем значение флага this.m_sortable=flag; //--- В цикле по количеству заголовков столбцов int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- получаем очередной объект заголовка столбца и устанавливаем ему флаг сортировки CColumnCaptionView *caption_view=this.GetColumnCaption(i); if(caption_view!=NULL) caption_view.SetSortableFlag(flag); } //--- Если сортируемая таблица - ставим нулевой столбец сортированным по возрастанию if(this.m_sortable) this.SetSortedColumnCaption(0); //--- Перерисовываем заголовок this.Draw(true); }
В метод передаётся флаг, записывается в переменную и в каждый из заголовков столбцов. Если переданный флаг сортировки имеет значение true, то самому первому столбцу устанавливаем направление сортировки по возрастанию.
По окончании установки флагов во все заголовки столбцов весь заголовок перерисовывается.
В обработчике пользовательского события элемента при щелчке на области объекта сделаем более тщательный поиск индекса заголовка столбца в значении sparamи будем учитывать флаг сортировки:
//+------------------------------------------------------------------+ //| CTableHeaderView::Обработчик пользовательского события элемента | //| при щелчке на области объекта | //+------------------------------------------------------------------+ void CTableHeaderView::MousePressHandler(const int id,const long lparam,const double dparam,const string sparam) { //--- Получаем из sparam наименование объекта заголовка таблицы int len=::StringLen(this.NameFG()); string header_str=::StringSubstr(sparam,0,len); //--- Если извлечённое имя не совпадает с именем этого объекта - не наше событие, уходим if(header_str!=this.NameFG()) return; //--- Найдём в sparam индекс заголовка столбца string capt_str=::StringSubstr(sparam,len+1); string index_str=::StringSubstr(capt_str,6,capt_str.Length()-8); //--- Первый символ перед "FG" (последняя цифра искомого индекса) int pos=(int)capt_str.Length()-3; int end=pos; //--- Ищем все цифры слева до первой "не цифры" while(!::IsStopped() && pos>=0 && capt_str.GetChar(pos)>='0' && capt_str.GetChar(pos)<='9') pos--; //--- Начало цифр искомого индекса int start=pos+1; //--- Если цифры индекса не найдены - уходим if(start>end) return; //--- Получаем индекс из строки index_str=StringSubstr(capt_str,start,end-start+1); //--- Записываем индекс заголовка столбца int index=(int)::StringToInteger(index_str); //--- Получаем заголовок столбца по индексу CColumnCaptionView *caption=this.GetColumnCaption(index); if(caption==NULL) return; //--- Если заголовок не имеет флага сортировки - ставим флаг сортировки по возрастанию if(caption.IsSortabe() && caption.SortMode()==TABLE_SORT_MODE_NONE) { this.SetSortedColumnCaption(index); } //--- Отправляем пользовательское событие на график с индексом заголовка в lparam, режимом сортировки в dparam и именем объекта в sparam //--- Так как в стандартном событии OBJECT_CLICK в lparam и dparam передаются координаты курсора, то здесь будем передавать отрицательные значения ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, -(10000+index), -(10000+caption.SortMode()), this.NameFG()); ::ChartRedraw(this.m_chart_id); }
На основании доработанного класса горизонтального заголовка таблицы создадим новый класс вертикального заголовка таблицы:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка строк таблицы | //+------------------------------------------------------------------+ class CTableRowsHeaderView : public CPanel { protected: CRowCaptionView m_temp_caption; // Временный объект заголовка строки для поиска string m_table_row_columns[]; // Массив заголовков строк таблицы //--- Создаёт и добавляет в список новый объект представления заголовка строки CRowCaptionView *InsertNewRowCaptionView(const string text, const int x, const int y, const int w, const int h); public: //--- (1) Устанавливает массив заголовков строк таблицы bool TableRowCaptionsAssign(string &captions_array[]); //--- Перерассчитывает области заголовков bool RecalculateBounds(CBound *bound,int new_width); //--- Распечатывает в журнале назначенную модель заголовка таблицы void TableRowHeaderModelPrint(void) { ::ArrayPrint(this.m_table_row_columns); } //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Получает заголовок строки по индексу CRowCaptionView *GetRowCaption(const uint index); //--- Виртуальные методы (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_ROWS_HEADER_VIEW); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Конструкторы/деструктор CTableRowsHeaderView(void); CTableRowsHeaderView(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); ~CTableRowsHeaderView (void){} }; //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Конструктор по умолчанию. Строит объект в | //| главном окне текущего графика в координатах 0,0 | //| с размерами по умолчанию | //+------------------------------------------------------------------+ CTableRowsHeaderView::CTableRowsHeaderView(void) : CPanel("TableRowHeader","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Конструктор параметрический. Строит объект | //| в указанном окне указанного графика с указанными текстом, | //| координатами и размерами | //+------------------------------------------------------------------+ CTableRowsHeaderView::CTableRowsHeaderView(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(); } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Инициализация | //+------------------------------------------------------------------+ void CTableRowsHeaderView::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Цвет фона - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки this.SetBorderWidth(1); } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CTableRowsHeaderView::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(C'230,230,230',C'230,230,230',C'230,230,230',clrWhiteSmoke); this.InitBackColorsAct(C'230,230,230',C'230,230,230',C'230,230,230',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); } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Создаёт и добавляет в список | //| новый объект представления заголовка строки | //+------------------------------------------------------------------+ CRowCaptionView *CTableRowsHeaderView::InsertNewRowCaptionView(const string text,const int x,const int y,const int w,const int h) { //--- Создаём наименование объекта и возвращаем результат создания нового заголовка столбца string user_name="RowCaptionView"+(string)this.m_list_elm.Total(); CRowCaptionView *caption_view=this.InsertNewElement(ELEMENT_TYPE_TABLE_ROW_CAPTION_VIEW,text,user_name,x,y,w,h); return(caption_view!=NULL ? caption_view : NULL); } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Устанавливает вертикальный заголовок | //+------------------------------------------------------------------+ bool CTableRowsHeaderView::TableRowCaptionsAssign(string &captions_array[]) { //--- Получаем указатель на объект таблицы (View) CPanel *obj=this.GetContainer(); if(obj==NULL) return false; CTableView *table_view=obj.GetContainer(); if(table_view==NULL) return false; //--- Из объекта таблицы получаем указатель на панель со строками таблицы CPanel *table_area=table_view.GetTableArea(); if(table_area==NULL) return false; //--- Получаем список строк таблицы CListElm *list=table_area.GetListAttachedElements(); int total_rows=list.Total(); //--- Сохраняем переданный массив заголовков строк таблицы ::ArrayCopy(this.m_table_row_columns,captions_array); int total_captions=(int)this.m_table_row_columns.Size(); //--- int total=::fmax(total_rows,total_captions); //--- Проходим в цикле по количеству создаваемых заголовков for(int i=0;i<total;i++) { //--- получаем очередную строку CTableRowView *row=table_area.GetAttachedElementAt(i); if(row==NULL) continue; //--- рассчитываем координату и создаём имя для области заголовка строки int y=row.Height()*i; string name="CaptionBound"+(string)i; //--- Создаём новую область заголовка строки CBound *caption_bound=this.InsertNewBound(name,0,y,this.Width(),row.Height()); if(caption_bound==NULL) return false; caption_bound.SetID(row.ID()); //--- Определяем текст для заголовка строки //--- Если массив заголовков меньше, чем строк в таблице, то сначала заголовки будут иметь значения из массива, а затем - номера строк //--- Если массив заголовков не имеет размера, то все строки будут озаглавлены порядковыми номерами string text=(this.m_table_row_columns.Size()>0 ? (i<(int)this.m_table_row_columns.Size() ? this.m_table_row_columns[i] : string(i+1)) : string(i+1)); //--- Создаём новый объект визуального представления заголовка строки CRowCaptionView *caption_view=this.InsertNewRowCaptionView(text,0,y,this.Width(),row.Height()); if(caption_view==NULL) return false; caption_view.SetIndex(i); //--- На текущую область заголовка строки назначаем соответствующий объект визуального представления заголовка строки caption_bound.AssignObject(caption_view); caption_view.AssignBoundNode(caption_bound); } //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Перерассчитывает области заголовков | //+------------------------------------------------------------------+ bool CTableRowsHeaderView::RecalculateBounds(CBound *bound,int new_width) { //--- Если передан пустой объект области или его ширина не изменилась - возвращаем false if(bound==NULL || bound.Width()==new_width) return false; //--- Получаем индекс области в списке int index=this.m_list_bounds.IndexOf(bound); if(index==WRONG_VALUE) return false; //--- Вычисляем смещение и, если его нет - возвращаем false int delta=new_width-bound.Width(); if(delta==0) return false; //--- Изменяем ширину текущей области и назначенного на область объекта bound.ResizeW(new_width); CElementBase *assigned_obj=bound.GetAssignedObj(); if(assigned_obj!=NULL) assigned_obj.ResizeW(new_width); //--- Получаем следующую область после текущей CBound *next_bound=this.m_list_bounds.GetNextNode(); //--- Пересчитываем координаты X для всех последующих областей while(!::IsStopped() && next_bound!=NULL) { //--- Сдвигаем область на значение delta int new_x = next_bound.X()+delta; int prev_width=next_bound.Width(); next_bound.SetX(new_x); next_bound.Resize(prev_width,next_bound.Height()); //--- Если в области есть назначенный объект, обновляем его положение CElementBase *assigned_obj=next_bound.GetAssignedObj(); if(assigned_obj!=NULL) { assigned_obj.Move(assigned_obj.X()+delta,assigned_obj.Y()); //--- Этот блок кода - часть мероприятий по поиску и устранению артефактов при перетаскивании заголовков CCanvasBase *base_obj=assigned_obj.GetContainer(); if(base_obj!=NULL) { if(assigned_obj.X()>base_obj.ContainerLimitRight()) assigned_obj.Hide(false); else assigned_obj.Show(false); } } //--- Переходим к следующей области next_bound=this.m_list_bounds.GetNextNode(); } //--- Рассчитаем новую ширину заголовка таблицы по ширине заголовков столбцов int header_width=0; for(int i=0;i<this.m_list_bounds.Total();i++) { CBound *bound=this.GetBoundAt(i); if(bound!=NULL) header_width+=bound.Width(); } //--- Если рассчитанная ширина заголовка таблицы отличается от текущей - изменяем ширину if(header_width!=this.Width()) { if(!this.ResizeW(header_width)) return false; } //--- Получаем указатель на объект таблицы (View) CPanel *obj=this.GetContainer(); if(obj==NULL) return false; CTableView *table_view=obj.GetContainer(); if(table_view==NULL) return false; //--- Из объекта таблицы получаем указатель на панель со строками таблицы CPanel *table_area=table_view.GetTableArea(); if(table_area==NULL) return false; //--- Меняем размер панели строк таблицы под общий размер заголовков столбцов if(!table_area.ResizeW(header_width)) return false; //--- Получаем список строк таблицы и проходим в цикле по всем строкам CListElm *list=table_area.GetListAttachedElements(); int total=list.Total(); for(int i=0;i<total;i++) { //--- Получаем очередную строку таблицы CTableRowView *row=table_area.GetAttachedElementAt(i); if(row!=NULL) { //--- Меняем размер строки под размер панели и перерассчитываем области ячеек row.ResizeW(table_area.Width()); row.RecalculateBounds(&this.m_list_bounds); } } //--- Перерисовываем все строки таблицы table_area.Draw(false); return true; } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Получает заголовок строки по индексу | //+------------------------------------------------------------------+ CRowCaptionView *CTableRowsHeaderView::GetRowCaption(const uint index) { //--- Получаем область заголовка строки по индексу CBound *capt_bound=this.GetBoundAt(index); if(capt_bound==NULL) return NULL; //--- Из области заголовка строки возвращаем указатель на присоединённый объект заголовка строки return capt_bound.GetAssignedObj(); } //+------------------------------------------------------------------+ //| CTableRowsHeaderView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableRowsHeaderView::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++) { //--- Получаем объект заголовка строки по индексу цикла CRowCaptionView *caption_view=this.GetRowCaption(i); //--- Рисуем визуальное представление заголовка строки if(caption_view!=NULL) { caption_view.Draw(false); } } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Этот класс — немного урезанная версия класса заголовка столбцов таблицы. Класс не использует модель вертикального заголовка, а вместо неё — обычный массив наименований заголовков строк. При создании таблицы нужно указать массив наименований строк, и будут созданы их заголовки. Если будет передан пустой массив, то в качестве текстов в заголовках строк будут вписаны их порядковые номера.
Все методы этого класса достаточно подробно прокомментированы, и их можно изучить самостоятельно, при этом учитывая, что класс вертикального заголовка повторяет логику ранее рассмотренного класса горизонтального заголовка таблицы, и она уже должна быть знакома по прошлым статьям этой серии.
Доработаем класс визуального представления таблицы CTableView. Объявим новые переменные и методы:
//+------------------------------------------------------------------+ //| Класс визуального представления таблицы | //+------------------------------------------------------------------+ class CTableView : public CPanel { private: int m_rows_header_panel_w; // Ширина при создании панели заголовка строк таблицы protected: //--- Получаемые данные таблицы CTable *m_table_obj; // Указатель на объект таблицы (включает модели таблицы и заголовка) CTableModel *m_table_model; // Указатель на модель таблицы (получаем из CTable) CTableHeader *m_header_model; // Указатель на модель заголовка таблицы (получаем из CTable) //--- Данные компонента View CPanel *m_header_panel; // Панель для размещения заголовка таблицы CTableHeaderView *m_header_view; // Указатель на заголовок таблицы (View) CPanel *m_rows_header_panel; // Панель для размещения заголовка строк таблицы CTableRowsHeaderView *m_rows_header_view; // Указатель на заголовок строк таблицы (View) CPanel *m_table_area; // Панель для размещения строк таблицы CContainer *m_table_area_container; // Контейнер для размещения панели со строками таблицы bool m_sortable; // Флаг сортируемой таблицы //--- (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) возвращает ширину заголовка строк таблицы void SetRowsHeaderPanelSize(const int width) { this.m_rows_header_panel_w=width; } int RowsHeaderWidth(void) const { return(this.m_rows_header_view!=NULL ? this.m_rows_header_view.Width() : 0); } //--- Создаёт из модели объект (1) таблицы, (2-3) заголовка, (4) обновляет изменённую таблицу bool CreateTable(void); bool CreateHeader(void); public: bool CreateRowsHeader(string &captions_array[]); bool UpdateTable(void); //--- (1) Устанавливает, (2) возвращает объект таблицы bool TableObjectAssign(CTable *table_obj); CTable *GetTableObj(void) { return this.m_table_obj; } //--- Возвращает (1-2) заголовок, (3) область размещения таблицы, (4) контейнер области таблицы CTableHeaderView *GetHeader(void) { return this.m_header_view; } CTableRowsHeaderView *GetRowsHeader(void) { return this.m_rows_header_view; } CPanel *GetTableArea(void) { return this.m_table_area; } CContainer *GetTableAreaContainer(void) { return this.m_table_area_container; } //--- Распечатывает в журнале назначенную модель (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); //--- Получает заголовок столбца (1) по индексу, (2) с флагом сортировки CColumnCaptionView *GetColumnCaption(const uint index) { return(this.GetHeader()!=NULL ? this.GetHeader().GetColumnCaption(index) : NULL); } CColumnCaptionView *GetSortedColumnCaption(void) { return(this.GetHeader()!=NULL ? this.GetHeader().GetSortedColumnCaption(): NULL); } //--- Возвращает объект визуального представления указанной (1) строки, (2) ячейки CTableRowView *GetRowView(const uint index) { return(this.GetTableArea()!=NULL ? this.GetTableArea().GetAttachedElementAt(index) : NULL); } CTableCellView *GetCellView(const uint row,const uint col) { return(this.GetRowView(row)!=NULL ? this.GetRowView(row).GetCellView(col) : NULL); } //--- Возвращает количество строк таблицы int RowsTotal(void) { return(this.GetTableArea()!=NULL ? this.GetTableArea().AttachedElementsTotal() : 0); } //--- Возвращает количество ячеек в указанной строке таблицы int CellsInRow(const uint row) { return(this.GetRowView(row)!=NULL ? this.GetRowView(row).CellsTotal() : 0); } //--- Устанавливает метод подсветки строк void SetRowsHighlightMode(const ENUM_ROWS_HIGHLIGHT_MODE mode); //--- (1) Устанавливает, (2) возвращает флаг сортируемой таблицы void SetSortable(const bool flag); bool IsSortable(void) const { return this.m_sortable; } //--- Рисует внешний вид 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_VIEW); } //--- Обработчик пользовательского события элемента при щелчке на области объекта virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam); //--- Сортирует таблицу по значению столбца и направлению bool Sort(const uint column,const ENUM_TABLE_SORT_MODE sort_mode); //--- Инициализация (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){} };
В конструкторах класса инициализируем новые переменные:
//+------------------------------------------------------------------+ //| 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_rows_header_view(NULL), m_table_area(NULL),m_table_area_container(NULL),m_rows_header_panel_w(0),m_sortable(true) { //--- Инициализация 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_rows_header_view(NULL),m_table_obj(NULL),m_header_view(NULL), m_table_area(NULL),m_table_area_container(NULL),m_rows_header_panel_w(0),m_sortable(true) { //--- Инициализация this.Init(); }
В методе инициализации создаётся объект таблицы со всеми его компонентами — панели с заголовками таблицы и контейнер с панелью строк таблицы. Теперь заголовки таблицы расположены на собственных панелях — для удобства их смещения с одновременной обрезкой по границам панели, так как именно у панели реализован функционал обрезания прикреплённых элементов по границам в случае, если этот объект выходит за пределы своей панели:
//+------------------------------------------------------------------+ //| CTableView::Инициализация | //+------------------------------------------------------------------+ void CTableView::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Ширина рамки, непрозрачность this.SetBorderWidth(1); this.SetAlphaBG(255); this.SetAlphaFG(255); //--- Инициализируем цвета заднего плана панели и делаем его текущим цветом фона this.InitBackColors(C'230,230,230',C'230,230,230',C'230,230,230',clrSilver); this.BackColorToDefault(); //--- Инициализируем цвета рамки панели и делаем его текущим цветом рамки this.InitBorderColors(C'180,180,180',C'180,180,180',C'180,180,180',clrSilver); this.BorderColorToDefault(); //--- Смещение координаты X для заголовка и строк таблицы (ширина вертикального заголовка строк) int dx=(int)::StringToInteger(this.Text()); this.m_rows_header_panel_w=dx; this.SetText(""); if(dx>DEF_TABLE_ROWS_HEADER_W) dx+=12; //--- Координаты и размеры панели заголовка таблицы (заголовок таблицы горизонтальный) int x=1+dx; int y=1; int w=this.Width()-2-dx; int h=DEF_TABLE_HEADER_H; //--- Создаём панель для заголовка таблицы this.m_header_panel=this.InsertNewElement(ELEMENT_TYPE_PANEL,"","TableHeaderPanel",x,y,w,h); if(this.m_header_panel==NULL) return; //--- Инициализируем цвета заднего плана панели и делаем его текущим цветом фона this.m_header_panel.InitBackColors(C'230,230,230',C'230,230,230',C'230,230,230',clrSilver); this.m_header_panel.BackColorToDefault(); this.m_header_panel.SetBorderWidth(0); this.m_header_panel.SetAlphaBG(255); //--- Создаём заголовок таблицы this.m_header_view=this.m_header_panel.InsertNewElement(ELEMENT_TYPE_TABLE_HEADER_VIEW,"","TableHeader",0,0,this.m_header_panel.Width(),this.m_header_panel.Height()); if(this.m_header_view==NULL) return; this.m_header_view.SetBorderWidth(0); //--- Координаты и размеры панели для заголовка строк таблицы (заголовок таблицы вертикальный) x=1; y=DEF_TABLE_HEADER_H; w=(dx>0 ? dx : 1); h=this.Height()-2-DEF_TABLE_HEADER_H; //--- Создаём панель this.m_rows_header_panel=this.InsertNewElement(ELEMENT_TYPE_PANEL,"","TableRowsHeaderPanel",x,y,w,h); if(this.m_rows_header_panel==NULL) return; //--- Инициализируем цвета заднего плана панели и делаем его текущим цветом фона this.m_rows_header_panel.InitBackColors(C'230,230,230',C'230,230,230',C'230,230,230',clrSilver); this.m_rows_header_panel.BackColorToDefault(); this.m_rows_header_panel.SetBorderWidth(0); this.m_rows_header_panel.SetAlphaBG(255); //--- Создаём заголовок строк таблицы this.m_rows_header_view=this.m_rows_header_panel.InsertNewElement(ELEMENT_TYPE_TABLE_ROWS_HEADER_VIEW,"","TableRowsHeader",0,0,this.m_rows_header_panel.Width(),this.m_rows_header_panel.Height()); if(this.m_rows_header_view==NULL) return; this.m_rows_header_view.SetBorderWidth(0); this.m_rows_header_view.SetAlphaBG(0); if(this.m_rows_header_panel_w==0) this.m_rows_header_view.Hide(false); //--- Координаты и размеры контейнера, в котором будет находиться панель строк таблицы x=1+dx; y=1+DEF_TABLE_HEADER_H; w=this.Width()-2-dx; h=this.Height()-2-DEF_TABLE_HEADER_H; //--- Создаём контейнер this.m_table_area_container=this.InsertNewElement(ELEMENT_TYPE_CONTAINER,"","TableAreaContainer",x,y,w,h); if(this.m_table_area_container==NULL) return; this.m_table_area_container.SetBorderWidth(0); this.m_table_area_container.SetScrollable(true); //--- Присоединяем к контейнеру панель для хранения строк таблицы this.m_table_area=this.m_table_area_container.InsertNewElement(ELEMENT_TYPE_PANEL,"","TableAreaPanel",0,0,this.m_table_area_container.Width()-0,this.m_table_area_container.Height()-0); if(m_table_area==NULL) return; this.m_table_area.SetBorderWidth(0); }
Метод, создающий объект заголовка строк:
//+------------------------------------------------------------------+ //| CTableView::Создаёт объект заголовка строк | //+------------------------------------------------------------------+ bool CTableView::CreateRowsHeader(string &captions_array[]) { if(this.m_rows_header_view==NULL) { ::PrintFormat("%s: Error. Table rows header object not created",__FUNCTION__); return false; } return this.m_rows_header_view.TableRowCaptionsAssign(captions_array); }
В метод передаётся массив наименований заголовков строк и возвращается результат назначения этого массива объекту заголовков строк класса CTableRowsHeaderView.
Доработаем метод рисования внешнего вида:
//+------------------------------------------------------------------+ //| CTableView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableView::Draw(const bool chart_redraw) { //--- Рисуем основу CPanel::Draw(false); //--- Рисуем заголовок и строки таблицы if(this.m_header_view!=NULL) this.m_header_view.Draw(false); if(this.m_table_area_container!=NULL) this.m_table_area_container.Draw(false); //--- Устанавливаем смещение и размеры области изображенеия int x=this.m_rows_header_panel.Width()-16; int y=this.m_header_panel.Height()-16; int w=11; int h=w; //--- Очищаем область и рисуем угол m_painter.Clear(x,y,w,h,false); m_painter.TriangleRB(x,y,w,h,BorderColor(),AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Теперь метод, кроме рисования заголовка и контейнера с панелью строк таблицы, дополнительно рисует закрашенный треугольник в левом верхнем углу панели.
Метод, устанавливающий режим подсветки строк:
//+------------------------------------------------------------------+ //| CTableView::Устанавливает метод подсветки строк | //+------------------------------------------------------------------+ void CTableView::SetRowsHighlightMode(const ENUM_ROWS_HIGHLIGHT_MODE highlight_mode) { int total=this.RowsTotal(); for(int i=0;i<total;i++) { CTableRowView *row=this.GetRowView(i); if(row!=NULL) row.SetHighlightMode(highlight_mode); } }
В цикле по количеству строк таблицы получаем очередной объект строки и устанавливаем ему указанный режим подсветки.
Метод, устанавливающий флаг сортируемой таблицы:
//+------------------------------------------------------------------+ //| CTableView::Устанавливает флаг сортируемой таблицы | //+------------------------------------------------------------------+ void CTableView::SetSortable(const bool flag) { this.m_sortable=flag; CTableHeaderView *header=this.GetHeader(); if(header!=NULL) header.SetSortableFlag(flag); }
В метод передаётся флаг сортировки и устанавливается в переменную m_sortableи для объекта горизонтального заголовка таблицы.
В методе сортировки таблицы теперь учитываем установленный для неё флаг возможности сортировки:
//+------------------------------------------------------------------+ //| CTableView::Сортирует таблицу по значению столбца и направлению | //+------------------------------------------------------------------+ bool CTableView::Sort(const uint column,const ENUM_TABLE_SORT_MODE sort_mode) { //--- Если модель таблицы не назначена, сообщаем об этом и возвращаем false if(this.m_table_model==NULL) { ::PrintFormat("%s: Error. The table model is not assigned. Please use the TableObjectAssign() method first",__FUNCTION__); return false; } //--- Если у таблицы нет заголовка или сортировка отсутствует - возвращаем false if(this.m_header_model==NULL || !this.m_sortable || sort_mode==TABLE_SORT_MODE_NONE) return false; //--- Устанавливаем флаг направления сортировки и сортируем модель таблицы по указанному столбцу и направлению bool descending=(sort_mode==TABLE_SORT_MODE_DESC); this.m_table_model.SortByColumn(column,descending); //--- Успешно return true; }
Теперь доработаем класс управления таблицами.
Для создания таблиц, имеющих только верхний горизонтальный заголовок, в классе определены четыре публичных метода и один защищённый, создающий таблицу и добавляющий её в список созданных таблиц. Этот последний метод нужно доработать так, чтобы в него передавался дополнительно массив наименований заголовков строк. И добавим ещё четыре публичных метода для создания таблиц с двумя заголовками — горизонтальным и вертикальным. Таким образом, у нас появится возможность создания таблиц с вертикальным заголовком и без него.
Объявим в классе дополнительные методы:
//+------------------------------------------------------------------+ //| Класс управления таблицами | //+------------------------------------------------------------------+ class CTableControl : public CPanel { private: //--- Возвращает максимальное значение целочисленного массива bool ArrayMaximumValue(int &array[],int &value); //--- Возвращает максимальную ширину текста в массиве заголовков строк int GetMaximumRowCaptionTextSize(string &array_row_captions[]); protected: CListObj m_list_table_model; //--- Добавляет объект (1) модели (CTable), (2) визуального представления (CTableView) таблицы в список bool TableModelAdd(CTable *table_model,const int table_id,const string source); CTableView *TableViewAdd(CTable *table_model,string &row_names[],const string source); //--- Обновляет указанный столбец указанной таблицы bool ColumnUpdate(const string source, CTable *table_model, const uint table, const uint col, const bool cells_redraw); public: //--- Возвращает (1) модель, (2) объект визуального представления таблицы, (3) тип объекта CTable *GetTableModel(const uint index) { return this.m_list_table_model.GetNodeAtIndex(index); } CTableView *GetTableView(const uint index) { return this.GetAttachedElementAt(index); } //--- Создание таблицы на основании переданных данных template<typename T> CTableView *TableCreate(T &row_data[][],const string &column_names[],const int table_id=WRONG_VALUE); CTableView *TableCreate(const uint num_rows, const uint num_columns,const int table_id=WRONG_VALUE); CTableView *TableCreate(const matrix &row_data,const string &column_names[],const int table_id=WRONG_VALUE); CTableView *TableCreate(CList &row_data,const string &column_names[],const int table_id=WRONG_VALUE); template<typename T> CTableView *TableCreate(T &row_data[][],const string &column_names[],string &row_names[],const int table_id=WRONG_VALUE); CTableView *TableCreate(const uint num_rows, const uint num_columns,string &row_names[],const int table_id=WRONG_VALUE); CTableView *TableCreate(const matrix &row_data,const string &column_names[],string &row_names[],const int table_id=WRONG_VALUE); CTableView *TableCreate(CList &row_data,const string &column_names[],string &row_names[],const int table_id=WRONG_VALUE); //--- Возвращает (1) строковое значение указанной ячейки (Model), указанную (2) строку, (3) ячейку таблицы (View) string CellValueAt(const uint table, const uint row, const uint col); CTableRowView *GetRowView(const uint table, const uint index); CTableCellView *GetCellView(const uint table, const uint row, const uint col); //--- Устанавливает (1) значение, (2) точность, (3) флаги отображения времени, (4) флаг отображения имён цветов в указанную ячейку (Model + View) template<typename T> void CellSetValue(const uint table, const uint row, const uint col, const T value, const bool chart_redraw); void CellSetDigits(const uint table, const uint row, const uint col, const int digits, const bool chart_redraw); void CellSetTimeFlags(const uint table, const uint row, const uint col, const uint flags, const bool chart_redraw); void CellSetColorNamesFlag(const uint table, const uint row, const uint col, const bool flag, const bool chart_redraw); //--- Устанавливает цвет (1) переднего, (2) заднего плана в указанную ячейку (View) void CellSetForeColor(const uint table, const uint row, const uint col, const color clr, const bool chart_redraw); void CellSetBackColor(const uint table, const uint row, const uint col, const color clr, const bool chart_redraw); //--- (1) Устанавливает, (2) возвращает точку привязки текста в указанной ячейке (View) void CellSetTextAnchor(const uint table, const uint row, const uint col, const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw); ENUM_ANCHOR_POINT CellTextAnchor(const uint table, const uint row, const uint col); //--- Устанавливает (1) точность, (2) флаги отображения времени, (3) флаг отображения имён цветов, (4) точку привязки текста, (5) тип данных в указанном столбце (View) void ColumnSetDigits(const uint table, const uint col, const int digits, const bool cells_redraw, const bool chart_redraw); void ColumnSetTimeFlags(const uint table, const uint col, const uint flags, const bool cells_redraw, const bool chart_redraw); void ColumnSetColorNamesFlag(const uint table, const uint col, const bool flag, const bool cells_redraw, const bool chart_redraw); void ColumnSetTextAnchor(const uint table, const uint col, const ENUM_ANCHOR_POINT anchor, const bool cells_redraw, const bool chart_redraw); void ColumnSetDatatype(const uint table, const uint col, const ENUM_DATATYPE type, const bool cells_redraw, const bool chart_redraw); //--- Возвращает количество (1) строк, (2) ячеек в строке в указанной таблице uint RowsTotal(const uint table); uint CellsInRow(const uint table,const uint row); //--- Устанавливает (1) режим подсветки строк, (2) возможность сортировки указанной таблицы void SetRowsHighlightMode(const uint table,const ENUM_ROWS_HIGHLIGHT_MODE highlight_mode); void SetSortable(const uint table,const bool flag); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_CONTROL_VIEW); } //--- Конструкторы/деструктор CTableControl(void) { this.m_list_table_model.Clear(); } CTableControl(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableControl(void) {} };
Рассмотрим реализацию новых методов.
Метод, возвращающий максимальное значение целочисленного массива:
//+------------------------------------------------------------------+ //| Возвращает максимальное значение целочисленного массива | //+------------------------------------------------------------------+ bool CTableControl::ArrayMaximumValue(int &array[],int &value) { ::ResetLastError(); int index=::ArrayMaximum(array); if(index<0) { ::PrintFormat("%s: ArrayMaximum() failed. Error %d",__FUNCTION__,::GetLastError()); return false; } value=array[index]; return true; }
В метод передаётся массив, в котором необходимо найти максимальное значение, и переменная, в которую это значение будет записано. Метод возвращает true и записывает в переменную найденное максимальное значение в массиве.
При ошибке возвращается false.
Метод, возвращающий максимальную ширину текста в массиве заголовков строк:
//+------------------------------------------------------------------+ //| Возвращает максимальную ширину текста в массиве заголовков строк | //+------------------------------------------------------------------+ int CTableControl::GetMaximumRowCaptionTextSize(string &row_captions[]) { int total=(int)row_captions.Size(); if(total==0) return 0; int array[]={}; ::ArrayResize(array,total); for(int i=0;i<total;i++) { string text=row_captions[i]; text.TrimLeft(); text.TrimRight(); array[i]=this.m_foreground.TextWidth(text); } int value=0; return(this.ArrayMaximumValue(array,value) ? value : 0); }
При создании вертикального заголовка необходимо рассчитать его ширину так, чтобы все наименования всех строк поместились по ширине создаваемых заголовков строк. Метод определяет максимальную ширину текста в массиве наименований заголовков строк, передаваемом в метод по ссылке. При ошибке метод возвращает 0.
Доработаем метод, создающий новый и добавляющий в список объект визуального представления таблицы:
//+------------------------------------------------------------------+ //| Создаёт новый и добавляет в список объект | //| визуального представления таблицы (CTableView) | //+------------------------------------------------------------------+ CTableView *CTableControl::TableViewAdd(CTable *table_model,string &row_names[],const string source) { //--- Проверяем объект модели таблицы if(table_model==NULL) { ::PrintFormat("%s::%s: Error. An invalid Table Model object was passed",source,__FUNCTION__); return NULL; } //--- Получаем максимальную ширину текста заголовков строк int w=this.GetMaximumRowCaptionTextSize(row_names); if(w>0 && w<DEF_TABLE_ROWS_HEADER_W) w=DEF_TABLE_ROWS_HEADER_W; //--- Создаём новый элемент - визуальное представление таблицы, прикреплённый к панели CTableView *table_view=this.InsertNewElement(ELEMENT_TYPE_TABLE_VIEW,(string)w,"TableView"+(string)table_model.ID(),1,1,this.Width()-2,this.Height()-2); if(table_view==NULL) { ::PrintFormat("%s::%s: Error. Failed to create Table View object",source,__FUNCTION__); return NULL; } //--- Графическому элементу "Таблица" (View) назначаем объект таблицы (Model) и его идентификатор table_view.TableObjectAssign(table_model); table_view.CreateRowsHeader(row_names); table_view.SetID(table_model.ID()); return table_view; }
Теперь в метод передаётся дополнительно массив наименований заголовков строк. Далее рассчитывается ширина для объекта вертикального заголовка таблицы (либо 0 при пустом массиве, либо ширина не менее значения DEF_TABLE_ROWS_HEADER_W). При создании объекта визуального представления таблицы, рассчитанная ширина вертикального заголовка передаётся в параметре текста панели в виде строкового значения (после создания таблицы с использованием ширины вертикального заголовка, в параметр текста панели вписывается пустая строка). После создания объекта таблицы вызывается его метод создания вертикального заголовка.
Доработаем четыре публичных метода для создания объекта визуального представления таблицы:
//| Создаёт таблицу с указанием массива таблицы и массива заголовков. | //| Определяет количество и наименования колонок согласно column_names| //| Количество строк определены размером массива данных row_data, | //| который используется и для заполнения таблицы | //+-------------------------------------------------------------------+ template<typename T> CTableView *CTableControl::TableCreate(T &row_data[][],const string &column_names[],const int table_id=WRONG_VALUE) { //--- Создаём объект таблицы по указанным параметрам CTable *table_model=new CTable(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу с пустым массивом заголовков строк string array[]={}; return this.TableViewAdd(table_model,array,__FUNCTION__); } //+------------------------------------------------------------------+ //| Создаёт таблицу с определением количества колонок и строк. | //| Колонки будут иметь Excel-наименования "A", "B", "C" и т.д. | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(const uint num_rows,const uint num_columns,const int table_id=WRONG_VALUE) { CTable *table_model=new CTable(num_rows,num_columns); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу с пустым массивом заголовков строк string array[]={}; return this.TableViewAdd(table_model,array,__FUNCTION__); } //+------------------------------------------------------------------+ //| Создаёт таблицу с инициализацией колонок согласно column_names | //| Количество строк определены параметром row_data, с типом matrix | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(const matrix &row_data,const string &column_names[],const int table_id=WRONG_VALUE) { CTable *table_model=new CTable(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу с пустым массивом заголовков строк string array[]={}; return this.TableViewAdd(table_model,array,__FUNCTION__); } //+------------------------------------------------------------------+ //| Создаёт таблицу с указанием массива таблицы на основе | //| списка row_data, содержащего объекты с данными полей структуры. | //| Определяет количество и наименования колонок согласно количеству | //| наименований столбцов в массиве column_names | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(CList &row_data,const string &column_names[],const int table_id=WRONG_VALUE) { CTableByParam *table_model=new CTableByParam(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу с пустым массивом заголовков строк string array[]={}; return this.TableViewAdd(table_model,array,__FUNCTION__); }
Так как теперь метод TableViewAdd() требует передачи в него массива заголовков строк, а здесь строятся таблицы без вертикального заголовка, то мы просто объявляем пустой массив и передаём его в метод создания новой таблицы.
Напишем четыре перегруженных метода для создания таблиц с вертикальным заголовком:
//+-------------------------------------------------------------------+ //| Создаёт таблицу с указанием массива таблицы и массива заголовков. | //| Определяет количество и наименования колонок согласно column_names| //| Количество строк определены размером массива данных row_data, | //| который используется и для заполнения таблицы | //+-------------------------------------------------------------------+ template<typename T> CTableView *CTableControl::TableCreate(T &row_data[][],const string &column_names[],string &row_names[],const int table_id=WRONG_VALUE) { //--- Создаём объект таблицы по указанным параметрам CTable *table_model=new CTable(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,row_names,__FUNCTION__); } //+------------------------------------------------------------------+ //| Создаёт таблицу с определением количества колонок и строк. | //| Колонки будут иметь Excel-наименования "A", "B", "C" и т.д. | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(const uint num_rows,const uint num_columns,string &row_names[],const int table_id=WRONG_VALUE) { CTable *table_model=new CTable(num_rows,num_columns); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,row_names,__FUNCTION__); } //+------------------------------------------------------------------+ //| Создаёт таблицу с инициализацией колонок согласно column_names | //| Количество строк определены параметром row_data, с типом matrix | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(const matrix &row_data,const string &column_names[],string &row_names[],const int table_id=WRONG_VALUE) { CTable *table_model=new CTable(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,row_names,__FUNCTION__); } //+------------------------------------------------------------------+ //| Создаёт таблицу с указанием массива таблицы на основе | //| списка row_data, содержащего объекты с данными полей структуры. | //| Определяет количество и наименования колонок согласно количеству | //| наименований столбцов в массиве column_names | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(CList &row_data,const string &column_names[],string &row_names[],const int table_id=WRONG_VALUE) { CTableByParam *table_model=new CTableByParam(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,row_names,__FUNCTION__); }
Здесь в методы передаётся массив наименований заголовков строк, и этот же массив передаётся в метод создания нового объекта таблицы.
Метод, устанавливающий цвет заднего плана в указанную ячейку:
//+------------------------------------------------------------------+ //| Устанавливает цвет заднего плана в указанную ячейку (View) | //+------------------------------------------------------------------+ void CTableControl::CellSetBackColor(const uint table,const uint row,const uint col,const color clr,const bool chart_redraw) { //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return; //--- В объект визуального представления ячейки устанавливаем цвет фона ячейки //--- Перерисовываем ячейку с флагом обновления графика cell_view.SetBackColor(clr); cell_view.Draw(chart_redraw); }
Логика метода полностью прокомментирована в коде.
Метод, возвращающий количество строк в указанной таблице:
//+------------------------------------------------------------------+ //| Возвращает количество строк в указанной таблице | //+------------------------------------------------------------------+ uint CTableControl::RowsTotal(const uint table) { CTableView *table_view=this.GetTableView(table); if(table_view==NULL) { ::PrintFormat("%s: Error. Failed to get CTableView object",__FUNCTION__); return NULL; } return table_view.RowsTotal(); }
Получаем объект таблицы по её индексу в списке и возвращаем из неё количество строк.
Метод, возвращающий количество ячеек в строке в указанной таблице:
//+------------------------------------------------------------------+ //| Возвращает количество ячеек в строке в указанной таблице | //+------------------------------------------------------------------+ uint CTableControl::CellsInRow(const uint table,const uint row) { CTableRowView *row_view=this.GetRowView(table,row); return(row_view!=NULL ? row_view.CellsTotal() : 0); }
Получаем таблицу по индексу и возвращаем количество ячеек в указанной строке этой таблицы.
Метод, устанавливающий режим подсветки строк указанной таблицы:
//+------------------------------------------------------------------+ //| Устанавливает режим подсветки строк указанной таблицы | //+------------------------------------------------------------------+ void CTableControl::SetRowsHighlightMode(const uint table,const ENUM_ROWS_HIGHLIGHT_MODE highlight_mode) { CTableView *table_view=this.GetTableView(table); if(table_view==NULL) { ::PrintFormat("%s: Error. Failed to get CTableView object",__FUNCTION__); return; } table_view.SetRowsHighlightMode(highlight_mode); }
Получаем объект таблицы по её индексу и устанавливаем для неё режим подсветки строк таблицы.
Метод, устанавливающий возможность сортировки указанной таблицы:
//+------------------------------------------------------------------+ //| Устанавливает возможность сортировки указанной таблицы | //+------------------------------------------------------------------+ void CTableControl::SetSortable(const uint table,const bool flag) { CTableView *table_view=this.GetTableView(table); if(table_view==NULL) { ::PrintFormat("%s: Error. Failed to get CTableView object",__FUNCTION__); return; } table_view.SetSortable(flag); }
Получаем объект таблицы по её индексу и устанавливаем для неё флаг возможности сортировки по столбцам.
Всё, все классы таблиц графической библиотеки мы доработали. Теперь создадим индикатор, отображающий в таблице симметричную корреляцию символов, указанных во входных параметрах.
Табличный индикатор корреляции символов
Индикатор будет расположен в папке \MQL5\Indicators\Tables\.
Создадим в ней новый индикатор с именем iCorrelationTable.mq5, отображающий данные в подокне графика, с такими свойствами, входными параметрами и глобальными переменными:
//+------------------------------------------------------------------+ //| iCorrelationTable.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 #define CHART_FLOAT_WIDTH 750 // Ширина открываемого графика символов #define CHART_FLOAT_HEIGHT 500 // Высота открываемого графика символов //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Controls\Controls.mqh" // Библиотека элементов управления //--- input parameters input(name="Bars Total (at least 10)") uint InpBarsTotal = 1000; // Количество баров данных для расчёта корреляции (не менее 10) input(name="Timeframe") ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Таймфрейм данных для расчёта корреляции input(name="Symbols for Correlation") string InpSymbols = "EURUSD,GBPUSD,USDJPY,USDCHF,AUDUSD,NZDUSD,USDCAD"; // Символы для расчёта корреляции //--- global variables string ExtSymbolsArray[]; // Массив символов для расчёта корреляции matrix ExtPricesData; // Матрица данных символов (цены Close) uint ExtBarsTotal; // Количество баров данных для расчёта корреляции matrix ExtCorrelationMatrix; // Матрица рассчитанных парных корреляций между всеми символами bool ExtDataReady; // Флаг готовности данных по всем символам long ExtSymbolsChart; // Идентификатор нового графика для символов корреляции CTableControl *ExtTableCtrl; // Указатель на объект CTableControl CTableView *ExtTableView; // Указатель на объект визуального представления таблицы
В обработчике OnInit() индикатора создадим список символов из тех, что указаны во входных параметрах, и запросим их данные:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- Идентификатор открываемого графика ExtSymbolsChart=0; //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Заполняем массив символов из указанных во входном параметре InpSymbols string sep=","; // разделитель в виде символа ushort u_sep; // код символа разделителя //--- получим код разделителя u_sep=StringGetCharacter(sep,0); //--- получим из строки InpSymbols подстроки по разделителю u_sep и запишем их в массив ExtSymbolsArray StringSplit(InpSymbols,u_sep,ExtSymbolsArray); //--- Распечатаем в журнале набор символов для расчёта корреляции Print("\nSymbols Array:"); ArrayPrint(ExtSymbolsArray); //--- Включаем все символы в обзор рынка SymbolsSelect(ExtSymbolsArray); //--- Получаем данные символов (не менее 10 баров) для расчёта корреляции ExtBarsTotal=(InpBarsTotal<10 ? 10 : InpBarsTotal); ExtDataReady=GetAndCalculateData(ExtBarsTotal); //--- Создаём графический элемент управления таблицами int w=500; int h=138; ExtTableCtrl=new CTableControl("TableControl0",0,wnd,8,8,w-0,h-0); if(ExtTableCtrl==NULL) { Print("Error. Failed to create TableControl object"); return INIT_FAILED; } //--- На графике обязательно должен быть один главный элемент ExtTableCtrl.SetAsMain(); //--- Можно установить параметры созданного элемента управления таблицами ExtTableCtrl.SetID(0); // Идентификатор ExtTableCtrl.SetName("Table Control 0"); // Наименование //--- Если данные символов и их корреляции успешно получены, //--- создаём объект таблицы 0 (компонент Model + View) внутри элемента управления таблицами //--- из вышесозданной матрицы ExtCorrelationMatrixSymmetric и //--- string-массива символов ExtSymbolsArray как заголовков столбцов if(ExtDataReady && !CreateTable(ExtTableCtrl)) return INIT_FAILED; //--- Всё успешно return(INIT_SUCCEEDED); }
После запроса данных по символам, создадим элемент управления таблицами. Если все данные всех символов успешно получены и по ним создана матрица симметричной корреляции, то на основе этих данных в элементе управления таблицами строим одну таблицу. Если же данные ещё не получены, то об этом будет говорить фдаг ExtDataReady, и в этом случае в OnCalculate() будем ожидать получение всех запрошенных данных, и после полного их получения и расчёта матрицы корреляций, создадим таблицу, и далее будем только обновлять данные в уже построенной таблице символов и их корреляций:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { //--- Получаем данные пока не будут готовы ExtDataReady=GetAndCalculateData(ExtBarsTotal); if(!ExtDataReady) { Print("The symbol data and their correlations have not yet been obtained. Waiting for the next tick..."); return 0; } //--- Если таблица ещё не создана //--- создаём объект таблицы 0 (компонент Model + View) внутри элемента управления таблицами //--- из вышесозданной матрицы ExtCorrelationMatrixSymmetric и //--- string-массива символов ExtSymbolsArray как заголовков столбцов if(ExtTableView==NULL && !CreateTable(ExtTableCtrl)) return 0; //--- Обновляем данные в таблице с установкой цветов корреляции UpdateTableValuesAndColors(ExtTableCtrl.GetTableView(0),ExtCorrelationMatrix); //--- return value of prev_calculated for next call return(rates_total); }
В обработчике OnDeinit() удаляем созданные объекты:
//+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int32_t reason) { //--- Удаляем элемент управления таблицами и уничтожаем менеджер общих ресурсов библиотеки delete ExtTableCtrl; CCommonManager::DestroyInstance(); }
В таймере индикатора каждые полторы минуты запрашиваем данные по всем символам, записанным в рабочий массив:
//+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- Раз в полторы минуты получаем данные по символам из массива static int count=0; count++; if(count>=3000) { double array[]; for(int i=0;i<(int)ExtSymbolsArray.Size();i++) CopyClose(ExtSymbolsArray[i],InpTimeframe,0,ExtBarsTotal,array); count=0; } //--- Вызываем обработчик OnTimer элемента управления таблицами ExtTableCtrl.OnTimer(); }
Это мультисимвольный индикатор, поэтому запрашивать данные символов необходимо для "удержания" данных по каждому символу, чтобы они всегда были актуальны, и не приходилось ожидать их загрузки заново.
В обработчике событий будем отслеживать щелчок по строке таблицы, определять из параметра sparam адрес ячейки таблицы (строка/столбец) и открывать два графика этих символов в отдельном окне графика:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик OnChartEvent элемента управления таблицами ExtTableCtrl.OnChartEvent(id,lparam,dparam,sparam); if(id>=CHARTEVENT_CUSTOM) { //--- Преобразуем идентификатор полученного пользовательского события к значениям стандартных событий ENUM_CHART_EVENT chart_event=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM); //--- Если событие щелчка по графическому объекту if(chart_event==CHARTEVENT_OBJECT_CLICK) { //--- Если в имени события (значение sparam) присутствует наименование строки таблицы (начинается с "TableCellView") if(StringFind(sparam,"TableCellView")==0) { //--- Получаем номер строки и столбца из параметров сорбытия int row=(int)lparam; int col=(int)dparam; string sep=";"; // разделитель в виде символа ushort u_sep; // код символа разделителя string result[]; // массив для получения строк //--- Получим код разделителя и разделим sparam на подстроки u_sep=StringGetCharacter(sep,0); int n=StringSplit(sparam,u_sep,result); //--- Должно быть три подстроки if(n==3) { //--- Получаем символ строки и символ столбца string row_symb=result[1]; string col_symb=result[2]; //--- Если график ещё не открыт - открываем его if(ExtSymbolsChart==0 || !IsExistChart(ExtSymbolsChart)) ExtSymbolsChart=OpenCharts(row_symb,col_symb); //--- Если график уже открыт if(ExtSymbolsChart!=0) { //--- Устанавливаем символы для двух объектов-графиков и перерисовываем график ObjectSetString(ExtSymbolsChart,"ChartRowSymbol",OBJPROP_SYMBOL,row_symb); ObjectSetString(ExtSymbolsChart,"ChartColSymbol",OBJPROP_SYMBOL,col_symb); ChartRedraw(ExtSymbolsChart); } } } } } }
Т.е. мы можем щёлкнуть по интересующей ячейки таблицы, в которой записано значение корреляции двух символов (на пересечении строка/столбец) и открыть графики интересующих символов.
Рассмотрим функции, используемые в индикаторе.
Функция, включающая символы из массива в обзор рынка:
//+------------------------------------------------------------------+ //| Включает символы из массива в обзор рынка | //+------------------------------------------------------------------+ bool SymbolsSelect(string &array[]) { bool res=true; for(int i=0;i<(int)array.Size();i++) res &=SymbolSelect(array[i],true); return res; }
Проходим в цикле по массиву символов и каждый символ помещаем в обзор рынка. Функция возвращает общий результат включения символов в обзор рынка. Здесь, и при создании массива символов из входных параметров, нет проверки наличия символов на сервере. Это сделано для упрощения примера. Поэтому в настройках нужно указывать реально существующие символы.
Функция, возвращающая символ по индексу массива:
//+------------------------------------------------------------------+ //| Возвращает символ по индексу массива | //+------------------------------------------------------------------+ string GetSymbolByIndex(const int index,string &array[]) { int total=(int)array.Size(); if(index<0 || index>total-1) return StringFormat("%s: Error. Invalid index (%d)",__FUNCTION__,index); return array[index]; }
Функция, заполняющая матрицу данных символов:
//+------------------------------------------------------------------+ //| Заполняет матрицу данных символов | //+------------------------------------------------------------------+ bool SymbolsDataMatrixFill(const ENUM_TIMEFRAMES timeframe,string &array[],matrix &data,const int data_count) { //--- В цикле по количеству символов в массиве array int total=(int)array.Size(); for(int i=0; i<total; i++) { //--- получаем цены закрытия в количестве data_count double close[]; int copied=CopyClose(array[i], timeframe, 0, data_count, close); if(copied!=data_count) return false; //--- Записываем цены в строку матрицы так, чтобы 0 ячейка строки соответствовала 0 бару for(int j=0; j<data_count; j++) { string symbol=GetSymbolByIndex(i,array); int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); data[i][data_count-1-j]=NormalizeDouble(close[j],digits); } } return true; }
В функцию передаётся таймфрейм получаемых данных, массив символов, с которых берём данные, матрица, в которую будут записаны данные, и количество данных, которые будут браться по каждому из символов, записанных в массиве.
Тут есть одно ограничение: для краткости здесь нет проверки количества имеющихся данных на сервере по каждому символу. Здесь просто используется значение из настроек индикатора InpBarsTotal, но не менее 10 баров. Если функция всегда завершается с ошибкой, значит для какого-то из символов не хватает данных. В этом случае можно уменьшить количество запрашиваемых данных в переменной InpBarsTotal.
Функция, рассчитывающая симметричную матрицу корреляций между всеми символами из массива:
//+------------------------------------------------------------------+ //|Рассчитывает симметричную матрицу корреляций между всеми символами| //+------------------------------------------------------------------+ bool SymbolsCorrelationMatrixSymmetric(const matrix &data, matrix &correlation) { int symb_total=(int)data.Rows(); // количество символов //--- Устанавливаем размер матрицы корреляций if(!correlation.Resize(symb_total,symb_total)) return false; //--- Внешний цикл по всем символам (строкам) for(int i=0;i<symb_total;i++) { //--- Получаем временной ряд цен для символа i vector vi=data.Row(i); //--- Внутренний цикл по всем символам (столбцам) for(int j=0;j<symb_total;j++) { //--- Если символы в строке и столбце одинаковы, то это корреляция с собой if(i==j) correlation[i][j]=1.0; //--- Символы в строке и столбце различаются else { //--- Получаем временной ряд цен для символа j и считаем корреляцию между символами i и j vector vj=data.Row(j); correlation[i][j]=vi.CorrCoef(vj); } } } //--- Всё успешно return true; }
В функцию передаётся ранее заполненная матрица данных символов и матрица, куда будут записываться значения корреляции между символами. Логика функции расписана в комментариях к коду.
Функция, распечатывающая в журнале симметричную матрицу корреляций:
//+------------------------------------------------------------------+ //| Распечатывает в журнале симметричную матрицу корреляций | //+------------------------------------------------------------------+ void SymbolsCorrelationMatrixSymmetricPrint(const string &symb_array[],matrix &correlation) { //--- Создаём и распечатываем заголовок Print("Correlation matrix:"); string header=" "; for(int j=0;j<(int)symb_array.Size();j++) header+=symb_array[j]+" "; Print(header); //--- Распечатываем данные корреляции символов for(int i=0;i<(int)symb_array.Size();i++) { string row=symb_array[i]+" "; for(int j=0;j<(int)symb_array.Size();j++) row+=DoubleToString(correlation[i][j],2)+" "; Print(row); } }
В функцию передаётся список символов и рассчитанная матрица корреляций. В двух циклах распечатываем символы и значения корреляции между ними.
Функция, получающая и рассчитывающая все необходимые данные по символам:
//+------------------------------------------------------------------+ //| Получает и рассчитывает все необходимые данные | //+------------------------------------------------------------------+ bool GetAndCalculateData(uint bars_total) { //--- Получаем значения для данных по символам const int symb_total=(int)ExtSymbolsArray.Size(); // количество символов //--- Изменяем размер матрицы: строки - символы, столбцы - бары if(!ExtPricesData.Resize(symb_total,bars_total)) { Print("Error. Failed to resize the symbol data matrix"); return false; } //--- Заполняем матрицу ценами закрытия символов и присваиваем значение флагу готовности данных ExtDataReady=SymbolsDataMatrixFill(InpTimeframe,ExtSymbolsArray,ExtPricesData,bars_total); if(!ExtDataReady) return false; //--- Рассчитываем симметричную матрицу корреляций if(!SymbolsCorrelationMatrixSymmetric(ExtPricesData,ExtCorrelationMatrix)) { Print("Error calculating correlation matrix"); return false; } return true; }
В функцию передаётся количество запрашиваемых данных. По каждому из символов запрашивается указанное количество данных. Если данные успешно получены, то рассчитывается матрица корреляций между символами. При успешности получения и расчёта всех данных функция возвращает true, в противном случае — false. Функцию необходимо вызывать до тех пор, пока не будут получены и рассчитаны все данные. Делается это сначала при инициализации, а затем на каждом тике. Как только все данные получены и рассчитаны, больше эта функция не вызывается.
Функция, создающая на панели таблицу символов с данными их корреляций:
//+------------------------------------------------------------------+ //| Создаёт на панели таблицу символов с данными их корреляций | //+------------------------------------------------------------------+ bool CreateTable(CTableControl *table_ctrl) { //--- создаём объект таблицы 0 (компонент Model + View) внутри элемента управления таблицами //--- из вышесозданной матрицы ExtCorrelationMatrixSymmetric и //--- string-массива символов ExtSymbolsArray как заголовков столбцов ExtTableView=table_ctrl.TableCreate(ExtCorrelationMatrix,ExtSymbolsArray,ExtSymbolsArray); if(ExtTableView==NULL) return false; //--- Установим для столбцов вывод текста по центру ячейки int total=(int)table_ctrl.RowsTotal(0); for(int i=0;i<total;i++) { table_ctrl.ColumnSetTextAnchor(0,i,ANCHOR_CENTER,true,false); } //--- Установим режим подсветки строк таблицы для отдельных ячеек table_ctrl.SetRowsHighlightMode(0,ROWS_HIGHLIGHT_MODE_CELLS); //--- и сделаем таблицу несортируемой table_ctrl.SetSortable(0,false); //--- Нарисуем и раскрасим таблицу в цвета корреляции символов table_ctrl.Draw(false); UpdateTableValuesAndColors(table_ctrl.GetTableView(0),ExtCorrelationMatrix); //--- Получим модель таблицы с индексом 0 и распечатаем в журнале CTable *table_model=table_ctrl.GetTableModel(0); table_model.Print(7); //--- Всё успешно return true; }
Функция создаёт внутри элемента управления таблицами одну таблицу на данных символов и корреляции между ними. В заголовках таблицы прописываются наименования символов по горизонтали и вертикали. В ячейках таблицы прописываются значения корреляции символов из расположения строка/столбец. Каждая ячейка окрашивается в цвет корреляции от красного цвета (корреляция -1) через желтый (корреляция 0) до зелёного (корреляция +1), создавая тем самым тепловую карту корреляций.
Функция, обновляющая значения и цвета ячеек таблицы по матрице корреляций:
//+------------------------------------------------------------------+ //| Обновляет значения и цвета ячеек таблицы по матрице корреляций | //+------------------------------------------------------------------+ void UpdateTableValuesAndColors(CTableView *table_view, matrix &corr_matrix) { //--- Проверим валидность указателя на таблицу if(table_view==NULL) return; //--- Количество строк и столбцов таблицы int total_row=table_view.RowsTotal(); int total_col=table_view.CellsInRow(0); //--- В цикле по строкам таблицы for(int r=0; r<total_row; r++) { //--- получаем очередной объект визуального представления строки CTableRowView *row_obj=table_view.GetRowView(r); if(row_obj==NULL) continue; //--- Получаем элемент управления цветом CColorElement *ce=row_obj.GetBackColorControl(); if(ce==NULL) continue; //--- В цикле по количеству ячеек в строке for(int c=0; c<total_col; c++) { //--- получаем очередной объект визуального представления ячейки CTableCellView *cell=table_view.GetCellView(r,c); if(cell==NULL) continue; //--- Берём значение корреляции из матрицы double val=corr_matrix[r][c]; //--- Обновляем текст ячейки, cell.SetText(DoubleToString(val,2)); //--- обновляем цвет ячейки color new_color=ce.InterpolateColorByCoeff(clrRed,clrYellow,clrGreen,val); cell.SetBackColor(new_color); //--- Перерисовываем ячейку (график обновляем на последней ячейке таблицы) bool flag=(r==total_row-1 && c==total_col-1 ? true : false); cell.Draw(flag); } } }
Вся логика функции подробно расписана в комментариях. Функция вызывается на каждом тике сразу после успешного получения всех данных и создания таблицы. При вызове функции она пересчитывает значения корреляции между символами для каждой ячейки таблицы, обновляет значения и цвет ячеек таблицы.
Функция, возвращающая флаг существования графика с указанным идентификатором:
//+------------------------------------------------------------------+ //| Возвращает флаг существования графика с указанным идентификатором| //+------------------------------------------------------------------+ bool IsExistChart(const long id) { //--- Переменные для идентификаторов графиков long curr_chart=0, prev_chart=0; int i=0; //--- Проходим по всем графикам while(!IsStopped() && i<CHARTS_MAX) { //--- На основании предыдущего получим новый график curr_chart=ChartNext(prev_chart); // prev_chart==0 - получить первый график //--- Если достигли конца списка графиков - выходим из цикла if(curr_chart<0) break; //--- Если идентификатор графика совпадает с искомым - такой график есть if(curr_chart==id) return true; //--- Запомним идентификатор текущего графика для следующего ChartNext() prev_chart=curr_chart; //--- увеличиваем счетчик i++; } //--- Нет искомого графика return false; }
Чтобы при каждом щелчке мышкой по ячейкам таблицы не открывать новое окно графика, нам нужно проверить существование уже ранее открытого графика. Именно это и делает функция.
Функция, открывающая графики символов:
//+------------------------------------------------------------------+ //| Открывает графики символов | //+------------------------------------------------------------------+ long OpenCharts(const string row_symb,const string col_symb) { //--- устанавливаем символ и таймфрейм для нового графика string symbol=row_symb; if(symbol==NULL || symbol=="") symbol=Symbol(); //--- открываем новый график с заданными символом и периодом long id=ChartOpen(symbol,PERIOD_CURRENT); if(id==0) { Print("ChartOpen() failed. Error ", GetLastError()); return 0; } //--- Открепляем график и делаем его пустым ChartSetInteger(id,CHART_IS_DOCKED,false); ChartSetInteger(id,CHART_SHOW,false); //--- Получаем координаты сторон откреплённого графика int top=(int)ChartGetInteger(id,CHART_FLOAT_TOP); int bottom=(int)ChartGetInteger(id,CHART_FLOAT_BOTTOM); int left=(int)ChartGetInteger(id,CHART_FLOAT_LEFT); int right=(int)ChartGetInteger(id,CHART_FLOAT_RIGHT); //--- Устанавливаем новые ширину и высоту графика ChartSetInteger(id,CHART_FLOAT_RIGHT,left+CHART_FLOAT_WIDTH); ChartSetInteger(id,CHART_FLOAT_BOTTOM,top+CHART_FLOAT_HEIGHT); //--- Получаем размеры графика в пикселях int cw=(int)ChartGetInteger(id,CHART_WIDTH_IN_PIXELS); int ch=(int)ChartGetInteger(id,CHART_HEIGHT_IN_PIXELS); //--- Задаём высоту для верхнего и нижнего объектов-графиков int h0=(int)round(ch/2); int h1=ch-h0; //--- Создаём на откреплённом графике два объекта-графика с символами строки и столбца if(!CreateChartObject(id,"ChartRowSymbol",row_symb,PERIOD_CURRENT,0,0,cw,h0)) return 0; if(!CreateChartObject(id,"ChartColSymbol",col_symb,PERIOD_CURRENT,0,h0-1,cw,h1+1)) return 0; //--- Обновляем открытый график и возвращаем его идентификатор ChartRedraw(id); return id; }
Функция открывает один график, делает его плавающим, запрещает отрисовку на нём ценового графика. Далее создаются два графических объекта "График" и устанавливаются для них ширина, высота и наименование символов. Таким образом мы получаем одно плавающее окно, в котором размещены два графика. При щелчке по ячейке таблицы откроется такое окно с ценовыми графиками символов, соответствующих выбранной ячейки таблицы. Отслеживание изменения размеров плавающего окна не реализовано с целью упрощения примера. Т.е. при изменении размеров окна, графики символов свои размеры менять не будут.
Функция, создающая объект-график указанного символа:
//+------------------------------------------------------------------+ //| Создаёт объект-график указанного символа | //+------------------------------------------------------------------+ bool CreateChartObject(const long chart_id,const string name,const string symbol,const ENUM_TIMEFRAMES timeframe,const int x,const int y,const int w,const int h) { //--- Создаём объект-график с указанными координатами и размерами //--- и устанавливаем его свойства - символ, период, координаты и размеры if(ObjectCreate(chart_id,name,OBJ_CHART,0,x,y,w,h)) { ObjectSetString(chart_id,name,OBJPROP_SYMBOL,symbol); ObjectSetInteger(chart_id,name,OBJPROP_PERIOD,timeframe); ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(chart_id,name,OBJPROP_XSIZE,w); ObjectSetInteger(chart_id,name,OBJPROP_YSIZE,h); return true; } //--- Ошибка создания объекта-графика return false; }
В функцию передаётся идентификатор графика, на котором нужно создать графический объект "График", имя создаваемого объекта, символ и период графика этого объекта, его координаты и размеры. После создания объекта, ему устанавливаются все указанные свойства.
Всё, индикатор готов.
Скомпилируем индикатор и запустим его на графике:

Весь заявленный функционал индикатора и таблиц корректно работает, что видно на прикреплённом изображении выше.
Заключение
Сегодня мы доработали классы таблиц для более наглядного их отображения, благодаря вертикальному заголовку строк таблицы, рассмотрели процесс создания индикатора, отображающего таблицу корреляций между символами в виде значений корреляции и тепловой карты. Такой подход позволяет не только анализировать числовые значения корреляции, но и визуально оценивать их распределение благодаря цветовой шкале. Использование цветовой интерполяции для окрашивания ячеек таблицы делает анализ данных более наглядным и интуитивно понятным, что особенно важно при работе с большими объёмами информации.
Тепловая карта корреляций может быть полезна в различных задачах, связанных с анализом финансовых рынков, например, для выявления взаимосвязей между инструментами, поиска активов с высокой корреляцией или, наоборот, слабо связанных инструментов для диверсификации портфеля.
Реализованная таблица с вертикальными и горизонтальными заголовками, и динамическим окрашиванием ячеек демонстрирует, как можно эффективно использовать парадигму MVC для построения сложных элементов интерфейса.
Мы можем легко расширять функционал таблицы, добавляя новые возможности без значительных изменений в существующем коде.
Созданный сегодня индикатор является не только полезным инструментом для анализа корреляций, но и примером того, как можно использовать программные возможности MQL5 для построения сложных и функциональных элементов интерфейса.
Программы, используемые в статье:
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Tables.mqh | Библиотека классов | Классы для создания модели таблицы |
| 2 | Base.mqh | Библиотека классов | Классы для создания базового объекта элементов управления |
| 3 | Controls.mqh | Библиотека классов | Классы элементов управления |
| 4 | iCorrelationTable.mq5 | Тестовый индикатор | Индикатор для отображения корреляции символов в таблице |
| 5 | MQL5.zip | Архив | Архив файлов, представленных выше, для распаковки в каталог MQL5 клиентского терминала |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Сеточная аппроксимация событийного потока как инструмент анализа ценовых паттернов (CDC-модуль)
Нейросети в трейдинге: Сеточная аппроксимация событийного потока как инструмент анализа ценовых паттернов (ADM-модуль)
Выборочные методы марковских цепей Монте-Карло. Алгоритм HMC
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования