English Русский 中文 Português Italiano
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 |
69 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 <Arrays\List.mqh>

//--- Форвард-декларация классов
class CTableCell;                   // Класс ячейки таблицы
class CTableRow;                    // Класс строки таблицы
class CTableModel;                  // Класс модели таблицы
class CColumnCaption;               // Класс заголовка столбца таблицы
class CTableHeader;                 // Класс заголовка таблицы
class CTable;                       // Класс таблицы
class CTableByParam;                // Класс таблицы на основе массива параметров

//+------------------------------------------------------------------+
//| Макросы                                                          |
//+------------------------------------------------------------------+

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 :

//+------------------------------------------------------------------+
//| Макросы                                                          |
//+------------------------------------------------------------------+
#define  MARKER_START_DATA    -1    // Маркер начала данных в файле
#define  MAX_STRING_LENGTH    128   // Максимальная длина строки в ячейке
#define  CELL_WIDTH_IN_CHARS  19    // Ширина ячейки таблицы в символах

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_TYPE               // Перечисление типов объектов
  {
   OBJECT_TYPE_TABLE_CELL=10000,    // Ячейка таблицы
   OBJECT_TYPE_TABLE_ROW,           // Строка таблицы
   OBJECT_TYPE_TABLE_MODEL,         // Модель таблицы
   OBJECT_TYPE_COLUMN_CAPTION,      // Заголовок столбца таблицы
   OBJECT_TYPE_TABLE_HEADER,        // Заголовок таблицы
   OBJECT_TYPE_TABLE,               // Таблица
   OBJECT_TYPE_TABLE_BY_PARAM,      // Таблица на данных массива параметров
  };

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 :

//+------------------------------------------------------------------+
//| Метод создания элемента списка                                   |
//+------------------------------------------------------------------+
CObject *CListObj::CreateElement(void)
  {
//--- В зависимости от типа объекта в m_element_type, создаём новый объект
   switch(this.m_element_type)
     {
      case OBJECT_TYPE_TABLE_CELL      :  return new CTableCell();
      case OBJECT_TYPE_TABLE_ROW       :  return new CTableRow();
      case OBJECT_TYPE_TABLE_MODEL     :  return new CTableModel();
      case OBJECT_TYPE_COLUMN_CAPTION  :  return new CColumnCaption();
      case OBJECT_TYPE_TABLE_HEADER    :  return new CTableHeader();
      case OBJECT_TYPE_TABLE           :  return new CTable();
      case OBJECT_TYPE_TABLE_BY_PARAM  :  return new CTableByParam();
      default                          :  return NULL;
     }
  }

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:
//--- Объединение для хранения значений ячейки (double, long, string)
   union DataType
     {
      protected:
      double         double_value;
      long           long_value;
      ushort         ushort_value[MAX_STRING_LENGTH];

      public:
      //--- Установка значений
      void           SetValueD(const double value) { this.double_value=value;                   }
      void           SetValueL(const long value)   { this.long_value=value;                     }
      void           SetValueS(const string value) { ::StringToShortArray(value,ushort_value);  }
      
      //--- Возврат значений
      double         ValueD(void) const { return this.double_value; }
      long           ValueL(void) const { return this.long_value; }
      string         ValueS(void) const
                       {
                        string res=::ShortArrayToString(this.ushort_value);
                        res.TrimLeft();
                        res.TrimRight();
                        return res;
                       }
     };
//--- Переменные
   DataType          m_datatype_value;                      // Значение
   ENUM_DATATYPE     m_datatype;                            // Тип данных
   CObject          *m_object;                              // Объект в ячейке
   ENUM_OBJECT_TYPE  m_object_type;                         // Тип объекта в ячейке
   int               m_row;                                 // Номер строки
   int               m_col;                                 // Номер столбца
   int               m_digits;                              // Точность представления данных
   uint              m_time_flags;                          // Флаги отображения даты/времени
   bool              m_color_flag;                          // Флаг отображения наименования цвета
   bool              m_editable;                            // Флаг редактируемой ячейки
   
//--- Устанавливает "пустое значение"
   void              SetEmptyValue(void)
                       {
                        switch(this.m_datatype)
                          {
                           case TYPE_LONG    :  
                           case TYPE_DATETIME:  
                           case TYPE_COLOR   :  this.SetValue(LONG_MAX);   break;
                           case TYPE_DOUBLE  :  this.SetValue(DBL_MAX);    break;
                           default           :  this.SetValue("");         break;
                          }
                       }
public:
//--- Возврат координат и свойств ячейки

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:
//--- Возврат координат и свойств ячейки
   uint              Row(void)                           const { return this.m_row;                      }
   uint              Col(void)                           const { return this.m_col;                      }
   ENUM_DATATYPE     Datatype(void)                      const { return this.m_datatype;                 }
   int               Digits(void)                        const { return this.m_digits;                   }
   uint              DatetimeFlags(void)                 const { return this.m_time_flags;               }
   bool              ColorNameFlag(void)                 const { return this.m_color_flag;               }
   bool              IsEditable(void)                    const { return this.m_editable;                 }
//--- Возвращает (1) double, (2) long, (3) string значение
   double            ValueD(void)                        const { return this.m_datatype_value.ValueD();  }
   long              ValueL(void)                        const { return this.m_datatype_value.ValueL();  }
   string            ValueS(void)                        const { return this.m_datatype_value.ValueS();  }
//--- Возвращает значение в виде форматированной строки
   string            Value(void) const
                       {
                        switch(this.m_datatype)
                          {
                           case TYPE_DOUBLE  :  return(this.ValueD()!=DBL_MAX  ? ::DoubleToString(this.ValueD(),this.Digits())            : "");
                           case TYPE_LONG    :  return(this.ValueL()!=LONG_MAX ? ::IntegerToString(this.ValueL())                         : "");
                           case TYPE_DATETIME:  return(this.ValueL()!=LONG_MAX ? ::TimeToString(this.ValueL(),this.m_time_flags)          : "");
                           case TYPE_COLOR   :  return(this.ValueL()!=LONG_MAX ? ::ColorToString((color)this.ValueL(),this.m_color_flag)  : "");
                           default           :  return this.ValueS();
                          }
                       }
//--- Возвращает описание типа хранимого значения
   string            DatatypeDescription(void) const
                       {
                        string type=::StringSubstr(::EnumToString(this.m_datatype),5);
                        type.Lower();
                        return type;
                       }
//--- Очищает данные
   void              ClearData(void)                           { this.SetEmptyValue();                   }

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) Возвращает, (2) выводит в журнал описание объекта
   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 :

