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 libraries | //+------------------------------------------------------------------+ #include <Arrays\List.mqh> //--- Forward declaration of classes class CTableCell; // Table cell class class CTableRow; // Table row class class CTableModel; // Table model class class CColumnCaption; // Table column header class class CTableHeader; // Table header class class CTable; // Table class class CTableByParam; // Table class based on parameter array //+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+
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:
//+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+ #define MARKER_START_DATA -1 // Data start marker in a file #define MAX_STRING_LENGTH 128 // Maximum length of a string in a cell #define CELL_WIDTH_IN_CHARS 19 // Table cell width in characters //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+
Na seção de enumerações, adicionaremos novas constantes à enumeração de tipos de objetos:
//+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_OBJECT_TYPE // Enumeration of object types { OBJECT_TYPE_TABLE_CELL=10000, // Table cell OBJECT_TYPE_TABLE_ROW, // Table row OBJECT_TYPE_TABLE_MODEL, // Table model OBJECT_TYPE_COLUMN_CAPTION, // Table column header OBJECT_TYPE_TABLE_HEADER, // Table header OBJECT_TYPE_TABLE, // Table OBJECT_TYPE_TABLE_BY_PARAM, // Table based on the parameter array data };
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:
//+------------------------------------------------------------------+ //| List element creation method | //+------------------------------------------------------------------+ CObject *CListObj::CreateElement(void) { //--- Create a new object depending on the object type in 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: //--- Combining for storing cell values (double, long, string) union DataType { protected: double double_value; long long_value; ushort ushort_value[MAX_STRING_LENGTH]; public: //--- Set values 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); } //--- Return values 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; } }; //--- Variables DataType m_datatype_value; // Value ENUM_DATATYPE m_datatype; // Data type CObject *m_object; // Cell object ENUM_OBJECT_TYPE m_object_type; // Object type in the cell int m_row; // Row index int m_col; // Column index int m_digits; // Data representation accuracy uint m_time_flags; // Date/time display flags bool m_color_flag; // Color name display flag bool m_editable; // Editable cell flag //--- Set "empty value" 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: //--- Return cell coordinates and properties
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: //--- Return cell coordinates and properties 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; } //--- Return (1) double, (2) long and (3) string value 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(); } //--- Return the value as a formatted string 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(); } } //--- Return a description of the stored value type string DatatypeDescription(void) const { string type=::StringSubstr(::EnumToString(this.m_datatype),5); type.Lower(); return type; } //--- Clear data 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) Return and (2) display the object description in the journal 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:
//+------------------------------------------------------------------+ //| Table row class | //+------------------------------------------------------------------+ class CTableRow : public CObject { protected: CTableCell m_cell_tmp; // Cell object to search in the list CListObj m_list_cells; // List of cells uint m_index; // Row index //--- Add the specified cell to the end of the list bool AddNewCell(CTableCell *cell); public: //--- (1) Set and (2) return the row index void SetIndex(const uint index) { this.m_index=index; } uint Index(void) const { return this.m_index; } //--- Set the row and column positions to all cells void CellsPositionUpdate(void); //--- Create a new cell and add it to the end of the list 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:
//+------------------------------------------------------------------+ //| Structure parameter object class | //+------------------------------------------------------------------+ class CMqlParamObj : public CObject { protected: public: MqlParam m_param; //--- Set the parameters 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; } //--- Return the parameters 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; } //--- Object description 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)); } //--- Constructors/destructor 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 for creating lists of data | //+------------------------------------------------------------------+ class DataListCreator { public: //--- Add a new row to the CList list_data list static CList *AddNewRowToDataList(CList *list_data) { CList *row=new CList; if(row==NULL || list_data.Add(row)<0) return NULL; return row; } //--- Create a new CMqlParamObj parameter object and add it to 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:
//+------------------------------------------------------------------+ //| Table model class | //+------------------------------------------------------------------+ class CTableModel : public CObject { protected: CTableRow m_row_tmp; // Row object to search in the list CListObj m_list_rows; // List of table rows //--- Create a table model from a two-dimensional array 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); //--- Return the correct data type ENUM_DATATYPE GetCorrectDatatype(string type_name) { return ( //--- Integer value 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 : //--- Real value type_name=="float"|| type_name=="double" ? TYPE_DOUBLE : //--- Date/time value type_name=="datetime" ? TYPE_DATETIME : //--- Color value type_name=="color" ? TYPE_COLOR : /*--- String value */ TYPE_STRING ); } //--- Create and add a new empty string to the end of the list CTableRow *CreateNewEmptyRow(void); //--- Add a string to the end of the list bool AddNewRow(CTableRow *row); //--- Set the row and column positions to all table cells void CellsPositionUpdate(void); public: //--- Return (1) cell, (2) row by index, number (3) of rows, cells (4) in the specified row and (5) in the table 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); //--- Set (1) value, (2) precision, (3) time display flags and (4) color name display flag to the specified cell 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) Assign and (2) cancel the object in the cell void CellAssignObject(const uint row, const uint col,CObject *object); void CellUnassignObject(const uint row, const uint col); //--- (1) Delete and (2) move the cell bool CellDelete(const uint row, const uint col); bool CellMoveTo(const uint row, const uint cell_index, const uint index_to); //--- (1) Return and (2) display the cell description and (3) the object assigned to the cell 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: //--- Create a new string and (1) add it to the end of the list, (2) insert to the specified list position CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Remove or (2) relocate the row, (3) clear the row data bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Return and (2) display the row description in the journal string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Add, (2) remove, (3) relocate a column, (4) clear data, set the column data (5) type and (6) accuracy 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) Return and (2) display the table description in the journal virtual string Description(void); void Print(const bool detail); void PrintTable(const int cell_width=CELL_WIDTH_IN_CHARS); //--- (1) Clear the data, (2) destroy the model void ClearData(void); void Destroy(void); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type 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); } //--- Constructors/destructor 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
//+------------------------------------------------------------------+ //| Create a table model with specified number of rows and columns | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(const uint num_rows,const uint num_columns) { //--- In the loop based on the number of rows for(uint r=0; r<num_rows; r++) { //--- create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); //--- If a row is created and added to the list, if(row!=NULL) { //--- In a loop by the number of columns //--- create all the cells, adding each new one to the end of the list of cells in the row 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
//+------------------------------------------------------------------+ //| Create a table model from the specified matrix | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(const matrix &row_data) { //--- The number of rows and columns ulong num_rows=row_data.Rows(); ulong num_columns=row_data.Cols(); //--- In the loop based on the number of rows for(uint r=0; r<num_rows; r++) { //--- create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); //--- If a row is created and added to the list, if(row!=NULL) { //--- In the loop by the number of columns, //--- create all the cells, adding each new one to the end of the list of cells in the row 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
//+------------------------------------------------------------------+ //| Create a table model from the list of parameters | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(CList &list_param) { //--- If an empty list is passed, report this and leave if(list_param.Total()==0) { ::PrintFormat("%s: Error. Empty list passed",__FUNCTION__); return; } //--- Get the pointer to the first row of the table to determine the number of columns //--- If the first row could not be obtained, or there are no cells in it, report this and leave 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; } //--- The number of rows and columns ulong num_rows=list_param.Total(); ulong num_columns=first_row.Total(); //--- In the loop based on the number of rows for(uint r=0; r<num_rows; r++) { //--- get the next table row from list_param CList *col_list=list_param.GetNodeAtIndex(r); if(col_list==NULL) continue; //--- create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); //--- If a row is created and added to the list, if(row!=NULL) { //--- In the loop by the number of columns, //--- create all the cells, adding each new one to the end of the list of cells in the row for(uint c=0; c<num_columns; c++) { CMqlParamObj *param=col_list.GetNodeAtIndex(c); if(param==NULL) continue; //--- Declare the pointer to a cell and the type of data to be contained in it CTableCell *cell=NULL; ENUM_DATATYPE datatype=param.Datatype(); //--- Depending on the data type switch(datatype) { //--- real data type case TYPE_FLOAT : case TYPE_DOUBLE : cell=row.CellAddNew((double)param.ValueD()); // Create a new cell with double data and if(cell!=NULL) cell.SetDigits((int)param.ValueL()); // set the precision of the displayed data break; //--- datetime data type case TYPE_DATETIME: cell=row.CellAddNew((datetime)param.ValueL()); // Create a new cell with datetime data and if(cell!=NULL) cell.SetDatetimeFlags((int)param.ValueD()); // set date/time display flags break; //--- color data type case TYPE_COLOR : cell=row.CellAddNew((color)param.ValueL()); // Create a new cell with color data and if(cell!=NULL) cell.SetColorNameFlag((bool)param.ValueD()); // set the flag for displaying the names of known colors break; //--- string data type case TYPE_STRING : cell=row.CellAddNew((string)param.ValueS()); // Create a new cell with string data break; //--- integer data type default : cell=row.CellAddNew((long)param.ValueL()); // Create a new cell with long data 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:
//--- Financial result of a trade 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
//+------------------------------------------------------------------+ //| Add a column | //+------------------------------------------------------------------+ bool CTableModel::ColumnAddNew(const int index=-1) { //--- Declare the variables CTableCell *cell=NULL; bool res=true; //--- In the loop based on the number of rows for(uint i=0;i<this.RowsTotal();i++) { //--- get the next row CTableRow *row=this.GetRow(i); if(row!=NULL) { //--- add a cell of double type to the end of the row cell=row.CellAddNew(0.0); if(cell==NULL) res &=false; //--- clear the cell else cell.ClearData(); } } //--- If the column index passed is not negative, shift the column to the specified position if(res && index>-1) res &=this.ColumnMoveTo(this.CellsInRow(0)-1,index); //--- Return the result 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
//+------------------------------------------------------------------+ //| Set the column data type | //+------------------------------------------------------------------+ void CTableModel::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- In a loop through all table rows for(uint i=0;i<this.RowsTotal();i++) { //--- get the cell with the column index from each row and set the data type 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
//+------------------------------------------------------------------+ //| Set the accuracy of the column data | //+------------------------------------------------------------------+ void CTableModel::ColumnSetDigits(const uint index,const int digits) { //--- In a loop through all table rows for(uint i=0;i<this.RowsTotal();i++) { //--- get the cell with the column index from each row and set the data accuracy 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:
//+------------------------------------------------------------------+ //| Table column header class | //+------------------------------------------------------------------+ class CColumnCaption : public CObject { protected: //--- Variables ushort m_ushort_array[MAX_STRING_LENGTH]; // Array of header symbols uint m_column; // Column index ENUM_DATATYPE m_datatype; // Data type public: //--- (1) Set and (2) return the column index void SetColumn(const uint column) { this.m_column=column; } uint Column(void) const { return this.m_column; } //--- (1) Set and (2) return the column data type ENUM_DATATYPE Datatype(void) const { return this.m_datatype; } void SetDatatype(const ENUM_DATATYPE datatype) { this.m_datatype=datatype;} //--- Clear data void ClearData(void) { this.SetValue(""); } //--- Set the header void SetValue(const string value) { ::StringToShortArray(value,this.m_ushort_array); } //--- Return the header text string Value(void) const { string res=::ShortArrayToString(this.m_ushort_array); res.TrimLeft(); res.TrimRight(); return res; } //--- (1) Return and (2) display the object description in the journal virtual string Description(void); void Print(void); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type 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); } //--- Constructors/destructor 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
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CColumnCaption::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the column index if(::FileWriteInteger(file_handle,this.m_column,INT_VALUE)!=INT_VALUE) return(false); //--- Save the value if(::FileWriteArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array)) return(false); //--- All is successful return true; }
Método de carregamento a partir de arquivo
//+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CColumnCaption::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the column index this.m_column=::FileReadInteger(file_handle,INT_VALUE); //--- Load the value if(::FileReadArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array)) return(false); //--- All is successful 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
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ 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:
//+------------------------------------------------------------------+ //| Table header class | //+------------------------------------------------------------------+ class CTableHeader : public CObject { protected: CColumnCaption m_caption_tmp; // Column header object to search in the list CListObj m_list_captions; // List of column headers //--- Add the specified header to the end of the list bool AddNewColumnCaption(CColumnCaption *caption); //--- Create a table header from a string array void CreateHeader(string &array[]); //--- Set the column position of all column headers void ColumnPositionUpdate(void); public: //--- Create a new header and add it to the end of the list CColumnCaption *CreateNewColumnCaption(const string caption); //--- Return (1) the header by index and (2) the number of column headers CColumnCaption *GetColumnCaption(const uint index) { return this.m_list_captions.GetNodeAtIndex(index); } uint ColumnsTotal(void) const { return this.m_list_captions.Total(); } //--- Set the value of the specified column header void ColumnCaptionSetValue(const uint index,const string value); //--- (1) Set and (2) return the data type for the specified column header void ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type); ENUM_DATATYPE ColumnCaptionDatatype(const uint index); //--- (1) Remove and (2) relocate the column header bool ColumnCaptionDelete(const uint index); bool ColumnCaptionMoveTo(const uint caption_index, const uint index_to); //--- Clear column header data void ClearData(void); //--- Clear the list of column headers void Destroy(void) { this.m_list_captions.Clear(); } //--- (1) Return and (2) display the object description in the journal virtual string Description(void); void Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type 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); } //--- Constructors/destructor 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
//+------------------------------------------------------------------+ //| Create a new header and add it to the end of the list | //+------------------------------------------------------------------+ CColumnCaption *CTableHeader::CreateNewColumnCaption(const string caption) { //--- Create a new header object 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; } //--- Add the created header to the end of the list if(!this.AddNewColumnCaption(caption_obj)) { delete caption_obj; return NULL; } //--- Return the pointer to the object 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
//+------------------------------------------------------------------+ //| Add the header to the end of the list | //+------------------------------------------------------------------+ bool CTableHeader::AddNewColumnCaption(CColumnCaption *caption) { //--- If an empty object is passed, report it and return 'false' if(caption==NULL) { ::PrintFormat("%s: Error. Empty CColumnCaption object passed",__FUNCTION__); return false; } //--- Set the header index in the list and add the created header to the end of the list 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; } //--- Successful 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
//+------------------------------------------------------------------+ //| Create a table header from the string array | //+------------------------------------------------------------------+ void CTableHeader::CreateHeader(string &array[]) { //--- Get the number of table columns from the array properties uint total=array.Size(); //--- In a loop by array size, //--- create all the headers, adding each new one to the end of the list 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
//+------------------------------------------------------------------+ //| Set the value to the specified column header | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetValue(const uint index,const string value) { //--- Get the required header from the list and set a new value to it 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
//+------------------------------------------------------------------+ //| Set the data type for the specified column header | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- Get the required header from the list and set a new value to it 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
//+------------------------------------------------------------------+ //| Return the data type of the specified column header | //+------------------------------------------------------------------+ ENUM_DATATYPE CTableHeader::ColumnCaptionDatatype(const uint index) { //--- Get the required header from the list and return the column data type from it 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
//+------------------------------------------------------------------+ //| Remove the header of the specified column | //+------------------------------------------------------------------+ bool CTableHeader::ColumnCaptionDelete(const uint index) { //--- Remove the header from the list by index if(!this.m_list_captions.Delete(index)) return false; //--- Update the indices for the remaining headers in the list 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
//+------------------------------------------------------------------+ //| Move the column header to the specified position | //+------------------------------------------------------------------+ bool CTableHeader::ColumnCaptionMoveTo(const uint caption_index,const uint index_to) { //--- Get the desired header by index in the list, turning it into the current one CColumnCaption *caption=this.GetColumnCaption(caption_index); //--- Move the current header to the specified position in the list if(caption==NULL || !this.m_list_captions.MoveToIndex(index_to)) return false; //--- Update the indices of all headers in the list 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
//+------------------------------------------------------------------+ //| Set the column positions of all headers | //+------------------------------------------------------------------+ void CTableHeader::ColumnPositionUpdate(void) { //--- In the loop through all the headings in the list for(int i=0;i<this.m_list_captions.Total();i++) { //--- get the next header and set the column index in it 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
//+------------------------------------------------------------------+ //| Clear column header data in the list | //+------------------------------------------------------------------+ void CTableHeader::ClearData(void) { //--- In the loop through all the headings in the list for(uint i=0;i<this.ColumnsTotal();i++) { //--- get the next header and set the empty value to it 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
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTableHeader::Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS) { //--- Number of headers int total=(int)this.ColumnsTotal(); //--- If the output is in tabular form string res=""; if(as_table) { //--- create a table row from the values of all headers res="|"; for(int i=0;i<total;i++) { CColumnCaption *caption=this.GetColumnCaption(i); if(caption==NULL) continue; res+=::StringFormat("%*s |",column_width,caption.Value()); } //--- Display a row in the journal and leave ::Print(res); return; } //--- Display the header as a row description ::Print(this.Description()+(detail ? ":" : "")); //--- If detailed description if(detail) { //--- In a loop by the list of row headers for(int i=0; i<total; i++) { //--- get the current header and add its description to the final row CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) res+=" "+caption.Description()+(i<total-1 ? "\n" : ""); } //--- Send the row created in the loop to the journal ::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
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CTableHeader::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the list of headers if(!this.m_list_captions.Save(file_handle)) return(false); //--- Successful return true; } //+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CTableHeader::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the list of headers if(!this.m_list_captions.Load(file_handle)) return(false); //--- Successful 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:
//+------------------------------------------------------------------+ //| Table class | //+------------------------------------------------------------------+ class CTable : public CObject { private: //--- Populate the array of column headers in Excel style bool FillArrayExcelNames(const uint num_columns); //--- Return the column name as in Excel string GetExcelColumnName(uint column_number); //--- Return the header availability bool HeaderCheck(void) const { return(this.m_table_header!=NULL && this.m_table_header.ColumnsTotal()>0); } protected: CTableModel *m_table_model; // Pointer to the table model CTableHeader *m_table_header; // Pointer to the table header CList m_list_rows; // List of parameter arrays from structure fields string m_array_names[]; // Array of column headers int m_id; // Table ID //--- Copy the array of header names bool ArrayNamesCopy(const string &column_names[],const uint columns_total); public: //--- (1) Set and (2) return the table model void SetTableModel(CTableModel *table_model) { this.m_table_model=table_model; } CTableModel *GetTableModel(void) { return this.m_table_model; } //--- (1) Set and (2) return the header void SetTableHeader(CTableHeader *table_header) { this.m_table_header=m_table_header; } CTableHeader *GetTableHeader(void) { return this.m_table_header; } //--- (1) Set and (2) return the table ID void SetID(const int id) { this.m_id=id; } int ID(void) const { return this.m_id; } //--- Clear column header data void HeaderClearData(void) { if(this.m_table_header!=NULL) this.m_table_header.ClearData(); } //--- Remove the table header void HeaderDestroy(void) { if(this.m_table_header==NULL) return; this.m_table_header.Destroy(); this.m_table_header=NULL; } //--- (1) Clear all data and (2) destroy the table model and header 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; } //--- Return (1) the header, (2) cell, (3) row by index, number (4) of rows, (5) columns, cells (6) in the specified row, (7) in the table 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); } //--- Set (1) value, (2) precision, (3) time display flags and (4) color name display flag to the specified cell 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) Assign and (2) cancel the object in the cell void CellAssignObject(const uint row, const uint col,CObject *object); void CellUnassignObject(const uint row, const uint col); //--- Return the string value of the specified cell virtual string CellValueAt(const uint row, const uint col); protected: //--- (1) Delete and (2) move the cell bool CellDelete(const uint row, const uint col); bool CellMoveTo(const uint row, const uint cell_index, const uint index_to); public: //--- (1) Return and (2) display the cell description and (3) the object assigned to the cell string CellDescription(const uint row, const uint col); void CellPrint(const uint row, const uint col); //--- Return (1) the object assigned to the cell and (2) the type of the object assigned to the cell CObject *CellGetObject(const uint row, const uint col); ENUM_OBJECT_TYPE CellGetObjType(const uint row, const uint col); //--- Create a new string and (1) add it to the end of the list, (2) insert to the specified list position CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Remove or (2) relocate the row, (3) clear the row data bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Return and (2) display the row description in the journal string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Add new, (2) remove, (3) relocate the column and (4) clear the column data 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); //--- Set (1) the value of the specified header and (2) data accuracy for the specified column void ColumnCaptionSetValue(const uint index,const string value); void ColumnSetDigits(const uint index,const int digits); //--- (1) Set and (2) return the data type for the specified column void ColumnSetDatatype(const uint index,const ENUM_DATATYPE type); ENUM_DATATYPE ColumnDatatype(const uint index); //--- (1) Return and (2) display the object description in the journal virtual string Description(void); void Print(const int column_width=CELL_WIDTH_IN_CHARS); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type 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); } //--- Constructors/destructor 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.
//+------------------------------------------------------------------+ //| Return the column name as in Excel | //+------------------------------------------------------------------+ string CTable::GetExcelColumnName(uint column_number) { string column_name=""; uint index=column_number; //--- Check that the column index is greater than 0 if(index==0) return (__FUNCTION__+": Error. Invalid column number passed"); //--- Convert the index to the column name while(!::IsStopped() && index>0) { index--; // Decrease the index by 1 to make it 0-indexed uint remainder =index % 26; // Remainder after division by 26 uchar char_code ='A'+(uchar)remainder; // Calculate the symbol code (letters) column_name=::CharToString(char_code)+column_name; // Add a letter to the beginning of the string index/=26; // Move on to the next rank } return column_name; } //+------------------------------------------------------------------+ //| Populate the array of column headers in Excel style | //+------------------------------------------------------------------+ 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
//+-------------------------------------------------------------------+ //| Constructor specifying a table array and a header array. | //| Defines the index and names of columns according to column_names | //| The number of rows is determined by the size of the row_data array| //| also used to fill the table | //+-------------------------------------------------------------------+ 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
//+-----------------------------------------------------------------------+ //| Table constructor with definition of the number of columns and rows. | //| The columns will have Excel names "A", "B", "C", etc. | //+-----------------------------------------------------------------------+ 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
//+-----------------------------------------------------------------------+ //| Table constructor with column initialization according to column_names| //| The number of rows is determined by row_data with matrix type | //+-----------------------------------------------------------------------+ 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.
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ 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:
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Copy the array of header names | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Set the value to the specified cell | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Set the accuracy to the specified cell | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Set the time display flags to the specified cell | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Set the flag for displaying color names in the specified cell | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Assign an object to a cell | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Cancel the object in the cell | //+------------------------------------------------------------------+ void CTable::CellUnassignObject(const uint row, const uint col) { if(this.m_table_model!=NULL) this.m_table_model.CellUnassignObject(row,col); } //+------------------------------------------------------------------+ //| Return the string value of the specified cell | //+------------------------------------------------------------------+ string CTable::CellValueAt(const uint row,const uint col) { CTableCell *cell=this.GetCell(row,col); return(cell!=NULL ? cell.Value() : ""); } //+------------------------------------------------------------------+ //| Delete a cell | //+------------------------------------------------------------------+ bool CTable::CellDelete(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellDelete(row,col) : false); } //+------------------------------------------------------------------+ //| Move the cell | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Return the object assigned to the cell | //+------------------------------------------------------------------+ CObject *CTable::CellGetObject(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellGetObject(row,col) : NULL); } //+------------------------------------------------------------------+ //| Returns the type of the object assigned to the cell | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Return the cell description | //+------------------------------------------------------------------+ string CTable::CellDescription(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellDescription(row,col) : ""); } //+------------------------------------------------------------------+ //| Display a cell description in the journal | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Create a new string and add it to the end of the list | //+------------------------------------------------------------------+ CTableRow *CTable::RowAddNew(void) { return(this.m_table_model!=NULL ? this.m_table_model.RowAddNew() : NULL); } //+--------------------------------------------------------------------------------+ //| Create a new string and insert it into the specified position of the list | //+--------------------------------------------------------------------------------+ CTableRow *CTable::RowInsertNewTo(const uint index_to) { return(this.m_table_model!=NULL ? this.m_table_model.RowInsertNewTo(index_to) : NULL); } //+------------------------------------------------------------------+ //| Delete a row | //+------------------------------------------------------------------+ bool CTable::RowDelete(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.RowDelete(index) : false); } //+------------------------------------------------------------------+ //| Move the row | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Clear the row data | //+------------------------------------------------------------------+ void CTable::RowClearData(const uint index) { if(this.m_table_model!=NULL) this.m_table_model.RowClearData(index); } //+------------------------------------------------------------------+ //| Return the row description | //+------------------------------------------------------------------+ string CTable::RowDescription(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.RowDescription(index) : ""); } //+------------------------------------------------------------------+ //| Display the row description in the journal | //+------------------------------------------------------------------+ 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
//+-----------------------------------------------------------------------+ //| Create a new column and adds it to the specified position in the table| //+-----------------------------------------------------------------------+ bool CTable::ColumnAddNew(const string caption,const int index=-1) { //--- If there is no table model, or there is an error adding a new column to the model, return 'false' if(this.m_table_model==NULL || !this.m_table_model.ColumnAddNew(index)) return false; //--- If there is no header, return 'true' (the column is added without a header) if(this.m_table_header==NULL) return true; //--- Check for the creation of a new column header and, if it has not been created, return 'false' CColumnCaption *caption_obj=this.m_table_header.CreateNewColumnCaption(caption); if(caption_obj==NULL) return false; //--- If a non-negative index has been passed, return the result of moving the header to the specified index //--- Otherwise, everything is ready - just return '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
//+------------------------------------------------------------------+ //| Remove the column | //+------------------------------------------------------------------+ bool CTable::ColumnDelete(const uint index) { if(!this.HeaderCheck() || !this.m_table_header.ColumnCaptionDelete(index)) return false; return this.m_table_model.ColumnDelete(index); } //+------------------------------------------------------------------+ //| Move the column | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Clear the column data | //+------------------------------------------------------------------+ void CTable::ColumnClearData(const uint index) { if(this.m_table_model!=NULL) this.m_table_model.ColumnClearData(index); } //+------------------------------------------------------------------+ //| Set the value of the specified header | //+------------------------------------------------------------------+ void CTable::ColumnCaptionSetValue(const uint index,const string value) { CColumnCaption *caption=this.m_table_header.GetColumnCaption(index); if(caption!=NULL) caption.SetValue(value); } //+------------------------------------------------------------------+ //| Set the data type for the specified column | //+------------------------------------------------------------------+ void CTable::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- If the table model exists, set the data type for the column if(this.m_table_model!=NULL) this.m_table_model.ColumnSetDatatype(index,type); //--- If there is a header, set the data type for it if(this.m_table_header!=NULL) this.m_table_header.ColumnCaptionSetDatatype(index,type); } //+------------------------------------------------------------------+ //| Set the data accuracy for the specified column | //+------------------------------------------------------------------+ void CTable::ColumnSetDigits(const uint index,const int digits) { if(this.m_table_model!=NULL) this.m_table_model.ColumnSetDigits(index,digits); } //+------------------------------------------------------------------+ //| Return the data type for the specified column | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ 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
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTable::Print(const int column_width=CELL_WIDTH_IN_CHARS) { if(this.HeaderCheck()) { //--- Display the header as a row description ::Print(this.Description()+":"); //--- Number of headers int total=(int)this.ColumnsTotal(); string res=""; //--- create a table row from the values of all table column headers res="|"; for(int i=0;i<total;i++) { CColumnCaption *caption=this.GetColumnCaption(i); if(caption==NULL) continue; res+=::StringFormat("%*s |",column_width,caption.Value()); } //--- Add a header to the left row string hd="|"; hd+=::StringFormat("%*s ",column_width,"n/n"); res=hd+res; //--- Display the header row in the journal ::Print(res); } //--- Loop through all the table rows and print them out in tabular form for(uint i=0;i<this.RowsTotal();i++) { CTableRow *row=this.GetRow(i); if(row!=NULL) { //--- create a table row from the values of all cells 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()); } //--- Display a row in the journal ::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
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CTable::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the ID if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return(false); //--- Check the table model if(this.m_table_model==NULL) return false; //--- Save the table model if(!this.m_table_model.Save(file_handle)) return(false); //--- Check the table header if(this.m_table_header==NULL) return false; //--- Save the table header if(!this.m_table_header.Save(file_handle)) return(false); //--- Successful 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
//+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CTable::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the ID this.m_id=::FileReadInteger(file_handle,INT_VALUE); //--- Check the table model if(this.m_table_model==NULL && (this.m_table_model=new CTableModel())==NULL) return(false); //--- Load the table model if(!this.m_table_model.Load(file_handle)) return(false); //--- Check the table header if(this.m_table_header==NULL && (this.m_table_header=new CTableHeader())==NULL) return false; //--- Load the table header if(!this.m_table_header.Load(file_handle)) return(false); //--- Successful 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 for creating tables based on the array of parameters | //+------------------------------------------------------------------+ class CTableByParam : public CTable { public: virtual int Type(void) const { return(OBJECT_TYPE_TABLE_BY_PARAM); } //--- Constructor/destructor 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:
//+------------------------------------------------------------------+ //| Constructor specifying a table array based on the row_data list | //| containing objects with structure field data. | //| Define the index and names of columns according to | //| column names in column_names | //+------------------------------------------------------------------+ CTableByParam::CTableByParam(CList &row_data,const string &column_names[]) { //--- Copy the passed list of data into a variable and //--- create a table model based on this list this.m_list_rows=row_data; this.m_table_model=new CTableModel(this.m_list_rows); //--- Copy the passed list of headers to m_array_names and //--- create a table header based on this list 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 libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Create an empty 4x4 table CTable *table=new CTable(4,4); if(table==NULL) return; //--- Display it in the journal and delete the created object 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 libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare and initialize a 4x4 double array double array[4][4]={{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Declare and initialize the column header array string headers[]={"Column 1","Column 2","Column 3","Column 4"}; //--- Create a table based on the data array and the header array CTable *table=new CTable(array,headers); if(table==NULL) return; //--- Display the table in the journal and delete the created object 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 libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare and initialize a 4x4 matrix matrix row_data = {{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Declare and initialize the column header array string headers[]={"Column 1","Column 2","Column 3","Column 4"}; //--- Create a table based on a matrix and an array of headers CTable *table=new CTable(row_data,headers); if(table==NULL) return; //--- Display the table in the journal and delete the created object 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 libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare a list of deals, the deal parameters object, and the structure of parameters CList rows_data; CMqlParamObj *cell=NULL; MqlParam param={}; //--- Select the entire history if(!HistorySelect(0,TimeCurrent())) return; //--- Create a list of deals in the array of arrays (CList in CList) //--- (one row is one deal, columns are deal property objects) int total=HistoryDealsTotal(); for(int i=0;i<total;i++) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) continue; //--- Add a new row of properties for the next deal to the list of deals CList *row=DataListCreator::AddNewRowToDataList(&rows_data); if(row==NULL) continue; //--- Create "cells" with the deal parameters and //--- add them to the created deal properties row string symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); //--- Deal time (column 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); //--- Symbol name (column 1) param.type=TYPE_STRING; param.string_value=symbol; DataListCreator::AddNewCellParamToRow(row,param); //--- Deal ticket (column 2) param.type=TYPE_LONG; param.integer_value=(long)ticket; DataListCreator::AddNewCellParamToRow(row,param); //--- The order the performed deal is based on (column 3) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_ORDER); DataListCreator::AddNewCellParamToRow(row,param); //--- Position ID (column 4) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_POSITION_ID); DataListCreator::AddNewCellParamToRow(row,param); //--- Deal type (column 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); //--- Deal direction (column 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); //--- Deal volume (column 7) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_VOLUME); param.integer_value=2; DataListCreator::AddNewCellParamToRow(row,param); //--- Deal price (column 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 level (column 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 level (column 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); //--- Deal financial result (column 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); //--- Deal magic number (column 12) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_MAGIC); DataListCreator::AddNewCellParamToRow(row,param); //--- Deal execution reason or source (column 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); //--- Deal comment (column 14) param.type=TYPE_STRING; param.string_value=HistoryDealGetString(ticket,DEAL_COMMENT); DataListCreator::AddNewCellParamToRow(row,param); } //--- Declare and initialize the table header string headers[]={"Time","Symbol","Ticket","Order","Position","Type","Entry","Volume","Price","SL","TP","Profit","Magic","Reason","Comment"}; //--- Create a table based on the created list of parameters and the header array CTableByParam *table=new CTableByParam(rows_data,headers); if(table==NULL) return; //--- Display the table in the journal and delete the created object 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