English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
preview
Classes de table et d'en-tête basées sur un modèle de tableau dans MQL5 : Application du concept MVC

Classes de table et d'en-tête basées sur un modèle de tableau dans MQL5 : Application du concept MVC

MetaTrader 5Exemples |
150 0
Artyom Trishkin
Artyom Trishkin

Sommaire


Introduction

Dans le premier article couvrant la création du Contrôle Tableau, nous avons créé un modèle de tableau dans MQL5 en utilisant le modèle d'architecture MVC. Des classes de cellules, de lignes et de modèles de tableaux ont été développées, ce qui a permis d'organiser les données sous une forme pratique et structurée.

Nous passons maintenant à l'étape suivante : le développement des classes de tableaux et des en-têtes de tableaux. Les en-têtes de colonne d'un tableau ne sont pas de simples étiquettes de colonne, mais un outil de gestion du tableau et de ses colonnes. Ils vous permettent d'ajouter, de supprimer et de renommer des colonnes. Bien entendu, un tableau peut fonctionner sans classe d'en-tête, mais ses fonctionnalités seront alors limitées. Un simple tableau statique sera créé sans en-tête de colonne et, par conséquent, sans la possibilité de contrôler les colonnes.

Pour mettre en œuvre la fonction de contrôle des colonnes, le modèle de tableau doit être affiné. Nous le compléterons par des méthodes permettant de travailler avec les colonnes : modifier leur structure, en ajouter de nouvelles ou en supprimer. Ces méthodes seront utilisées par la classe d'en-tête du tableau pour permettre un contrôle pratique de sa structure.

Cette phase de développement constituera la base de la mise en œuvre ultérieure des composants Vue et Contrôleur, qui seront abordés dans les articles suivants. Cette étape est un jalon important vers la création d'une interface à part entière pour les données d'exploitation.


Raffinement du modèle de tableau

Pour l'instant, le modèle de tableau est créé à partir d'un tableau à 2 dimensions. Mais pour améliorer la flexibilité et la facilité d'utilisation du tableau, nous allons ajouter des méthodes d'initialisation supplémentaires. Cela permettra d'adapter le modèle à différents scénarios d'utilisation. Les méthodes suivantes apparaîtront dans la version actualisée de la classe de modèle de tableau :

  • Création d'un modèle à partir d'un tableau à 2 dimensions

    void CreateTableModel(T &array[][]);

    Cette méthode permet de créer rapidement un modèle de tableau basé sur un tableau de données bidimensionnel existant.

  • Création d'un modèle vide avec un nombre déterminé de lignes et de colonnes

    void CreateTableModel(const uint num_rows, const uint num_columns);

    Cette méthode convient lorsque la structure du tableau est connue à l'avance, mais que les données seront ajoutées ultérieurement.

  • Créer un modèle à partir d'une matrice de données

    void CreateTableModel(const matrix &row_data);

    Cette méthode permet d'utiliser une matrice de données pour initialiser un tableau, ce qui est pratique pour travailler avec des ensembles de données préparés à l'avance.

  • Création d'un modèle à partir d'une liste chaînée

    void CreateTableModel(CList &list_param);

    Dans ce cas, un tableau de tableaux sera utilisé pour le stockage des données, où un objet CList (données sur les lignes du tableau) contient d'autres objets CList qui contiennent des données sur les cellules du tableau. Cette approche permet de contrôler dynamiquement la structure du tableau et son contenu.

Ces changements rendront le modèle de tableau plus polyvalent et plus pratique à utiliser dans différents scénarios. Par exemple, il sera possible de créer facilement des tableaux à partir de tableaux de données préparés à l'avance et de listes générées dynamiquement.

Dans le dernier article, nous avons décrit toutes les classes permettant de créer un modèle de tableau directement dans le fichier du script de test. Aujourd'hui, nous allons transférer ces classes dans notre propre fichier include.

Dans le dossier où est stocké le script de l'article précédent (par défaut : \MQL5\Scripts\TableModel\), créez un nouveau fichier include nommé Tables.mqh et copiez-y, à partir du fichier TableModelTest.mq5 situé dans le même dossier, tout ce qui se trouve entre le début du fichier et le début du code du script de test : toutes les classes pour la création d'un modèle de tableau. Nous disposons à présent d'un fichier distinct contenant les classes du modèle de tableau, nommé Tables.mqh. Apportons des modifications et des améliorations à ce fichier.

Déplacez le fichier créé dans un nouveau dossier, MQL5\Scripts\Tables\ — nous créerons ce projet dans ce dossier.

Dans la section des fichiers inclus/bibliothèques, ajouter une déclaration forward des nouvelles classes. Nous les ferons aujourd'hui, la déclaration des classes est nécessaire pour que la classe de liste d'objets CListObj créée dans l'article précédent puisse créer des objets de ces classes dans sa méthode 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                                                           |
//+------------------------------------------------------------------+

Dans la section macros , ajoutez une constante pour la largeur de la cellule du tableau, égale à 19 caractères. Il s'agit de la valeur de largeur minimale à laquelle le texte de la date et de l'heure tient entièrement dans l'espace de la cellule du journal, sans déplacer le bord droit de la cellule, ce qui désynchronise la taille de toutes les cellules du tableau dessiné dans le journal :

//+------------------------------------------------------------------+
//| 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                                                     |
//+------------------------------------------------------------------+

Dans la section énumération, ajouter de nouvelles constantes à l'énumération des types d'objets :

//+------------------------------------------------------------------+
//| 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
  };

Dans la classe de liste d'objets CListObj, dans la méthode de création d'éléments, ajouter de nouveaux cas de création de nouveaux types d'objets :

//+------------------------------------------------------------------+
//| 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;
     }
  }

Après avoir créé de nouvelles classes, la liste d'objets CListObj pourra créer des objets de ces types. Cela permettra d'enregistrer et de charger des listes à partir de fichiers contenant ces types d'objets.

Pour définir une valeur "vide" dans une cellule, qui sera affichée comme une ligne vide, et non comme une valeur "0", comme c'est le cas actuellement, il est nécessaire de déterminer quelle valeur doit être considérée comme "vide". Il est clair que pour les valeurs de type chaîne de caractères, une ligne vide correspondra à une telle valeur. Pour les valeurs numériques, définissez DBL_MAX pour les types réels et LONG_MAX pour les entiers.

Pour définir une telle valeur, dans la classe d'objets d'une cellule de tableau, dans la zone protégée, écrivez la méthode suivante :

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

La méthode qui renvoie la valeur stockée dans une cellule sous la forme d'une chaîne formatée vérifie maintenant que la valeur de la cellule n'est pas "vide". Si la valeur est "vide", elle renvoie une ligne vide :

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();                   }

La méthode d'effacement des données dans une cellule ne met plus zéro dans la cellule, mais appelle une méthode pour mettre une valeur vide dans la cellule.

Les méthodes de toutes les classes à partir desquelles l'objet modèle de tableau est créé, qui renvoient la description de l'objet, sont désormais virtuelles — en cas d'héritage de ces objets :

//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);

Dans la classe des lignes de tableau, toutes les méthodes CreateNewCell() qui créent une nouvelle cellule et l'ajoutent à la fin de la liste ont été renommées :

//+------------------------------------------------------------------+
//| 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);

Nous pouvons voir que toutes les méthodes responsables de l'accès aux cellules commencent par la chaîne de caractères "Cell". Dans d'autres classes, les méthodes permettant d'accéder, par exemple, à une ligne de tableau commenceront par la chaîne de caractères "Row". Cela met de l'ordre dans les méthodes des classes.

Pour construire un modèle de tableau, nous devons développer une approche universelle qui permettra de créer des tableaux à partir de presque toutes les données. Il peut s'agir, par exemple, de structures, de listes de transactions, d'ordres, de positions ou de toute autre donnée. L'idée est de créer une boîte à outils qui permettra de créer une liste de lignes, où chaque ligne sera une liste de propriétés. Chaque propriété de la liste correspondra à une cellule du tableau.

Il convient ici de prêter attention à la structure des paramètres d'entrée de type MqlParam. Elle présente les caractéristiques suivantes :

  • Spécification du type de données stockées dans la structure ENUM_DATATYPE.
  • Stockage de valeurs dans 3 champs :
    1. integer_value — pour les données entières,
    2. double_value — pour les données réelles,
    3. string_value — pour les données de type chaîne de caractères.

Cette structure permet de travailler avec différents types de données, ce qui permet de stocker toutes les propriétés, telles que les paramètres des transactions, des ordres ou d'autres objets.

Pour un stockage pratique des données, créez la classe CMqlParamObj, héritée de la classe de base CObject de la bibliothèque standard. Cette classe comprendra la structure MqlParam et fournira des méthodes pour définir et récupérer des données. En héritant de CObject, ces objets peuvent être stockés dans des listes CList.

Considérez l'ensemble de la classe :

//+------------------------------------------------------------------+
//| Structure parameter object class                                 |
//+------------------------------------------------------------------+
class CMqlParamObj : public CObject
  {
protected:
public:
   MqlParam          m_param;
//--- Set the parameters
   void              Set(const MqlParam &param)
                       {
                        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 &param) { this.Set(param);  }
                    ~CMqlParamObj(void){}
  };

Il s'agit d'une enveloppe commune autour de la structure MqlParam, puisqu'elle nécessite un objet hérité de CObject afin de stocker ces objets dans une liste CList.

La structure des données créées sera la suivante :

  • Un objet de la classe CMqlParamObj représente une propriété, par exemple le prix d'une transaction, son volume ou l'heure d'ouverture,
  • Une seule liste CList représentera une ligne de tableau contenant toutes les propriétés d'une seule transaction,
  • La liste CList principale contient un ensemble de lignes (listes CList), chacune correspondant à une transaction, un ordre, une position ou toute autre entité.

Nous obtiendrons ainsi une structure similaire à un tableau de tableaux :

  • La liste principale CList est un "tableau de lignes",
  • Chaque liste CList imbriquée est un "tableau de cellules" (propriétés d'un objet).

Voici un exemple d'une telle structure de données pour une liste de transactions historiques :

  1. La liste principale CList stocke les lignes d'un tableau. Chaque ligne est une liste CList distincte.
  2. Listes imbriquées CList — chaque liste imbriquée représente une ligne d'un tableau et contient des objets de la classe CMqlParamObj qui stockent des propriétés. Par exemple :
    • première ligne : propriétés de la transaction n° 1 (prix, volume, heure d'ouverture, etc.),
    • deuxième ligne : propriétés de la transaction n° 2 (prix, volume, heure d'ouverture, etc.),
    • troisième ligne : propriétés de la transaction n° 3 (prix, volume, heure d'ouverture, etc.),
    • etc.
  3. Objets de propriété (CMqlParamObj) — chaque objet stocke une propriété, par exemple le prix d'une transaction ou son volume.

Après avoir formé la structure de données (listes CList), celle-ci peut être transmise à la méthode CreateTableModel (CList &list_param) du modèle de table. Cette méthode permet d'interpréter les données comme suit :

  • La liste principale CList est une liste de lignes du tableau,
  • Chaque liste CList imbriquée est constituée des cellules de chaque ligne,
  • Les objets CMqlParamObj à l'intérieur des listes imbriquées sont des valeurs de cellules.

Ainsi, sur la base de la liste transmise, un tableau correspondant entièrement aux données sources sera créé.

Pour faciliter la création de telles listes, une classe spéciale a été créée. Cette classe fournira des méthodes pour :

  1. Création d'une nouvelle ligne (de la liste CList) et ajout à la liste principale,
  2. Ajout d'une nouvelle propriété (l'objet CMqlParamObj) à la ligne.
//+------------------------------------------------------------------+
//| 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 &param)
                       {
                        CMqlParamObj *cell=new CMqlParamObj(param);
                        if(cell==NULL)
                           return false;
                        if(row.Add(cell)<0)
                          {
                           delete cell;
                           return false;
                          }
                        return true;
                       }
  };

Il s'agit d'une classe statique qui permet de créer des listes avec la structure correcte pour les transmettre aux méthodes de création de modèles de tableaux :

  1. Spécifier la propriété nécessaire (par exemple, le prix de l'échange),
  2. La classe crée automatiquement un objet CMqlParamObj, y inscrit la valeur de la propriété et l'ajoute à la ligne,
  3. La ligne est ajoutée à la liste principale.

Ensuite, la liste terminée peut être transmise au modèle de tableau pour être développée.

Par conséquent, l'approche développée permet de convertir des données provenant de n'importe quelle structure ou objet dans un format adapté à la construction d'un tableau. L'utilisation de listes CList et d'objets CMqlParamObj offre souplesse et commodité d'utilisation, et la classe auxiliaire DataListCreator simplifie le processus de création de ces listes. Ce modèle servira de base à la construction d'un modèle de tableau universel créé à partir de n'importe quelles données et pour n'importe quelle tâche.

Dans la classe des modèles de tableau, ajoutez 3 nouvelles méthodes pour la création d'un modèle de tableau, 3 méthodes pour la manipulation des colonnes. Au lieu de cinq constructeurs paramétriques surchargés, ajoutez un constructeur modèle et 3 nouveaux constructeurs basés sur les types de paramètres d'entrée des nouvelles méthodes de création d'un modèle de tableau:

//+------------------------------------------------------------------+
//| 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){}
  };

Envisageons de nouvelles méthodes.

Une méthode qui crée un modèle de tableau à partir du nombre de lignes et de colonnes spécifié.

//+------------------------------------------------------------------+
//| 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();
           }
            
        }
     }
  }

La méthode crée un modèle vide avec le nombre de lignes et de colonnes spécifié. Elle convient lorsque la structure du tableau est connue à l'avance, mais que les données sont censées être ajoutées ultérieurement.

Une méthode qui crée un modèle de tableau à partir de la matrice spécifiée

//+------------------------------------------------------------------+
//| 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]);
        }
     }
  }

Cette méthode permet d'utiliser une matrice de données pour initialiser un tableau, ce qui est pratique pour exploiter des ensembles de données préparés à l'avance.

Une méthode qui crée un modèle de tableau à partir d’une liste de paramètres

//+------------------------------------------------------------------+
//| 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; 
              }
           }
        }
     }
  }

Cette méthode permet de créer un modèle de tableau basé sur une liste chaînée, ce qui peut être utile pour exploiter des structures de données dynamiques.

Il convient de noter que lors de la création de cellules avec un type de données, par exemple double, la précision de l'affichage est tirée de la valeur longue de l'objet param de la classe CMqlParamObj. Cela signifie que lors de la création d'une structure de tableau à l'aide de la classe DataListCreator, comme nous l'avons vu plus haut, nous pouvons également transmettre les informations nécessaires à l'objet paramètre. Pour le résultat financier d'une transaction, on peut procéder comme suit :

//--- 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);

De la même manière, on peut passer des drapeaux d'affichage de l'heure pour les cellules de tableau de type datetime et un drapeau d'affichage du nom de la couleur pour une cellule de type color.

Le tableau affiche les types de cellules de tableau et les types de paramètres qui leur sont transmis via l'objet CMqlParamObj :

Type dans CMqlParamObj
Type de cellule double Type de cellule long
Type de cellule datetime
Type de cellule couleur
Type de cellule string
  double_value valeur de la cellule non utilisé drapeaux d'affichage de la date et de l'heure drapeau d’affichage du nom de la couleur non utilisé
  integer_value précision de la valeur de la cellule valeur de la cellule valeur de la cellule valeur de la cellule non utilisé
  string_value non utilisé non utilisé non utilisé non utilisé valeur de la cellule

Le tableau montre que lors de la création d'une structure de tableau à partir de certaines données, si ces données sont de type réel (écrites dans le champ de la structure MqlParam double_value), vous pouvez également écrire dans le champ integer_value la valeur de la précision avec laquelle les données seront affichées dans la cellule du tableau. Il en va de même pour les données de type datetime et color, mais les drapeaux sont écrits dans le champ double_value, puisque le champ integer est occupé par la valeur de la propriété elle-même.

Cette option est facultative. En même temps, les valeurs des drapeaux et de la précision dans la cellule seront mises à zéro. Cette valeur peut ensuite être modifiée à la fois pour une cellule spécifique et pour l'ensemble de la colonne du tableau.

Une méthode qui ajoute une nouvelle colonne à un tableau

//+------------------------------------------------------------------+
//| 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;
  }

L'index de la nouvelle colonne est transmis à la méthode. Tout d'abord, de nouvelles cellules sont ajoutées à la fin de la ligne dans toutes les lignes du tableau, puis, si l'indice transmis n'est pas négatif, toutes les nouvelles cellules sont décalées de l'indice spécifié.

Une méthode qui définit le type de données de la colonne

//+------------------------------------------------------------------+
//| 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);
     }
  }

Dans une boucle parcourant toutes les lignes du tableau, récupérez une cellule de chaque ligne par index et définissez un type de données pour cette cellule. Par conséquent, une valeur identique est définie dans les cellules de la colonne entière.

Une méthode qui définit la précision des données de la colonne

//+------------------------------------------------------------------+
//| 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);
     }
  }

Dans une boucle parcourant toutes les lignes du tableau, récupérez une cellule de chaque ligne par index et définissez un type de données pour cette cellule. Par conséquent, la même valeur est définie dans les cellules de la colonne entière.

Les méthodes ajoutées à la classe de modèle de tableau permettent d'utiliser un ensemble de cellules comme une colonne de tableau entière. Les colonnes d'un tableau ne peuvent être contrôlées que si le tableau possède un en-tête. Le tableau sera statique s’il n’a pas d'en-tête.


Classe d'en-tête de tableau Header

L'en-tête du tableau est une liste d'objets d'en-tête de colonne avec une valeur de chaîne. Ils sont situés dans une liste dynamique CListObj. La liste dynamique constitue la base de la classe d'en-tête de tableau.

Sur cette base, deux classes devront être créées :

  1. Classe de l'objet d'en-tête de la colonne du tableau.
    Il contient la valeur textuelle de l'en-tête, le numéro de la colonne, le type de données pour l'ensemble de la colonne et les méthodes de contrôle des cellules de la colonne.
  2. La classe d'en-tête du tableau.
    Il contient une liste d'objets d'en-tête de colonne et de méthodes d'accès pour contrôler les colonnes du tableau.

Continuez à écrire le code dans le même fichier \MQL5\Scripts\Tables\Tables.mqh et écrivez la classe d'en-tête de colonne de table :

//+------------------------------------------------------------------+
//| 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) {}
  };

Il s'agit d'une version très simplifiée de la classe des cellules de tableau. Considérons quelques méthodes de la classe.

Méthode virtuelle de comparaison de deux objets

//+------------------------------------------------------------------+
//| 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);
  }

La comparaison est effectuée par l'index de la colonne pour laquelle l'en-tête a été créé.

Méthode d'enregistrement dans un fichier

//+------------------------------------------------------------------+
//| 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éthode de téléchargement à partir d'un fichier

//+------------------------------------------------------------------+
//| 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;
  }

Des méthodes similaires ont été examinées en détail dans l'article précédent. La logique est exactement la même : les marqueurs de début de données et le type d'objet sont d'abord enregistrés. Puis, toutes ses propriétés, en termes d'éléments. La lecture se déroule dans le même ordre.

Une méthode qui renvoie la description d'un objet

//+------------------------------------------------------------------+
//| 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()));
  }

Une chaîne de description est créée et renvoyée au format (Object Type : Colonne XX, Value "Valeur")

Méthode permettant d'afficher la description de l'objet dans le journal

//+------------------------------------------------------------------+
//| Display the object description in the journal                    |
//+------------------------------------------------------------------+
void CColumnCaption::Print(void)
  {
   ::Print(this.Description());
  }

Elle se contente d'imprimer la description de l'en-tête dans le journal.

Ces objets doivent maintenant être placés dans la liste qui constituera l'en-tête du tableau. Écrire la classe d'en-tête de tableau :

//+------------------------------------------------------------------+
//| 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){}
  };

Considérons les méthodes de classe.

Une méthode qui crée un nouvel en-tête et l'ajoute à la fin de la liste des en-têtes de colonnes.

//+------------------------------------------------------------------+
//| 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;
  }

Le texte de l'en-tête est transmis à la méthode. Un nouvel objet d'en-tête de colonne est créé avec le texte spécifié et un index égal au nombre d'en-têtes dans la liste. Il s'agit de l'indice du dernier en-tête. Ensuite, l'objet créé est placé à la fin de la liste des en-têtes de colonne et un pointeur sur l'en-tête créé est renvoyé.

Une méthode qui ajoute l'en-tête spécifié à la fin de la liste

//+------------------------------------------------------------------+
//| 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;
  }

Un pointeur sur l'objet d'en-tête de colonne est transmis à la méthode. Il doit être placé à la fin de la liste d'en-têtes. La méthode renvoie le résultat de l'ajout d'un objet à la liste.

Une méthode qui crée un en-tête de tableau à partir d'un tableau de chaînes de caractères.

//+------------------------------------------------------------------+
//| 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]);
  }

Un tableau d'en-têtes est transmis à la méthode. La taille du tableau détermine le nombre d'objets d'en-tête de colonne à créer, qui sont créés lors du passage en boucle des valeurs des textes d'en-tête dans le tableau.

Une méthode qui fixe une valeur à l'en-tête spécifié d'une colonne

//+------------------------------------------------------------------+
//| 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);
  }

Cette méthode vous permet de définir une nouvelle valeur de texte telle que spécifiée par l'index de l'en-tête.

Une méthode qui définit un type de données pour l'en-tête de colonne spécifié

//+------------------------------------------------------------------+
//| 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);
  }

Cette méthode permet de définir une nouvelle valeur des données stockées dans la colonne indiquée par l'index de l'en-tête. Pour chaque colonne du tableau, vous pouvez définir le type de données stockées dans les cellules de la colonne. La définition d'un type de données pour l'objet d'en-tête permet de définir ultérieurement la même valeur pour l'ensemble de la colonne. Et vous pouvez lire la valeur des données de la colonne entière en lisant cette valeur dans l'en-tête de cette colonne.

Une méthode qui renvoie le type de données de l'en-tête de colonne spécifié.

//+------------------------------------------------------------------+
//| 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);
  }

Cette méthode permet de récupérer une valeur des données stockées dans la colonne par l'index de l'en-tête. L'obtention de la valeur de l'en-tête permet de connaître le type de valeurs stockées dans toutes les cellules de cette colonne du tableau.

Une méthode qui supprime l'en-tête de la colonne spécifiée

//+------------------------------------------------------------------+
//| 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;
  }

L'objet à l'index spécifié est supprimé de la liste des en-têtes. Après la suppression de l'objet "en-tête de colonne", il est nécessaire de mettre à jour les index des objets restants de la liste.

Une méthode qui déplace un en-tête de colonne à la position spécifiée

//+------------------------------------------------------------------+
//| 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;
  }

Elle permet de déplacer l'en-tête de l'index spécifié vers une nouvelle position dans la liste.

Une méthode qui définit la position des colonnes pour tous les en-têtes

//+------------------------------------------------------------------+
//| 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));
     }
  }

Après avoir supprimé ou déplacé un objet de la liste, il est nécessaire de réattribuer des index à tous les autres objets de la liste afin que leurs index correspondent à la position réelle dans la liste. La méthode passe en revue tous les objets de la liste dans une boucle, obtient l'indice réel de chaque objet et le définit comme une propriété de l'objet.

Une méthode qui efface les données d'en-tête de colonne dans la liste

//+------------------------------------------------------------------+
//| 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();
     }
  }

Dans une boucle parcourant tous les objets d'en-tête de colonne de la liste, la méthode récupère chaque objet régulier et donne au texte de l'en-tête une valeur vide. Cela permet d'effacer complètement les en-têtes de chaque colonne du tableau.

Une méthode qui renvoie la description d'un objet

//+------------------------------------------------------------------+
//| Return the object description                                    |
//+------------------------------------------------------------------+
string CTableHeader::Description(void)
  {
   return(::StringFormat("%s: Captions total: %u",
                         TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.ColumnsTotal()));
  }

Une chaîne est créée et renvoyée au format (Object Type : Captions total: XX)

Méthode permettant d'afficher la description de l'objet dans le journal

//+------------------------------------------------------------------+
//| 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);
     }
  }

La méthode permet d'enregistrer la description des en-têtes sous forme de tableau et de liste d'en-têtes de colonnes.

Méthodes d'enregistrement dans un fichier et de téléchargement d'un en-tête à partir d'un fichier

//+------------------------------------------------------------------+
//| 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;
  }

La logique des méthodes est commentée dans le code et ne diffère en rien des méthodes similaires d'autres classes déjà créées pour la création de tableaux.

Nous avons tout ce qu'il faut pour commencer à assembler les classes de table. La classe de tableau doit être capable de construire une table basée sur son modèle et doit avoir un en-tête par lequel les colonnes de la table seront nommées. Si vous ne spécifiez pas l'en-tête du tableau, celui-ci sera construit uniquement en fonction du modèle, il sera statique et ses fonctionnalités seront limitées uniquement par la visualisation du tableau. Pour les tableaux simples, cela suffit amplement. Cependant, pour interagir avec l'utilisateur à l'aide du composant Contrôleur, l'en-tête du tableau doit être déterminé dans le tableau. Cela permettra de disposer d'un large éventail d'options pour contrôler les tableaux et leurs données. Mais nous ferons tout cela plus tard. Examinons maintenant les classes de tableaux.


Classes de tableaux

Continuez à écrire le code dans le même fichier et mettez en œuvre la classe tableau :

//+------------------------------------------------------------------+
//| 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);
  };

Les pointeurs vers l'en-tête et la table des modèles sont déclarés dans la classe. Pour construire un tableau, vous devez d'abord créer un modèle de tableau à partir des données transmises aux constructeurs de la classe. Le tableau peut générer automatiquement un en-tête avec des noms de colonnes dans le style de MS Excel, où chaque colonne se voit attribuer un nom composé de lettres latines.

L'algorithme de calcul des noms est le suivant :

  1. Noms à une lettre - les 26 premières colonnes sont indiquées par des lettres de "A" à "Z".

  2. Noms à deux lettres - après "Z", les colonnes sont désignées par une combinaison de deux lettres. La première lettre change plus lentement, et la seconde itère sur l'ensemble de l'alphabet. Par exemple :

    • "AA", "AB", "AC", ..., "AZ",
    • puis "BA", "BB", ..., "BZ",
    • etc.
  3. Noms à trois lettres - après "ZZ", les colonnes sont désignées par une combinaison de 3 lettres. Le principe est le même :

    • "AAA", "AAB", ..., "AAZ",
    • puis "ABA", "ABB", ..., "ABZ",
    • etc.
  4. Le principe général est que les noms de colonnes peuvent être considérés comme des nombres dans le système numérique de base 26, où "A" correspond à 1, "B" correspond à 2, ..., "Z" - 26. Par exemple :

    • "A" = 1,
    • "Z" = 26,
    • "AA" = 27 (1 * 26^1 + 1),
    • "AB" = 28 (1 * 26^1 + 2),
    • "BA" = 53 (2 * 26^1 + 1).

Ainsi, l'algorithme génère automatiquement des noms de colonnes, en les augmentant conformément au principe considéré. Le nombre maximal de colonnes dans Excel dépend de la version du programme (par exemple, dans Excel 2007 et les versions ultérieures, il y a 16 384 colonnes, se terminant par "XFD"). L'algorithme créé ici ne se limite pas à cette figure. Il peut donner des noms à un nombre de colonnes égal à 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;
  }

Les méthodes permettent de remplir un tableau de noms de colonnes de type Excel.

Considérons les constructeurs paramétriques d'une classe.

Un constructeur de modèle spécifiant un tableau de données à deux dimensions et un tableau d'en-têtes sous forme de chaîne de caractères.

//+-------------------------------------------------------------------+
//| 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);
  }

Un tableau de données de n'importe quel type est transmis au constructeur du modèle à partir de l'énumération ENUM_DATATYPE. Ensuite, il sera converti dans le type de données utilisé par les tableaux(double, long, datetime, color, string) pour créer un modèle de tableau et un tableau d'en-têtes de colonnes. Si le tableau d'en-têtes est vide, des en-têtes de type MS Excel seront créés.

Constructeur avec indication du nombre de lignes et de colonnes du tableau

//+-----------------------------------------------------------------------+
//| 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);
  }

Le constructeur crée un modèle de tableau vide avec un en-tête de type MS Excel.

Le constructeur se base sur une matrice de données et un tableau d'en-têtes de colonnes.

//+-----------------------------------------------------------------------+
//| 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);
  }

Une matrice de données de type double est transmise au constructeur pour créer un modèle de tableau et un tableau d'en-têtes de colonnes. Si le tableau d'en-têtes est vide, des en-têtes de type MS Excel seront créés.

Dans le destructeur de la classe, le modèle et l'en-tête du tableau sont détruits.

//+------------------------------------------------------------------+
//| 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éthode de comparaison de deux objets

//+------------------------------------------------------------------+
//| 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);
  }

Un identifiant peut être attribué à chaque table si le programme est censé créer plusieurs tables. Les tables du programme peuvent être identifiées par l'identifiant de l'ensemble, qui a par défaut la valeur -1. Si les tableaux créés sont placés dans des listes(CList, CArrayObj, etc.), la méthode de comparaison permet de comparer les tableaux par leurs identifiants pour les rechercher et les trier :

//+------------------------------------------------------------------+
//| 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);
  }

Une méthode qui copie un tableau de noms d'en-têtes

//+------------------------------------------------------------------+
//| 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);
  }

Un tableau d'en-têtes et le nombre de colonnes du modèle de table créé sont transmis à la méthode. Si le tableau ne comporte aucune colonne, il n'est pas nécessaire de créer des en-têtes. Ce fait est noté et la méthode renvoie false. S'il y a plus de colonnes dans le modèle de tableau que d'en-têtes dans le tableau transmis, tous les en-têtes seront créés dans le style Excel afin que le tableau ne comporte pas de colonnes sans légende dans les en-têtes.

Une méthode qui fixe une valeur à la cellule spécifiée

//+------------------------------------------------------------------+
//| 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);
  }

Nous nous référons ici à la méthode portant le même nom pour l'objet modèle de table.

Dans cette classe, de nombreuses méthodes sont essentiellement dupliquées à partir de la classe de modèle de tableau. Si le modèle est créé, sa méthode similaire d'obtention ou de définition de la propriété est appelée.

Méthode de fonctionnement des cellules de tableau

//+------------------------------------------------------------------+
//| 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éthodes de travail avec les lignes de tableau

//+------------------------------------------------------------------+
//| 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);
  }

Une méthode qui crée une nouvelle colonne et l'ajoute à la position spécifiée dans le tableau.

//+-----------------------------------------------------------------------+
//| 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);
  }

S'il n'y a pas de modèle de tableau, la méthode renvoie immédiatement une erreur. Si la colonne a été ajoutée avec succès au modèle de tableau, la méthode essaye d'ajouter l'en-tête approprié. Si le tableau n'a pas d'en-tête, la création d'une nouvelle colonne est renvoyée avec succès. S'il existe un en-tête, ajoute un nouvel en-tête de colonne et le déplace à la position spécifiée dans la liste.

Autres méthodes pour travailler avec des colonnes

//+------------------------------------------------------------------+
//| 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);
  }

Une méthode qui renvoie la description de l'objet

//+------------------------------------------------------------------+
//| 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()));
  }

Crée et renvoie la chaîne au format (Object Type : Rows total: XX, Columns total: XX)

Méthode permettant d'afficher la description de l'objet dans le journal

//+------------------------------------------------------------------+
//| 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);
        }
     }
  }

La méthode produit une description dans le journal, et en-dessous un tableau avec l'en-tête et les données.

Méthode d'enregistrement du tableau dans un fichier

//+------------------------------------------------------------------+
//| 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;
  }

L'enregistrement n'aboutira que si le modèle de tableau et son en-tête ont été créés. L'en-tête peut être vide, c'est-à-dire qu'il ne peut avoir aucune colonne, mais l'objet doit être créé.

Méthode de téléchargement du tableau à partir d'un fichier

//+------------------------------------------------------------------+
//| 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;
  }

Étant donné que le tableau stocke à la fois les données du modèle et celles de l'en-tête, dans cette méthode (si le modèle ou l'en-tête n'est pas créé dans le tableau), ils sont pré-créés, puis leurs données sont chargées à partir du fichier.

La classe de tableau simple est prête.

Envisagez maintenant la possibilité d'hériter d'une classe de tableau simple - créez une classe de table qui s'appuie sur les données enregistrées dans la 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) {}
  };

Ici, le type de table est renvoyé sous la forme OBJECT_TYPE_TABLE_BY_PARAM, et le modèle de tableau et l'en-tête sont construits dans le constructeur de la 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);
  }

Sur la base de cet exemple, il est possible de créer d'autres classes de tableaux, mais nous partons du principe que tout ce qui a été créé aujourd'hui est suffisant pour créer une grande variété de tableaux et un vaste ensemble de données possibles.

Testons tout ce que nous avons.


Test du résultat

Dans le dossier \MQL5\Scripts\Tables\, créez un nouveau script nommé TestEmptyTable.mq5, connectez-y le fichier de classe de tableau créé et créez un tableau 4x4 vide :

//+------------------------------------------------------------------+
//|                                               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;
  }

Le script produira la sortie suivante dans le journal :

Table: Rows total: 4, Columns total: 4:
|       n/n |         A |         B |         C |         D |
| 0         |           |           |           |           |
| 1         |           |           |           |           |
| 2         |           |           |           |           |
| 3         |           |           |           |           |

Ici, les en-têtes de colonne sont créés automatiquement dans le style MS Excel.

Ecrivez un autre 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;
  }

Suite à l'exécution du script, le tableau suivant s'affiche dans le journal :

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 |

Ici, les colonnes sont déjà titrées à partir du tableau d'en-têtes transmis au constructeur de la classe. Un tableau à deux dimensions représentant les données pour la création d'un tableau peut être de n'importe quel type de l'énumération ENUM_DATATYPE.

Tous les types sont automatiquement convertis en 5 types utilisés dans la classe de modèle de tableau : double, long, datetime, color et string.

Ecrivez le script \MQL5\Scripts\Tables\TestTableMatrix.mq5 pour tester le tableau sur des données matricielles :

//+------------------------------------------------------------------+
//|                                              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;
  }

Le résultat sera un tableau similaire à celui construit sur la base d'un tableau bidimensionnel 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 |

Maintenant, écrivez le script \MQL5\Scripts\Tables\TestDealsTable.mq5, dans lequel nous comptons toutes les transactions historiques, créons une table de transactions basée sur celles-ci et imprimons dans le journal :

//+------------------------------------------------------------------+
//|                                               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;
  }

En conséquence, un tableau de toutes les transactions avec une largeur de cellule de 19 caractères sera imprimé (par défaut dans la méthode Print de la classe de tableau) :

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] |

L'exemple ici montre les quatre premières et les quatre dernières transactions, mais il donne une idée du tableau imprimé dans le journal.

Tous les fichiers créés sont joints à l'article pour votre auto-apprentissage. Le fichier d'archive peut être décompressé dans le dossier du terminal et tous les fichiers seront placés dans le dossier souhaité : MQL5\Scripts\Tables.


Conclusion

Nous avons donc terminé le travail sur les composants de base du modèle de tableau (Model) dans le cadre de l'architecture MVC. Nous avons créé des classes pour travailler avec des tableaux et des en-têtes, et nous les avons également testées sur différents types de données : tableaux bidimensionnels, matrices et historique de trading.

Nous passons maintenant à la phase suivante, qui consiste à développer les composants Vue et Contrôleur. Dans MQL5, ces deux composantes sont étroitement liées grâce au système d'événements intégré qui permet aux objets de réagir aux actions de l'utilisateur.

Cela nous donne l'occasion de développer la visualisation du tableau (composant View) et son contrôle (composant Controller) en même temps. Cela simplifiera légèrement la mise en œuvre plutôt complexe et à plusieurs niveaux du composant View.

Tous les exemples et fichiers de l'article peuvent être téléchargés. Dans les prochains articles, nous créerons un composant Vue combiné à un Contrôleur pour mettre en œuvre un outil complet d'exploitation des tableaux dans MQL5.

Une fois le projet achevé, de nouvelles possibilités s'offriront à nous pour créer d'autres contrôles d'interface utilisateur à utiliser dans nos développements.


Programmes utilisés dans l'article :

#
Nom
Type
Description
 1  Tables.mqh Bibliothèque de classe Bibliothèque de classes pour la création de tableaux
 2  TestEmptyTable.mq5 Script Un script pour tester la création d'un tableau vide avec un nombre défini de lignes et de colonnes
 3  TestTArrayTable.mq5 Script Un script pour tester la création d'une table basée sur un tableau de données à deux dimensions
 4  TestMatrixTable.mq5 Script Un script pour tester la création d'un tableau basé sur une matrice de données
 5  TestDealsTable.mq5 Script Un script pour tester la création d'un tableau basé sur les données de l'utilisateur (transactions historiques)
 6  MQL5.zip Archive Une archive des fichiers présentés ci-dessus, à décompresser dans le répertoire MQL5 du terminal client

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/17803

Fichiers joints |
Tables.mqh (261.24 KB)
TestEmptyTable.mq5 (3.22 KB)
TestDealsTable.mq5 (24.63 KB)
MQL5.zip (30.86 KB)
Les méthodes de William Gann (première partie) : Création de l'indicateur Angles de Gann Les méthodes de William Gann (première partie) : Création de l'indicateur Angles de Gann
Quelle est l'essence de la théorie de Gann ? Comment sont construits les angles de Gann ? Nous allons créer l'indicateur Angles de Gann pour MetaTrader 5.
Implémentation d'un modèle de table dans MQL5 : Application du concept MVC Implémentation d'un modèle de table dans MQL5 : Application du concept MVC
Dans cet article, nous examinons le processus de développement d'un modèle de table dans MQL5 en utilisant le modèle architectural MVC (Modèle - Vue - Contrôleur) pour séparer la logique des données, la présentation et le contrôle, ce qui permet d'obtenir un code structuré, flexible et évolutif. Nous envisageons la mise en œuvre de classes pour la construction d'un modèle de table, y compris l'utilisation de listes chaînées pour le stockage des données.
Les méthodes de William Gann (Partie II) : Création de l'indicateur Carré de Gann (Gann Square) Les méthodes de William Gann (Partie II) : Création de l'indicateur Carré de Gann (Gann Square)
Nous allons créer un indicateur basé sur le Carré de 9 de Gann, construit en élevant le temps et le prix au carré. Nous allons préparer le code et tester l'indicateur dans la plateforme sur différents intervalles de temps.
Passer à MQL5 Algo Forge (Partie 4) : Travailler avec les versions et les mises à jour Passer à MQL5 Algo Forge (Partie 4) : Travailler avec les versions et les mises à jour
Nous continuerons à développer les projets ’Simple Candles’ et ’Adwizard’, tout en décrivant les aspects les plus fins de l'utilisation du système de contrôle de version et de dépôts MQL5 Algo Forge.