//+------------------------------------------------------------------+
//| Класс строки таблицы                                             |
//+------------------------------------------------------------------+
class CTableRow : public CObject
  {
protected:
   CTableCell        m_cell_tmp;                            // Объект ячейки для поиска в списке
   CListObj          m_list_cells;                          // Список ячеек
   uint              m_index;                               // Индекс строки
   
//--- Добавляет указанную ячейку в конец списка
   bool              AddNewCell(CTableCell *cell);
   
public:
//--- (1) Устанавливает, (2) возвращает индекс строки
   void              SetIndex(const uint index)                { this.m_index=index;  }
   uint              Index(void)                         const { return this.m_index; }
//--- Устанавливает позиции строки и колонки всем ячейкам
   void              CellsPositionUpdate(void);
   
//--- Создаёт новую ячейку и добавляет в конец списка
   CTableCell       *CellAddNew(const double value);
   CTableCell       *CellAddNew(const long value);
   CTableCell       *CellAddNew(const datetime value);
   CTableCell       *CellAddNew(const color value);
   CTableCell       *CellAddNew(const string value);

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 :

//+------------------------------------------------------------------+
//| Класс объекта параметра структуры                                |
//+------------------------------------------------------------------+
class CMqlParamObj : public CObject
  {
protected:
public:
   MqlParam          m_param;
//--- Установка параметров
   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;
                       }
//--- Возврат параметров
   MqlParam          Param(void)       const { return this.m_param;              }
   ENUM_DATATYPE     Datatype(void)    const { return this.m_param.type;         }
   double            ValueD(void)      const { return this.m_param.double_value; }
   long              ValueL(void)      const { return this.m_param.integer_value;}
   string            ValueS(void)      const { return this.m_param.string_value; }
//--- Описание объекта
   virtual string    Description(void)
                       {
                        string t=::StringSubstr(::EnumToString(this.m_param.type),5);
                        t.Lower();
                        string v="";
                        switch(this.m_param.type)
                          {
                           case TYPE_STRING  :  v=this.ValueS(); break;
                           case TYPE_FLOAT   :  case TYPE_DOUBLE : v=::DoubleToString(this.ValueD()); break;
                           case TYPE_DATETIME:  v=::TimeToString(this.ValueL(),TIME_DATE|TIME_MINUTES|TIME_SECONDS); break;
                           default           :  v=(string)this.ValueL(); break;
                          }
                        return(::StringFormat("<%s>%s",t,v));
                       }
   
//--- Конструкторы/деструктор
                     CMqlParamObj(void){}
                     CMqlParamObj(const MqlParam &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 DataListCreator
  {
public:
//--- Добавляет новую строку к списку CList list_data
   static CList     *AddNewRowToDataList(CList *list_data)
                       {
                        CList *row=new CList;
                        if(row==NULL || list_data.Add(row)<0)
                           return NULL;
                        return row;
                       }
//--- Создаёт новый объект параметров CMqlParamObj и добавляет его к списку CList
   static bool       AddNewCellParamToRow(CList *row,MqlParam &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:

//+------------------------------------------------------------------+
//| Класс модели таблицы                                             |
//+------------------------------------------------------------------+
class CTableModel : public CObject
  {
protected:
   CTableRow         m_row_tmp;                             // Объект строки для поиска в списке
   CListObj          m_list_rows;                           // Список строк таблицы
//--- Создаёт модель таблицы из двумерного массива
template<typename T>
   void              CreateTableModel(T &array[][]);
   void              CreateTableModel(const uint num_rows,const uint num_columns);
   void              CreateTableModel(const matrix &row_data);
   void              CreateTableModel(CList &list_param);
//--- Возвращает корректный тип данных
   ENUM_DATATYPE     GetCorrectDatatype(string type_name)
                       {
                        return
                          (
                           //--- Целочисленное значение
                           type_name=="bool" || type_name=="char"    || type_name=="uchar"   ||
                           type_name=="short"|| type_name=="ushort"  || type_name=="int"     ||
                           type_name=="uint" || type_name=="long"    || type_name=="ulong"   ?  TYPE_LONG      :
                           //--- Вещественное значение
                           type_name=="float"|| type_name=="double"                          ?  TYPE_DOUBLE    :
                           //--- Значение даты/времени
                           type_name=="datetime"                                             ?  TYPE_DATETIME  :
                           //--- Значение цвета
                           type_name=="color"                                                ?  TYPE_COLOR     :
                           /*--- Строковое значение */                                          TYPE_STRING    );
                       }
     
//--- Создаёт и добавляет новую пустую строку в конец списка
   CTableRow        *CreateNewEmptyRow(void);
//--- Добавляет строку в конец списка
   bool              AddNewRow(CTableRow *row);
//--- Устанавливает позиции строки и колонки всем ячейкам таблицы
   void              CellsPositionUpdate(void);
   
public:
//--- Возвращает (1) ячейку, (2) строку по индексу, количество (3) строк, ячеек (4) в указанной строке, (5) в таблице
   CTableCell       *GetCell(const uint row, const uint col);
   CTableRow        *GetRow(const uint index)                  { return this.m_list_rows.GetNodeAtIndex(index);   }
   uint              RowsTotal(void)                     const { return this.m_list_rows.Total();  }
   uint              CellsInRow(const uint index);
   uint              CellsTotal(void);

//--- Устанавливает (1) значение, (2) точность, (3) флаги отображения времени, (4) флаг отображения имён цветов в указанную ячейку
template<typename T>
   void              CellSetValue(const uint row, const uint col, const T value);
   void              CellSetDigits(const uint row, const uint col, const int digits);
   void              CellSetTimeFlags(const uint row, const uint col, const uint flags);
   void              CellSetColorNamesFlag(const uint row, const uint col, const bool flag);
//--- (1) Назначает, (2) отменяет объект в ячейке
   void              CellAssignObject(const uint row, const uint col,CObject *object);
   void              CellUnassignObject(const uint row, const uint col);
//--- (1) Удаляет (2) перемещает ячейку
   bool              CellDelete(const uint row, const uint col);
   bool              CellMoveTo(const uint row, const uint cell_index, const uint index_to);
   
//--- (1) Возвращает, (2) выводит в журнал описание ячейки, (3) назначенный в ячейку объект
   string            CellDescription(const uint row, const uint col);
   void              CellPrint(const uint row, const uint col);
   CObject          *CellGetObject(const uint row, const uint col);

public:
//--- Создаёт новую строку и (1) добавляет в конец списка, (2) вставляет в указанную позицию списка
   CTableRow        *RowAddNew(void);
   CTableRow        *RowInsertNewTo(const uint index_to);
//--- (1) Удаляет (2) перемещает строку, (3) очищает данные строки
   bool              RowDelete(const uint index);
   bool              RowMoveTo(const uint row_index, const uint index_to);
   void              RowClearData(const uint index);
//--- (1) Возвращает, (2) выводит в журнал описание строки
   string            RowDescription(const uint index);
   void              RowPrint(const uint index,const bool detail);
   
//--- (1) Добавляет, (2) удаляет (3) перемещает столбец, (4) очищает данные, устанавливает (5) тип, (6) точность данных столбца
   bool              ColumnAddNew(const int index=-1);
   bool              ColumnDelete(const uint index);
   bool              ColumnMoveTo(const uint col_index, const uint index_to);
   void              ColumnClearData(const uint index);
   void              ColumnSetDatatype(const uint index,const ENUM_DATATYPE type);
   void              ColumnSetDigits(const uint index,const int digits);
   
//--- (1) Возвращает, (2) выводит в журнал описание таблицы
   virtual string    Description(void);
   void              Print(const bool detail);
   void              PrintTable(const int cell_width=CELL_WIDTH_IN_CHARS);
   
//--- (1) Очищает данные, (2) уничтожает модель
   void              ClearData(void);
   void              Destroy(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_MODEL);  }
   
//--- Конструкторы/деструктор
template<typename T> CTableModel(T &array[][])                                { this.CreateTableModel(array);                 }
                     CTableModel(const uint num_rows,const uint num_columns)  { this.CreateTableModel(num_rows,num_columns);  }
                     CTableModel(const matrix &row_data)                      { this.CreateTableModel(row_data);              }
                     CTableModel(CList &row_data)                             { this.CreateTableModel(row_data);              }
                     CTableModel(void){}
                    ~CTableModel(void){}
  };

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é.

//+------------------------------------------------------------------+
//| Создаёт модель таблицы из указанного количества строк и столбцов |
//+------------------------------------------------------------------+
void CTableModel::CreateTableModel(const uint num_rows,const uint num_columns)
  {
//--- В цикле по количеству строк
   for(uint r=0; r<num_rows; r++)
     {
      //--- создаём новую пустую строку и добавляем её в конец списка строк
      CTableRow *row=this.CreateNewEmptyRow();
      //--- Если строка создана и добавлена в список,
      if(row!=NULL)
        {
         //--- В цикле по количеству столбцов 
         //--- создаём все ячейки, добавляя каждую новую в конец списка ячеек строки
         for(uint c=0; c<num_columns; c++)
           {
            CTableCell *cell=row.CellAddNew(0.0);
            if(cell!=NULL)
               cell.ClearData();
           }
            
        }
     }
  }

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

//+------------------------------------------------------------------+
//| Создаёт модель таблицы из указанной матрицы                      |
//+------------------------------------------------------------------+
void CTableModel::CreateTableModel(const matrix &row_data)
  {
//--- Количество строк и столбцов
   ulong num_rows=row_data.Rows();
   ulong num_columns=row_data.Cols();
//--- В цикле по количеству строк
   for(uint r=0; r<num_rows; r++)
     {
      //--- создаём новую пустую строку и добавляем её в конец списка строк
      CTableRow *row=this.CreateNewEmptyRow();
      //--- Если строка создана и добавлена в список,
      if(row!=NULL)
        {
         //--- В цикле по количеству столбцов
         //--- создаём все ячейки, добавляя каждую новую в конец списка ячеек строки
         for(uint c=0; c<num_columns; c++)
            row.CellAddNew(row_data[r][c]);
        }
     }
  }

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

//+------------------------------------------------------------------+
//| Создаёт модель таблицы из списка параметров                      |
//+------------------------------------------------------------------+
void CTableModel::CreateTableModel(CList &list_param)
  {
//--- Если передан пустой список - сообщаем об этом и уходим
   if(list_param.Total()==0)
     {
      ::PrintFormat("%s: Error. Empty list passed",__FUNCTION__);
      return;
     }
//--- Получаем указатель на первую строку таблицы для определения количества столбцов
//--- Если первую строку получить не удалось, или в ней нет ячеек - сообщаем об этом и уходим
   CList *first_row=list_param.GetFirstNode();
   if(first_row==NULL || first_row.Total()==0)
     {
      if(first_row==NULL)
         ::PrintFormat("%s: Error. Failed to get first row of list",__FUNCTION__);
      else
         ::PrintFormat("%s: Error. First row does not contain data",__FUNCTION__);
      return;
     }
//--- Количество строк и столбцов
   ulong num_rows=list_param.Total();
   ulong num_columns=first_row.Total();
//--- В цикле по количеству строк
   for(uint r=0; r<num_rows; r++)
     {
      //--- получаем очередную строку таблицы из списка list_param
      CList *col_list=list_param.GetNodeAtIndex(r);
      if(col_list==NULL)
         continue;
      //--- создаём новую пустую строку и добавляем её в конец списка строк
      CTableRow *row=this.CreateNewEmptyRow();
      //--- Если строка создана и добавлена в список,
      if(row!=NULL)
        {
         //--- В цикле по количеству столбцов
         //--- создаём все ячейки, добавляя каждую новую в конец списка ячеек строки
         for(uint c=0; c<num_columns; c++)
           {
            CMqlParamObj *param=col_list.GetNodeAtIndex(c);
            if(param==NULL)
               continue;

            //--- Объявляем указатель на ячейку и тип данных, которые будут в ней содержаться
            CTableCell *cell=NULL;
            ENUM_DATATYPE datatype=param.Datatype();
            //--- В зависимости от типа данных
            switch(datatype)
              {
               //--- вещественный тип данных
               case TYPE_FLOAT   :
               case TYPE_DOUBLE  :  cell=row.CellAddNew((double)param.ValueD());    // Создаём новую ячейку с double-данными и
                                    if(cell!=NULL)
                                       cell.SetDigits((int)param.ValueL());         // записываем точность отображаемых данных
                                    break;
               //--- тип данных datetime
               case TYPE_DATETIME:  cell=row.CellAddNew((datetime)param.ValueL());  // Создаём новую ячейку с datetime-данными и
                                    if(cell!=NULL)
                                       cell.SetDatetimeFlags((int)param.ValueD());  // записываем флаги отображения даты/времени
                                    break;
               //--- тип данных color
               case TYPE_COLOR   :  cell=row.CellAddNew((color)param.ValueL());     // Создаём новую ячейку с color-данными и
                                    if(cell!=NULL)
                                       cell.SetColorNameFlag((bool)param.ValueD()); // записваем флаг отображения наименования известных цветов
                                    break;
               //--- строковый тип данных
               case TYPE_STRING  :  cell=row.CellAddNew((string)param.ValueS());    // Создаём новую ячейку со string-данными
                                    break; 
               //--- целочисленный тип данных
               default           :  cell=row.CellAddNew((long)param.ValueL());      // Создаём новую ячейку с long-данными
                                    break; 
              }
           }
        }
     }
  }

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 :

//--- Финансовый результат сделки
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

//+------------------------------------------------------------------+
//| Добавляет столбец                                                |
//+------------------------------------------------------------------+
bool CTableModel::ColumnAddNew(const int index=-1)
  {
//--- Объявляем переменные
   CTableCell *cell=NULL;
   bool res=true;
//--- В цикле по количеству строк
   for(uint i=0;i<this.RowsTotal();i++)
     {
      //--- получаем очередную строку
      CTableRow *row=this.GetRow(i);
      if(row!=NULL)
        {
         //--- добавляем в конец строки ячейку с типом double
         cell=row.CellAddNew(0.0);
         if(cell==NULL)
            res &=false;
         //--- очищаем ячейку
         else
            cell.ClearData();
        }
     }
//--- Если передан индекс колонки не отрицательный - сдвигаем колонку на указанную позицию
   if(res && index>-1)
      res &=this.ColumnMoveTo(this.CellsInRow(0)-1,index);
//--- Возвращаем результат
   return res;
  }

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

//+------------------------------------------------------------------+
//| Устанавливает тип данных столбца                                 |
//+------------------------------------------------------------------+
void CTableModel::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type)
  {
//--- В цикле по всем строкам таблицы
   for(uint i=0;i<this.RowsTotal();i++)
     {
      //--- получаем из каждой строки ячейку с индексом столбца и устанавливаем тип данных
      CTableCell *cell=this.GetCell(i, index);
      if(cell!=NULL)
         cell.SetDatatype(type);
     }
  }

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

//+------------------------------------------------------------------+
//| Устанавливает точность данных столбца                            |
//+------------------------------------------------------------------+
void CTableModel::ColumnSetDigits(const uint index,const int digits)
  {
//--- В цикле по всем строкам таблицы
   for(uint i=0;i<this.RowsTotal();i++)
     {
      //--- получаем из каждой строки ячейку с индексом столбца и устанавливаем точность данных
      CTableCell *cell=this.GetCell(i, index);
      if(cell!=NULL)
         cell.SetDigits(digits);
     }
  }

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 :

//+------------------------------------------------------------------+
//| Класс заголовка столбца таблицы                                  |
//+------------------------------------------------------------------+
class CColumnCaption : public CObject
  {
protected:
//--- Переменные
   ushort            m_ushort_array[MAX_STRING_LENGTH];        // Массив символов заголовка
   uint              m_column;                                 // Номер столбца
   ENUM_DATATYPE     m_datatype;                               // Тип данных

public:
//--- (1) Устанавливает, (2) возвращает номер столбца
   void              SetColumn(const uint column)              { this.m_column=column;    }
   uint              Column(void)                        const { return this.m_column;    }

//--- (1) Устанавливает, (2) возвращает тип данных столбца
   ENUM_DATATYPE     Datatype(void)                      const { return this.m_datatype;  }
   void              SetDatatype(const ENUM_DATATYPE datatype) { this.m_datatype=datatype;}
   
//--- Очищает данные
   void              ClearData(void)                           { this.SetValue("");       }
   
//--- Устанавливает заголовок
   void              SetValue(const string value)
                       {
                        ::StringToShortArray(value,this.m_ushort_array);
                       }
//--- Возвращает текст заголовка
   string            Value(void) const
                       {
                        string res=::ShortArrayToString(this.m_ushort_array);
                        res.TrimLeft();
                        res.TrimRight();
                        return res;
                       }
   
//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(void);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_COLUMN_CAPTION);  }
   
   
//--- Конструкторы/деструктор
                     CColumnCaption(void) : m_column(0) { this.SetValue(""); }
                     CColumnCaption(const uint column,const string value) : m_column(column) { this.SetValue(value); }
                    ~CColumnCaption(void) {}
  };

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

//+------------------------------------------------------------------+
//| Сравнение двух объектов                                          |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Сохранение в файл                                                |
//+------------------------------------------------------------------+
bool CColumnCaption::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long))
      return(false);
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return(false);

   //--- Сохраняем номер столбца
   if(::FileWriteInteger(file_handle,this.m_column,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем значение
   if(::FileWriteArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array))
      return(false);
   
//--- Всё успешно
   return true;
  }

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

//+------------------------------------------------------------------+
//| Загрузка из файла                                                |
//+------------------------------------------------------------------+
bool CColumnCaption::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return(false);

   //--- Загружаем номер столбца
   this.m_column=::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем значение
   if(::FileReadArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array))
      return(false);
   
//--- Всё успешно
   return true;
  }

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

//+------------------------------------------------------------------+
//| Возвращает описание объекта                                      |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Выводит в журнал описание объекта                                |
//+------------------------------------------------------------------+
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 :

//+------------------------------------------------------------------+
//| Класс заголовка таблицы                                          |
//+------------------------------------------------------------------+
class CTableHeader : public CObject
  {
protected:
   CColumnCaption    m_caption_tmp;                         // Объект заголовка столбца для поиска в списке
   CListObj          m_list_captions;                       // Список заголовков столбцлв
   
//--- Добавляет указанный заголовок в конец списка
   bool              AddNewColumnCaption(CColumnCaption *caption);
//--- Создаёт заголовок таблицы из строкового массива
   void              CreateHeader(string &array[]);
//--- Устанавливает позицию столбца всем заголовкам столбцов
   void              ColumnPositionUpdate(void);
   
public:
//--- Создаёт новый заголовок и добавляет в конец списка
   CColumnCaption   *CreateNewColumnCaption(const string caption);
   
//--- Возвращает (1) заголовок по индексу, (2) количество заголовков столбцов
   CColumnCaption   *GetColumnCaption(const uint index)        { return this.m_list_captions.GetNodeAtIndex(index);  }
   uint              ColumnsTotal(void)                  const { return this.m_list_captions.Total();                }
   
//--- Устанавливает значение указанному заголовку столбца
   void              ColumnCaptionSetValue(const uint index,const string value);
   
//--- (1) Устанавливает, (2) возвращает тип данных для указанного заголовка столбца
   void              ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type);
   ENUM_DATATYPE     ColumnCaptionDatatype(const uint index);
   
//--- (1) Удаляет (2) перемещает заголовок столбца
   bool              ColumnCaptionDelete(const uint index);
   bool              ColumnCaptionMoveTo(const uint caption_index, const uint index_to);
   
//--- Очищает данные заголовков столбцов
   void              ClearData(void);

//--- Очищает список заголовков столбцов
   void              Destroy(void)                             { this.m_list_captions.Clear();                       }

//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_HEADER); }
   
//--- Конструкторы/деструктор
                     CTableHeader(void) {}
                     CTableHeader(string &array[]) { this.CreateHeader(array);   }
                    ~CTableHeader(void){}
  };

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.

//+------------------------------------------------------------------+
//| Создаёт новый заголовок и добавляет в конец списка               |
//+------------------------------------------------------------------+
CColumnCaption *CTableHeader::CreateNewColumnCaption(const string caption)
  {
//--- Создаём новый объект заголовка
   CColumnCaption *caption_obj=new CColumnCaption(this.ColumnsTotal(),caption);
   if(caption_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create new column caption at position %u",__FUNCTION__, this.ColumnsTotal());
      return NULL;
     }
//--- Добавляем созданный заголовок в конец списка
   if(!this.AddNewColumnCaption(caption_obj))
     {
      delete caption_obj;
      return NULL;
     }
//--- Возвращаем указатель на объект
   return caption_obj;
  }

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

//+------------------------------------------------------------------+
//| Добавляет заголовок в конец списка                               |
//+------------------------------------------------------------------+
bool CTableHeader::AddNewColumnCaption(CColumnCaption *caption)
  {
//--- Если передан пустой объект - сообщаем и возвращаем false
   if(caption==NULL)
     {
      ::PrintFormat("%s: Error. Empty CColumnCaption object passed",__FUNCTION__);
      return false;
     }
//--- Устанавливаем индекс заголовка в списке и добавляем созданный заголовок в конец списка
   caption.SetColumn(this.ColumnsTotal());
   if(this.m_list_captions.Add(caption)==WRONG_VALUE)
     {
      ::PrintFormat("%s: Error. Failed to add caption (%u) to list",__FUNCTION__,this.ColumnsTotal());
      return false;
     }
//--- Успешно
   return true;
  }

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.

