Classes de tabela e cabeçalho baseadas no modelo de tabela em MQL5: Aplicação do conceito MVC
Conteúdo
- Introdução
- Aprimoramento do modelo de tabela
- Classe de cabeçalho da tabela
- Classes da tabela
- Testando o resultado
- Considerações finais
Introdução
No primeiro artigo, dedicado à criação do elemento de controle Table Control, criamos o modelo de tabela em MQL5, utilizando o padrão arquitetural MVC. Foram desenvolvidas as classes de células, linhas e do modelo de tabela, o que permitiu estruturar os dados de forma prática e organizada.
Agora passamos para a próxima etapa, que é o desenvolvimento das classes da tabela e de seus cabeçalhos. Os cabeçalhos das colunas da tabela não são apenas rótulos para as colunas, mas sim ferramentas de controle da tabela e de suas colunas. Eles permitem adicionar, remover e renomear colunas. Naturalmente, a tabela pode funcionar sem a classe de cabeçalho, mas nesse caso suas funcionalidades serão limitadas, pois será criada uma tabela estática simples, sem cabeçalhos de colunas e, portanto, sem possibilidade de gerenciar as colunas.
Para implementar o gerenciamento das colunas, será necessário aprimorar o modelo de tabela. Vamos adicionar métodos que permitam trabalhar com colunas: alterar sua estrutura, adicionar novas ou remover as existentes. Esses métodos serão utilizados pela classe de cabeçalho da tabela, com o objetivo de oferecer uma maneira prática de gerenciar sua estrutura.
Esta etapa do desenvolvimento vai preparar a base para a futura implementação dos componentes de visualização (View) e controle (Controller), que serão discutidos em artigos seguintes. Este passo representa uma etapa importante no caminho para a criação de uma interface completa para manipulação de dados.
Aprimoramento do modelo de tabela
No momento, o modelo de tabela é criado a partir de um array bidimensional, mas para aumentar a flexibilidade e facilitar o uso da tabela, serão adicionadas formas alternativas de inicialização. Isso permitirá adaptar o modelo a diferentes cenários de uso. Na versão atualizada da classe do modelo de tabela, serão incluídos os seguintes métodos:
-
Criação do modelo a partir de um array bidimensional
void CreateTableModel(T &array[][]);Esse método permite criar rapidamente um modelo de tabela com base em um array bidimensional de dados já existente.
-
Criação de um modelo vazio com número definido de linhas e colunas
void CreateTableModel(const uint num_rows, const uint num_columns);
Esse método é adequado para os casos em que a estrutura da tabela é conhecida antecipadamente, mas os dados serão adicionados posteriormente.
-
Criação do modelo a partir de uma matriz de dados
void CreateTableModel(const matrix &row_data);
Esse método permite usar uma matriz de dados para inicializar a tabela, o que é conveniente ao se trabalhar com conjuntos de dados previamente preparados.
-
Criação do modelo a partir de uma lista encadeada
void CreateTableModel(CList &list_param);Neste caso, os dados serão armazenados em um array de arrays, onde um objeto CList (dados sobre as linhas da tabela) conterá outros objetos CList, nos quais estarão localizados os dados das células da tabela. Essa abordagem permite gerenciar dinamicamente a estrutura da tabela e seu conteúdo.
Essas modificações tornarão o modelo de tabela mais versátil e conveniente para uso em diversos cenários. Por exemplo, será possível criar tabelas facilmente tanto a partir de arrays de dados já preparados quanto a partir de listas formadas dinamicamente.
No artigo anterior, escrevemos todas as classes para criação do modelo de tabela diretamente no arquivo do script de teste. Hoje, essas classes serão transferidas para um novo arquivo de inclusão próprio.
Na pasta onde está localizado o script do artigo anterior (por padrão: \MQL5\Scripts\TableModel\), vamos criar um novo arquivo de inclusão com o nome Tables.mqh e copiar para ele, a partir do arquivo TableModelTest.mq5 (localizado na mesma pasta), tudo desde o início do arquivo até o início do código do script de teste, ou seja, todas as classes responsáveis pela criação do modelo de tabela. Agora temos um arquivo separado contendo as classes do modelo de tabela com o nome Tables.mqh, pois será nele que faremos as modificações e melhorias.
Vamos mover o arquivo recém-criado para a nova pasta MQL5\Scripts\Tables\ — este projeto será desenvolvido nessa pasta.
Na seção de arquivos/bibliotecas incluídos, adicionaremos a declaração antecipada (forward declaration) das novas classes — elas serão criadas hoje, e essa declaração é necessária para que a classe da lista de objetos CListObj, criada no artigo anterior, consiga instanciar objetos dessas novas classes dentro do seu método CreateElement():
//+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Arrays\List.mqh> //--- Форвард-декларация классов class CTableCell; // Класс ячейки таблицы class CTableRow; // Класс строки таблицы class CTableModel; // Класс модели таблицы class CColumnCaption; // Класс заголовка столбца таблицы class CTableHeader; // Класс заголовка таблицы class CTable; // Класс таблицы class CTableByParam; // Класс таблицы на основе массива параметров //+------------------------------------------------------------------+ //| Макросы | //+------------------------------------------------------------------+
Na seção de macros, será adicionada a definição da largura da célula da tabela em número de caracteres, definida como 19 — esse é o valor mínimo de largura no qual um texto com data e hora cabe totalmente no espaço da célula no diário, sem deslocar o limite direito da célula, o que causaria uma dessincronização nos tamanhos de todas as células desenhadas no diário da tabela:
//+------------------------------------------------------------------+ //| Макросы | //+------------------------------------------------------------------+ #define MARKER_START_DATA -1 // Маркер начала данных в файле #define MAX_STRING_LENGTH 128 // Максимальная длина строки в ячейке #define CELL_WIDTH_IN_CHARS 19 // Ширина ячейки таблицы в символах //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+
Na seção de enumerações, adicionaremos novas constantes à enumeração de tipos de objetos:
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ 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, // Таблица на данных массива параметров };
Na classe da lista de objetos CListObj, dentro do método de criação de elemento, adicionaremos novos casos (cases) para a criação dos novos tipos de objetos:
//+------------------------------------------------------------------+ //| Метод создания элемента списка | //+------------------------------------------------------------------+ 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; } }
Após a criação dessas novas classes, a lista de objetos CListObj será capaz de instanciar objetos desses tipos, o que permitirá salvar e carregar listas a partir de arquivos que contenham tais tipos de objetos.
Para que seja possível atribuir um valor "vazio" a uma célula, que seja exibido como uma string vazia em vez de "0", como ocorre atualmente, é necessário definir qual valor será considerado como "vazio". Fica claro que, para valores do tipo string, a própria string vazia será esse valor. Já para valores numéricos, adotaremos DBL_MAX para tipos reais e LONG_MAX para tipos inteiros.
Para definir esse valor na célula, dentro da área protegida da classe do objeto célula da tabela, escreveremos um método:
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: //--- Возврат координат и свойств ячейки
O método que retorna o valor armazenado na célula como uma string formatada agora verifica se o valor da célula não é "vazio" e, no caso de o valor ser "vazio", retorna uma string vazia:
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(); }
O método de limpeza dos dados da célula agora não define o valor como zero, mas sim invoca o método responsável por atribuir o valor vazio à célula.
Todos os métodos das classes que compõem o objeto do modelo de tabela e que retornam a descrição do objeto agora foram declarados como virtuais — para permitir herança a partir desses objetos:
//--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); void Print(void);
Na classe da linha da tabela, todos os métodos CreateNewCell() — responsáveis por criar uma nova célula e adicioná-la ao final da lista — agora foram renomeados:
//+------------------------------------------------------------------+ //| Класс строки таблицы | //+------------------------------------------------------------------+ 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);
Essa renomeação tem como objetivo padronizar todos os métodos relacionados ao acesso às células para que comecem com a substring "Cell". Em outras classes, os métodos de acesso, por exemplo, às linhas da tabela, começarão com a substring "Row". Isso traz organização aos métodos das classes.
Para montar o modelo da tabela, precisamos desenvolver uma abordagem universal que permita criar tabelas a partir de praticamente qualquer dado. Isso pode incluir, por exemplo, estruturas, listas de negócios, ordens, posições ou qualquer outro tipo de dado. A ideia é criar uma ferramenta que permita formar uma lista de linhas, onde cada linha representará uma lista de propriedades. Cada propriedade da lista corresponderá a uma célula da tabela.
Aqui vale a pena prestar atenção à estrutura dos parâmetros de entrada MqlParam. Ela oferece os seguintes recursos:
- Indicação do tipo de dado armazenado na estrutura (ENUM_DATATYPE).
- Armazenamento de valores em três campos distintos:
- integer_value — para dados inteiros,
- double_value — para dados reais,
- string_value — para dados do tipo string.
Essa estrutura permite trabalhar com diferentes tipos de dados, o que possibilita armazenar qualquer propriedade, como os parâmetros de negócios, ordens ou outros objetos.
Para facilitar o armazenamento desses dados, vamos criar a classe CMqlParamObj, herdada da classe base CObject da Biblioteca Padrão. Essa classe vai conter a estrutura MqlParam e fornecer métodos para definir e recuperar os dados. Graças à herança de CObject, esses objetos poderão ser armazenados em listas do tipo CList.
Vamos analisar a classe por completo:
//+------------------------------------------------------------------+ //| Класс объекта параметра структуры | //+------------------------------------------------------------------+ 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){} };
Trata-se de um encapsulamento simples da estrutura MqlParam, já que é necessário um objeto herdado de CObject para que esses objetos possam ser armazenados em uma lista CList.
A estrutura dos dados que serão criados será a seguinte:
- Um objeto da classe CMqlParamObj representará uma propriedade única, como por exemplo o preço de uma negociação, seu volume ou o horário de abertura,
- Uma lista CList representará uma linha da tabela, contendo todas as propriedades de uma única negociação,
- A lista principal CList conterá o conjunto de linhas (listas CList), sendo que cada uma corresponde a uma negociação, ordem, posição ou qualquer outra entidade.
Dessa forma, obtemos uma estrutura semelhante a um array de arrays:
- A lista principal CList é o "array de linhas",
- Cada lista interna CList é o "array de células" (propriedades de um determinado objeto).
Exemplo dessa estrutura de dados para uma lista de negociações históricas:
- A lista principal CList armazena as linhas da tabela. Cada linha é uma lista CList separada.
- As listas internas CList representam cada linha da tabela e contêm objetos da classe CMqlParamObj, que armazenam as propriedades. Por exemplo:
- primeira linha: propriedades da negociação nº 1 (preço, volume, horário de abertura, etc.),
- segunda linha: propriedades da negociação nº 2 (preço, volume, horário de abertura, etc.),
- terceira linha: propriedades da negociação nº 3 (preço, volume, horário de abertura, etc.),
- e assim por diante.
- Os objetos de propriedade (CMqlParamObj) — cada objeto armazena uma propriedade, como por exemplo o preço ou volume da negociação.
Após a formação dessa estrutura de dados (listas CList), ela pode ser passada para o método CreateTableModel(CList &list_param) do modelo de tabela. Esse método interpretará os dados da seguinte maneira:
- A lista principal CList será tratada como a lista de linhas da tabela,
- Cada lista interna CList será tratada como as células de cada linha,
- Os objetos CMqlParamObj dentro dessas listas internas serão os valores das células.
Assim, com base na lista fornecida, será criada uma tabela que reproduz fielmente os dados de origem.
Para facilitar a criação dessas listas, será desenvolvido uma classe especial. Essa classe fornecerá métodos para:
- Criar uma nova linha (lista CList) e adicioná-la à lista principal,
- Adicionar uma nova propriedade (objeto CMqlParamObj) à linha.
//+------------------------------------------------------------------+ //| Класс для создания списков данных | //+------------------------------------------------------------------+ 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; } };
Essa será uma classe estática, oferecendo uma maneira prática de criar listas com a estrutura correta para que possam ser passadas aos métodos de criação dos modelos de tabela:
- Indicamos a propriedade desejada (por exemplo, o preço de uma negociação),
- A classe cria automaticamente um objeto CMqlParamObj, grava nele o valor da propriedade e o adiciona à linha,
- A linha é então adicionada à lista principal.
Depois disso, a lista pronta pode ser passada ao modelo de tabela para construção.
Como resultado, a abordagem desenvolvida permite converter dados de qualquer estrutura ou objeto para um formato compatível com a criação de tabelas. O uso das listas CList e dos objetos CMqlParamObj garante flexibilidade e praticidade, enquanto a classe auxiliar DataListCreator simplifica o processo de criação dessas listas. Isso servirá de base para a construção de um modelo de tabela universal, criado a partir de quaisquer dados e adequado para qualquer finalidade.
Na classe do modelo de tabela, definiremos três novos métodos para criação do modelo de tabela, três novos métodos para trabalhar com colunas da tabela, no lugar de cinco construtores com parâmetros sobrecarregados, será adicionado um construtor genérico (template) e mais três novos — com base nos tipos de parâmetros de entrada dos novos métodos de criação do modelo de tabela:
//+------------------------------------------------------------------+ //| Класс модели таблицы | //+------------------------------------------------------------------+ 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){} };
Vejamos os novos métodos.
Método que cria o modelo da tabela a partir de uma quantidade especificada de linhas e colunas
//+------------------------------------------------------------------+ //| Создаёт модель таблицы из указанного количества строк и столбцов | //+------------------------------------------------------------------+ 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(); } } } }
Esse método cria um modelo vazio com a quantidade definida de linhas e colunas. É apropriado para casos em que a estrutura da tabela já é conhecida, mas os dados serão adicionados posteriormente.
Método que cria o modelo da tabela a partir de uma matriz especificada
//+------------------------------------------------------------------+ //| Создаёт модель таблицы из указанной матрицы | //+------------------------------------------------------------------+ 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]); } } }
Esse método permite usar uma matriz de dados para inicializar a tabela, o que é útil ao se trabalhar com conjuntos de dados previamente preparados.
Método que cria o modelo da tabela a partir de uma lista de parâmetros
//+------------------------------------------------------------------+ //| Создаёт модель таблицы из списка параметров | //+------------------------------------------------------------------+ 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; } } } } }
Esse método possibilita a criação do modelo da tabela com base em uma lista encadeada, o que pode ser vantajoso ao lidar com estruturas de dados dinâmicas.
Vale destacar que, ao criar células com tipo de dado como, por exemplo, double, a precisão com que o dado é exibido é determinada pelo valor do tipo long presente no objeto param da classe CMqlParamObj. Isso significa que, ao criar a estrutura da tabela por meio da classe DataListCreator, mencionada anteriormente, podemos ainda passar ao objeto de parâmetros as informações adicionais desejadas. Para o resultado financeiro de uma negociação, isso poderia ser feito da seguinte forma:
//--- Финансовый результат сделки param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_PROFIT); param.integer_value=(param.double_value!=0 ? 2 : 1); DataListCreator::AddNewCellParamToRow(row,param);
Do mesmo modo, é possível passar os flags de exibição de tempo para células do tipo datetime, e o flag de exibição do nome da cor para células do tipo color.
A tabela abaixo apresenta os tipos de células da tabela e os tipos de parâmetros que podem ser passados para elas por meio do objeto CMqlParamObj:
| Tipo em CMqlParamObj | Célula do tipo double | Célula do tipo long | Célula do tipo datetime | Célula do tipo color | Célula do tipo string |
|---|---|---|---|---|---|
| double_value | valor da propriedade na célula | não utilizado | flags de exibição de data/hora | flag de exibição do nome da cor | não utilizado |
| integer_value | precisão do valor na célula | valor da propriedade na célula | valor da propriedade na célula | valor da propriedade na célula | não utilizado |
| string_value | não utilizado | não utilizado | não utilizado | não utilizado | valor da propriedade na célula |
A partir da tabela, é possível observar que, ao criar a estrutura da tabela a partir de certos dados, se esses dados forem do tipo real (armazenados no campo double_value da estrutura MqlParam), pode-se adicionalmente gravar no campo integer_value o valor da precisão com que os dados serão exibidos na célula da tabela. O mesmo vale para dados do tipo datetime e color, porém os flags são armazenados no campo double_value, pois o campo inteiro está ocupado com o próprio valor da propriedade.
Fazer isso não é obrigatório. Caso não seja feito, a precisão e os flags da célula serão inicializados com valor zero, e isso poderá ser alterado posteriormente, seja para uma célula específica, seja para toda a coluna da tabela.
Método que adiciona uma nova coluna à tabela
//+------------------------------------------------------------------+ //| Добавляет столбец | //+------------------------------------------------------------------+ 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; }
O índice da nova coluna é passado como parâmetro do método. Primeiro, novas células são adicionadas ao final de cada linha da tabela; em seguida, se o índice fornecido não for negativo, todas as novas células são reposicionadas de acordo com o índice indicado.
Método que define o tipo de dado da coluna
//+------------------------------------------------------------------+ //| Устанавливает тип данных столбца | //+------------------------------------------------------------------+ 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); } }
Iterando por todas as linhas da tabela, obtém-se a célula correspondente ao índice da coluna e define-se seu tipo de dado. Como resultado, todas as células da coluna passam a ter o mesmo tipo definido.
Método que define a precisão dos dados da coluna
//+------------------------------------------------------------------+ //| Устанавливает точность данных столбца | //+------------------------------------------------------------------+ 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); } }
Iterando por todas as linhas da tabela, obtém-se a célula correspondente ao índice da coluna e define-se seu tipo de dado. Assim, todas as células da coluna passam a ter a mesma configuração de precisão.
Os métodos adicionados à classe do modelo de tabela permitirão trabalhar com um conjunto de células como se fosse uma única coluna da tabela. No entanto, o gerenciamento de colunas da tabela só é possível se a tabela possuir um cabeçalho. Sem o cabeçalho, a tabela será estática.
Classe de cabeçalho da tabela
O cabeçalho da tabela é, na prática, uma lista comum de objetos que representam os cabeçalhos das colunas, cada um com um valor do tipo string, armazenados em uma lista dinâmica CListObj. Essa lista dinâmica compõe a estrutura base da classe do cabeçalho da tabela.
Diante disso, será necessário criar duas classes:
- Classe do objeto de cabeçalho da coluna da tabela.
Contém o valor textual do cabeçalho, o número da coluna, o tipo de dado para toda a coluna e os métodos de gerenciamento das células dessa coluna. - Classe do cabeçalho da tabela.
Contém a lista de objetos de cabeçalhos de colunas e métodos para acessar o gerenciamento das colunas da tabela.
Continuaremos escrevendo o código no mesmo arquivo \MQL5\Scripts\Tables\Tables.mqh, e escreveremos agora a classe do cabeçalho de coluna da tabela:
//+------------------------------------------------------------------+ //| Класс заголовка столбца таблицы | //+------------------------------------------------------------------+ 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) {} };
Esta é uma versão bastante simplificada da classe de célula da tabela. Vamos analisar alguns métodos da classe.
Método virtual de comparação entre dois objetos
//+------------------------------------------------------------------+ //| Сравнение двух объектов | //+------------------------------------------------------------------+ 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); }
A comparação é feita com base no índice da coluna para a qual o cabeçalho foi criado.
Método de salvamento em arquivo
//+------------------------------------------------------------------+ //| Сохранение в файл | //+------------------------------------------------------------------+ 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; }
Método de carregamento a partir de arquivo
//+------------------------------------------------------------------+ //| Загрузка из файла | //+------------------------------------------------------------------+ 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; }
Esses métodos foram detalhados no artigo anterior. Aqui, a lógica é exatamente a mesma: primeiro, são gravados o marcador de início dos dados e o tipo do objeto e, em seguida, cada uma de suas propriedades é gravada individualmente. A leitura é feita na mesma ordem.
Método que retorna a descrição do objeto
//+------------------------------------------------------------------+ //| Возвращает описание объекта | //+------------------------------------------------------------------+ string CColumnCaption::Description(void) { return(::StringFormat("%s: Column %u, Value: \"%s\"", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.Column(),this.Value())); }
Cria-se e retorna-se uma string com a descrição no formato: (Tipo do Objeto: Column XX, Value "Valor")
Método que imprime a descrição do objeto no diário
//+------------------------------------------------------------------+ //| Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CColumnCaption::Print(void) { ::Print(this.Description()); }
Apenas exibe no diário a descrição do cabeçalho.
Agora, esses objetos precisam ser colocados em uma lista que representará o cabeçalho da tabela. Vamos escrever a classe do cabeçalho da tabela:
//+------------------------------------------------------------------+ //| Класс заголовка таблицы | //+------------------------------------------------------------------+ 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){} };
Analisemos os métodos da classe.
Método que cria um novo cabeçalho e o adiciona ao final da lista de cabeçalhos de colunas
//+------------------------------------------------------------------+ //| Создаёт новый заголовок и добавляет в конец списка | //+------------------------------------------------------------------+ 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; }
O método recebe como parâmetro o texto do cabeçalho. Um novo objeto de cabeçalho de coluna é criado com o texto especificado e o índice igual ao número atual de cabeçalhos na lista, que será o índice do último cabeçalho. Em seguida, o objeto criado é adicionado ao final da lista de cabeçalhos de colunas, e um ponteiro para esse novo cabeçalho é retornado.
Método que adiciona um cabeçalho fornecido ao final da lista
//+------------------------------------------------------------------+ //| Добавляет заголовок в конец списка | //+------------------------------------------------------------------+ 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; }
O método recebe como parâmetro um ponteiro para o objeto de cabeçalho de coluna que deve ser inserido no final da lista de cabeçalhos. O método retorna o resultado da tentativa de inserção do objeto na lista.
Método que cria o cabeçalho da tabela a partir de um array de strings
//+------------------------------------------------------------------+ //| Создаёт заголовок таблицы из строкового массива | //+------------------------------------------------------------------+ void CTableHeader::CreateHeader(string &array[]) { //--- Получаем из свойств массива количество столбцов таблицы uint total=array.Size(); //--- В цикле по размеру массива //--- создаём все заголовки, добавляя каждый новый в конец списка for(uint i=0; i<total; i++) this.CreateNewColumnCaption(array[i]); }
O método recebe como parâmetro um array de strings contendo os textos dos cabeçalhos. O tamanho do array determina a quantidade de objetos de cabeçalhos de colunas a serem criados, que são gerados iterativamente a partir dos textos presentes no array.
Método que define o valor de um cabeçalho de coluna indicado
//+------------------------------------------------------------------+ //| Устанавливает значение в указанный заголовок столбца | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetValue(const uint index,const string value) { //--- Получаем из списка нужный заголовок и записываем в него новое значение CColumnCaption *caption=this.GetColumnCaption(index); if(caption!=NULL) caption.SetValue(value); }
O método permite atribuir um novo texto ao cabeçalho localizado no índice especificado.
Método que define o tipo de dado para um cabeçalho de coluna indicado
//+------------------------------------------------------------------+ //| Устанавливает тип данных для указанного заголовка столбца | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- Получаем из списка нужный заголовок и записываем в него новое значение CColumnCaption *caption=this.GetColumnCaption(index); if(caption!=NULL) caption.SetDatatype(type); }
Esse método permite definir o novo tipo de dado armazenado na coluna correspondente ao índice do cabeçalho. Para cada coluna da tabela, é possível definir o tipo dos dados armazenados em suas células. Atribuir o tipo de dado ao objeto do cabeçalho possibilita, posteriormente, aplicar esse mesmo tipo a toda a coluna. E, para obter o tipo de dado da coluna, basta ler esse valor diretamente do objeto do cabeçalho.
Método que retorna o tipo de dado de um cabeçalho de coluna indicado
//+------------------------------------------------------------------+ //| Возвращает тип данных указанного заголовка столбца | //+------------------------------------------------------------------+ ENUM_DATATYPE CTableHeader::ColumnCaptionDatatype(const uint index) { //--- Получаем из списка нужный заголовок и возвращаем из него тип данных столбца CColumnCaption *caption=this.GetColumnCaption(index); return(caption!=NULL ? caption.Datatype() : (ENUM_DATATYPE)WRONG_VALUE); }
O método permite obter o tipo dos dados armazenados na coluna correspondente ao índice do cabeçalho. A leitura a partir do cabeçalho permite saber qual tipo de valor está presente em todas as células daquela coluna da tabela.
Método que remove o cabeçalho da coluna indicada
//+------------------------------------------------------------------+ //| Удаляет заголовок указанного столбца | //+------------------------------------------------------------------+ bool CTableHeader::ColumnCaptionDelete(const uint index) { //--- Удаляем заголовок в списке по индексу if(!this.m_list_captions.Delete(index)) return false; //--- Обновляем индексы для оставшихся заголовков в списке this.ColumnPositionUpdate(); return true; }
O objeto correspondente ao índice especificado é removido da lista de cabeçalhos. Após a remoção bem-sucedida do objeto de cabeçalho da coluna, é necessário atualizar os índices dos demais objetos presentes na lista.
Método que move o cabeçalho da coluna para a posição indicada
//+------------------------------------------------------------------+ //| Перемещает заголовок столбца на указанную позицию | //+------------------------------------------------------------------+ 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; }
Permite mover um cabeçalho de sua posição atual (índice informado) para uma nova posição dentro da lista.
Método que define as posições das colunas para todos os cabeçalhos
//+------------------------------------------------------------------+ //| Устанавливает позиции столбца всем заголовкам | //+------------------------------------------------------------------+ 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)); } }
Após a exclusão ou movimentação de um objeto na lista para uma nova posição, é preciso reatribuir os índices de todos os demais objetos, garantindo que esses índices reflitam corretamente sua posição real dentro da lista. O método percorre todos os objetos da lista em um laço, obtém o índice real de cada objeto e o define como propriedade do próprio objeto.
Método que limpa os dados dos cabeçalhos de coluna na lista
//+------------------------------------------------------------------+ //| Очищает данные заголовков столбцов в списке | //+------------------------------------------------------------------+ void CTableHeader::ClearData(void) { //--- В цикле по всем заголовкам в списке for(uint i=0;i<this.ColumnsTotal();i++) { //--- получаем очередной заголовок и устанавливаем в него пустое значение CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) caption.ClearData(); } }
Percorrendo em laço todos os objetos da lista de cabeçalhos de coluna, o método obtém cada objeto individualmente e define uma string vazia como valor do seu texto. Assim, os cabeçalhos de todas as colunas da tabela são completamente limpos.
Método que retorna a descrição do objeto
//+------------------------------------------------------------------+ //| Возвращает описание объекта | //+------------------------------------------------------------------+ string CTableHeader::Description(void) { return(::StringFormat("%s: Captions total: %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.ColumnsTotal())); }
É criada e retornada uma string no formato (Tipo do Objeto: Captions total: XX)
Método que imprime a descrição do objeto no diário
//+------------------------------------------------------------------+ //| Выводит в журнал описание объекта | //+------------------------------------------------------------------+ 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); } }
Esse método pode imprimir no diário a descrição do cabeçalho tanto em formato de tabela quanto como uma lista simples dos nomes dos cabeçalhos de coluna.
Métodos de salvamento em arquivo e carregamento do cabeçalho a partir de arquivo
//+------------------------------------------------------------------+ //| Сохранение в файл | //+------------------------------------------------------------------+ 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; }
A lógica desses métodos está comentada no código e segue exatamente o mesmo padrão já utilizado em métodos similares de outras classes usadas na criação de tabelas.
Agora temos tudo pronto para começar a construir as classes de tabela. A classe da tabela deve ser capaz de construir a própria tabela com base em seu modelo e deve conter um cabeçalho, que fornecerá os nomes das colunas. Se a tabela for criada sem um cabeçalho definido, ela será construída apenas com base no modelo, sendo uma tabela estática, cujas funcionalidades se limitam à exibição dos dados. Para tabelas simples, isso é suficiente. No entanto, para possibilitar a interação com o usuário por meio do componente Controller, a tabela precisa obrigatoriamente de um cabeçalho. Isso abrirá um amplo conjunto de recursos para o gerenciamento da tabela e seus dados. Mas isso será feito mais adiante. Agora, vamos nos concentrar nas classes da tabela.
Classes da tabela
Continuaremos escrevendo o código no mesmo arquivo e implementaremos a classe da tabela:
//+------------------------------------------------------------------+ //| Класс таблицы | //+------------------------------------------------------------------+ 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); };
Na classe, são declarados ponteiros para o cabeçalho e para o modelo da tabela. Para construir uma tabela, o mínimo necessário é construir seu modelo a partir dos dados fornecidos nos construtores da classe. A tabela pode preencher automaticamente um cabeçalho vazio com nomes de colunas no estilo do MS Excel, onde cada coluna recebe um nome composto por letras do alfabeto latino.
O algoritmo de geração dos nomes de colunas segue a lógica abaixo:
-
Nomes com uma única letra: as primeiras 26 colunas são nomeadas com letras de "A" a "Z".
-
Nomes com duas letras: após "Z", as colunas passam a ser nomeadas com combinações de duas letras. A primeira letra varia mais lentamente, enquanto a segunda percorre todo o alfabeto. Por exemplo:
- "AA", "AB", "AC", ..., "AZ",
- em seguida "BA", "BB", ..., "BZ",
- e assim por diante.
-
Nomes com três letras: após "ZZ", as colunas passam a ser nomeadas com combinações de três letras. O princípio é o mesmo:
- "AAA", "AAB", ..., "AAZ",
- depois "ABA", "ABB", ..., "ABZ",
- e assim por diante.
-
O princípio geral é que os nomes das colunas podem ser vistos como números em um sistema de numeração de base 26, onde "A" representa 1, "B" representa 2, ..., "Z" representa 26. Por exemplo:
- "A" = 1,
- "Z" = 26,
- "AA" = 27 (1 * 26^1 + 1),
- "AB" = 28 (1 * 26^1 + 2),
- "BA" = 53 (2 * 26^1 + 1).
Assim, o algoritmo gera automaticamente os nomes das colunas, aumentando-os de acordo com o princípio descrito. O número máximo de colunas no Excel depende da versão do programa (por exemplo, a partir do Excel 2007 são 16.384 colunas, terminando em "XFD"). O algoritmo criado aqui não possui essa limitação. Ele pode gerar nomes para um número de colunas equivalente a 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; }
Os métodos permitem preencher o array de nomes de colunas com identificadores no estilo do MS Excel.
Vamos agora analisar os construtores com parâmetros da classe.
Construtor genérico com array bidimensional de dados e array de cabeçalhos em formato string
//+-------------------------------------------------------------------+ //| Конструктор с указанием массива таблицы и массива заголовков. | //| Определяет количество и наименования колонок согласно 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); }
Esse construtor recebe um array de dados com qualquer tipo do conjunto definido em ENUM_DATATYPE. Os dados são então convertidos para os tipos utilizados pelas tabelas (double, long, datetime, color, string) para criação do modelo da tabela, junto com o array de cabeçalhos. Se o array de cabeçalhos estiver vazio, serão criados nomes de colunas no estilo MS Excel.
Construtor com definição de quantidade de linhas e colunas da tabela
//+------------------------------------------------------------------+ //| Конструктор таблицы с определением количества колонок и строк. | //| Колонки будут иметь 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); }
Este construtor cria um modelo de tabela vazio, com um cabeçalho no estilo MS Excel.
Construtor baseado em matriz de dados e array de cabeçalhos
//+-------------------------------------------------------------------+ //| Конструктор таблицы с инициализацией колонок согласно 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); }
Neste construtor, é passada uma matriz de dados do tipo double, para a criação do modelo da tabela, juntamente com o array de cabeçalhos. Se o array de cabeçalhos estiver vazio, serão criados nomes de colunas no estilo MS Excel.
No destrutor da classe o modelo e o cabeçalho da tabela são destruídos.
//+------------------------------------------------------------------+ //| Деструктор | //+------------------------------------------------------------------+ 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; } }
Método de comparação entre dois objetos
//+------------------------------------------------------------------+ //| Сравнение двух объектов | //+------------------------------------------------------------------+ 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); }
Cada tabela pode receber um identificador, o que é útil nos casos em que o programa precisa lidar com múltiplas tabelas. As tabelas no programa podem ser identificadas por esse ID, que por padrão é definido como -1. Se as tabelas forem armazenadas em listas (CList, CArrayObj etc.), esse método de comparação permite comparar as tabelas com base em seus identificadores, facilitando a busca e ordenação:
//+------------------------------------------------------------------+ //| Сравнение двух объектов | //+------------------------------------------------------------------+ 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); }
Método que copia o array de nomes dos cabeçalhos
//+------------------------------------------------------------------+ //| Копирует массив наименований заголовков | //+------------------------------------------------------------------+ 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); }
Este método recebe como parâmetros um array de cabeçalhos e a quantidade de colunas do modelo de tabela já criado. Se a tabela não tiver colunas, não há por que criar cabeçalhos, pois isso será informado, e o método retornará false. Se o número de colunas no modelo for maior que a quantidade de cabeçalhos fornecida no array, os cabeçalhos restantes serão gerados no estilo Excel, garantindo que não existam colunas sem rótulo.
Método que define um valor em uma célula especificada
//+------------------------------------------------------------------+ //| Устанавливает значение в указанную ячейку | //+------------------------------------------------------------------+ 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); }
Esse método apenas encaminha a chamada para o método com o mesmo nome do objeto do modelo de tabela.
Na prática, muitos dos métodos desta classe são duplicações de métodos do modelo da tabela. Se o modelo estiver criado, então os métodos com nomes equivalentes de acesso e modificação de propriedades são chamados diretamente nele.
Métodos para manipulação de células da tabela
//+------------------------------------------------------------------+ //| Устанавливает точность в указанную ячейку | //+------------------------------------------------------------------+ 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); }
Métodos para manipulação de linhas da tabela
//+------------------------------------------------------------------+ //| Создаёт новую строку и добавляет в конец списка | //+------------------------------------------------------------------+ 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); }
Método que cria uma nova coluna e a adiciona na posição especificada da tabela
//+------------------------------------------------------------------+ //| Создаёт новый столбец и добавляет его в указанную позицию таблицы| //+------------------------------------------------------------------+ 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); }
Caso o modelo da tabela ainda não tenha sido criado, o método retorna imediatamente uma falha. Se a coluna for adicionada com sucesso ao modelo da tabela, tenta-se também adicionar o cabeçalho correspondente. Se a tabela não possuir cabeçalho, apenas o sucesso da criação da nova coluna é retornado. Se houver cabeçalho, um novo cabeçalho é criado para a coluna e movido para a posição correspondente na lista.
Demais métodos para manipulação de colunas
//+------------------------------------------------------------------+ //| Удаляет столбец | //+------------------------------------------------------------------+ 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); }
Método que retorna a descrição do objeto
//+------------------------------------------------------------------+ //| Возвращает описание объекта | //+------------------------------------------------------------------+ string CTable::Description(void) { return(::StringFormat("%s: Rows total: %u, Columns total: %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.RowsTotal(),this.ColumnsTotal())); }
Cria e retorna uma string no seguinte formato (Tipo do Objeto: Rows total: XX, Columns total: XX)
Método que imprime a descrição do objeto no diário
//+------------------------------------------------------------------+ //| Выводит в журнал описание объекта | //+------------------------------------------------------------------+ 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); } } }
O método exibe no diário a descrição e, em seguida, imprime a tabela completa com cabeçalho e dados.
Método que salva a tabela em arquivo
//+------------------------------------------------------------------+ //| Сохранение в файл | //+------------------------------------------------------------------+ 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; }
A tabela só será salva com sucesso se tanto o modelo quanto o cabeçalho estiverem criados. O cabeçalho pode estar vazio, ou seja, sem colunas, mas o objeto precisa existir.
Método que carrega a tabela a partir de um arquivo
//+------------------------------------------------------------------+ //| Загрузка из файла | //+------------------------------------------------------------------+ 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; }
Como a tabela armazena tanto os dados do modelo quanto do cabeçalho, este método verifica se a tabela já possui modelo e cabeçalho. Caso contrário, os objetos são criados previamente, e em seguida seus dados são carregados do arquivo.
A classe de tabela simples está finalizada.
Agora vamos considerar uma extensão da classe de tabela simples — criaremos uma classe de tabela construída a partir dos dados registrados em um 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) {} };
Neste caso, o tipo da tabela é retornado como OBJECT_TYPE_TABLE_BY_PARAM, e tanto o modelo quanto o cabeçalho da tabela são criados diretamente dentro do construtor da classe:
//+------------------------------------------------------------------+ //| Конструктор с указанием массива таблицы на основе списка 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); }
Com base nesse exemplo, podem ser criadas outras variações de classes de tabela, mas por ora vamos considerar que tudo o que foi criado até aqui já é mais do que suficiente para a construção dos mais diversos tipos de tabelas e para um vasto conjunto de dados possíveis.
Vamos testar tudo o que foi construído até agora.
Testando o resultado
Na pasta \MQL5\Scripts\Tables\, vamos criar um novo script com o nome TestEmptyTable.mq5, incluir nele o arquivo de classes de tabela que criamos e gerar uma tabela vazia 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; }
O resultado da execução do script será uma tabela impressa no diário com o seguinte formato:
Table: Rows total: 4, Columns total: 4: | n/n | A | B | C | D | | 0 | | | | | | 1 | | | | | | 2 | | | | | | 3 | | | | |
Aqui, os cabeçalhos das colunas foram criados automaticamente no estilo do MS Excel.
Vamos escrever mais um script: \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; }
Como resultado da execução, será exibida no diário uma tabela com os dados:
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 |
Neste exemplo, os cabeçalhos das colunas são obtidos diretamente do array de nomes de colunas passado ao construtor da classe. O array bidimensional utilizado como fonte de dados para criação da tabela pode conter qualquer tipo dos definidos no ENUM_DATATYPE.
Todos os tipos são automaticamente convertidos para os cinco tipos utilizados pelo modelo de tabela: double, long, datetime, color e string.
Agora, criamos o script \MQL5\Scripts\Tables\TestMatrixTable.mq5 para testar uma tabela baseada em dados de uma matriz:
//+------------------------------------------------------------------+ //| 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; }
O resultado será uma tabela idêntica à construída a partir de um array bidimensional 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 |
Em seguida, vamos escrever o script \MQL5\Scripts\Tables\TestDealsTable.mq5, no qual todas as negociações históricas são coletadas, e a partir delas criamos uma tabela de negociações, que será impressa no diário:
//+------------------------------------------------------------------+ //| 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; }
Como resultado, será exibida a tabela completa das negociações, com largura de célula de 19 caracteres (padrão do método Print da classe de tabela):
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] |
No exemplo, são mostradas as quatro primeiras e quatro últimas negociações, o que já dá uma boa noção de como a tabela é impressa no diário.
Todos os arquivos criados estão incluídos junto ao artigo para estudo individual. O arquivo compactado pode ser extraído diretamente na pasta do terminal, e todos os arquivos serão posicionados corretamente na pasta: MQL5\Scripts\Tables.
Considerações finais
Com isso, concluímos o desenvolvimento dos componentes básicos da Model da tabela dentro da arquitetura MVC. Criamos as classes para trabalhar com tabelas e cabeçalhos, e testamos essas estruturas com diversos tipos de dados: arrays bidimensionais, matrizes e histórico de negociações.
Agora passamos para a próxima etapa, que é o desenvolvimento dos componentes View e Controller. No MQL5, esses dois componentes são fortemente integrados graças ao sistema de eventos interno, que permite que objetos reajam a ações do usuário.
Isso nos oferece a oportunidade de desenvolver simultaneamente a visualização da tabela (componente View) e seu controle (componente Controller), o que tornará um pouco mais simples a complexa e multifacetada implementação da interface visual.
Todos os exemplos e arquivos mencionados no artigo estão disponíveis para download. Nos próximos artigos, criaremos o componente View, já integrado com o Controller, para construir uma ferramenta completa de manipulação de tabelas no MQL5.
Ao concluir este projeto, novas possibilidades se abrirão para a criação de outros elementos de controle, que poderão ser utilizados em nossos próprios desenvolvimentos.
Programas utilizados neste artigo:
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | Tables.mqh | Biblioteca de classes | Biblioteca de classes para criação de tabelas |
| 2 | TestEmptyTable.mq5 | Script | Script para testar a criação de uma tabela vazia com quantidade definida de linhas e colunas |
| 3 | TestTArrayTable.mq5 | Script | Script para testar a criação de uma tabela a partir de um array bidimensional de dados |
| 4 | TestMatrixTable.mq5 | Script | Script para testar a criação de uma tabela com base em uma matriz de dados |
| 5 | TestDealsTable.mq5 | Script | Script para testar a criação de uma tabela a partir de dados personalizados (negociações históricas) |
| 6 | MQL5.zip | Arquivo compactado | Arquivo com todos os itens acima, pronto para ser extraído no diretório MQL5 do terminal cliente |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/17803
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Do básico ao intermediário: Sobrecarga de operadores (I)
Simulação de mercado: Position View (XX)
Como construir e otimizar um sistema de trading baseado em volume (Chaikin Money Flow - CMF)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso