
Классы таблицы и заголовка на базе модели таблицы в MQL5: Применение концепции MVC
Содержание
- Введение
- Доработка модели таблицы
- Класс заголовка таблицы
- Классы таблицы
- Тестируем результат
- Заключение
Введение
В первой статье, посвящённой созданию элемента управления Table Control, мы создали модель таблицы в MQL5, используя архитектурный шаблон MVC. Были разработаны классы ячеек, строк и модели таблицы, что позволило организовать данные в удобной и структурированной форме.
Теперь переходим к следующему этапу — разработке классов таблицы и её заголовков. Заголовки столбцов таблицы — это не просто подписи к столбцам, а инструмент управления таблицей и её столбцами. Они позволяют добавлять, удалять и переименовывать столбцы. Конечно, таблица может работать и без класса заголовка, но тогда её возможности будут ограничены — будет создаваться простая статичная таблица без заголовков столбцов и, соответственно, без возможности управления столбцами.
Для реализации управления столбцами, потребуется доработать модель таблицы. Мы добавим в неё методы, которые позволят работать со столбцами: изменять их структуру, добавлять новые или удалять существующие. Эти методы будут использоваться классом заголовка таблицы, чтобы обеспечить удобное управление её структурой.
Данный этап разработки подготовит основу для дальнейшей реализации компонентов представления (View) и управления (Controller), которые будут рассмотрены в следующих статьях. Этот шаг станет важным этапом на пути к созданию полноценного интерфейса для работы с данными.
Доработка модели таблицы
На данный момент модель таблицы создаётся из двумерного массива, но для повышения гибкости и удобства работы с таблицей, будут добавлены дополнительные способы её инициализации. Это позволит адаптировать модель под различные сценарии использования. В обновлённой версии класса модели таблицы появятся следующие методы:
-
Создание модели из двумерного массива
void CreateTableModel(T &array[][]);
Этот метод позволяет быстро создать модель таблицы на основе уже существующего двумерного массива данных.
-
Создание пустой модели с заданным количеством строк и столбцов
void CreateTableModel(const uint num_rows, const uint num_columns);
Этот метод подойдёт для случаев, когда структура таблицы известна заранее, но данные будут добавлены позже.
-
Создание модели из матрицы данных
void CreateTableModel(const matrix &row_data);
Этот метод позволяет использовать матрицу данных для инициализации таблицы, что удобно для работы с заранее подготовленными наборами данных.
-
Создание модели из связанного списка
void CreateTableModel(CList &list_param);
В этом случае, для хранения данных будет использоваться массив массивов, где один объект CList (данные о строках таблицы) содержит в себе другие объекты CList, в которых расположены данные о ячейках таблицы. Такой подход позволяет динамически управлять структурой таблицы и её содержимым.
Эти изменения сделают модель таблицы более универсальной и удобной для использования в различных сценариях. Например, можно будет легко создавать таблицы как из заранее подготовленных массивов данных, так и из динамически формируемых списков.
В прошлой статье мы писали все классы для создания модели таблицы прямо в файле тестового скрипта. Сегодня мы эти классы перенесём в свой собственный подключаемый файл.
В папке, где хранится скрипт от прошлой статьи (по умолчанию: \MQL5\Scripts\TableModel\), создадим новый подключаемый файл с именем Tables.mqh и скопируем в него из файла TableModelTest.mq5, находящегося в той же папке, всё от начала файла и до начала кода тестового скрипта — все классы для создания модели таблицы. Теперь у нас есть отдельный файл с классами модели таблицы с названием Tables.mqh — в него и будем вносить изменения и доработки.
Переместим созданный файл в новую папку MQL5\Scripts\Tables\ — данный проект будем делать в этой папке.
В раздел включаемых файлов/библиотек добавим форвард-декларацию новых классов — их будем делать сегодня, и объявление классов нужно для того, чтобы класс списка объектов CListObj, созданный в прошлой статье, мог создавать объекты этих классов в своём методе CreateElement():
//+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Arrays\List.mqh> //--- Форвард-декларация классов class CTableCell; // Класс ячейки таблицы class CTableRow; // Класс строки таблицы class CTableModel; // Класс модели таблицы class CColumnCaption; // Класс заголовка столбца таблицы class CTableHeader; // Класс заголовка таблицы class CTable; // Класс таблицы class CTableByParam; // Класс таблицы на основе массива параметров //+------------------------------------------------------------------+ //| Макросы | //+------------------------------------------------------------------+
В разделе макросов добавим определение ширины ячейки таблицы в символах, равное 19 — это минимальное значение ширины, при котором текст даты-времени полностью помещается в пространство ячейки в журнале, не сдвигая при этом правую границу ячейки, что вызывает рассинхронизацию размеров всех ячеек нарисованной в журнале таблицы:
//+------------------------------------------------------------------+ //| Макросы | //+------------------------------------------------------------------+ #define MARKER_START_DATA -1 // Маркер начала данных в файле #define MAX_STRING_LENGTH 128 // Максимальная длина строки в ячейке #define CELL_WIDTH_IN_CHARS 19 // Ширина ячейки таблицы в символах //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+
В разделе перечислений добавим новые константы к перечислению типов объектов:
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_OBJECT_TYPE // Перечисление типов объектов { OBJECT_TYPE_TABLE_CELL=10000, // Ячейка таблицы OBJECT_TYPE_TABLE_ROW, // Строка таблицы OBJECT_TYPE_TABLE_MODEL, // Модель таблицы OBJECT_TYPE_COLUMN_CAPTION, // Заголовок столбца таблицы OBJECT_TYPE_TABLE_HEADER, // Заголовок таблицы OBJECT_TYPE_TABLE, // Таблица OBJECT_TYPE_TABLE_BY_PARAM, // Таблица на данных массива параметров };
В классе списка объектов CListObj, в методе создания элемента, добавим новые кейсы для создания новых типов объектов:
//+------------------------------------------------------------------+ //| Метод создания элемента списка | //+------------------------------------------------------------------+ CObject *CListObj::CreateElement(void) { //--- В зависимости от типа объекта в m_element_type, создаём новый объект switch(this.m_element_type) { case OBJECT_TYPE_TABLE_CELL : return new CTableCell(); case OBJECT_TYPE_TABLE_ROW : return new CTableRow(); case OBJECT_TYPE_TABLE_MODEL : return new CTableModel(); case OBJECT_TYPE_COLUMN_CAPTION : return new CColumnCaption(); case OBJECT_TYPE_TABLE_HEADER : return new CTableHeader(); case OBJECT_TYPE_TABLE : return new CTable(); case OBJECT_TYPE_TABLE_BY_PARAM : return new CTableByParam(); default : return NULL; } }
После создания новых классов, список объектов CListObj сможет создавать объекты этих типов, что позволит сохранять и загружать списки из файлов, содержащие такие типы объектов.
Чтобы в ячейку можно было установить "пустое" значение, которое будет отображаться пустой строкой, а не значением "0", как это сделано сейчас, необходимо определить, какое значение считать "пустым". Понятно, что для строковых значений пустая строка и будет таким значением. А вот для числовых значений определим DBL_MAX — для вещественных типов и LONG_MAX — для целочисленных.
Для установки такого значения, в классе объекта ячейки таблицы, в защищённой области напишем метод:
class CTableCell : public CObject { protected: //--- Объединение для хранения значений ячейки (double, long, string) union DataType { protected: double double_value; long long_value; ushort ushort_value[MAX_STRING_LENGTH]; public: //--- Установка значений void SetValueD(const double value) { this.double_value=value; } void SetValueL(const long value) { this.long_value=value; } void SetValueS(const string value) { ::StringToShortArray(value,ushort_value); } //--- Возврат значений double ValueD(void) const { return this.double_value; } long ValueL(void) const { return this.long_value; } string ValueS(void) const { string res=::ShortArrayToString(this.ushort_value); res.TrimLeft(); res.TrimRight(); return res; } }; //--- Переменные DataType m_datatype_value; // Значение ENUM_DATATYPE m_datatype; // Тип данных CObject *m_object; // Объект в ячейке ENUM_OBJECT_TYPE m_object_type; // Тип объекта в ячейке int m_row; // Номер строки int m_col; // Номер столбца int m_digits; // Точность представления данных uint m_time_flags; // Флаги отображения даты/времени bool m_color_flag; // Флаг отображения наименования цвета bool m_editable; // Флаг редактируемой ячейки //--- Устанавливает "пустое значение" void SetEmptyValue(void) { switch(this.m_datatype) { case TYPE_LONG : case TYPE_DATETIME: case TYPE_COLOR : this.SetValue(LONG_MAX); break; case TYPE_DOUBLE : this.SetValue(DBL_MAX); break; default : this.SetValue(""); break; } } public: //--- Возврат координат и свойств ячейки
Метод, возвращающий значение, записанное в ячейке, в виде форматированной строки, теперь проверяет значение в ячейке на не "пустое" и, в случае, если значение "пустое", возвращает пустую строку:
public: //--- Возврат координат и свойств ячейки uint Row(void) const { return this.m_row; } uint Col(void) const { return this.m_col; } ENUM_DATATYPE Datatype(void) const { return this.m_datatype; } int Digits(void) const { return this.m_digits; } uint DatetimeFlags(void) const { return this.m_time_flags; } bool ColorNameFlag(void) const { return this.m_color_flag; } bool IsEditable(void) const { return this.m_editable; } //--- Возвращает (1) double, (2) long, (3) string значение double ValueD(void) const { return this.m_datatype_value.ValueD(); } long ValueL(void) const { return this.m_datatype_value.ValueL(); } string ValueS(void) const { return this.m_datatype_value.ValueS(); } //--- Возвращает значение в виде форматированной строки string Value(void) const { switch(this.m_datatype) { case TYPE_DOUBLE : return(this.ValueD()!=DBL_MAX ? ::DoubleToString(this.ValueD(),this.Digits()) : ""); case TYPE_LONG : return(this.ValueL()!=LONG_MAX ? ::IntegerToString(this.ValueL()) : ""); case TYPE_DATETIME: return(this.ValueL()!=LONG_MAX ? ::TimeToString(this.ValueL(),this.m_time_flags) : ""); case TYPE_COLOR : return(this.ValueL()!=LONG_MAX ? ::ColorToString((color)this.ValueL(),this.m_color_flag) : ""); default : return this.ValueS(); } } //--- Возвращает описание типа хранимого значения string DatatypeDescription(void) const { string type=::StringSubstr(::EnumToString(this.m_datatype),5); type.Lower(); return type; } //--- Очищает данные void ClearData(void) { this.SetEmptyValue(); }
Метод для очистки данных в ячейке теперь не устанавливает в неё ноль, а вызывает метод для установки пустого значения в ячейку.
Методы всех классов, из которых создаётся объект модели таблицы, возвращающие описание объекта, теперь сделаны виртуальными — для случая наследования от этих объектов:
//--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); void Print(void);
В классе строки таблицы все методы CreateNewCell(), создающие новую ячейку и добавляющие её в конец списка, теперь переименованы:
//+------------------------------------------------------------------+ //| Класс строки таблицы | //+------------------------------------------------------------------+ class CTableRow : public CObject { protected: CTableCell m_cell_tmp; // Объект ячейки для поиска в списке CListObj m_list_cells; // Список ячеек uint m_index; // Индекс строки //--- Добавляет указанную ячейку в конец списка bool AddNewCell(CTableCell *cell); public: //--- (1) Устанавливает, (2) возвращает индекс строки void SetIndex(const uint index) { this.m_index=index; } uint Index(void) const { return this.m_index; } //--- Устанавливает позиции строки и колонки всем ячейкам void CellsPositionUpdate(void); //--- Создаёт новую ячейку и добавляет в конец списка CTableCell *CellAddNew(const double value); CTableCell *CellAddNew(const long value); CTableCell *CellAddNew(const datetime value); CTableCell *CellAddNew(const color value); CTableCell *CellAddNew(const string value);
Сделано это для того, чтобы все методы, отвечающие за доступ к ячейкам, начинались с подстроки "Cell". В иных классах, методы для доступа, например, к строке таблицы будут начинаться с подстроки "Row". Это вносит упорядоченность в методы классов.
Для построения модели таблицы, нам необходимо разработать универсальный подход, который позволит создавать таблицы, практически, из любых данных. Это могут быть, например, структуры, списки сделок, ордеров, позиций или любые другие данные. Идея заключается в создании инструментария, который позволит формировать список строк, где каждая строка будет представлять собой список свойств. Каждое свойство в списке будет соответствовать одной ячейке таблицы.
Здесь стоит обратить внимание на структуру входных параметров MqlParam. Она предоставляет следующие возможности:
- Указание типа данных, которые хранятся в структуре (ENUM_DATATYPE).
- Хранение значений в трёх полях:
- integer_value — для целочисленных данных,
- double_value — для вещественных данных,
- string_value — для строковых данных.
Эта структура позволяет работать с различными типами данных, что позволит хранить любые свойства, такие как параметры сделок, ордеров или других объектов.
Для удобного хранения данных, создадим класс CMqlParamObj, унаследованный от базового класса CObject из Стандартной библиотеки. Этот класс будет включать в себя структуру MqlParam и предоставлять методы для установки и получения данных. Благодаря наследованию от CObject, такие объекты можно будет хранить в списках CList.
Рассмотрим класс целиком:
//+------------------------------------------------------------------+ //| Класс объекта параметра структуры | //+------------------------------------------------------------------+ class CMqlParamObj : public CObject { protected: public: MqlParam m_param; //--- Установка параметров void Set(const MqlParam ¶m) { this.m_param.type=param.type; this.m_param.double_value=param.double_value; this.m_param.integer_value=param.integer_value; this.m_param.string_value=param.string_value; } //--- Возврат параметров MqlParam Param(void) const { return this.m_param; } ENUM_DATATYPE Datatype(void) const { return this.m_param.type; } double ValueD(void) const { return this.m_param.double_value; } long ValueL(void) const { return this.m_param.integer_value;} string ValueS(void) const { return this.m_param.string_value; } //--- Описание объекта virtual string Description(void) { string t=::StringSubstr(::EnumToString(this.m_param.type),5); t.Lower(); string v=""; switch(this.m_param.type) { case TYPE_STRING : v=this.ValueS(); break; case TYPE_FLOAT : case TYPE_DOUBLE : v=::DoubleToString(this.ValueD()); break; case TYPE_DATETIME: v=::TimeToString(this.ValueL(),TIME_DATE|TIME_MINUTES|TIME_SECONDS); break; default : v=(string)this.ValueL(); break; } return(::StringFormat("<%s>%s",t,v)); } //--- Конструкторы/деструктор CMqlParamObj(void){} CMqlParamObj(const MqlParam ¶m) { this.Set(param); } ~CMqlParamObj(void){} };
Это обычная обёртка вокруг структуры MqlParam, так как требуется именно объект, унаследованный от CObject, чтобы можно было хранить такие объекты в списке CList.
Структура создаваемых данных будет такой:
- Один объект класса CMqlParamObj будет представлять одно свойство, например, цену сделки, её объём или время открытия,
- Один список CList будет представлять строку таблицы, содержащую все свойства одной сделки,
- Главный список CList будет содержать набор строк (списков CList), каждая из которых соответствует одной сделке, ордеру, позиции, или любой иной сущности.
Таким образом, мы получим структуру, аналогичную массиву массивов:
- Главный список CList — это "массив строк",
- Каждый вложенный список CList — это "массив ячеек" (свойств некоего объекта).
Пример такой структуры данных для списка исторических сделок:
- Главный список CList — хранит строки таблицы. Каждая строка — это отдельный список CList.
- Вложенные списки CList — каждый вложенный список представляет строку таблицы и содержит объекты класса CMqlParamObj, хранящие свойства. Например:
- первая строка: свойства сделки №1 (цена, объём, время открытия и т.д.),
- вторая строка: свойства сделки №2 (цена, объём, время открытия и т.д.),
- третья строка: свойства сделки №3 (цена, объём, время открытия и т.д.),
- и так далее.
- Объекты свойств (CMqlParamObj) — каждый объект хранит одно свойство, например, цену сделки или её объём.
После формирования структуры данных (списков CList), её можно передать в метод CreateTableModel (CList &list_param) модели таблицы. Этот метод будет интерпретировать данные следующим образом:
- Главный список CList — это список строк таблицы,
- Каждый вложенный список CList — это ячейки каждой строки,
- Объекты CMqlParamObj внутри вложенных списков — это значения ячеек.
Таким образом, на основе переданного списка будет создана таблица, полностью соответствующая исходным данным.
Для удобства создания таких списков разработаем специальный класс. Этот класс будет предоставлять методы для:
- Создания новой строки (списка CList) и добавления её в главный список,
- Добавления нового свойства (объекта CMqlParamObj) в строку.
//+------------------------------------------------------------------+ //| Класс для создания списков данных | //+------------------------------------------------------------------+ class DataListCreator { public: //--- Добавляет новую строку к списку CList list_data static CList *AddNewRowToDataList(CList *list_data) { CList *row=new CList; if(row==NULL || list_data.Add(row)<0) return NULL; return row; } //--- Создаёт новый объект параметров CMqlParamObj и добавляет его к списку CList static bool AddNewCellParamToRow(CList *row,MqlParam ¶m) { CMqlParamObj *cell=new CMqlParamObj(param); if(cell==NULL) return false; if(row.Add(cell)<0) { delete cell; return false; } return true; } };
Это статический класс, предоставляющий возможность удобно создавать списки правильной структуры для передачи их в методы создания моделей таблиц:
- Указываем нужное свойство (например, цену сделки),
- Класс автоматически создаёт объект CMqlParamObj, записывает в него значение свойства и добавляет его в строку,
- Строка добавляется в главный список.
После этого, готовый список можно передать в модель таблицы для построения.
В итоге, разработанный подход позволяет преобразовывать данные любой структуры или объекта в формат, пригодный для построения таблицы. Использование списков CList и объектов CMqlParamObj обеспечивает гибкость и удобство работы, а вспомогательный класс DataListCreator упрощает процесс создания таких списков. Это будет основой для построения универсальной модели таблицы, создаваемой из любых данных и под любые задачи.
В классе модели таблицы определим три новых метода создания модели таблицы, три новых метода для работы со столбцами таблицы, вместо пяти перегруженных параметрических конструкторов, добавим один шаблонный конструктор и три новых — по типам входных параметров новых методов для создания модели таблицы:
//+------------------------------------------------------------------+ //| Класс модели таблицы | //+------------------------------------------------------------------+ class CTableModel : public CObject { protected: CTableRow m_row_tmp; // Объект строки для поиска в списке CListObj m_list_rows; // Список строк таблицы //--- Создаёт модель таблицы из двумерного массива template<typename T> void CreateTableModel(T &array[][]); void CreateTableModel(const uint num_rows,const uint num_columns); void CreateTableModel(const matrix &row_data); void CreateTableModel(CList &list_param); //--- Возвращает корректный тип данных ENUM_DATATYPE GetCorrectDatatype(string type_name) { return ( //--- Целочисленное значение type_name=="bool" || type_name=="char" || type_name=="uchar" || type_name=="short"|| type_name=="ushort" || type_name=="int" || type_name=="uint" || type_name=="long" || type_name=="ulong" ? TYPE_LONG : //--- Вещественное значение type_name=="float"|| type_name=="double" ? TYPE_DOUBLE : //--- Значение даты/времени type_name=="datetime" ? TYPE_DATETIME : //--- Значение цвета type_name=="color" ? TYPE_COLOR : /*--- Строковое значение */ TYPE_STRING ); } //--- Создаёт и добавляет новую пустую строку в конец списка CTableRow *CreateNewEmptyRow(void); //--- Добавляет строку в конец списка bool AddNewRow(CTableRow *row); //--- Устанавливает позиции строки и колонки всем ячейкам таблицы void CellsPositionUpdate(void); public: //--- Возвращает (1) ячейку, (2) строку по индексу, количество (3) строк, ячеек (4) в указанной строке, (5) в таблице CTableCell *GetCell(const uint row, const uint col); CTableRow *GetRow(const uint index) { return this.m_list_rows.GetNodeAtIndex(index); } uint RowsTotal(void) const { return this.m_list_rows.Total(); } uint CellsInRow(const uint index); uint CellsTotal(void); //--- Устанавливает (1) значение, (2) точность, (3) флаги отображения времени, (4) флаг отображения имён цветов в указанную ячейку template<typename T> void CellSetValue(const uint row, const uint col, const T value); void CellSetDigits(const uint row, const uint col, const int digits); void CellSetTimeFlags(const uint row, const uint col, const uint flags); void CellSetColorNamesFlag(const uint row, const uint col, const bool flag); //--- (1) Назначает, (2) отменяет объект в ячейке void CellAssignObject(const uint row, const uint col,CObject *object); void CellUnassignObject(const uint row, const uint col); //--- (1) Удаляет (2) перемещает ячейку bool CellDelete(const uint row, const uint col); bool CellMoveTo(const uint row, const uint cell_index, const uint index_to); //--- (1) Возвращает, (2) выводит в журнал описание ячейки, (3) назначенный в ячейку объект string CellDescription(const uint row, const uint col); void CellPrint(const uint row, const uint col); CObject *CellGetObject(const uint row, const uint col); public: //--- Создаёт новую строку и (1) добавляет в конец списка, (2) вставляет в указанную позицию списка CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Удаляет (2) перемещает строку, (3) очищает данные строки bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Возвращает, (2) выводит в журнал описание строки string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Добавляет, (2) удаляет (3) перемещает столбец, (4) очищает данные, устанавливает (5) тип, (6) точность данных столбца bool ColumnAddNew(const int index=-1); bool ColumnDelete(const uint index); bool ColumnMoveTo(const uint col_index, const uint index_to); void ColumnClearData(const uint index); void ColumnSetDatatype(const uint index,const ENUM_DATATYPE type); void ColumnSetDigits(const uint index,const int digits); //--- (1) Возвращает, (2) выводит в журнал описание таблицы virtual string Description(void); void Print(const bool detail); void PrintTable(const int cell_width=CELL_WIDTH_IN_CHARS); //--- (1) Очищает данные, (2) уничтожает модель void ClearData(void); void Destroy(void); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_TABLE_MODEL); } //--- Конструкторы/деструктор template<typename T> CTableModel(T &array[][]) { this.CreateTableModel(array); } CTableModel(const uint num_rows,const uint num_columns) { this.CreateTableModel(num_rows,num_columns); } CTableModel(const matrix &row_data) { this.CreateTableModel(row_data); } CTableModel(CList &row_data) { this.CreateTableModel(row_data); } CTableModel(void){} ~CTableModel(void){} };
Рассмотрим новые методы.
Метод, создающий модель таблицы из указанного количества строк и столбцов
//+------------------------------------------------------------------+ //| Создаёт модель таблицы из указанного количества строк и столбцов | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(const uint num_rows,const uint num_columns) { //--- В цикле по количеству строк for(uint r=0; r<num_rows; r++) { //--- создаём новую пустую строку и добавляем её в конец списка строк CTableRow *row=this.CreateNewEmptyRow(); //--- Если строка создана и добавлена в список, if(row!=NULL) { //--- В цикле по количеству столбцов //--- создаём все ячейки, добавляя каждую новую в конец списка ячеек строки for(uint c=0; c<num_columns; c++) { CTableCell *cell=row.CellAddNew(0.0); if(cell!=NULL) cell.ClearData(); } } } }
Метод создаёт пустую модель с заданным количеством строк и столбцов. Подходит для случаев, когда структура таблицы известна заранее, но данные предполагается добавлять позже.
Метод, создающий модель таблицы из указанной матрицы
//+------------------------------------------------------------------+ //| Создаёт модель таблицы из указанной матрицы | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(const matrix &row_data) { //--- Количество строк и столбцов ulong num_rows=row_data.Rows(); ulong num_columns=row_data.Cols(); //--- В цикле по количеству строк for(uint r=0; r<num_rows; r++) { //--- создаём новую пустую строку и добавляем её в конец списка строк CTableRow *row=this.CreateNewEmptyRow(); //--- Если строка создана и добавлена в список, if(row!=NULL) { //--- В цикле по количеству столбцов //--- создаём все ячейки, добавляя каждую новую в конец списка ячеек строки for(uint c=0; c<num_columns; c++) row.CellAddNew(row_data[r][c]); } } }
Метод позволяет использовать матрицу данных для инициализации таблицы, что удобно для работы с заранее подготовленными наборами данных.
Метод, создающий модель таблицы из списка параметров
//+------------------------------------------------------------------+ //| Создаёт модель таблицы из списка параметров | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(CList &list_param) { //--- Если передан пустой список - сообщаем об этом и уходим if(list_param.Total()==0) { ::PrintFormat("%s: Error. Empty list passed",__FUNCTION__); return; } //--- Получаем указатель на первую строку таблицы для определения количества столбцов //--- Если первую строку получить не удалось, или в ней нет ячеек - сообщаем об этом и уходим CList *first_row=list_param.GetFirstNode(); if(first_row==NULL || first_row.Total()==0) { if(first_row==NULL) ::PrintFormat("%s: Error. Failed to get first row of list",__FUNCTION__); else ::PrintFormat("%s: Error. First row does not contain data",__FUNCTION__); return; } //--- Количество строк и столбцов ulong num_rows=list_param.Total(); ulong num_columns=first_row.Total(); //--- В цикле по количеству строк for(uint r=0; r<num_rows; r++) { //--- получаем очередную строку таблицы из списка list_param CList *col_list=list_param.GetNodeAtIndex(r); if(col_list==NULL) continue; //--- создаём новую пустую строку и добавляем её в конец списка строк CTableRow *row=this.CreateNewEmptyRow(); //--- Если строка создана и добавлена в список, if(row!=NULL) { //--- В цикле по количеству столбцов //--- создаём все ячейки, добавляя каждую новую в конец списка ячеек строки for(uint c=0; c<num_columns; c++) { CMqlParamObj *param=col_list.GetNodeAtIndex(c); if(param==NULL) continue; //--- Объявляем указатель на ячейку и тип данных, которые будут в ней содержаться CTableCell *cell=NULL; ENUM_DATATYPE datatype=param.Datatype(); //--- В зависимости от типа данных switch(datatype) { //--- вещественный тип данных case TYPE_FLOAT : case TYPE_DOUBLE : cell=row.CellAddNew((double)param.ValueD()); // Создаём новую ячейку с double-данными и if(cell!=NULL) cell.SetDigits((int)param.ValueL()); // записываем точность отображаемых данных break; //--- тип данных datetime case TYPE_DATETIME: cell=row.CellAddNew((datetime)param.ValueL()); // Создаём новую ячейку с datetime-данными и if(cell!=NULL) cell.SetDatetimeFlags((int)param.ValueD()); // записываем флаги отображения даты/времени break; //--- тип данных color case TYPE_COLOR : cell=row.CellAddNew((color)param.ValueL()); // Создаём новую ячейку с color-данными и if(cell!=NULL) cell.SetColorNameFlag((bool)param.ValueD()); // записваем флаг отображения наименования известных цветов break; //--- строковый тип данных case TYPE_STRING : cell=row.CellAddNew((string)param.ValueS()); // Создаём новую ячейку со string-данными break; //--- целочисленный тип данных default : cell=row.CellAddNew((long)param.ValueL()); // Создаём новую ячейку с long-данными break; } } } } }
Метод даёт возможность создать модель таблицы на основе связанного списка, что может быть полезно для работы с динамическими структурами данных.
Стоит отметить, что при создании ячеек, имеющих тип данних, например, double, точность отображения данных берётся из long-значения объекта param класса CMqlParamObj. Это говорит о том, что при создании структуры таблицы при помощи класса DataListCreator, рассмотренного выше, мы можем дополнительно передать в объект параметров нужную уточняющую информацию. Для финансового результата сделки это может быть сделано так:
//--- Финансовый результат сделки param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_PROFIT); param.integer_value=(param.double_value!=0 ? 2 : 1); DataListCreator::AddNewCellParamToRow(row,param);
Точно так же можно передавать флаги отображения времени для ячеек таблицы с типом datetime и флаг отображения наименования цвета для ячейки с типом color.
В таблице отображены типы ячеек таблиц и типы передаваемых параметров для них через объект CMqlParamObj:
Тип в CMqlParamObj | Тип ячейки double | Тип ячейки long | Тип ячейки datetime | Тип ячейки color | Тип ячейки string |
---|---|---|---|---|---|
double_value | значение свойства в ячейке | не используется | флаги отображения даты/времени | флаг отображения имени цвета | не используется |
integer_value | точность значения в ячейке | значение свойства в ячейке | значение свойства в ячейке | значение свойства в ячейке | не используется |
string_value | не используется | не используется | не используется | не используется | значение свойства в ячейке |
Из таблицы видно, что при создании структуры таблицы из неких данных, если эти данные имеют вещественный тип (записывается в поле структуры MqlParam double_value), то дополнительно можно записать в поле integer_value значение точности, с которой данные будут отображаться в ячейке таблицы. То же относится и к данным с типом datetime и color, но флаги записываются в поле double_value, так как целочисленное поле занято самим значением свойства.
Это не обязательно делать. При этом, в значения флагов и точности в ячейке будет установлено нулевое значение, которое затем можно изменить, как для конкретной ячейки, так и для всего столбца таблицы.
Метод, добавляющий новый столбец к таблице
//+------------------------------------------------------------------+ //| Добавляет столбец | //+------------------------------------------------------------------+ bool CTableModel::ColumnAddNew(const int index=-1) { //--- Объявляем переменные CTableCell *cell=NULL; bool res=true; //--- В цикле по количеству строк for(uint i=0;i<this.RowsTotal();i++) { //--- получаем очередную строку CTableRow *row=this.GetRow(i); if(row!=NULL) { //--- добавляем в конец строки ячейку с типом double cell=row.CellAddNew(0.0); if(cell==NULL) res &=false; //--- очищаем ячейку else cell.ClearData(); } } //--- Если передан индекс колонки не отрицательный - сдвигаем колонку на указанную позицию if(res && index>-1) res &=this.ColumnMoveTo(this.CellsInRow(0)-1,index); //--- Возвращаем результат return res; }
В метод передаётся индекс новой колонки. Сначала по всем строкам таблицы добавляются новые ячейки в конец строки, а затем, если индекс передан не отрицательным, все новые ячейки смещаются на указанный индекс.
Метод, устанавливающий тип данных столбца
//+------------------------------------------------------------------+ //| Устанавливает тип данных столбца | //+------------------------------------------------------------------+ void CTableModel::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- В цикле по всем строкам таблицы for(uint i=0;i<this.RowsTotal();i++) { //--- получаем из каждой строки ячейку с индексом столбца и устанавливаем тип данных CTableCell *cell=this.GetCell(i, index); if(cell!=NULL) cell.SetDatatype(type); } }
В цикле по всем строкам таблицы получаем ячейку каждой строки по индексу и устанавливаем для неё тип данных. В итоге, в ячейки всего столбца устанавливается одно одинаковое значение.
Метод, устанавливающий точность данных столбца
//+------------------------------------------------------------------+ //| Устанавливает точность данных столбца | //+------------------------------------------------------------------+ void CTableModel::ColumnSetDigits(const uint index,const int digits) { //--- В цикле по всем строкам таблицы for(uint i=0;i<this.RowsTotal();i++) { //--- получаем из каждой строки ячейку с индексом столбца и устанавливаем точность данных CTableCell *cell=this.GetCell(i, index); if(cell!=NULL) cell.SetDigits(digits); } }
В цикле по всем строкам таблицы получаем ячейку каждой строки по индексу и устанавливаем для неё тип данных. В итоге, в ячейки всего столбца устанавливается одно значение.
Добавленные методы в класс модели таблицы позволят работать с набором ячеек как с целым столбцом таблицы. Управление же столбцами таблицы может осуществляться только в том случае, если у таблицы есть заголовок. Без заголовка таблица будет статичной.
Класс заголовка таблицы
Загаловок таблицы — это обычный список объектов-заголовков столбцов таблицы со строковым значением, расположенных в динамическом списке CListObj. А динамический список составляет основу класса заголовка таблицы.
Исходя из этого, нам потребуется создать два класса:
- Класс объекта заголовка столбца таблицы.
Содержит текстовое значение заголовка, номер столбца, тип данных для всего столбца и методы управления ячейками столбца. - Класс заголовка таблицы.
Содержит список объектов заголовков столбцов и методы доступа к управлению столбцами таблицы.
Продолжим писать код в том же самом файле \MQL5\Scripts\Tables\Tables.mqh и напишем класс заголовка столбца таблицы:
//+------------------------------------------------------------------+ //| Класс заголовка столбца таблицы | //+------------------------------------------------------------------+ class CColumnCaption : public CObject { protected: //--- Переменные ushort m_ushort_array[MAX_STRING_LENGTH]; // Массив символов заголовка uint m_column; // Номер столбца ENUM_DATATYPE m_datatype; // Тип данных public: //--- (1) Устанавливает, (2) возвращает номер столбца void SetColumn(const uint column) { this.m_column=column; } uint Column(void) const { return this.m_column; } //--- (1) Устанавливает, (2) возвращает тип данных столбца ENUM_DATATYPE Datatype(void) const { return this.m_datatype; } void SetDatatype(const ENUM_DATATYPE datatype) { this.m_datatype=datatype;} //--- Очищает данные void ClearData(void) { this.SetValue(""); } //--- Устанавливает заголовок void SetValue(const string value) { ::StringToShortArray(value,this.m_ushort_array); } //--- Возвращает текст заголовка string Value(void) const { string res=::ShortArrayToString(this.m_ushort_array); res.TrimLeft(); res.TrimRight(); return res; } //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); void Print(void); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_COLUMN_CAPTION); } //--- Конструкторы/деструктор CColumnCaption(void) : m_column(0) { this.SetValue(""); } CColumnCaption(const uint column,const string value) : m_column(column) { this.SetValue(value); } ~CColumnCaption(void) {} };
Это сильно упрощенная версия класса ячейки таблицы. Рассмотрим некоторые методы класса.
Виртуальный метод сравнения двух объектов
//+------------------------------------------------------------------+ //| Сравнение двух объектов | //+------------------------------------------------------------------+ int CColumnCaption::Compare(const CObject *node,const int mode=0) const { const CColumnCaption *obj=node; return(this.Column()>obj.Column() ? 1 : this.Column()<obj.Column() ? -1 : 0); }
Сравнение производится по индексу столбца, для которого создан заголовок.
Метод сохранения в файл
//+------------------------------------------------------------------+ //| Сохранение в файл | //+------------------------------------------------------------------+ bool CColumnCaption::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return(false); //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Сохраняем номер столбца if(::FileWriteInteger(file_handle,this.m_column,INT_VALUE)!=INT_VALUE) return(false); //--- Сохраняем значение if(::FileWriteArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array)) return(false); //--- Всё успешно return true; }
Метод загрузки из файла
//+------------------------------------------------------------------+ //| Загрузка из файла | //+------------------------------------------------------------------+ bool CColumnCaption::Load(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return(false); //--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Загружаем тип объекта if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Загружаем номер столбца this.m_column=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем значение if(::FileReadArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array)) return(false); //--- Всё успешно return true; }
Подобные методы подробно рассматривались в предыдущей статье. Здесь логика совершенно такая же: сначала записываются маркер начала данных и тип объекта, а затем, поэлементно, все его свойства. Чтение происходит в том же порядке.
Метод, возвращающий описание объекта
//+------------------------------------------------------------------+ //| Возвращает описание объекта | //+------------------------------------------------------------------+ string CColumnCaption::Description(void) { return(::StringFormat("%s: Column %u, Value: \"%s\"", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.Column(),this.Value())); }
Создаётся и возвращается строка описания в формате (Тип Объекта: Column XX, Value "Значение")
Метод, выводящий в журнал описание объекта
//+------------------------------------------------------------------+ //| Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CColumnCaption::Print(void) { ::Print(this.Description()); }
Просто распечатывает в журнале описание заголовка.
Теперь такие объекты нужно разместить в списке, который будет являться заголовком таблицы. Напишем класс заголовка таблицы:
//+------------------------------------------------------------------+ //| Класс заголовка таблицы | //+------------------------------------------------------------------+ class CTableHeader : public CObject { protected: CColumnCaption m_caption_tmp; // Объект заголовка столбца для поиска в списке CListObj m_list_captions; // Список заголовков столбцлв //--- Добавляет указанный заголовок в конец списка bool AddNewColumnCaption(CColumnCaption *caption); //--- Создаёт заголовок таблицы из строкового массива void CreateHeader(string &array[]); //--- Устанавливает позицию столбца всем заголовкам столбцов void ColumnPositionUpdate(void); public: //--- Создаёт новый заголовок и добавляет в конец списка CColumnCaption *CreateNewColumnCaption(const string caption); //--- Возвращает (1) заголовок по индексу, (2) количество заголовков столбцов CColumnCaption *GetColumnCaption(const uint index) { return this.m_list_captions.GetNodeAtIndex(index); } uint ColumnsTotal(void) const { return this.m_list_captions.Total(); } //--- Устанавливает значение указанному заголовку столбца void ColumnCaptionSetValue(const uint index,const string value); //--- (1) Устанавливает, (2) возвращает тип данных для указанного заголовка столбца void ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type); ENUM_DATATYPE ColumnCaptionDatatype(const uint index); //--- (1) Удаляет (2) перемещает заголовок столбца bool ColumnCaptionDelete(const uint index); bool ColumnCaptionMoveTo(const uint caption_index, const uint index_to); //--- Очищает данные заголовков столбцов void ClearData(void); //--- Очищает список заголовков столбцов void Destroy(void) { this.m_list_captions.Clear(); } //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); void Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS); //--- Виртуальные методы (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(OBJECT_TYPE_TABLE_HEADER); } //--- Конструкторы/деструктор CTableHeader(void) {} CTableHeader(string &array[]) { this.CreateHeader(array); } ~CTableHeader(void){} };
Рассмотрим методы класса.
Метод, создающий новый заголовок и добавляющий его в конец списка заголовков столбцов
//+------------------------------------------------------------------+ //| Создаёт новый заголовок и добавляет в конец списка | //+------------------------------------------------------------------+ CColumnCaption *CTableHeader::CreateNewColumnCaption(const string caption) { //--- Создаём новый объект заголовка CColumnCaption *caption_obj=new CColumnCaption(this.ColumnsTotal(),caption); if(caption_obj==NULL) { ::PrintFormat("%s: Error. Failed to create new column caption at position %u",__FUNCTION__, this.ColumnsTotal()); return NULL; } //--- Добавляем созданный заголовок в конец списка if(!this.AddNewColumnCaption(caption_obj)) { delete caption_obj; return NULL; } //--- Возвращаем указатель на объект return caption_obj; }
В метод передаётся текст заголовка. Создаётся новый объект заголовка столбца с указанным текстом и индексом, равным количеству заголовков в списке. Это будет индекс последнего заголовка. Далее, созданный объект помещается в конец списка заголовков столбцов и возвращается указатель на созданный заголовок.
Метод, добавляющий указанный заголовок в конец списка
//+------------------------------------------------------------------+ //| Добавляет заголовок в конец списка | //+------------------------------------------------------------------+ bool CTableHeader::AddNewColumnCaption(CColumnCaption *caption) { //--- Если передан пустой объект - сообщаем и возвращаем false if(caption==NULL) { ::PrintFormat("%s: Error. Empty CColumnCaption object passed",__FUNCTION__); return false; } //--- Устанавливаем индекс заголовка в списке и добавляем созданный заголовок в конец списка caption.SetColumn(this.ColumnsTotal()); if(this.m_list_captions.Add(caption)==WRONG_VALUE) { ::PrintFormat("%s: Error. Failed to add caption (%u) to list",__FUNCTION__,this.ColumnsTotal()); return false; } //--- Успешно return true; }
В метод передаётся указатель на объект заголовка столбца, который необходимо поместить в конец списка заголовков. Метод возвращает результат добавления объекта в список.
Метод, создающий заголовок таблицы из строкового массива
//+------------------------------------------------------------------+ //| Создаёт заголовок таблицы из строкового массива | //+------------------------------------------------------------------+ void CTableHeader::CreateHeader(string &array[]) { //--- Получаем из свойств массива количество столбцов таблицы uint total=array.Size(); //--- В цикле по размеру массива //--- создаём все заголовки, добавляя каждый новый в конец списка for(uint i=0; i<total; i++) this.CreateNewColumnCaption(array[i]); }
В метод передаётся текстовый массив заголовков. Размер массива определяет количество создаваемых объектов заголовков столбцов, которые создаются при проходе в цикле по значениям текстов заголовков в массиве.
Метод, устанавливающий значение в указанный заголовок столбца
//+------------------------------------------------------------------+ //| Устанавливает значение в указанный заголовок столбца | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetValue(const uint index,const string value) { //--- Получаем из списка нужный заголовок и записываем в него новое значение CColumnCaption *caption=this.GetColumnCaption(index); if(caption!=NULL) caption.SetValue(value); }
Метод позволяет установить новое значение текста указанному по индексу заголовка.
Метод, устанавливающий тип данных для указанного заголовка столбца
//+------------------------------------------------------------------+ //| Устанавливает тип данных для указанного заголовка столбца | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- Получаем из списка нужный заголовок и записываем в него новое значение CColumnCaption *caption=this.GetColumnCaption(index); if(caption!=NULL) caption.SetDatatype(type); }
Метод позволяет установить новое значение хранимых в столбце данных, указанному по индексу заголовка. Для каждого столбца таблицы можно устанавливать тип хранимых в ячейках столбца данных. Установка типа данных в объект заголовка позволяет потом установить и для всего столбца такое же значение. И прочитать значение данных всего столбца можно будет, прочитав это значение у заголовка этого столбца.
Метод, возвращающий тип данных указанного заголовка столбца
//+------------------------------------------------------------------+ //| Возвращает тип данных указанного заголовка столбца | //+------------------------------------------------------------------+ ENUM_DATATYPE CTableHeader::ColumnCaptionDatatype(const uint index) { //--- Получаем из списка нужный заголовок и возвращаем из него тип данных столбца CColumnCaption *caption=this.GetColumnCaption(index); return(caption!=NULL ? caption.Datatype() : (ENUM_DATATYPE)WRONG_VALUE); }
Метод позволяет получить значение хранимых в столбце данных по индексу заголовка. Получение значения из заголовка позволяет узнать тип значений, хранимых во всех ячейках этого столбца таблицы.
Метод, удаляющий заголовок указанного столбца
//+------------------------------------------------------------------+ //| Удаляет заголовок указанного столбца | //+------------------------------------------------------------------+ bool CTableHeader::ColumnCaptionDelete(const uint index) { //--- Удаляем заголовок в списке по индексу if(!this.m_list_captions.Delete(index)) return false; //--- Обновляем индексы для оставшихся заголовков в списке this.ColumnPositionUpdate(); return true; }
Из списка заголовков удаляется объект по указанному индексу. После успешного удаления объекта заголовка столбца, необходимо обновить индексы оставшихся объектов в списке.
Метод, перемещающий заголовок столбца на указанную позицию
//+------------------------------------------------------------------+ //| Перемещает заголовок столбца на указанную позицию | //+------------------------------------------------------------------+ bool CTableHeader::ColumnCaptionMoveTo(const uint caption_index,const uint index_to) { //--- Получаем нужный заголовок по индексу в списке, делая его текущим CColumnCaption *caption=this.GetColumnCaption(caption_index); //--- Перемещаем текущий заголовок на указанную позицию в списке if(caption==NULL || !this.m_list_captions.MoveToIndex(index_to)) return false; //--- Обновляем индексы всех заголовков в списке this.ColumnPositionUpdate(); return true; }
Позволяет переместить заголовок из указанного индекса в новую позицию в списке.
Метод, устанавливающий позиции столбца всем заголовкам
//+------------------------------------------------------------------+ //| Устанавливает позиции столбца всем заголовкам | //+------------------------------------------------------------------+ void CTableHeader::ColumnPositionUpdate(void) { //--- В цикле по всем заголовкам в списке for(int i=0;i<this.m_list_captions.Total();i++) { //--- получаем очередной заголовок и устанавливаем в него индекс столбца CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) caption.SetColumn(this.m_list_captions.IndexOf(caption)); } }
После удаления или перемещения объекта в списке на иное место, необходимо переназначить индексы всем остальным объектам списка, чтобы их индексы соответствовали реальному положению в списке. Метод проходит в цикле по всем объектам в списке, получает реальный индекс каждого объекта и устанавливает его как свойство объекта.
Метод, очищающий данные заголовков столбцов в списке
//+------------------------------------------------------------------+ //| Очищает данные заголовков столбцов в списке | //+------------------------------------------------------------------+ void CTableHeader::ClearData(void) { //--- В цикле по всем заголовкам в списке for(uint i=0;i<this.ColumnsTotal();i++) { //--- получаем очередной заголовок и устанавливаем в него пустое значение CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) caption.ClearData(); } }
В цикле по всем объектам заголовков столбцов в списке, получаем каждый очередной объект и устанавливаем тексту заголовка пустое значение. Тем самым полностью очищаются заголовки каждого столбца таблицы.
Метод, возвращающий описание объекта
//+------------------------------------------------------------------+ //| Возвращает описание объекта | //+------------------------------------------------------------------+ string CTableHeader::Description(void) { return(::StringFormat("%s: Captions total: %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.ColumnsTotal())); }
Создаётся и возвращается строка в формате (Тип Объекта: Captions total: XX)
Метод, выводящий в журнал описание объекта
//+------------------------------------------------------------------+ //| Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CTableHeader::Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS) { //--- Количество заголовков int total=(int)this.ColumnsTotal(); //--- Если вывод в табличном виде string res=""; if(as_table) { //--- создаём строку таблицы из значений всех заголовков res="|"; for(int i=0;i<total;i++) { CColumnCaption *caption=this.GetColumnCaption(i); if(caption==NULL) continue; res+=::StringFormat("%*s |",column_width,caption.Value()); } //--- Выводим строку в журнал и уходим ::Print(res); return; } //--- Выводим заголовок в виде описания строки ::Print(this.Description()+(detail ? ":" : "")); //--- Если детализированное описание if(detail) { //--- В цикле по списку заголовков строки for(int i=0; i<total; i++) { //--- получаем текущий заголовок и добавляем в итоговую строку его описание CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) res+=" "+caption.Description()+(i<total-1 ? "\n" : ""); } //--- Выводим в журнал созданную в цикле строку ::Print(res); } }
Метод может выводить в журнал описание заголовка в табличном виде и в виде списка заголовков столбцов.
Методы сохранения в файл и загрузки заголовка из файла
//+------------------------------------------------------------------+ //| Сохранение в файл | //+------------------------------------------------------------------+ bool CTableHeader::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return(false); //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Сохраняем список заголовков if(!this.m_list_captions.Save(file_handle)) return(false); //--- Успешно return true; } //+------------------------------------------------------------------+ //| Загрузка из файла | //+------------------------------------------------------------------+ bool CTableHeader::Load(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return(false); //--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Загружаем тип объекта if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Загружаем список заголовков if(!this.m_list_captions.Load(file_handle)) return(false); //--- Успешно return true; }
Логика методов прокомментирована в коде и ничем не отличается от подобных методов других, уже созданных классов, для создания таблиц.
У нас всё готово для того, чтобы начать сборку классов таблиц. Класс таблицы должен уметь строить таблицу по её модели, и должен иметь заголовок, по которому столбцы таблицы будут именованы. Если в таблице не указать её заголовок, то она будет построена только по модели, будет статичной, и её возможности будут ограничены только просмотром таблицы. Для простых таблиц этого вполне достаточно. Но для взаимодействия с пользователем при помощи компонента Controller, в таблице должен быть определён её заголовок. Это даст широкий спектр возможностей по управлению таблицами и её данными. Но это всё будем делать позже. Сейчас же займёмся классами таблиц.
Классы таблицы
Продолжим писать код в том же файле и напишем класс таблицы:
//+------------------------------------------------------------------+ //| Класс таблицы | //+------------------------------------------------------------------+ class CTable : public CObject { private: //--- Заполняет массив заголовков столбцов в стиле Excel bool FillArrayExcelNames(const uint num_columns); //--- Возвращает наименование столбца как в Excel string GetExcelColumnName(uint column_number); //--- Возвращает доступность заголовка bool HeaderCheck(void) const { return(this.m_table_header!=NULL && this.m_table_header.ColumnsTotal()>0); } protected: CTableModel *m_table_model; // Указатель на модель таблицы CTableHeader *m_table_header; // Указатель на заголовок таблицы CList m_list_rows; // Список массивов параметров из полей структуры string m_array_names[]; // Массив заголовков столбцов int m_id; // Идентификатор таблицы //--- Копирует массив наименований заголовков bool ArrayNamesCopy(const string &column_names[],const uint columns_total); public: //--- (1) Устанавливает, (2) возвращает модель таблицы void SetTableModel(CTableModel *table_model) { this.m_table_model=table_model; } CTableModel *GetTableModel(void) { return this.m_table_model; } //--- (1) Устанавливает, (2) возвращает заголовок void SetTableHeader(CTableHeader *table_header) { this.m_table_header=m_table_header; } CTableHeader *GetTableHeader(void) { return this.m_table_header; } //--- (1) Устанавливает, (2) возвращает идентификатор таблицы void SetID(const int id) { this.m_id=id; } int ID(void) const { return this.m_id; } //--- Очищает данные заголовков столбцов void HeaderClearData(void) { if(this.m_table_header!=NULL) this.m_table_header.ClearData(); } //--- Удаляет заголовок таблицы void HeaderDestroy(void) { if(this.m_table_header==NULL) return; this.m_table_header.Destroy(); this.m_table_header=NULL; } //--- (1) Очищает все данные, (2) уничтожает модель таблицы и заголовок void ClearData(void) { if(this.m_table_model!=NULL) this.m_table_model.ClearData(); } void Destroy(void) { if(this.m_table_model==NULL) return; this.m_table_model.Destroy(); this.m_table_model=NULL; } //--- Возвращает (1) заголовок, (2) ячейку, (3) строку по индексу, количество (4) строк, (5) столбцов, ячеек (6) в указанной строке, (7) в таблице CColumnCaption *GetColumnCaption(const uint index) { return(this.m_table_header!=NULL ? this.m_table_header.GetColumnCaption(index) : NULL); } CTableCell *GetCell(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.GetCell(row,col) : NULL); } CTableRow *GetRow(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.GetRow(index) : NULL); } uint RowsTotal(void) const { return(this.m_table_model!=NULL ? this.m_table_model.RowsTotal() : 0); } uint ColumnsTotal(void) const { return(this.m_table_model!=NULL ? this.m_table_model.CellsInRow(0) : 0); } uint CellsInRow(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.CellsInRow(index) : 0); } uint CellsTotal(void) { return(this.m_table_model!=NULL ? this.m_table_model.CellsTotal() : 0); } //--- Устанавливает (1) значение, (2) точность, (3) флаги отображения времени, (4) флаг отображения имён цветов в указанную ячейку template<typename T> void CellSetValue(const uint row, const uint col, const T value); void CellSetDigits(const uint row, const uint col, const int digits); void CellSetTimeFlags(const uint row, const uint col, const uint flags); void CellSetColorNamesFlag(const uint row, const uint col, const bool flag); //--- (1) Назначает, (2) отменяет объект в ячейке void CellAssignObject(const uint row, const uint col,CObject *object); void CellUnassignObject(const uint row, const uint col); //--- Возвращает строковое значение указанной ячейки virtual string CellValueAt(const uint row, const uint col); protected: //--- (1) Удаляет (2) перемещает ячейку bool CellDelete(const uint row, const uint col); bool CellMoveTo(const uint row, const uint cell_index, const uint index_to); public: //--- (1) Возвращает, (2) выводит в журнал описание ячейки, (3) назначенный в ячейку объект string CellDescription(const uint row, const uint col); void CellPrint(const uint row, const uint col); //---Возвращает (1) назначенный в ячейку объект, (2) тип назначенного в ячейку объекта CObject *CellGetObject(const uint row, const uint col); ENUM_OBJECT_TYPE CellGetObjType(const uint row, const uint col); //--- Создаёт новую строку и (1) добавляет в конец списка, (2) вставляет в указанную позицию списка CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Удаляет (2) перемещает строку, (3) очищает данные строки bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Возвращает, (2) выводит в журнал описание строки string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Добавляет новый, (2) удаляет, (3) перемещает столбец, (4) очищает данные столбца bool ColumnAddNew(const string caption,const int index=-1); bool ColumnDelete(const uint index); bool ColumnMoveTo(const uint index, const uint index_to); void ColumnClearData(const uint index); //--- Устанавливает (1) значение указанному заголовку, (2) точность данных указанному столбцу void ColumnCaptionSetValue(const uint index,const string value); void ColumnSetDigits(const uint index,const int digits); //--- (1) Устанавливает, (2) возвращает тип данных для указанного столбца void ColumnSetDatatype(const uint index,const ENUM_DATATYPE type); ENUM_DATATYPE ColumnDatatype(const uint index); //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); void Print(const int column_width=CELL_WIDTH_IN_CHARS); //--- Виртуальные методы (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(OBJECT_TYPE_TABLE); } //--- Конструкторы/деструктор CTable(void) : m_table_model(NULL), m_table_header(NULL) { this.m_list_rows.Clear();} template<typename T> CTable(T &row_data[][],const string &column_names[]); CTable(const uint num_rows, const uint num_columns); CTable(const matrix &row_data,const string &column_names[]); ~CTable (void); };
В классе объявлены указатели на заголовок и модель таблицы. Для построения таблицы нужно минимум построить модель таблицы из данных, передаваемых в конструкторы класса. Таблица может заполнять пустой заголовок наименованиями столбцов в стиле MS Excel, где каждому столбцу назначается имя из букв латинского алфавита.
Алгоритм расчёта наименований следующий:
-
Однобуквенные названия — первые 26 столбцов обозначаются буквами от "A" до "Z".
-
Двухбуквенные названия — после "Z" столбцы начинают обозначаться комбинацией двух букв. Первая буква меняется медленнее, а вторая перебирает весь алфавит. Например:
- "AA", "AB", "AC", ..., "AZ",
- затем "BA", "BB", ..., "BZ",
- и так далее.
-
Трёхбуквенные названия — после "ZZ" столбцы начинают обозначаться комбинацией из трёх букв. Принцип тот же:
- "AAA", "AAB", ..., "AAZ",
- затем "ABA", "ABB", ..., "ABZ",
- и так далее.
-
Общий принцип — названия столбцов можно рассматривать, как числа в системе счисления с основанием 26, где "A" соответствует 1, "B" — 2, ..., "Z" — 26. Например:
- "A" = 1,
- "Z" = 26,
- "AA" = 27 (1 * 26^1 + 1),
- "AB" = 28 (1 * 26^1 + 2),
- "BA" = 53 (2 * 26^1 + 1).
Таким образом, алгоритм автоматически генерирует названия столбцов, увеличивая их в соответствии с рассмотренным принципом. Максимальное количество столбцов в Excel зависит от версии программы (например, в Excel 2007 и более поздних версиях их 16 384, заканчиваются на "XFD"). Алгоритм, созданный здесь, не ограничен этой цифрой. Он может дать наименования количеству столбцов, равному INT_MAX:
//+------------------------------------------------------------------+ //| Возвращает наименование столбца как в Excel | //+------------------------------------------------------------------+ string CTable::GetExcelColumnName(uint column_number) { string column_name=""; uint index=column_number; //--- Проверяем, что номер столбца больше 0 if(index==0) return (__FUNCTION__+": Error. Invalid column number passed"); //--- Преобразование номера в название столбца while(!::IsStopped() && index>0) { index--; // Уменьшаем номер на 1, чтобы сделать его 0-индексным uint remainder =index % 26; // Остаток от деления на 26 uchar char_code ='A'+(uchar)remainder; // Рассчитываем код символа (буквы) column_name=::CharToString(char_code)+column_name; // Добавляем букву в начало строки index/=26; // Переходим к следующему разряду } return column_name; } //+------------------------------------------------------------------+ //| Заполняет массив заголовков столбцов в стиле Excel | //+------------------------------------------------------------------+ bool CTable::FillArrayExcelNames(const uint num_columns) { ::ResetLastError(); if(::ArrayResize(this.m_array_names,num_columns,num_columns)!=num_columns) { ::PrintFormat("%s: ArrayResize() failed. Error %d",__FUNCTION__,::GetLastError()); return false; } for(int i=0;i<(int)num_columns;i++) this.m_array_names[i]=this.GetExcelColumnName(i+1); return true; }
Методы позволяют заполнить массив имён столбцов наименованиями как в MS EXcel.
Рассмотрим параметрические конструкторы класса.
Шаблонный конструктор с указанием двумерного массива данных и строкового массива заголовков
//+-------------------------------------------------------------------+ //| Конструктор с указанием массива таблицы и массива заголовков. | //| Определяет количество и наименования колонок согласно column_names| //| Количество строк определены размером массива данных row_data, | //| который используется и для заполнения таблицы | //+-------------------------------------------------------------------+ template<typename T> CTable::CTable(T &row_data[][],const string &column_names[]) : m_id(-1) { this.m_table_model=new CTableModel(row_data); if(column_names.Size()>0) this.ArrayNamesCopy(column_names,row_data.Range(1)); else { ::PrintFormat("%s: An empty array names was passed. The header array will be filled in Excel style (A, B, C)",__FUNCTION__); this.FillArrayExcelNames((uint)::ArrayRange(row_data,1)); } this.m_table_header=new CTableHeader(this.m_array_names); }
В шаблонный конструктор передаётся массив данных с любым типом из перечисления ENUM_DATATYPE. Далее он будет преобразован в используемый таблицами тип данных (double, long, datetime, color, string) для создания модели таблицы и массив заголовков столбцов. Если массив заголовков пустой, то будут созданы заголовки в стиле MS Excel.
Конструктор с указанием количества строк и столбцов таблицы
//+------------------------------------------------------------------+ //| Конструктор таблицы с определением количества колонок и строк. | //| Колонки будут иметь Excel-наименования "A", "B", "C" и т.д. | //+------------------------------------------------------------------+ CTable::CTable(const uint num_rows,const uint num_columns) : m_table_header(NULL), m_id(-1) { this.m_table_model=new CTableModel(num_rows,num_columns); if(this.FillArrayExcelNames(num_columns)) this.m_table_header=new CTableHeader(this.m_array_names); }
Конструктор создаёт пустую модель таблицы с заголовком в стиле MS Excel.
Конструктор на основе матрицы данных и массива заголовков столбцов
//+-------------------------------------------------------------------+ //| Конструктор таблицы с инициализацией колонок согласно column_names| //| Количество строк определены параметром row_data, с типом matrix | //+-------------------------------------------------------------------+ CTable::CTable(const matrix &row_data,const string &column_names[]) : m_id(-1) { this.m_table_model=new CTableModel(row_data); if(column_names.Size()>0) this.ArrayNamesCopy(column_names,(uint)row_data.Cols()); else { ::PrintFormat("%s: An empty array names was passed. The header array will be filled in Excel style (A, B, C)",__FUNCTION__); this.FillArrayExcelNames((uint)row_data.Cols()); } this.m_table_header=new CTableHeader(this.m_array_names); }
В конструктор передаётся матрица данных с типом double, для создания модели таблицы и массив заголовков столбцов. Если массив заголовков пустой, то будут созданы заголовки в стиле MS Excel.
В деструкторе класса уничтожаются модель и заголовок таблицы
//+------------------------------------------------------------------+ //| Деструктор | //+------------------------------------------------------------------+ CTable::~CTable(void) { if(this.m_table_model!=NULL) { this.m_table_model.Destroy(); delete this.m_table_model; } if(this.m_table_header!=NULL) { this.m_table_header.Destroy(); delete this.m_table_header; } }
Метод сравнения двух объектов
//+------------------------------------------------------------------+ //| Сравнение двух объектов | //+------------------------------------------------------------------+ int CTable::Compare(const CObject *node,const int mode=0) const { const CTable *obj=node; return(this.ID()>obj.ID() ? 1 : this.ID()<obj.ID() ? -1 : 0); }
Каждой таблице может быть назначен идентификатор, в случае, если в программе предполагается создание множества таблиц. Таблицы в программе могут идентифицироваться по установленному идентификатору, который по умолчанию имеет значение -1. Если созданные таблицы размещаются в списках (CList, CArrayObj и т.п.), то метод сравнения позволяет сравнить таблицы по их идентификаторам для их поиска и сортировки:
//+------------------------------------------------------------------+ //| Сравнение двух объектов | //+------------------------------------------------------------------+ int CTable::Compare(const CObject *node,const int mode=0) const { const CTable *obj=node; return(this.ID()>obj.ID() ? 1 : this.ID()<obj.ID() ? -1 : 0); }
Метод, копирующий массив наименований заголовков
//+------------------------------------------------------------------+ //| Копирует массив наименований заголовков | //+------------------------------------------------------------------+ bool CTable::ArrayNamesCopy(const string &column_names[],const uint columns_total) { if(columns_total==0) { ::PrintFormat("%s: Error. The table has no columns",__FUNCTION__); return false; } if(columns_total>column_names.Size()) { ::PrintFormat("%s: The number of header names is less than the number of columns. The header array will be filled in Excel style (A, B, C)",__FUNCTION__); return this.FillArrayExcelNames(columns_total); } uint total=::fmin(columns_total,column_names.Size()); return(::ArrayCopy(this.m_array_names,column_names,0,0,total)==total); }
В метод передаётся массив заголовков и количество столбцов в созданной модели таблицы. Если в таблице нет столбцов, то и заголовки создавать не к чему — сообщаем об этом и возвращаем false. Если столбцов в модели таблицы больше, чем заголовков в переданном массиве, то все заголовки будут созданы в стиле Excel — чтобы у таблицы не оказалось столбцов без подписей в заголовках.
Метод, устанавливающий значение в указанную ячейку
//+------------------------------------------------------------------+ //| Устанавливает значение в указанную ячейку | //+------------------------------------------------------------------+ template<typename T> void CTable::CellSetValue(const uint row, const uint col, const T value) { if(this.m_table_model!=NULL) this.m_table_model.CellSetValue(row,col,value); }
Здесь идёт обращение к одноимённому методу объекта модели таблицы.
По сути, в этом классе многие методы продублированы из класса модели таблицы. Если модель создана, то вызывается её одноимённый метод получения или установки свойства.
Методы для работы с ячейками таблицы
//+------------------------------------------------------------------+ //| Устанавливает точность в указанную ячейку | //+------------------------------------------------------------------+ void CTable::CellSetDigits(const uint row, const uint col, const int digits) { if(this.m_table_model!=NULL) this.m_table_model.CellSetDigits(row,col,digits); } //+------------------------------------------------------------------+ //| Устанавливает флаги отображения времени в указанную ячейку | //+------------------------------------------------------------------+ void CTable::CellSetTimeFlags(const uint row, const uint col, const uint flags) { if(this.m_table_model!=NULL) this.m_table_model.CellSetTimeFlags(row,col,flags); } //+------------------------------------------------------------------+ //| Устанавливает флаг отображения имён цветов в указанную ячейку | //+------------------------------------------------------------------+ void CTable::CellSetColorNamesFlag(const uint row, const uint col, const bool flag) { if(this.m_table_model!=NULL) this.m_table_model.CellSetColorNamesFlag(row,col,flag); } //+------------------------------------------------------------------+ //| Назначает объект в ячейку | //+------------------------------------------------------------------+ void CTable::CellAssignObject(const uint row, const uint col,CObject *object) { if(this.m_table_model!=NULL) this.m_table_model.CellAssignObject(row,col,object); } //+------------------------------------------------------------------+ //| Отменяет объект в ячейке | //+------------------------------------------------------------------+ void CTable::CellUnassignObject(const uint row, const uint col) { if(this.m_table_model!=NULL) this.m_table_model.CellUnassignObject(row,col); } //+------------------------------------------------------------------+ //| Возвращает строковое значение указанной ячейки | //+------------------------------------------------------------------+ string CTable::CellValueAt(const uint row,const uint col) { CTableCell *cell=this.GetCell(row,col); return(cell!=NULL ? cell.Value() : ""); } //+------------------------------------------------------------------+ //| Удаляет ячейку | //+------------------------------------------------------------------+ bool CTable::CellDelete(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellDelete(row,col) : false); } //+------------------------------------------------------------------+ //| Перемещает ячейку | //+------------------------------------------------------------------+ bool CTable::CellMoveTo(const uint row, const uint cell_index, const uint index_to) { return(this.m_table_model!=NULL ? this.m_table_model.CellMoveTo(row,cell_index,index_to) : false); } //+------------------------------------------------------------------+ //| Возвращает назначенный в ячейку объект | //+------------------------------------------------------------------+ CObject *CTable::CellGetObject(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellGetObject(row,col) : NULL); } //+------------------------------------------------------------------+ //| Возвращает тип назначенного в ячейку объекта | //+------------------------------------------------------------------+ ENUM_OBJECT_TYPE CTable::CellGetObjType(const uint row,const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellGetObjType(row,col) : (ENUM_OBJECT_TYPE)WRONG_VALUE); } //+------------------------------------------------------------------+ //| Возвращает описание ячейки | //+------------------------------------------------------------------+ string CTable::CellDescription(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellDescription(row,col) : ""); } //+------------------------------------------------------------------+ //| Выводит в журнал описание ячейки | //+------------------------------------------------------------------+ void CTable::CellPrint(const uint row, const uint col) { if(this.m_table_model!=NULL) this.m_table_model.CellPrint(row,col); }
Методы для работы со строками таблицы
//+------------------------------------------------------------------+ //| Создаёт новую строку и добавляет в конец списка | //+------------------------------------------------------------------+ CTableRow *CTable::RowAddNew(void) { return(this.m_table_model!=NULL ? this.m_table_model.RowAddNew() : NULL); } //+------------------------------------------------------------------+ //| Создаёт новую строку и вставляет в указанную позицию списка | //+------------------------------------------------------------------+ CTableRow *CTable::RowInsertNewTo(const uint index_to) { return(this.m_table_model!=NULL ? this.m_table_model.RowInsertNewTo(index_to) : NULL); } //+------------------------------------------------------------------+ //| Удаляет строку | //+------------------------------------------------------------------+ bool CTable::RowDelete(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.RowDelete(index) : false); } //+------------------------------------------------------------------+ //| Перемещает строку | //+------------------------------------------------------------------+ bool CTable::RowMoveTo(const uint row_index, const uint index_to) { return(this.m_table_model!=NULL ? this.m_table_model.RowMoveTo(row_index,index_to) : false); } //+------------------------------------------------------------------+ //| Очищает данные строки | //+------------------------------------------------------------------+ void CTable::RowClearData(const uint index) { if(this.m_table_model!=NULL) this.m_table_model.RowClearData(index); } //+------------------------------------------------------------------+ //| Возвращает описание строки | //+------------------------------------------------------------------+ string CTable::RowDescription(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.RowDescription(index) : ""); } //+------------------------------------------------------------------+ //| Выводит в журнал описание строки | //+------------------------------------------------------------------+ void CTable::RowPrint(const uint index,const bool detail) { if(this.m_table_model!=NULL) this.m_table_model.RowPrint(index,detail); }
Метод, создающий новый столбец и добавляющий его в указанную позицию таблицы
//+------------------------------------------------------------------+ //| Создаёт новый столбец и добавляет его в указанную позицию таблицы| //+------------------------------------------------------------------+ bool CTable::ColumnAddNew(const string caption,const int index=-1) { //--- Если нет модели таблицы, либо ошибка добавления нового столбца к модели - возвращаем false if(this.m_table_model==NULL || !this.m_table_model.ColumnAddNew(index)) return false; //--- Если нет заголовка - возвращаем true (столбец добавлен без заголовка) if(this.m_table_header==NULL) return true; //--- Проверяем создание нового заголовка столбца и, если не создан - возвращаем false CColumnCaption *caption_obj=this.m_table_header.CreateNewColumnCaption(caption); if(caption_obj==NULL) return false; //--- Если передан не отрицательный индекс - возвращаем результат перемещения заголовка на указанный индекс //--- В ином случае уже всё готово - просто возвращаем true return(index>-1 ? this.m_table_header.ColumnCaptionMoveTo(caption_obj.Column(),index) : true); }
При отсутствии модели таблицы, метод сразу возвращает ошибку. Если столбец успешно добавлен к модели таблицы, пробуем добавить соответствующий заголовок. Если у таблицы нет заголовка — возвращаем успешность создания нового столбца. Если заголовок есть — добавляем новый заголовок столбца и перемещаем его на указанную позицию в списке.
Остальные методы для работы со столбцами
//+------------------------------------------------------------------+ //| Удаляет столбец | //+------------------------------------------------------------------+ bool CTable::ColumnDelete(const uint index) { if(!this.HeaderCheck() || !this.m_table_header.ColumnCaptionDelete(index)) return false; return this.m_table_model.ColumnDelete(index); } //+------------------------------------------------------------------+ //| Перемещает столбец | //+------------------------------------------------------------------+ bool CTable::ColumnMoveTo(const uint index, const uint index_to) { if(!this.HeaderCheck() || !this.m_table_header.ColumnCaptionMoveTo(index,index_to)) return false; return this.m_table_model.ColumnMoveTo(index,index_to); } //+------------------------------------------------------------------+ //| Очищает данные столбца | //+------------------------------------------------------------------+ void CTable::ColumnClearData(const uint index) { if(this.m_table_model!=NULL) this.m_table_model.ColumnClearData(index); } //+------------------------------------------------------------------+ //| Устанавливает значение указанному заголовку | //+------------------------------------------------------------------+ void CTable::ColumnCaptionSetValue(const uint index,const string value) { CColumnCaption *caption=this.m_table_header.GetColumnCaption(index); if(caption!=NULL) caption.SetValue(value); } //+------------------------------------------------------------------+ //| Устанавливает тип данных для указанного столбца | //+------------------------------------------------------------------+ void CTable::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- Если модель таблицы есть - устанавливаем тип данных для столбца if(this.m_table_model!=NULL) this.m_table_model.ColumnSetDatatype(index,type); //--- Если заголовок есть - устанавливаем тип данных для заголовка if(this.m_table_header!=NULL) this.m_table_header.ColumnCaptionSetDatatype(index,type); } //+------------------------------------------------------------------+ //| Устанавливает точность данных указанному столбцу | //+------------------------------------------------------------------+ void CTable::ColumnSetDigits(const uint index,const int digits) { if(this.m_table_model!=NULL) this.m_table_model.ColumnSetDigits(index,digits); } //+------------------------------------------------------------------+ //| Возвращает тип данных для указанного столбца | //+------------------------------------------------------------------+ ENUM_DATATYPE CTable::ColumnDatatype(const uint index) { return(this.m_table_header!=NULL ? this.m_table_header.ColumnCaptionDatatype(index) : (ENUM_DATATYPE)WRONG_VALUE); }
Метод, возвращающий описание объекта
//+------------------------------------------------------------------+ //| Возвращает описание объекта | //+------------------------------------------------------------------+ string CTable::Description(void) { return(::StringFormat("%s: Rows total: %u, Columns total: %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.RowsTotal(),this.ColumnsTotal())); }
Создаёт и возвращает строку в формате (Тип Объекта: Rows total: XX, Columns total: XX)
Метод, выводящий в журнал описание объекта
//+------------------------------------------------------------------+ //| Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CTable::Print(const int column_width=CELL_WIDTH_IN_CHARS) { if(this.HeaderCheck()) { //--- Выводим заголовок в виде описания строки ::Print(this.Description()+":"); //--- Количество заголовков int total=(int)this.ColumnsTotal(); string res=""; //--- создаём строку из значений всех заголовков столбцов таблицы res="|"; for(int i=0;i<total;i++) { CColumnCaption *caption=this.GetColumnCaption(i); if(caption==NULL) continue; res+=::StringFormat("%*s |",column_width,caption.Value()); } //--- Дополняем строку слева заголовком string hd="|"; hd+=::StringFormat("%*s ",column_width,"n/n"); res=hd+res; //--- Выводим строку заголовка в журнал ::Print(res); } //--- Пройдём в цикле по всем строкам таблицы и распечатаем их в табличном виде for(uint i=0;i<this.RowsTotal();i++) { CTableRow *row=this.GetRow(i); if(row!=NULL) { //--- создаём строку таблицы из значений всех ячеек string head=" "+(string)row.Index(); string res=::StringFormat("|%-*s |",column_width,head); for(int i=0;i<(int)row.CellsTotal();i++) { CTableCell *cell=row.GetCell(i); if(cell==NULL) continue; res+=::StringFormat("%*s |",column_width,cell.Value()); } //--- Выводим строку в журнал ::Print(res); } } }
Метод выводит в журнал описание, и ниже — таблицу с заголовком и данными.
Метод сохранения таблицы в файл
//+------------------------------------------------------------------+ //| Сохранение в файл | //+------------------------------------------------------------------+ bool CTable::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return(false); //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return(false); //--- Проверяем модель таблицы if(this.m_table_model==NULL) return false; //--- Сохраняем модель таблицы if(!this.m_table_model.Save(file_handle)) return(false); //--- Проверяем заголовок таблицы if(this.m_table_header==NULL) return false; //--- Сохраняем заголовок таблицы if(!this.m_table_header.Save(file_handle)) return(false); //--- Успешно return true; }
Успешное сохранение будет только в том случае, если и модель таблицы, и её заголовок созданы. Заголовок может быть пустым, тое есть — не иметь столбцов, но объект должен быть создан.
Метод, загружающий таблицу из файла
//+------------------------------------------------------------------+ //| Загрузка из файла | //+------------------------------------------------------------------+ bool CTable::Load(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return(false); //--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Загружаем тип объекта if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Загружаем идентификатор this.m_id=::FileReadInteger(file_handle,INT_VALUE); //--- Проверяем модель таблицы if(this.m_table_model==NULL && (this.m_table_model=new CTableModel())==NULL) return(false); //--- Загружаем модель таблицы if(!this.m_table_model.Load(file_handle)) return(false); //--- Проверяем заголовок таблицы if(this.m_table_header==NULL && (this.m_table_header=new CTableHeader())==NULL) return false; //--- Загружаем заголовок таблицы if(!this.m_table_header.Load(file_handle)) return(false); //--- Успешно return true; }
С учётом того, что таблица сохраняет как данные модели, так и данные заголовка, то в этом методе, если модель или заголовок не созданы в таблице, они предварительно создаются, и, уже после, загружаются их данные из файла.
Класс простой таблицы готов.
Теперь рассмотрим вариант наследования от класса простой таблицы — создадим класс таблицы, которая строится на данных, записанных в CList:
//+------------------------------------------------------------------+ //| Класс для создания таблиц на основе массива параметров | //+------------------------------------------------------------------+ class CTableByParam : public CTable { public: virtual int Type(void) const { return(OBJECT_TYPE_TABLE_BY_PARAM); } //--- Конструктор/деструктор CTableByParam(void) { this.m_list_rows.Clear(); } CTableByParam(CList &row_data,const string &column_names[]); ~CTableByParam(void) {} };
Здесь тип таблицы возвращается как OBJECT_TYPE_TABLE_BY_PARAM, а модель таблицы и заголовок строятся в конструкторе класса:
//+------------------------------------------------------------------+ //| Конструктор с указанием массива таблицы на основе списка row_data| //| содержащего объекты с данными полей структуры. | //| Определяет количество и наименования колонок согласно количеству | //| наименований столбцов в массиве column_names | //+------------------------------------------------------------------+ CTableByParam::CTableByParam(CList &row_data,const string &column_names[]) { //--- Копируем переданный список данных в переменную и //--- создаём на основе этого списка модель таблицы this.m_list_rows=row_data; this.m_table_model=new CTableModel(this.m_list_rows); //--- Копируем переданный список заголовков в m_array_names и //--- создаём на основе этого списка заголовок таблицы this.ArrayNamesCopy(column_names,column_names.Size()); this.m_table_header=new CTableHeader(this.m_array_names); }
На основе этого примера можно создать какие-либо иные классы таблиц, но пока будем считать, что всего созданного сегодня вполне достаточно для создания самых разных таблиц и обширного набора возможных данных.
Протестируем всё, что у нас получилось.
Тестируем результат
В папке \MQL5\Scripts\Tables\ создадим новый скрипт с именем TestEmptyTable.mq5, подключим к нему созданный файл классов таблиц и создадим пустую таблицу 4x4:
//+------------------------------------------------------------------+ //| TestEmptyTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Создаём пустую таблицу 4x4 CTable *table=new CTable(4,4); if(table==NULL) return; //--- Распечатываем её в журнале и удаляем созданный объект table.Print(10); delete table; }
Результатом работы скрипта будет такая табличка в журнале:
Table: Rows total: 4, Columns total: 4: | n/n | A | B | C | D | | 0 | | | | | | 1 | | | | | | 2 | | | | | | 3 | | | | |
Здесь заголовки столбцов созданы автоматически в стиле MS Excel.
Напишем ещё один скрипт \MQL5\Scripts\Tables\TestTArrayTable.mq5:
//+------------------------------------------------------------------+ //| TestTArrayTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Объявляем и инициализируем double-массив 4x4 double array[4][4]={{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Объявляем и инициализируем массив заголовков столбцов string headers[]={"Column 1","Column 2","Column 3","Column 4"}; //--- Создаём таблицу на основе массива данных и массива заголовков CTable *table=new CTable(array,headers); if(table==NULL) return; //--- Распечатываем таблицу в журнале и удаляем созданный объект table.Print(10); delete table; }
В результате работы скрипта, в журнал будет выведена такая табличка:
Table: Rows total: 4, Columns total: 4: | n/n | Column 1 | Column 2 | Column 3 | Column 4 | | 0 | 1.00 | 2.00 | 3.00 | 4.00 | | 1 | 5.00 | 6.00 | 7.00 | 8.00 | | 2 | 9.00 | 10.00 | 11.00 | 12.00 | | 3 | 13.00 | 14.00 | 15.00 | 16.00 |
Здесь столбцы уже озаглавлены из массива заголовков, передаваемого в конструктор класса. Двумерный массив, представляющий собой данные для создания таблицы, может быть любого типа из перечисления ENUM_DATATYPE.
Все типы автоматически преобразуются к пяти типам, используемым в классе модели таблицы: double, long, datetime, color и string.
Напишем скрипт \MQL5\Scripts\Tables\TestMatrixTable.mq5 для тестирования таблицы на данных матрицы:
//+------------------------------------------------------------------+ //| TestMatrixTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Объявляем и инициализируем матрицу 4x4 matrix row_data = {{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Объявляем и инициализируем массив заголовков столбцов string headers[]={"Column 1","Column 2","Column 3","Column 4"}; //--- Создаём таблицу на основе матрицы и массива заголовков CTable *table=new CTable(row_data,headers); if(table==NULL) return; //--- Распечатываем таблицу в журнале и удаляем созданный объект table.Print(10); delete table; }
Результатом будет таблица, аналогичная построенной на основе двумерного массива 4x4:
Table: Rows total: 4, Columns total: 4: | n/n | Column 1 | Column 2 | Column 3 | Column 4 | | 0 | 1.00 | 2.00 | 3.00 | 4.00 | | 1 | 5.00 | 6.00 | 7.00 | 8.00 | | 2 | 9.00 | 10.00 | 11.00 | 12.00 | | 3 | 13.00 | 14.00 | 15.00 | 16.00 |
Теперь напишем скрипт \MQL5\Scripts\Tables\TestDealsTable.mq5, где считаем все исторические сделки, создадим на их основе таблицу сделок и распечатаем её в журнеале:
//+------------------------------------------------------------------+ //| TestDealsTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Объявляем список сделок, объект параметров сделки и структуру параметров CList rows_data; CMqlParamObj *cell=NULL; MqlParam param={}; //--- Выбираем всю историю if(!HistorySelect(0,TimeCurrent())) return; //--- Создаём список сделок в массиве массивов (CList in CList) //--- (одна строка - одна сделка, столбцы - объекты свойств сделки) int total=HistoryDealsTotal(); for(int i=0;i<total;i++) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) continue; //--- Добавляем к списку сделок новую строку свойств очередной сделки CList *row=DataListCreator::AddNewRowToDataList(&rows_data); if(row==NULL) continue; //--- Создаём "ячейки" с параметрами сделки и //--- добавляем их к созданной строке свойств сделки string symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); //--- Время совершения сделки (столбец 0) param.type=TYPE_DATETIME; param.integer_value=HistoryDealGetInteger(ticket,DEAL_TIME); param.double_value=(TIME_DATE|TIME_MINUTES|TIME_SECONDS); DataListCreator::AddNewCellParamToRow(row,param); //--- Имя символа (столбец 1) param.type=TYPE_STRING; param.string_value=symbol; DataListCreator::AddNewCellParamToRow(row,param); //--- Тикет сделки (столбец 2) param.type=TYPE_LONG; param.integer_value=(long)ticket; DataListCreator::AddNewCellParamToRow(row,param); //--- Ордер, на основание которого выполнена сделка (столбец 3) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_ORDER); DataListCreator::AddNewCellParamToRow(row,param); //--- Идентификатор позиции (столбец 4) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_POSITION_ID); DataListCreator::AddNewCellParamToRow(row,param); //--- Тип сделки (столбец 5) param.type=TYPE_STRING; ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE); param.integer_value=deal_type; string type=""; switch(deal_type) { case DEAL_TYPE_BUY : type="Buy"; break; case DEAL_TYPE_SELL : type="Sell"; break; case DEAL_TYPE_BALANCE : type="Balance"; break; case DEAL_TYPE_CREDIT : type="Credit"; break; case DEAL_TYPE_CHARGE : type="Charge"; break; case DEAL_TYPE_CORRECTION : type="Correction"; break; case DEAL_TYPE_BONUS : type="Bonus"; break; case DEAL_TYPE_COMMISSION : type="Commission"; break; case DEAL_TYPE_COMMISSION_DAILY : type="Commission daily"; break; case DEAL_TYPE_COMMISSION_MONTHLY : type="Commission monthly"; break; case DEAL_TYPE_COMMISSION_AGENT_DAILY : type="Commission agent daily"; break; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: type="Commission agent monthly"; break; case DEAL_TYPE_INTEREST : type="Interest"; break; case DEAL_TYPE_BUY_CANCELED : type="Buy canceled"; break; case DEAL_TYPE_SELL_CANCELED : type="Sell canceled"; break; case DEAL_DIVIDEND : type="Dividend"; break; case DEAL_DIVIDEND_FRANKED : type="Dividend franked"; break; case DEAL_TAX : type="Tax"; break; default : break; } param.string_value=type; DataListCreator::AddNewCellParamToRow(row,param); //--- Направление сделки (столбец 6) param.type=TYPE_STRING; ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); param.integer_value=deal_entry; string entry=""; switch(deal_entry) { case DEAL_ENTRY_IN : entry="In"; break; case DEAL_ENTRY_OUT : entry="Out"; break; case DEAL_ENTRY_INOUT : entry="InOut"; break; case DEAL_ENTRY_OUT_BY : entry="OutBy"; break; default : break; } param.string_value=entry; DataListCreator::AddNewCellParamToRow(row,param); //--- Объем сделки (столбец 7) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_VOLUME); param.integer_value=2; DataListCreator::AddNewCellParamToRow(row,param); //--- Цена сделки (столбец 8) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_PRICE); param.integer_value=(param.double_value>0 ? digits : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Уровень Stop Loss (столбец 9) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_SL); param.integer_value=(param.double_value>0 ? digits : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Уровень Take Profit (столбец 10) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_TP); param.integer_value=(param.double_value>0 ? digits : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Финансовый результат сделки (столбец 11) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_PROFIT); param.integer_value=(param.double_value!=0 ? 2 : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Magic number для сделки (столбец 12) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_MAGIC); DataListCreator::AddNewCellParamToRow(row,param); //--- Причина или источник проведения сделки (столбец 13) param.type=TYPE_STRING; ENUM_DEAL_REASON deal_reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); param.integer_value=deal_reason; string reason=""; switch(deal_reason) { case DEAL_REASON_CLIENT : reason="Client"; break; case DEAL_REASON_MOBILE : reason="Mobile"; break; case DEAL_REASON_WEB : reason="Web"; break; case DEAL_REASON_EXPERT : reason="Expert"; break; case DEAL_REASON_SL : reason="SL"; break; case DEAL_REASON_TP : reason="TP"; break; case DEAL_REASON_SO : reason="StopOut"; break; case DEAL_REASON_ROLLOVER : reason="Rollover"; break; case DEAL_REASON_VMARGIN : reason="VMargin"; break; case DEAL_REASON_SPLIT : reason="Split"; break; case DEAL_REASON_CORPORATE_ACTION: reason="Corporate action"; break; default : break; } param.string_value=reason; DataListCreator::AddNewCellParamToRow(row,param); //--- Комментарий к сделке (столбец 14) param.type=TYPE_STRING; param.string_value=HistoryDealGetString(ticket,DEAL_COMMENT); DataListCreator::AddNewCellParamToRow(row,param); } //--- Объявляем и инициализируем заголовок таблицы string headers[]={"Time","Symbol","Ticket","Order","Position","Type","Entry","Volume","Price","SL","TP","Profit","Magic","Reason","Comment"}; //--- Создаём таблицу на основе созданного списка параметров и массива заголовков CTableByParam *table=new CTableByParam(rows_data,headers); if(table==NULL) return; //--- Распечатываем таблицу в журнале и удаляем созданный объект table.Print(); delete table; }
В результате, будет распечатана таблица всех сделок с шириной ячеек 19 символов (по умолчанию в методе Print класса таблицы):
Table By Param: Rows total: 797, Columns total: 15: | n/n | Time | Symbol | Ticket | Order | Position | Type | Entry | Volume | Price | SL | TP | Profit | Magic | Reason | Comment | | 0 |2025.01.01 10:20:10 | | 3152565660 | 0 | 0 | Balance | In | 0.00 | 0.0 | 0.0 | 0.0 | 100000.00 | 0 | Client | | | 1 |2025.01.02 00:01:31 | GBPAUD | 3152603334 | 3191672408 | 3191672408 | Sell | In | 0.25 | 2.02111 | 0.0 | 0.0 | 0.0 | 112 | Expert | | | 2 |2025.01.02 02:50:31 | GBPAUD | 3152749152 | 3191820118 | 3191672408 | Buy | Out | 0.25 | 2.02001 | 0.0 | 2.02001 | 17.04 | 112 | TP | [tp 2.02001] | | 3 |2025.01.02 04:43:43 | GBPUSD | 3152949278 | 3191671491 | 3191671491 | Sell | In | 0.10 | 1.25270 | 0.0 | 1.24970 | 0.0 | 12 | Expert | | ... ... | 793 |2025.04.18 03:22:11 | EURCAD | 3602552747 | 3652159095 | 3652048415 | Sell | Out | 0.25 | 1.57503 | 0.0 | 1.57503 | 12.64 | 112 | TP | [tp 1.57503] | | 794 |2025.04.18 04:06:52 | GBPAUD | 3602588574 | 3652200103 | 3645122489 | Sell | Out | 0.25 | 2.07977 | 0.0 | 2.07977 | 3.35 | 112 | TP | [tp 2.07977] | | 795 |2025.04.18 04:06:52 | GBPAUD | 3602588575 | 3652200104 | 3652048983 | Sell | Out | 0.25 | 2.07977 | 0.0 | 2.07977 | 12.93 | 112 | TP | [tp 2.07977] | | 796 |2025.04.18 05:57:48 | AUDJPY | 3602664574 | 3652277665 | 3652048316 | Buy | Out | 0.25 | 90.672 | 0.0 | 90.672 | 19.15 | 112 | TP | [tp 90.672] |
Здесь в примере отображены первые и последние четыре сделки, но это даёт представление о таблице, распечатываемой в журнале.
Все созданные файлы прилагаются к статье для самостоятельного изучения. Файл архива можно распаковать в папку терминала, и все файлы будут расположены в нужной папке: MQL5\Scripts\Tables.
Заключение
Итак, мы завершили работу над базовыми компонентами модели таблицы (Model) в рамках архитектуры MVC. Создали классы для работы с таблицами и заголовками, а также протестировали их на разных типах данных: двумерных массивах, матрицах и истории сделок.
Далее переходим к следующему этапу — разработке компонентов View и Controller. В MQL5 эти два компонента тесно связаны благодаря встроенной системе событий, которая позволяет объектам реагировать на действия пользователя.
Это даёт нам возможность разрабатывать визуализацию таблицы (компонент View) и управление ею (компонент Controller) одновременно. Это немного упростит достаточно сложную и многоуровневую реализацию компонента View.
Все примеры и файлы из статьи доступны для скачивания. В последующих статьях мы будем создавать компонент View, объединённый с Controller, чтобы реализовать полноценный инструмент для работы с таблицами в MQL5.
По завершении проекта, нам откроются новые возможности для создания иных элементов управления для использования их в своих разработках.
Программы, используемые в статье:
# | Имя | Тип | Описание |
---|---|---|---|
1 | Tables.mqh | Библиотека классов | Библиотека классов для создания таблиц |
2 | TestEmptyTable.mq5 | Скрипт | Скрипт для тестирования создания пустой таблицы с заданным количество строк и столбцов |
3 | TestTArrayTable.mq5 | Скрипт | Скрипт для тестирования создания таблицы на основе двумерного массива данных |
4 | TestMatrixTable.mq5 | Скрипт | Скрипт для тестирования создания таблицы на основе матрицы данных |
5 | TestDealsTable.mq5 | Скрипт | Скрипт для тестирования создания таблицы на основе пользовательских данных (исторических сделок) |
6 | MQL5.zip | Архив | Архив файлов, представленных выше, для распаковки в каталог MQL5 клиентского терминала |





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