//+------------------------------------------------------------------+
//| Создаёт заголовок таблицы из строкового массива                  |
//+------------------------------------------------------------------+
void CTableHeader::CreateHeader(string &array[])
  {
//--- Получаем из свойств массива количество столбцов таблицы
   uint total=array.Size();
//--- В цикле по размеру массива
//--- создаём все заголовки, добавляя каждый новый в конец списка
   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

//+------------------------------------------------------------------+
//| Устанавливает значение в указанный заголовок столбца             |
//+------------------------------------------------------------------+
void CTableHeader::ColumnCaptionSetValue(const uint index,const string value)
  {
//--- Получаем из списка нужный заголовок и записываем в него новое значение
   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é

//+------------------------------------------------------------------+
//| Устанавливает тип данных для указанного заголовка столбца        |
//+------------------------------------------------------------------+
void CTableHeader::ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type)
  {
//--- Получаем из списка нужный заголовок и записываем в него новое значение
   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é.

//+------------------------------------------------------------------+
//| Возвращает тип данных указанного заголовка столбца               |
//+------------------------------------------------------------------+
ENUM_DATATYPE CTableHeader::ColumnCaptionDatatype(const uint index)
  {
//--- Получаем из списка нужный заголовок и возвращаем из него тип данных столбца
   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

//+------------------------------------------------------------------+
//| Удаляет заголовок указанного столбца                             |
//+------------------------------------------------------------------+
bool CTableHeader::ColumnCaptionDelete(const uint index)
  {
//--- Удаляем заголовок в списке по индексу
   if(!this.m_list_captions.Delete(index))
      return false;
//--- Обновляем индексы для оставшихся заголовков в списке
   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

//+------------------------------------------------------------------+
//| Перемещает заголовок столбца на указанную позицию                |
//+------------------------------------------------------------------+
bool CTableHeader::ColumnCaptionMoveTo(const uint caption_index,const uint index_to)
  {
//--- Получаем нужный заголовок по индексу в списке, делая его текущим
   CColumnCaption *caption=this.GetColumnCaption(caption_index);
//--- Перемещаем текущий заголовок на указанную позицию в списке
   if(caption==NULL || !this.m_list_captions.MoveToIndex(index_to))
      return false;
//--- Обновляем индексы всех заголовков в списке
   this.ColumnPositionUpdate();
   return true;
  }

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

//+------------------------------------------------------------------+
//| Устанавливает позиции столбца всем заголовкам                    |
//+------------------------------------------------------------------+
void CTableHeader::ColumnPositionUpdate(void)
  {
//--- В цикле по всем заголовкам в списке
   for(int i=0;i<this.m_list_captions.Total();i++)
     {
      //--- получаем очередной заголовок и устанавливаем в него индекс столбца
      CColumnCaption *caption=this.GetColumnCaption(i);
      if(caption!=NULL)
         caption.SetColumn(this.m_list_captions.IndexOf(caption));
     }
  }

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

//+------------------------------------------------------------------+
//| Очищает данные заголовков столбцов в списке                      |
//+------------------------------------------------------------------+
void CTableHeader::ClearData(void)
  {
//--- В цикле по всем заголовкам в списке
   for(uint i=0;i<this.ColumnsTotal();i++)
     {
      //--- получаем очередной заголовок и устанавливаем в него пустое значение
      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

//+------------------------------------------------------------------+
//| Возвращает описание объекта                                      |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Выводит в журнал описание объекта                                |
//+------------------------------------------------------------------+
void CTableHeader::Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS)
  {
//--- Количество заголовков
   int total=(int)this.ColumnsTotal();
   
//--- Если вывод в табличном виде
   string res="";
   if(as_table)
     {
      //--- создаём строку таблицы из значений всех заголовков
      res="|";
      for(int i=0;i<total;i++)
        {
         CColumnCaption *caption=this.GetColumnCaption(i);
         if(caption==NULL)
            continue;
         res+=::StringFormat("%*s |",column_width,caption.Value());
        }
      //--- Выводим строку в журнал и уходим
      ::Print(res);
      return;
     }
     
//--- Выводим заголовок в виде описания строки
   ::Print(this.Description()+(detail ? ":" : ""));
   
//--- Если детализированное описание
   if(detail)
     {
      //--- В цикле по списку заголовков строки
      for(int i=0; i<total; i++)
        {
         //--- получаем текущий заголовок и добавляем в итоговую строку его описание
         CColumnCaption *caption=this.GetColumnCaption(i);
         if(caption!=NULL)
            res+="  "+caption.Description()+(i<total-1 ? "\n" : "");
        }
      //--- Выводим в журнал созданную в цикле строку
      ::Print(res);
     }
  }

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

//+------------------------------------------------------------------+
//| Сохранение в файл                                                |
//+------------------------------------------------------------------+
bool CTableHeader::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long))
      return(false);
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return(false);

//--- Сохраняем список заголовков
   if(!this.m_list_captions.Save(file_handle))
      return(false);
   
//--- Успешно
   return true;
  }
//+------------------------------------------------------------------+
//| Загрузка из файла                                                |
//+------------------------------------------------------------------+
bool CTableHeader::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return(false);

//--- Загружаем список заголовков
   if(!this.m_list_captions.Load(file_handle))
      return(false);
   
//--- Успешно
   return true;
  }

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 :

//+------------------------------------------------------------------+
//| Класс таблицы                                                    |
//+------------------------------------------------------------------+
class CTable : public CObject 
  {
private:
//--- Заполняет массив заголовков столбцов в стиле Excel
   bool              FillArrayExcelNames(const uint num_columns);
//--- Возвращает наименование столбца как в Excel
   string            GetExcelColumnName(uint column_number);
//--- Возвращает доступность заголовка
   bool              HeaderCheck(void) const { return(this.m_table_header!=NULL && this.m_table_header.ColumnsTotal()>0);  }
   
protected:
   CTableModel      *m_table_model;                               // Указатель на модель таблицы
   CTableHeader     *m_table_header;                              // Указатель на заголовок таблицы
   CList             m_list_rows;                                 // Список массивов параметров из полей структуры
   string            m_array_names[];                             // Массив заголовков столбцов
   int               m_id;                                        // Идентификатор таблицы
//--- Копирует массив наименований заголовков
   bool              ArrayNamesCopy(const string &column_names[],const uint columns_total);
   
public:
//--- (1) Устанавливает, (2) возвращает модель таблицы
   void              SetTableModel(CTableModel *table_model)      { this.m_table_model=table_model;      }
   CTableModel      *GetTableModel(void)                          { return this.m_table_model;           }
//--- (1) Устанавливает, (2) возвращает заголовок
   void              SetTableHeader(CTableHeader *table_header)   { this.m_table_header=m_table_header;  }
   CTableHeader     *GetTableHeader(void)                         { return this.m_table_header;          }

//--- (1) Устанавливает, (2) возвращает идентификатор таблицы
   void              SetID(const int id)                          { this.m_id=id;                        }
   int               ID(void)                               const { return this.m_id;                    }
   
//--- Очищает данные заголовков столбцов
   void              HeaderClearData(void)
                       {
                        if(this.m_table_header!=NULL)
                           this.m_table_header.ClearData();
                       }
//--- Удаляет заголовок таблицы
   void              HeaderDestroy(void)
                       {
                        if(this.m_table_header==NULL)
                           return;
                        this.m_table_header.Destroy();
                        this.m_table_header=NULL;
                       }
                       
//--- (1) Очищает все данные, (2) уничтожает модель таблицы и заголовок
   void              ClearData(void)
                       {
                        if(this.m_table_model!=NULL)
                           this.m_table_model.ClearData();
                       }
   void              Destroy(void)
                       {
                        if(this.m_table_model==NULL)
                           return;
                        this.m_table_model.Destroy();
                        this.m_table_model=NULL;
                       }
   
//--- Возвращает (1) заголовок, (2) ячейку, (3) строку по индексу, количество (4) строк, (5) столбцов, ячеек (6) в указанной строке, (7) в таблице
   CColumnCaption   *GetColumnCaption(const uint index)        { return(this.m_table_header!=NULL  ?  this.m_table_header.GetColumnCaption(index)  :  NULL);   }
   CTableCell       *GetCell(const uint row, const uint col)   { return(this.m_table_model!=NULL   ?  this.m_table_model.GetCell(row,col)          :  NULL);   }
   CTableRow        *GetRow(const uint index)                  { return(this.m_table_model!=NULL   ?  this.m_table_model.GetRow(index)             :  NULL);   }
   uint              RowsTotal(void)                     const { return(this.m_table_model!=NULL   ?  this.m_table_model.RowsTotal()               :  0);      }
   uint              ColumnsTotal(void)                  const { return(this.m_table_model!=NULL   ?  this.m_table_model.CellsInRow(0)             :  0);      }
   uint              CellsInRow(const uint index)              { return(this.m_table_model!=NULL   ?  this.m_table_model.CellsInRow(index)         :  0);      }
   uint              CellsTotal(void)                          { return(this.m_table_model!=NULL   ?  this.m_table_model.CellsTotal()              :  0);      }

//--- Устанавливает (1) значение, (2) точность, (3) флаги отображения времени, (4) флаг отображения имён цветов в указанную ячейку
template<typename T>
   void              CellSetValue(const uint row, const uint col, const T value);
   void              CellSetDigits(const uint row, const uint col, const int digits);
   void              CellSetTimeFlags(const uint row, const uint col, const uint flags);
   void              CellSetColorNamesFlag(const uint row, const uint col, const bool flag);
//--- (1) Назначает, (2) отменяет объект в ячейке
   void              CellAssignObject(const uint row, const uint col,CObject *object);
   void              CellUnassignObject(const uint row, const uint col);
//--- Возвращает строковое значение указанной ячейки
   virtual string    CellValueAt(const uint row, const uint col);

protected:
//--- (1) Удаляет (2) перемещает ячейку
   bool              CellDelete(const uint row, const uint col);
   bool              CellMoveTo(const uint row, const uint cell_index, const uint index_to);
   
public:
//--- (1) Возвращает, (2) выводит в журнал описание ячейки, (3) назначенный в ячейку объект
   string            CellDescription(const uint row, const uint col);
   void              CellPrint(const uint row, const uint col);
//---Возвращает (1) назначенный в ячейку объект, (2) тип назначенного в ячейку объекта
   CObject          *CellGetObject(const uint row, const uint col);
   ENUM_OBJECT_TYPE  CellGetObjType(const uint row, const uint col);
   
//--- Создаёт новую строку и (1) добавляет в конец списка, (2) вставляет в указанную позицию списка
   CTableRow        *RowAddNew(void);
   CTableRow        *RowInsertNewTo(const uint index_to);
//--- (1) Удаляет (2) перемещает строку, (3) очищает данные строки
   bool              RowDelete(const uint index);
   bool              RowMoveTo(const uint row_index, const uint index_to);
   void              RowClearData(const uint index);
//--- (1) Возвращает, (2) выводит в журнал описание строки
   string            RowDescription(const uint index);
   void              RowPrint(const uint index,const bool detail);
   
//--- (1) Добавляет новый, (2) удаляет, (3) перемещает столбец, (4) очищает данные столбца
   bool              ColumnAddNew(const string caption,const int index=-1);
   bool              ColumnDelete(const uint index);
   bool              ColumnMoveTo(const uint index, const uint index_to);
   void              ColumnClearData(const uint index);
   
//--- Устанавливает (1) значение указанному заголовку, (2) точность данных указанному столбцу
   void              ColumnCaptionSetValue(const uint index,const string value);
   void              ColumnSetDigits(const uint index,const int digits);
   
//--- (1) Устанавливает, (2) возвращает тип данных для указанного столбца
   void              ColumnSetDatatype(const uint index,const ENUM_DATATYPE type);
   ENUM_DATATYPE     ColumnDatatype(const uint index);
   
//--- (1) Возвращает, (2) выводит в журнал описание объекта
   virtual string    Description(void);
   void              Print(const int column_width=CELL_WIDTH_IN_CHARS);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                             const { return(OBJECT_TYPE_TABLE);           }
   
//--- Конструкторы/деструктор
                     CTable(void) : m_table_model(NULL), m_table_header(NULL) { this.m_list_rows.Clear();}
template<typename T> CTable(T &row_data[][],const string &column_names[]);
                     CTable(const uint num_rows, const uint num_columns);
                     CTable(const matrix &row_data,const string &column_names[]);
                    ~CTable (void);
  };

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 :

//+------------------------------------------------------------------+
//| Возвращает наименование столбца как в Excel                      |
//+------------------------------------------------------------------+
string CTable::GetExcelColumnName(uint column_number)
  {
   string column_name="";
   uint index=column_number;

//--- Проверяем, что номер столбца больше 0
   if(index==0)
      return (__FUNCTION__+": Error. Invalid column number passed");
   
//--- Преобразование номера в название столбца
   while(!::IsStopped() && index>0)
     {
      index--;                                           // Уменьшаем номер на 1, чтобы сделать его 0-индексным
      uint  remainder =index % 26;                       // Остаток от деления на 26
      uchar char_code ='A'+(uchar)remainder;             // Рассчитываем код символа (буквы)
      column_name=::CharToString(char_code)+column_name; // Добавляем букву в начало строки
      index/=26;                                         // Переходим к следующему разряду
     }
   return column_name;
  }
//+------------------------------------------------------------------+
//| Заполняет массив заголовков столбцов в стиле Excel               |
//+------------------------------------------------------------------+
bool CTable::FillArrayExcelNames(const uint num_columns)
  {
   ::ResetLastError();
   if(::ArrayResize(this.m_array_names,num_columns,num_columns)!=num_columns)
     {
      ::PrintFormat("%s: ArrayResize() failed. Error %d",__FUNCTION__,::GetLastError());
      return false;
     }
   for(int i=0;i<(int)num_columns;i++)
      this.m_array_names[i]=this.GetExcelColumnName(i+1);

   return true;
  }

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.

//+-------------------------------------------------------------------+
//| Конструктор с указанием массива таблицы и массива заголовков.     | 
//| Определяет количество и наименования колонок согласно column_names|
//| Количество строк определены размером массива данных row_data,     |
//| который используется и для заполнения таблицы                     |
//+-------------------------------------------------------------------+
template<typename T>
CTable::CTable(T &row_data[][],const string &column_names[]) : m_id(-1)
  {
   this.m_table_model=new CTableModel(row_data);
   if(column_names.Size()>0)
      this.ArrayNamesCopy(column_names,row_data.Range(1));
   else
     {
      ::PrintFormat("%s: An empty array names was passed. The header array will be filled in Excel style (A, B, C)",__FUNCTION__);
      this.FillArrayExcelNames((uint)::ArrayRange(row_data,1));
     }
   this.m_table_header=new CTableHeader(this.m_array_names);
  }

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

//+------------------------------------------------------------------+
//| Конструктор таблицы с определением количества колонок и строк.   |
//| Колонки будут иметь Excel-наименования "A", "B", "C" и т.д.      |
//+------------------------------------------------------------------+
CTable::CTable(const uint num_rows,const uint num_columns) : m_table_header(NULL), m_id(-1)
  {
   this.m_table_model=new CTableModel(num_rows,num_columns);
   if(this.FillArrayExcelNames(num_columns))
      this.m_table_header=new CTableHeader(this.m_array_names);
  }

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.

//+-------------------------------------------------------------------+
//| Конструктор таблицы с инициализацией колонок согласно column_names|
//| Количество строк определены параметром row_data, с типом matrix   |
//+-------------------------------------------------------------------+
CTable::CTable(const matrix &row_data,const string &column_names[]) : m_id(-1)
  {
   this.m_table_model=new CTableModel(row_data);
   if(column_names.Size()>0)
      this.ArrayNamesCopy(column_names,(uint)row_data.Cols());
   else
     {
      ::PrintFormat("%s: An empty array names was passed. The header array will be filled in Excel style (A, B, C)",__FUNCTION__);
      this.FillArrayExcelNames((uint)row_data.Cols());
     }
   this.m_table_header=new CTableHeader(this.m_array_names);
  }

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.

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Сравнение двух объектов                                          |
//+------------------------------------------------------------------+
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 :

//+------------------------------------------------------------------+
//| Сравнение двух объектов                                          |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Копирует массив наименований заголовков                          |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Устанавливает значение в указанную ячейку                        |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Устанавливает точность в указанную ячейку                        |
//+------------------------------------------------------------------+
void CTable::CellSetDigits(const uint row, const uint col, const int digits)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.CellSetDigits(row,col,digits);
  }
//+------------------------------------------------------------------+
//| Устанавливает флаги отображения времени в указанную ячейку       |
//+------------------------------------------------------------------+
void CTable::CellSetTimeFlags(const uint row, const uint col, const uint flags)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.CellSetTimeFlags(row,col,flags);
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг отображения имён цветов в указанную ячейку    |
//+------------------------------------------------------------------+
void CTable::CellSetColorNamesFlag(const uint row, const uint col, const bool flag)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.CellSetColorNamesFlag(row,col,flag);
  }
//+------------------------------------------------------------------+
//| Назначает объект в ячейку                                        |
//+------------------------------------------------------------------+
void CTable::CellAssignObject(const uint row, const uint col,CObject *object)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.CellAssignObject(row,col,object);
  }
//+------------------------------------------------------------------+
//| Отменяет объект в ячейке                                         |
//+------------------------------------------------------------------+
void CTable::CellUnassignObject(const uint row, const uint col)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.CellUnassignObject(row,col);
  }
//+------------------------------------------------------------------+
//| Возвращает строковое значение указанной ячейки                   |
//+------------------------------------------------------------------+
string CTable::CellValueAt(const uint row,const uint col)
  {
   CTableCell *cell=this.GetCell(row,col);
   return(cell!=NULL ? cell.Value() : "");
  }
//+------------------------------------------------------------------+
//| Удаляет ячейку                                                   |
//+------------------------------------------------------------------+
bool CTable::CellDelete(const uint row, const uint col)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.CellDelete(row,col) : false);
  }
//+------------------------------------------------------------------+
//| Перемещает ячейку                                                |
//+------------------------------------------------------------------+
bool CTable::CellMoveTo(const uint row, const uint cell_index, const uint index_to)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.CellMoveTo(row,cell_index,index_to) : false);
  }
//+------------------------------------------------------------------+
//| Возвращает назначенный в ячейку объект                           |
//+------------------------------------------------------------------+
CObject *CTable::CellGetObject(const uint row, const uint col)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.CellGetObject(row,col) : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает тип назначенного в ячейку объекта                     |
//+------------------------------------------------------------------+
ENUM_OBJECT_TYPE CTable::CellGetObjType(const uint row,const uint col)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.CellGetObjType(row,col) : (ENUM_OBJECT_TYPE)WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает описание ячейки                                       |
//+------------------------------------------------------------------+
string CTable::CellDescription(const uint row, const uint col)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.CellDescription(row,col) : "");
  }
//+------------------------------------------------------------------+
//| Выводит в журнал описание ячейки                                 |
//+------------------------------------------------------------------+
void CTable::CellPrint(const uint row, const uint col)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.CellPrint(row,col);
  }

Méthodes de travail avec les lignes de tableau

//+------------------------------------------------------------------+
//| Создаёт новую строку и добавляет в конец списка                  |
//+------------------------------------------------------------------+
CTableRow *CTable::RowAddNew(void)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.RowAddNew() : NULL);
  }
//+------------------------------------------------------------------+
//| Создаёт новую строку и вставляет в указанную позицию списка      |
//+------------------------------------------------------------------+
CTableRow *CTable::RowInsertNewTo(const uint index_to)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.RowInsertNewTo(index_to) : NULL);
  }
//+------------------------------------------------------------------+
//| Удаляет строку                                                   |
//+------------------------------------------------------------------+
bool CTable::RowDelete(const uint index)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.RowDelete(index) : false);
  }
//+------------------------------------------------------------------+
//| Перемещает строку                                                |
//+------------------------------------------------------------------+
bool CTable::RowMoveTo(const uint row_index, const uint index_to)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.RowMoveTo(row_index,index_to) : false);
  }
//+------------------------------------------------------------------+
//| Очищает данные строки                                            |
//+------------------------------------------------------------------+
void CTable::RowClearData(const uint index)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.RowClearData(index);
  }
//+------------------------------------------------------------------+
//| Возвращает описание строки                                       |
//+------------------------------------------------------------------+
string CTable::RowDescription(const uint index)
  {
   return(this.m_table_model!=NULL ? this.m_table_model.RowDescription(index) : "");
  }
//+------------------------------------------------------------------+
//| Выводит в журнал описание строки                                 |
//+------------------------------------------------------------------+
void CTable::RowPrint(const uint index,const bool detail)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.RowPrint(index,detail);
  }

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

//+------------------------------------------------------------------+
//| Создаёт новый столбец и добавляет его в указанную позицию таблицы|
//+------------------------------------------------------------------+
bool CTable::ColumnAddNew(const string caption,const int index=-1)
  {
//--- Если нет модели таблицы, либо ошибка добавления нового столбца к модели - возвращаем false
   if(this.m_table_model==NULL || !this.m_table_model.ColumnAddNew(index))
      return false;
//--- Если нет заголовка - возвращаем true (столбец добавлен без заголовка)
   if(this.m_table_header==NULL)
      return true;
   
//--- Проверяем создание нового заголовка столбца и, если не создан - возвращаем false
   CColumnCaption *caption_obj=this.m_table_header.CreateNewColumnCaption(caption);
   if(caption_obj==NULL)
      return false;
//--- Если передан не отрицательный индекс - возвращаем результат перемещения заголовка на указанный индекс
//--- В ином случае уже всё готово - просто возвращаем true
   return(index>-1 ? this.m_table_header.ColumnCaptionMoveTo(caption_obj.Column(),index) : true);
  }

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

//+------------------------------------------------------------------+
//| Удаляет столбец                                                  |
//+------------------------------------------------------------------+
bool CTable::ColumnDelete(const uint index)
  {
   if(!this.HeaderCheck() || !this.m_table_header.ColumnCaptionDelete(index))
      return false;
   return this.m_table_model.ColumnDelete(index);
  }
//+------------------------------------------------------------------+
//| Перемещает столбец                                               |
//+------------------------------------------------------------------+
bool CTable::ColumnMoveTo(const uint index, const uint index_to)
  {
   if(!this.HeaderCheck() || !this.m_table_header.ColumnCaptionMoveTo(index,index_to))
      return false;
   return this.m_table_model.ColumnMoveTo(index,index_to);
  }
//+------------------------------------------------------------------+
//| Очищает данные столбца                                           |
//+------------------------------------------------------------------+
void CTable::ColumnClearData(const uint index)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.ColumnClearData(index);
  }
//+------------------------------------------------------------------+
//| Устанавливает значение указанному заголовку                      |
//+------------------------------------------------------------------+
void CTable::ColumnCaptionSetValue(const uint index,const string value)
  {
   CColumnCaption *caption=this.m_table_header.GetColumnCaption(index);
   if(caption!=NULL)
      caption.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Устанавливает тип данных для указанного столбца                  |
//+------------------------------------------------------------------+
void CTable::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type)
  {
//--- Если модель таблицы есть - устанавливаем тип данных для столбца
   if(this.m_table_model!=NULL)
      this.m_table_model.ColumnSetDatatype(index,type);
//--- Если заголовок есть - устанавливаем тип данных для заголовка
   if(this.m_table_header!=NULL)
      this.m_table_header.ColumnCaptionSetDatatype(index,type);
  }
//+------------------------------------------------------------------+
//| Устанавливает точность данных указанному столбцу                 |
//+------------------------------------------------------------------+
void CTable::ColumnSetDigits(const uint index,const int digits)
  {
   if(this.m_table_model!=NULL)
      this.m_table_model.ColumnSetDigits(index,digits);
  }
//+------------------------------------------------------------------+
//| Возвращает тип данных для указанного столбца                     |
//+------------------------------------------------------------------+
ENUM_DATATYPE CTable::ColumnDatatype(const uint index)
  {
   return(this.m_table_header!=NULL ? this.m_table_header.ColumnCaptionDatatype(index) : (ENUM_DATATYPE)WRONG_VALUE);
  }

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

//+------------------------------------------------------------------+
//| Возвращает описание объекта                                      |
//+------------------------------------------------------------------+
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

//+------------------------------------------------------------------+
//| Выводит в журнал описание объекта                                |
//+------------------------------------------------------------------+
void CTable::Print(const int column_width=CELL_WIDTH_IN_CHARS)
  {
   if(this.HeaderCheck())
     {
      //--- Выводим заголовок в виде описания строки
      ::Print(this.Description()+":");
        
      //--- Количество заголовков
      int total=(int)this.ColumnsTotal();
      
      string res="";
      //--- создаём строку из значений всех заголовков столбцов таблицы
      res="|";
      for(int i=0;i<total;i++)
        {
         CColumnCaption *caption=this.GetColumnCaption(i);
         if(caption==NULL)
            continue;
         res+=::StringFormat("%*s |",column_width,caption.Value());
        }
      //--- Дополняем строку слева заголовком
      string hd="|";
      hd+=::StringFormat("%*s ",column_width,"n/n");
      res=hd+res;
      //--- Выводим строку заголовка в журнал
      ::Print(res);
     }
     
//--- Пройдём в цикле по всем строкам таблицы и распечатаем их в табличном виде
   for(uint i=0;i<this.RowsTotal();i++)
     {
      CTableRow *row=this.GetRow(i);
      if(row!=NULL)
        {
         //--- создаём строку таблицы из значений всех ячеек
         string head=" "+(string)row.Index();
         string res=::StringFormat("|%-*s |",column_width,head);
         for(int i=0;i<(int)row.CellsTotal();i++)
           {
            CTableCell *cell=row.GetCell(i);
            if(cell==NULL)
               continue;
            res+=::StringFormat("%*s |",column_width,cell.Value());
           }
         //--- Выводим строку в журнал
         ::Print(res);
        }
     }
  }

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

//+------------------------------------------------------------------+
//| Сохранение в файл                                                |
//+------------------------------------------------------------------+
bool CTable::Save(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long))
      return(false);
//--- Сохраняем тип объекта
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return(false);
      
//--- Сохраняем идентификатор
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return(false);
//--- Проверяем модель таблицы
   if(this.m_table_model==NULL)
      return false;
//--- Сохраняем модель таблицы
   if(!this.m_table_model.Save(file_handle))
      return(false);

//--- Проверяем заголовок таблицы
   if(this.m_table_header==NULL)
      return false;
//--- Сохраняем заголовок таблицы
   if(!this.m_table_header.Save(file_handle))
      return(false);
   
//--- Успешно
   return true;
  }

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

//+------------------------------------------------------------------+
//| Загрузка из файла                                                |
//+------------------------------------------------------------------+
bool CTable::Load(const int file_handle)
  {
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Загружаем тип объекта
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return(false);

//--- Загружаем идентификатор
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Проверяем модель таблицы
   if(this.m_table_model==NULL && (this.m_table_model=new CTableModel())==NULL)
      return(false);
//--- Загружаем модель таблицы
   if(!this.m_table_model.Load(file_handle))
      return(false);

//--- Проверяем заголовок таблицы
   if(this.m_table_header==NULL && (this.m_table_header=new CTableHeader())==NULL)
      return false;
//--- Загружаем заголовок таблицы
   if(!this.m_table_header.Load(file_handle))
      return(false);
   
//--- Успешно
   return true;
  }

É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 CTableByParam : public CTable
  {
public:
   virtual int       Type(void)  const { return(OBJECT_TYPE_TABLE_BY_PARAM);  }
//--- Конструктор/деструктор
                     CTableByParam(void) { this.m_list_rows.Clear(); }
                     CTableByParam(CList &row_data,const string &column_names[]);
                    ~CTableByParam(void) {}
  };

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 :

//+------------------------------------------------------------------+
//| Конструктор с указанием массива таблицы на основе списка row_data|
//| содержащего объекты с данными полей структуры.                   | 
//| Определяет количество и наименования колонок согласно количеству |
//| наименований столбцов в массиве column_names                     |
//+------------------------------------------------------------------+
CTableByParam::CTableByParam(CList &row_data,const string &column_names[])
  {
//--- Копируем переданный список данных в переменную и
//--- создаём на основе этого списка модель таблицы
   this.m_list_rows=row_data;
   this.m_table_model=new CTableModel(this.m_list_rows);
   
//--- Копируем переданный список заголовков в m_array_names и
//--- создаём на основе этого списка заголовок таблицы
   this.ArrayNamesCopy(column_names,column_names.Size());
   this.m_table_header=new CTableHeader(this.m_array_names);
  }

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 "Tables.mqh"
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Создаём пустую таблицу 4x4
   CTable *table=new CTable(4,4);
   if(table==NULL)
      return;
//--- Распечатываем её в журнале и удаляем созданный объект
   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 "Tables.mqh"
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Объявляем и инициализируем double-массив 4x4
   double array[4][4]={{ 1,  2,  3,  4},
                       { 5,  6,  7,  8},
                       { 9, 10, 11, 12},
                       {13, 14, 15, 16}};
                       
//--- Объявляем и инициализируем массив заголовков столбцов
   string headers[]={"Column 1","Column 2","Column 3","Column 4"};
   
//--- Создаём таблицу на основе массива данных и массива заголовков
   CTable *table=new CTable(array,headers);
   if(table==NULL)
      return;
//--- Распечатываем таблицу в журнале и удаляем созданный объект
   table.Print(10);
   delete table;
  }

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 "Tables.mqh"
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Объявляем и инициализируем матрицу 4x4
   matrix row_data = {{ 1,  2,  3,  4},
                      { 5,  6,  7,  8},
                      { 9, 10, 11, 12},
                      {13, 14, 15, 16}};
                       
//--- Объявляем и инициализируем массив заголовков столбцов
   string headers[]={"Column 1","Column 2","Column 3","Column 4"};
   
//--- Создаём таблицу на основе матрицы и массива заголовков
   CTable *table=new CTable(row_data,headers);
   if(table==NULL)
      return;
//--- Распечатываем таблицу в журнале и удаляем созданный объект
   table.Print(10);
   delete table;
  }

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 "Tables.mqh"
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Объявляем список сделок, объект параметров сделки и структуру параметров
   CList rows_data;
   CMqlParamObj *cell=NULL;
   MqlParam param={};
   
//--- Выбираем всю историю
   if(!HistorySelect(0,TimeCurrent()))
      return;
      
//--- Создаём список сделок в массиве массивов (CList in CList)
//--- (одна строка - одна сделка, столбцы - объекты свойств сделки)
   int total=HistoryDealsTotal();
   for(int i=0;i<total;i++)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket==0)
         continue;
      
      //--- Добавляем к списку сделок новую строку свойств очередной сделки
      CList *row=DataListCreator::AddNewRowToDataList(&rows_data);
      if(row==NULL)
         continue;
      
      //--- Создаём "ячейки" с параметрами сделки и
      //--- добавляем их к созданной строке свойств сделки
      string symbol=HistoryDealGetString(ticket,DEAL_SYMBOL);
      int    digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
      
      //--- Время совершения сделки (столбец 0)
      param.type=TYPE_DATETIME;
      param.integer_value=HistoryDealGetInteger(ticket,DEAL_TIME);
      param.double_value=(TIME_DATE|TIME_MINUTES|TIME_SECONDS);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Имя символа (столбец 1)
      param.type=TYPE_STRING;
      param.string_value=symbol;
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Тикет сделки (столбец 2)
      param.type=TYPE_LONG;
      param.integer_value=(long)ticket;
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Ордер, на основание которого выполнена сделка (столбец 3)
      param.type=TYPE_LONG;
      param.integer_value=HistoryDealGetInteger(ticket,DEAL_ORDER);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Идентификатор позиции (столбец 4)
      param.type=TYPE_LONG;
      param.integer_value=HistoryDealGetInteger(ticket,DEAL_POSITION_ID);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Тип сделки (столбец 5)
      param.type=TYPE_STRING;
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE);
      param.integer_value=deal_type;
      
      string type="";
      switch(deal_type)
        {
         case DEAL_TYPE_BUY                     :  type="Buy";                      break;
         case DEAL_TYPE_SELL                    :  type="Sell";                     break;
         case DEAL_TYPE_BALANCE                 :  type="Balance";                  break;
         case DEAL_TYPE_CREDIT                  :  type="Credit";                   break;
         case DEAL_TYPE_CHARGE                  :  type="Charge";                   break;
         case DEAL_TYPE_CORRECTION              :  type="Correction";               break;
         case DEAL_TYPE_BONUS                   :  type="Bonus";                    break;
         case DEAL_TYPE_COMMISSION              :  type="Commission";               break;
         case DEAL_TYPE_COMMISSION_DAILY        :  type="Commission daily";         break;
         case DEAL_TYPE_COMMISSION_MONTHLY      :  type="Commission monthly";       break;
         case DEAL_TYPE_COMMISSION_AGENT_DAILY  :  type="Commission agent daily";   break;
         case DEAL_TYPE_COMMISSION_AGENT_MONTHLY:  type="Commission agent monthly"; break;
         case DEAL_TYPE_INTEREST                :  type="Interest";                 break;
         case DEAL_TYPE_BUY_CANCELED            :  type="Buy canceled";             break;
         case DEAL_TYPE_SELL_CANCELED           :  type="Sell canceled";            break;
         case DEAL_DIVIDEND                     :  type="Dividend";                 break;
         case DEAL_DIVIDEND_FRANKED             :  type="Dividend franked";         break;
         case DEAL_TAX                          :  type="Tax";                      break;
         default                                :  break;
        }
      param.string_value=type;
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Направление сделки (столбец 6)
      param.type=TYPE_STRING;
      ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);
      param.integer_value=deal_entry;
      
      string entry="";
      switch(deal_entry)
        {
         case DEAL_ENTRY_IN      :  entry="In";    break;
         case DEAL_ENTRY_OUT     :  entry="Out";   break;
         case DEAL_ENTRY_INOUT   :  entry="InOut"; break;
         case DEAL_ENTRY_OUT_BY  :  entry="OutBy"; break;
         default                 :  break;
        }
      param.string_value=entry;
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Объем сделки (столбец 7)
      param.type=TYPE_DOUBLE;
      param.double_value=HistoryDealGetDouble(ticket,DEAL_VOLUME);
      param.integer_value=2;
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Цена сделки (столбец 8)
      param.type=TYPE_DOUBLE;
      param.double_value=HistoryDealGetDouble(ticket,DEAL_PRICE);
      param.integer_value=(param.double_value>0 ? digits : 1);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Уровень Stop Loss (столбец 9)
      param.type=TYPE_DOUBLE;
      param.double_value=HistoryDealGetDouble(ticket,DEAL_SL);
      param.integer_value=(param.double_value>0 ? digits : 1);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Уровень Take Profit (столбец 10)
      param.type=TYPE_DOUBLE;
      param.double_value=HistoryDealGetDouble(ticket,DEAL_TP);
      param.integer_value=(param.double_value>0 ? digits : 1);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Финансовый результат сделки (столбец 11)
      param.type=TYPE_DOUBLE;
      param.double_value=HistoryDealGetDouble(ticket,DEAL_PROFIT);
      param.integer_value=(param.double_value!=0 ? 2 : 1);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Magic number для сделки (столбец 12)
      param.type=TYPE_LONG;
      param.integer_value=HistoryDealGetInteger(ticket,DEAL_MAGIC);
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Причина или источник проведения сделки (столбец 13)
      param.type=TYPE_STRING;
      ENUM_DEAL_REASON deal_reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON);
      param.integer_value=deal_reason;
      
      string reason="";
      switch(deal_reason)
        {
         case DEAL_REASON_CLIENT          :  reason="Client";           break;
         case DEAL_REASON_MOBILE          :  reason="Mobile";           break;
         case DEAL_REASON_WEB             :  reason="Web";              break;
         case DEAL_REASON_EXPERT          :  reason="Expert";           break;
         case DEAL_REASON_SL              :  reason="SL";               break;
         case DEAL_REASON_TP              :  reason="TP";               break;
         case DEAL_REASON_SO              :  reason="StopOut";          break;
         case DEAL_REASON_ROLLOVER        :  reason="Rollover";         break;
         case DEAL_REASON_VMARGIN         :  reason="VMargin";          break;
         case DEAL_REASON_SPLIT           :  reason="Split";            break;
         case DEAL_REASON_CORPORATE_ACTION:  reason="Corporate action"; break;
         default                          :  break;
        }
      param.string_value=reason;
      DataListCreator::AddNewCellParamToRow(row,param);
      
      //--- Комментарий к сделке (столбец 14)
      param.type=TYPE_STRING;
      param.string_value=HistoryDealGetString(ticket,DEAL_COMMENT);
      DataListCreator::AddNewCellParamToRow(row,param);
     }
   
//--- Объявляем и инициализируем заголовок таблицы
   string headers[]={"Time","Symbol","Ticket","Order","Position","Type","Entry","Volume","Price","SL","TP","Profit","Magic","Reason","Comment"};
   
//--- Создаём таблицу на основе созданного списка параметров и массива заголовков
   CTableByParam *table=new CTableByParam(rows_data,headers);
   if(table==NULL)
      return;
//--- Распечатываем таблицу в журнале и удаляем созданный объект
   table.Print();
   delete table;
  }

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.
L'Histogramme des prix (Profile du Marché) et son implémentation  en MQL5 L'Histogramme des prix (Profile du Marché) et son implémentation en MQL5
Le Profile du Marché a été élaboré par le brillant penseur Peter Steidlmayer. Il a suggéré l’utilisation de la représentation alternative de l'information sur les mouvements de marché « horizontaux » et « verticaux » qui conduit à un ensemble de modèles complètement différent. Il a assumé qu'il existe une impulsion sous-jacente du marché ou un modèle fondamental appelé cycle d'équilibre et de déséquilibre. Dans cet article, j’examinerai l'Histogramme des Prix - un modèle simplifié de profil de marché, et décrirai son implémentation dans MQL5.
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.