English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
preview
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

MetaTrader 5Exemples |
1 577 9
Artyom Trishkin
Artyom Trishkin

Sommaire


Introduction

En programmation, l'architecture des applications joue un rôle clé pour garantir la fiabilité, l'évolutivité et la facilité d'assistance. L'une des approches permettant d'atteindre ces objectifs consiste à tirer parti d'un modèle d'architecture appelé MVC (Modèle-Vue-Contrôleur).

Le concept MVC permet de diviser une application en 3 composants interdépendants : le modèle (gestion des données et de la logique), la vue (affichage des données) et le contrôleur (traitement des actions de l'utilisateur). Cette séparation simplifie le développement, le test et la maintenance du code, le rendant plus structuré et plus flexible.

Dans cet article, nous examinons comment appliquer les principes MVC pour mettre en œuvre un modèle de tableau dans le langage MQL5. Les tableaux sont un outil important pour le stockage, le traitement et l'affichage des données, et une bonne organisation peut faciliter grandement le travail avec les informations. Nous allons créer des classes pour travailler avec des tableaux : cellules de tableau, lignes et modèle de tableau. Pour stocker les cellules dans les lignes et les lignes dans le modèle de tableau, nous utiliserons les classes de listes liées (linked list) de la bibliothèque standard MQL5 qui permettent un stockage et une utilisation efficaces des données.


Quelques mots sur le concept MVC : qu'est-ce que c'est et pourquoi le voulons-nous ?

Imaginez l'application comme une production théâtrale. Il existe un scénario qui décrit ce qui devrait se passer (c'est le modèle). Il y a la scène - ce que le spectateur voit (c'est la vue). Enfin, il y a le directeur qui gère l'ensemble du processus et relie les autres éléments (c'est le contrôleur). C'est ainsi que fonctionne le modèle architectural MVC (Modèle-Vue-Contrôleur).

Ce concept permet de séparer les responsabilités au sein de l'application. Le modèle est responsable des données et de la logique, la vue est responsable de l'affichage et de l'apparence, et le contrôleur est responsable du traitement des actions de l'utilisateur. Cette séparation rend le code plus clair, plus souple et plus pratique pour le travail en équipe.

Supposons que vous créiez un tableau. Le modèle connaît les lignes et les cellules qu'il contient et sait comment les modifier. La vue dessine le tableau à l'écran. Le contrôleur réagit lorsque l'utilisateur clique sur "Ajouter une ligne" et transmet la tâche au modèle, puis demande à la vue de se mettre à jour.

Le modèle MVC est particulièrement utile lorsque l'application devient plus complexe : de nouvelles fonctionnalités sont ajoutées, l'interface change et plusieurs développeurs travaillent. Avec une architecture claire, il est plus facile d'apporter des modifications, de tester les composants individuellement et de réutiliser le code.

Cette approche présente également quelques inconvénients. Pour les projets très simples, le modèle MVC peut être redondant : il faudra séparer même ce qui pourrait tenir dans quelques fonctions. Toutefois, pour les applications sérieuses et évolutives, cette structure s'avère rapidement payante.

En résumé :

Le modèle MVC est un modèle architectural puissant qui permet d'organiser le code, de le rendre plus compréhensible, testable et évolutif. Il est particulièrement utile pour les applications complexes où la séparation de la logique des données, de l'interface utilisateur et de la gestion est importante. Pour les petits projets, son utilisation est superflue.

Le paradigme Modèle-Vue-Contrôleur convient parfaitement à notre tâche. Le tableau sera créé à partir d'objets indépendants :

  • Cellule
    Un objet qui stocke une valeur de l'un de ces types : réel, entier ou chaîne - est équipé d'outils permettant de gérer la valeur, de la définir et de la récupérer
  • Ligne
    Un objet qui stocke une liste d'objets dans des cellules de tableau est doté d'outils de gestion des cellules, de leur emplacement, de l'ajout et de la suppression
  • Modèle de tableau
    Un objet qui stocke une liste d'objets de chaînes de tableau, est équipé d'outils permettant de gérer les chaînes de tableau et les colonnes, leur emplacement, l'ajout et la suppression, et a également accès aux contrôles des chaînes et des cellules

La figure ci-dessous montre schématiquement la structure d'un modèle de tableau 4x4 :

Fig. 1 : Modèle de tableau 4x4

Passons maintenant de la théorie à la pratique.


Écrire des classes pour construire un modèle de tableau

Nous utiliserons la bibliothèque standard MQL5 pour créer tous les objets.

Chaque objet hérite de la classe de basede la bibliothèque. Cela vous permettra de stocker ces objets dans des listes d'objets.

Nous écrirons toutes les classes dans un seul fichier de script de test afin que tout soit dans un seul fichier, visible et rapidement accessible. À l'avenir, nous distribuerons les classes écrites dans des fichiers include distincts.


1. Les listes chaînées comme base de stockage des données tabulaires

La liste chaînée CList est très bien adaptée au stockage de données tabulaires. Contrairement à la liste CArrayObj similaire, elle implémente des méthodes d'accès aux objets de liste voisins situés à gauche et à droite de l'objet actuel. Il sera ainsi facile de déplacer des cellules dans une ligne, ou de déplacer des lignes dans un tableau, d'en ajouter et d'en supprimer. Parallèlement, la liste elle-même se charge de l'indexation correcte des objets déplacés, ajoutés ou supprimés dans la liste.

Mais il y a une nuance à apporter. Si vous vous reportez aux méthodes de chargement et d'enregistrement d'une liste dans un fichier, vous constaterez que lors du chargement à partir d'un fichier, la classe de liste doit créer un nouvel objet dans la méthode virtuelle CreateElement().

Cette méthode de cette classe renvoie simplement NULL :

   //--- method of creating an element of the list
   virtual CObject  *CreateElement(void) { return(NULL); }

Cela signifie que pour travailler avec des listes chaînées, et à condition que nous ayons besoin d'opérations sur les fichiers, nous devons hériter de la classe CList et implémenter cette méthode dans notre classe.

Si vous examinez les méthodes d'enregistrement des objets de la bibliothèque standard dans un fichier, vous pouvez voir l'algorithme suivant pour l'enregistrement des propriétés des objets :

  1. Le marqueur de début de données (-1) est écrit dans le fichier,
  2. Le type d'objet est écrit dans le fichier,
  3. Toutes les propriétés de l'objet sont écrites dans le fichier une par une.

Les premier et deuxième points sont inhérents à toutes les méthodes de sauvegarde/chargement implémentées que possèdent les objets de la bibliothèque standard. Par conséquent, en suivant la même logique, nous voulons connaître le type de l'objet enregistré dans la liste, afin de pouvoir, lors de la lecture d'un fichier, créer un objet de ce type dans la méthode virtuelle CreateElement() de la classe de liste héritée de CList.

Plus tous les objets qui peuvent être chargés dans la liste — leurs classes doivent être déclarées ou créées avant que la classe de liste ne soit implémentée. Dans ce cas, la liste "saura" quels objets sont "en question" et lesquels doivent être créés.

Dans le répertoire du terminal \MQL5\Scripts\, créez un nouveau dossier TableModel\, et dans celui-ci, créez un nouveau fichier de script de test TableModelTest.mq5.

Connectez le fichier de listes chaînées et déclarez les futures classes de modèles de tableaux :

//+------------------------------------------------------------------+
//|                                               TableModelTest.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include <Arrays\List.mqh>

//--- Forward declaration of classes
class CTableCell;                   // Table cell class
class CTableRow;                    // Table row class
class CTableModel;                  // Table model class

La déclaration préalable des classes futures est nécessaire ici pour que la classe de liste chaînée qui hérite de CList connaisse ces types de classes, ainsi que les types d'objets qu'elle devra créer. Pour ce faire, nous écrirons des énumérations de types d'objets, des macros auxiliaires et des énumérations pour les façons de trier les listes :

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include <Arrays\List.mqh>

//--- Forward declaration of classes
class CTableCell;                   // Table cell class
class CTableRow;                    // Table row class
class CTableModel;                  // Table model class

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+
#define  MARKER_START_DATA    -1    // Data start marker in a file
#define  MAX_STRING_LENGTH    128   // Maximum length of a string in a cell

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_TYPE               // Enumeration of object types
  {
   OBJECT_TYPE_TABLE_CELL=10000,    // Table cell
   OBJECT_TYPE_TABLE_ROW,           // Table row
   OBJECT_TYPE_TABLE_MODEL,         // Table model
  };
  
enum ENUM_CELL_COMPARE_MODE         // Table cell comparison modes
  {
   CELL_COMPARE_MODE_COL,           // Comparison by column number
   CELL_COMPARE_MODE_ROW,           // Comparison by string number
   CELL_COMPARE_MODE_ROW_COL,       // Comparison by row and column
  };
  
//+------------------------------------------------------------------+
//| Functions                                                        |
//+------------------------------------------------------------------+
//--- Return the object type as a string
string TypeDescription(const ENUM_OBJECT_TYPE type)
  {
   string array[];
   int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array);
   string result="";
   for(int i=2;i<total;i++)
     {
      array[i]+=" ";
      array[i].Lower();
      array[i].SetChar(0,ushort(array[i].GetChar(0)-0x20));
      result+=array[i];
     }
   result.TrimLeft();
   result.TrimRight();
   return result;
  }
//+------------------------------------------------------------------+
//| Classes                                                          |
//+------------------------------------------------------------------+

La fonction qui renvoie la description du type d'objet est basée sur l'hypothèse que tous les noms des constantes de type objet commencent par la chaîne de caractères "OBJECT_TYPE_". Vous pouvez ensuite prendre la sous-chaîne qui suit celle-ci, convertir tous les caractères de la ligne résultante en minuscules, convertir le premier caractère en majuscules et supprimer tous les espaces et caractères de contrôle de la chaîne finale à gauche et à droite.

Écrivons notre propre classe de listes chaînées. Nous continuerons à écrire le code dans le même fichier :

//+------------------------------------------------------------------+
//| Classes                                                          |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Linked object list class                                         |
//+------------------------------------------------------------------+
class CListObj : public CList
  {
protected:
   ENUM_OBJECT_TYPE  m_element_type;   // Created object type in CreateElement()
public:
//--- Virtual method (1) for loading a list from a file, (2) for creating a list element
   virtual bool      Load(const int file_handle);
   virtual CObject  *CreateElement(void);
  };

La classe CListObj est notre nouvelle classe de liste chaînée, héritée de la classe CList de la bibliothèque standard.

La seule variable de la classe sera celle dans laquelle sera inscrit le type de l'objet créé. Comme la méthode CreateElement() est virtuelle et doit avoir exactement la même signature que la méthode de la classe mère, nous ne pouvons pas lui transmettre le type de l'objet en cours de création. Mais nous pouvons écrire ce type dans une variable déclarée et lire le type de l'objet créé à partir de cette variable.

Nous devons redéfinir 2 méthodes virtuelles de la classe mère : la méthode de chargement à partir d'un fichier et la méthode de création d'un nouvel objet. Examinons-les.

Méthode de chargement de la liste à partir d'un fichier :

//+------------------------------------------------------------------+
//| Load a list from the file                                        |
//+------------------------------------------------------------------+
bool CListObj::Load(const int file_handle)
  {
//--- Variables
   CObject *node;
   bool     result=true;
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Load and check the list start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Load and check the list type
   if(::FileReadInteger(file_handle,INT_VALUE)!=Type())
      return(false);
//--- Read the list size (number of objects)
   uint num=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Sequentially recreate the list elements by calling the Load() method of node objects
   this.Clear();
   for(uint i=0; i<num; i++)
     {
      //--- Read and check the object data start marker - 0xFFFFFFFFFFFFFFFF
      if(::FileReadLong(file_handle)!=MARKER_START_DATA)
         return false;
      //--- Read the object type
      this.m_element_type=(ENUM_OBJECT_TYPE)::FileReadInteger(file_handle,INT_VALUE);
      node=this.CreateElement();
      if(node==NULL)
         return false;
      this.Add(node);
      //--- Now the file pointer is offset relative to the beginning of the object marker by 12 bytes (8 - marker, 4 - type)
      //--- Set the pointer to the beginning of the object data and load the object properties from the file using the Load() method of the node element.
      if(!::FileSeek(file_handle,-12,SEEK_CUR))
         return false;
      result &=node.Load(file_handle);
     }
//--- Result
   return result;
  }

Ici, le début de la liste est d'abord contrôlé, ainsi que son type et sa taille, c'est-à-dire le nombre d'éléments de la liste ; puis, dans une boucle sur le nombre d'éléments, les marqueurs de début de données de chaque objet et son type sont lus à partir du fichier. Le type résultant est écrit dans la variable m_element_type et une méthode de création d'un nouvel élément est appelée. Dans cette méthode, un nouvel élément du type reçu est créé et écrit dans la variable pointeur node, qui est à son tour ajoutée à la liste. Toute la logique de la méthode est expliquée en détail dans les commentaires. Considérons une méthode de création d'un nouvel élément de liste.

Méthode de création d'un élément de liste :

//+------------------------------------------------------------------+
//| List element creation method                                     |
//+------------------------------------------------------------------+
CObject *CListObj::CreateElement(void)
  {
//--- Create a new object depending on the object type in m_element_type 
   switch(this.m_element_type)
     {
      case OBJECT_TYPE_TABLE_CELL   :  return new CTableCell();
      case OBJECT_TYPE_TABLE_ROW    :  return new CTableRow();
      case OBJECT_TYPE_TABLE_MODEL  :  return new CTableModel();
      default                       :  return NULL;
     }
  }

Cela signifie qu'avant d'appeler la méthode, le type de l'objet créé est déjà inscrit dans la variable m_element_type. En fonction du type d'élément, un nouvel objet du type approprié est créé et un pointeur sur cet objet est renvoyé. À l'avenir, lors du développement de nouveaux contrôles, leurs types seront inscrits dans l'énumération ENUM_OBJECT_TYPE. Et de nouveaux cas seront ajoutés ici pour créer de nouveaux types d'objets. La classe de liste chaînée basée sur le standard CList est prête. Elle peut désormais stocker tous les objets de types connus, enregistrer les listes dans un fichier, les charger à partir de ce fichier et les restaurer correctement.


2. Classe de cellules du tableau

Une cellule de tableau est l'élément le plus simple d'un tableau qui stocke une certaine valeur. Les cellules composent des listes représentant les lignes d'un tableau. Chaque liste représente une ligne du tableau. Dans notre tableau, les cellules ne pourront stocker qu'une seule valeur de plusieurs types à la fois - une valeur réelle, un nombre entier ou une chaîne de caractères.

En plus d'une simple valeur, une cellule peut se voir attribuer un objet d'un type connu de l'énumération ENUM_OBJECT_TYPE. Dans ce cas, la cellule peut stocker une valeur de l'un des types énumérés, ainsi qu'un pointeur vers un objet dont le type est écrit dans une variable spéciale. Ainsi, à l'avenir, on pourra demander au composant View d'afficher un tel objet dans une cellule afin d'interagir avec lui à l'aide du composant Controller.

Étant donné que plusieurs types de valeurs peuvent être stockés dans une cellule, nous utiliserons l'union pour les écrire, les stocker et les renvoyer. L'union est un type spécial de données qui stocke plusieurs champs dans la même zone de mémoire. Une union est similaire à une structure, mais ici, contrairement à une structure, les différents termes de l'union appartiennent à la même zone de mémoire. Dans la structure, chaque champ se voit attribuer sa propre zone de mémoire.

Continuons à écrire le code dans le fichier déjà créé. Commençons à écrire une nouvelle classe. Dans la section protégée, nous écrivons une union et déclarons des variables :

//+------------------------------------------------------------------+
//| Table cell class                                                 |
//+------------------------------------------------------------------+
class CTableCell : public CObject
  {
protected:
//--- Combining for storing cell values (double, long, string)
   union DataType
     {
      protected:
      double         double_value;
      long           long_value;
      ushort         ushort_value[MAX_STRING_LENGTH];

      public:
      //--- Set values
      void           SetValueD(const double value) { this.double_value=value;                   }
      void           SetValueL(const long value)   { this.long_value=value;                     }
      void           SetValueS(const string value) { ::StringToShortArray(value,ushort_value);  }
      
      //--- Return values
      double         ValueD(void) const { return this.double_value; }
      long           ValueL(void) const { return this.long_value; }
      string         ValueS(void) const
                       {
                        string res=::ShortArrayToString(this.ushort_value);
                        res.TrimLeft();
                        res.TrimRight();
                        return res;
                       }
     };
//--- Variables
   DataType          m_datatype_value;                      // Value
   ENUM_DATATYPE     m_datatype;                            // Data type
   CObject          *m_object;                              // Cell object
   ENUM_OBJECT_TYPE  m_object_type;                         // Object type in the cell
   int               m_row;                                 // Row index
   int               m_col;                                 // Column index
   int               m_digits;                              // Data representation accuracy
   uint              m_time_flags;                          // Date/time display flags
   bool              m_color_flag;                          // Color name display flag
   bool              m_editable;                            // Editable cell flag
   
public:

Dans la section publique, écrivez des méthodes d'accès aux variables protégées, aux méthodes virtuelles et aux constructeurs de classe pour différents types de données stockées dans une cellule :

public:
//--- Return cell coordinates and properties
   uint              Row(void)                           const { return this.m_row;                            }
   uint              Col(void)                           const { return this.m_col;                            }
   ENUM_DATATYPE     Datatype(void)                      const { return this.m_datatype;                       }
   int               Digits(void)                        const { return this.m_digits;                         }
   uint              DatetimeFlags(void)                 const { return this.m_time_flags;                     }
   bool              ColorNameFlag(void)                 const { return this.m_color_flag;                     }
   bool              IsEditable(void)                    const { return this.m_editable;                       }
//--- Return (1) double, (2) long and (3) string value
   double            ValueD(void)                        const { return this.m_datatype_value.ValueD();        }
   long              ValueL(void)                        const { return this.m_datatype_value.ValueL();        }
   string            ValueS(void)                        const { return this.m_datatype_value.ValueS();        }
//--- Return the value as a formatted string
   string            Value(void) const
                       {
                        switch(this.m_datatype)
                          {
                           case TYPE_DOUBLE  :  return(::DoubleToString(this.ValueD(),this.Digits()));
                           case TYPE_LONG    :  return(::IntegerToString(this.ValueL()));
                           case TYPE_DATETIME:  return(::TimeToString(this.ValueL(),this.m_time_flags));
                           case TYPE_COLOR   :  return(::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;
                       }
//--- Set variable values
   void              SetRow(const uint row)                    { this.m_row=(int)row;                          }
   void              SetCol(const uint col)                    { this.m_col=(int)col;                          }
   void              SetDatatype(const ENUM_DATATYPE datatype) { this.m_datatype=datatype;                     }
   void              SetDigits(const int digits)               { this.m_digits=digits;                         }
   void              SetDatetimeFlags(const uint flags)        { this.m_time_flags=flags;                      }
   void              SetColorNameFlag(const bool flag)         { this.m_color_flag=flag;                       }
   void              SetEditable(const bool flag)              { this.m_editable=flag;                         }
   void              SetPositionInTable(const uint row,const uint col)
                       {
                        this.SetRow(row);
                        this.SetCol(col);
                       }
//--- Assign an object to a cell
   void              AssignObject(CObject *object)
                       {
                        if(object==NULL)
                          {
                           ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__);
                           return;
                          }
                        this.m_object=object;
                        this.m_object_type=(ENUM_OBJECT_TYPE)object.Type();
                       }
//--- Remove the object assignment
   void              UnassignObject(void)
                       {
                        this.m_object=NULL;
                        this.m_object_type=-1;
                       }
                       
//--- Set double value
   void              SetValue(const double value)
                       {
                        this.m_datatype=TYPE_DOUBLE;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueD(value);
                       }
//--- Set long value
   void              SetValue(const long value)
                       {
                        this.m_datatype=TYPE_LONG;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueL(value);
                       }
//--- Set datetime value
   void              SetValue(const datetime value)
                       {
                        this.m_datatype=TYPE_DATETIME;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueL(value);
                       }
//--- Set color value
   void              SetValue(const color value)
                       {
                        this.m_datatype=TYPE_COLOR;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueL(value);
                       }
//--- Set string value
   void              SetValue(const string value)
                       {
                        this.m_datatype=TYPE_STRING;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueS(value);
                       }
//--- Clear data
   void              ClearData(void)
                       {
                        if(this.Datatype()==TYPE_STRING)
                           this.SetValue("");
                        else
                           this.SetValue(0.0);
                       }
//--- (1) Return and (2) display the object description in the journal
   string            Description(void);
   void              Print(void);

//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_CELL);}
   
   
//--- Constructors/destructor
                     CTableCell(void) : m_row(0), m_col(0), m_datatype(-1), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
                       {
                        this.m_datatype_value.SetValueD(0);
                       }
                     //--- Accept a double value
                     CTableCell(const uint row,const uint col,const double value,const int digits) :
                        m_row((int)row), m_col((int)col), m_datatype(TYPE_DOUBLE), m_digits(digits), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
                       {
                        this.m_datatype_value.SetValueD(value);
                       }
                     //--- Accept a long value
                     CTableCell(const uint row,const uint col,const long value) :
                        m_row((int)row), m_col((int)col), m_datatype(TYPE_LONG), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
                       {
                        this.m_datatype_value.SetValueL(value);
                       }
                     //--- Accept a datetime value
                     CTableCell(const uint row,const uint col,const datetime value,const uint time_flags) :
                        m_row((int)row), m_col((int)col), m_datatype(TYPE_DATETIME), m_digits(0), m_time_flags(time_flags), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
                       {
                        this.m_datatype_value.SetValueL(value);
                       }
                     //--- Accept color value
                     CTableCell(const uint row,const uint col,const color value,const bool color_names_flag) :
                        m_row((int)row), m_col((int)col), m_datatype(TYPE_COLOR), m_digits(0), m_time_flags(0), m_color_flag(color_names_flag), m_editable(true), m_object(NULL), m_object_type(-1)
                       {
                        this.m_datatype_value.SetValueL(value);
                       }
                     //--- Accept string value
                     CTableCell(const uint row,const uint col,const string value) :
                        m_row((int)row), m_col((int)col), m_datatype(TYPE_STRING), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
                       {
                        this.m_datatype_value.SetValueS(value);
                       }
                    ~CTableCell(void) {}
  };

Dans les méthodes de définition des valeurs, le type de la valeur stockée dans la cellule est d'abord défini, puis l'indicateur de la fonction de modification des valeurs dans la cellule est vérifié. Ce n'est que lorsque l'indicateur est activé que la nouvelle valeur est enregistrée dans la cellule :

//--- Set double value
   void              SetValue(const double value)
                       {
                        this.m_datatype=TYPE_DOUBLE;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueD(value);
                       }

Pourquoi procède-t-on ainsi ? Lors de la création d'une nouvelle cellule, celle-ci est créée avec le type réel de la valeur stockée. Si vous souhaitez modifier le type de valeur, mais que la cellule n'est pas modifiable, vous pouvez appeler la méthode pour définir la valeur du type souhaité avec n'importe quelle valeur. Le type de la valeur stockée sera modifié, mais la valeur de la cellule elle-même ne sera pas affectée.

La méthode de nettoyage des données met à zéro les valeurs numériques et insère un espace dans les chaînes de caractères :

//--- Clear data
   void              ClearData(void)
                       {
                        if(this.Datatype()==TYPE_STRING)
                           this.SetValue("");
                        else
                           this.SetValue(0.0);
                       }

Plus tard, nous procéderons différemment — afin qu'aucune donnée ne soit affichée dans les cellules effacées — pour que la cellule reste vide, nous créerons une valeur "vide" pour la cellule, puis, lorsque la cellule sera effacée, toutes les valeurs enregistrées et affichées dans cette cellule seront supprimées. Après tout, le zéro est aussi une valeur à part entière et, lorsque la cellule est effacée, les données numériques sont remplies de zéros. Ce n'est pas le cas.

Dans les constructeurs paramétriques de la classe, les coordonnées des cellules du tableau sont transmises — le nombre de lignes et de colonnes et la valeur du type requis (double, long, datetime, color, string). Certains types de valeurs nécessitent des informations supplémentaires :

  • double — précision de la valeur de sortie (nombre de décimales),
  • datetime — indicateurs de temps (date / heures / minutes / secondes),
  • color — drapeau permettant d'afficher les noms des couleurs standard connues.

Dans les constructeurs avec ces types de valeurs stockées dans les cellules , des paramètres supplémentaires sont transmis pour définir le format des valeurs affichées dans les cellules :

 //--- Accept a double value
 CTableCell(const uint row,const uint col,const double value,const int digits) :
    m_row((int)row), m_col((int)col), m_datatype(TYPE_DOUBLE), m_digits(digits), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
   {
    this.m_datatype_value.SetValueD(value);
   }
 //--- Accept a long value
 CTableCell(const uint row,const uint col,const long value) :
    m_row((int)row), m_col((int)col), m_datatype(TYPE_LONG), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
   {
    this.m_datatype_value.SetValueL(value);
   }
 //--- Accept a datetime value
 CTableCell(const uint row,const uint col,const datetime value,const uint time_flags) :
    m_row((int)row), m_col((int)col), m_datatype(TYPE_DATETIME), m_digits(0), m_time_flags(time_flags), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
   {
    this.m_datatype_value.SetValueL(value);
   }

 //--- Accept color value
 CTableCell(const uint row,const uint col,const color value,const bool color_names_flag) :
    m_row((int)row), m_col((int)col), m_datatype(TYPE_COLOR), m_digits(0), m_time_flags(0), m_color_flag(color_names_flag), m_editable(true), m_object(NULL), m_object_type(-1)
   {
    this.m_datatype_value.SetValueL(value);
   }
 //--- Accept string value
 CTableCell(const uint row,const uint col,const string value) :
    m_row((int)row), m_col((int)col), m_datatype(TYPE_STRING), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1)
   {
    this.m_datatype_value.SetValueS(value);
   }

Méthode de comparaison de deux objets :

//+------------------------------------------------------------------+
//| Compare two objects                                              |
//+------------------------------------------------------------------+
int CTableCell::Compare(const CObject *node,const int mode=0) const
  {
   const CTableCell *obj=node;
   switch(mode)
     {
      case CELL_COMPARE_MODE_COL :  return(this.Col()>obj.Col() ? 1 : this.Col()<obj.Col() ? -1 : 0);
      case CELL_COMPARE_MODE_ROW :  return(this.Row()>obj.Row() ? 1 : this.Row()<obj.Row() ? -1 : 0);
      //---CELL_COMPARE_MODE_ROW_COL
      default                    :  return
                                      (
                                       this.Row()>obj.Row() ? 1 : this.Row()<obj.Row() ? -1 :
                                       this.Col()>obj.Col() ? 1 : this.Col()<obj.Col() ? -1 : 0
                                      );
     }
  }

Cette méthode permet de comparer les paramètres de deux objets selon l'un des trois critères de comparaison suivants : par numéro de colonne, par numéro de ligne et simultanément par numéro de ligne et de colonne.

Cette méthode est nécessaire pour pouvoir trier les lignes du tableau en fonction des valeurs des colonnes du tableau. Cette question sera traitée par le Contrôleur dans des articles ultérieurs.

Méthode d'enregistrement des propriétés des cellules dans un fichier :

//+------------------------------------------------------------------+
//| Save to file                                                     |
//+------------------------------------------------------------------+
bool CTableCell::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long))
      return(false);
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return(false);

   //--- Save the data type
   if(::FileWriteInteger(file_handle,this.m_datatype,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Save the object type in the cell
   if(::FileWriteInteger(file_handle,this.m_object_type,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Save the row index
   if(::FileWriteInteger(file_handle,this.m_row,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Save the column index
   if(::FileWriteInteger(file_handle,this.m_col,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Maintain the accuracy of data representation
   if(::FileWriteInteger(file_handle,this.m_digits,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Save date/time display flags
   if(::FileWriteInteger(file_handle,this.m_time_flags,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Save the color name display flag
   if(::FileWriteInteger(file_handle,this.m_color_flag,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Save the edited cell flag
   if(::FileWriteInteger(file_handle,this.m_editable,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Save the value
   if(::FileWriteStruct(file_handle,this.m_datatype_value)!=sizeof(this.m_datatype_value))
      return(false);
   
//--- All is successful
   return true;
  }

Après avoir écrit le marqueur de données de départ et le type d'objet dans le fichier, toutes les propriétés de la cellule sont sauvegardées à leur tour. L'union doit être sauvegardée sous forme de structure à l'aide de FileWriteStruct().

Méthode de chargement des propriétés des cellules à partir d'un fichier :

//+------------------------------------------------------------------+
//| Load from file                                                   |
//+------------------------------------------------------------------+
bool CTableCell::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return(false);

   //--- Load the data type
   this.m_datatype=(ENUM_DATATYPE)::FileReadInteger(file_handle,INT_VALUE);
   //--- Load the object type in the cell
   this.m_object_type=(ENUM_OBJECT_TYPE)::FileReadInteger(file_handle,INT_VALUE);
   //--- Load the row index
   this.m_row=::FileReadInteger(file_handle,INT_VALUE);
   //--- Load the column index
   this.m_col=::FileReadInteger(file_handle,INT_VALUE);
   //--- Load the precision of the data representation
   this.m_digits=::FileReadInteger(file_handle,INT_VALUE);
   //--- Load date/time display flags
   this.m_time_flags=::FileReadInteger(file_handle,INT_VALUE);
   //--- Load the color name display flag
   this.m_color_flag=::FileReadInteger(file_handle,INT_VALUE);
   //--- Load the edited cell flag
   this.m_editable=::FileReadInteger(file_handle,INT_VALUE);
   //--- Load the value
   if(::FileReadStruct(file_handle,this.m_datatype_value)!=sizeof(this.m_datatype_value))
      return(false);
   
//--- All is successful
   return true;
  }

Après avoir lu et vérifié les marqueurs de début de données et le type d'objet, toutes les propriétés de l'objet sont chargées à tour de rôle dans le même ordre que celui dans lequel elles ont été sauvegardées. Les unions sont lues à l'aide de FileReadStruct().

Méthode qui renvoie la description de l'objet :

//+------------------------------------------------------------------+
//| Return the object description                                    |
//+------------------------------------------------------------------+
string CTableCell::Description(void)
  {
   return(::StringFormat("%s: Row %u, Col %u, %s <%s>Value: %s",
                         TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.Row(),this.Col(),
                         (this.m_editable ? "Editable" : "Uneditable"),this.DatatypeDescription(),this.Value()));
  }

Ici, une ligne est créée à partir de certains paramètres de la cellule et renvoyée, par exemple, pour le type double, sous ce format :

  Table Cell: Row 2, Col 2, Uneditable <double>Value: 0.00

Méthode qui produit une description de l'objet dans le journal :

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

Ici, la description de l'objet est simplement écrite dans le journal.

//+------------------------------------------------------------------+
//| Table row class                                                  |
//+------------------------------------------------------------------+
class CTableRow : public CObject
  {
protected:
   CTableCell        m_cell_tmp;                            // Cell object to search in the list
   CListObj          m_list_cells;                          // List of cells
   uint              m_index;                               // Row index
   
//--- Add the specified cell to the end of the list
   bool              AddNewCell(CTableCell *cell);
   
public:
//--- (1) Set and (2) return the row index
   void              SetIndex(const uint index)                { this.m_index=index;  }
   uint              Index(void)                         const { return this.m_index; }
//--- Set the row and column positions to all cells
   void              CellsPositionUpdate(void);
   
//--- Create a new cell and add it to the end of the list
   CTableCell       *CreateNewCell(const double value);
   CTableCell       *CreateNewCell(const long value);
   CTableCell       *CreateNewCell(const datetime value);
   CTableCell       *CreateNewCell(const color value);
   CTableCell       *CreateNewCell(const string value);
   
//--- Return (1) the cell by index and (2) the number of cells
   CTableCell       *GetCell(const uint index)                 { return this.m_list_cells.GetNodeAtIndex(index);  }
   uint              CellsTotal(void)                    const { return this.m_list_cells.Total();                }
   
//--- Set the value to the specified cell
   void              CellSetValue(const uint index,const double value);
   void              CellSetValue(const uint index,const long value);
   void              CellSetValue(const uint index,const datetime value);
   void              CellSetValue(const uint index,const color value);
   void              CellSetValue(const uint index,const string value);
//--- (1) assign to a cell and (2) remove an assigned object from the cell
   void              CellAssignObject(const uint index,CObject *object);
   void              CellUnassignObject(const uint index);
   
//--- (1) Delete and (2) move the cell
   bool              CellDelete(const uint index);
   bool              CellMoveTo(const uint cell_index, const uint index_to);
   
//--- Reset the data of the row cells
   void              ClearData(void);

//--- (1) Return and (2) display the object description in the journal
   string            Description(void);
   void              Print(const bool detail, const bool as_table=false, const int cell_width=10);

//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_ROW); }
   
//--- Constructors/destructor
                     CTableRow(void) : m_index(0) {}
                     CTableRow(const uint index) : m_index(index) {}
                    ~CTableRow(void){}
  };


3. Classe de ligne du tableau

Une ligne de tableau est essentiellement une liste chaînée de cellules. La classe de ligne doit permettre d'ajouter, de supprimer et de réorganiser les cellules de la liste à un nouvel emplacement. La classe doit disposer d'un ensemble minimum de méthodes pour gérer les cellules de la liste.

Continuons à écrire le code dans le même fichier. Une seule variable est disponible dans les paramètres de classe : l'indice de ligne dans le tableau. Toutes les autres méthodes permettent de travailler avec les propriétés d'une ligne et avec une liste de ses cellules.

Considérons les méthodes de classe.

Méthode de comparaison de deux lignes de tableau :

//+------------------------------------------------------------------+
//| Compare two objects                                              |
//+------------------------------------------------------------------+
int CTableRow::Compare(const CObject *node,const int mode=0) const
  {
   const CTableRow *obj=node;
   return(this.Index()>obj.Index() ? 1 : this.Index()<obj.Index() ? -1 : 0);
  }

Étant donné que les lignes ne peuvent être comparées qu'en fonction de leur seul paramètre — l'index de la ligne — cette comparaison est mise en œuvre ici. Cette méthode sera nécessaire pour trier les lignes du tableau.

Méthodes surchargées pour créer des cellules avec différents types de données stockées :

//+------------------------------------------------------------------+
//| Create a new double cell and add it to the end of the list       |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const double value)
  {
//--- Create a new cell object storing a value of double type
   CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value,2);
   if(cell==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal());
      return NULL;
     }
//--- Add the created cell to the end of the list
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Return the pointer to the object
   return cell;
  }
//+------------------------------------------------------------------+
//| Create a new long cell and add it to the end of the list         |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const long value)
  {
//--- Create a new cell object storing a long value
   CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value);
   if(cell==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal());
      return NULL;
     }
//--- Add the created cell to the end of the list
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Return the pointer to the object
   return cell;
  }
//+------------------------------------------------------------------+
//| Create a new datetime cell and add it to the end of the list     |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const datetime value)
  {
//--- Create a new cell object storing a value of datetime type
   CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value,TIME_DATE|TIME_MINUTES|TIME_SECONDS);
   if(cell==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal());
      return NULL;
     }
//--- Add the created cell to the end of the list
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Return the pointer to the object
   return cell;
  }
//+------------------------------------------------------------------+
//| Create a new color cell and add it to the end of the list        |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const color value)
  {
//--- Create a new cell object storing a value of color type
   CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value,true);
   if(cell==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal());
      return NULL;
     }
//--- Add the created cell to the end of the list
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Return the pointer to the object
   return cell;
  }
//+------------------------------------------------------------------+
//| Create a new string cell and add it to the end of the list       |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const string value)
  {
//--- Create a new cell object storing a value of string type
   CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value);
   if(cell==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal());
      return NULL;
     }
//--- Add the created cell to the end of the list
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Return the pointer to the object
   return cell;
  }

Cinq méthodes pour créer une nouvelle cellule (double, long, datetime, color, string) et l'ajouter à la fin de la liste. Les autres paramètres du format de sortie des données dans la cellule sont définis avec des valeurs par défaut. Ils peuvent être modifiés après la création de la cellule. Tout d'abord, un nouvel objet cellule est créé, puis ajouté à la fin de la liste. Si l'objet n'a pas été créé, il est supprimé pour éviter les fuites de mémoire.

Méthode qui ajoute une cellule à la fin de la liste :

//+------------------------------------------------------------------+
//| Add a cell to the end of the list                                |
//+------------------------------------------------------------------+
bool CTableRow::AddNewCell(CTableCell *cell)
  {
//--- If an empty object is passed, report it and return 'false'
   if(cell==NULL)
     {
      ::PrintFormat("%s: Error. Empty CTableCell object passed",__FUNCTION__);
      return false;
     }
//--- Set the cell index in the list and add the created cell to the end of the list
   cell.SetPositionInTable(this.m_index,this.CellsTotal());
   if(this.m_list_cells.Add(cell)==WRONG_VALUE)
     {
      ::PrintFormat("%s: Error. Failed to add cell (%u,%u) to list",__FUNCTION__,this.m_index,this.CellsTotal());
      return false;
     }
//--- Successful
   return true;
  }

Toute nouvelle cellule est toujours ajoutée à la fin de la liste. Elle peut ensuite être déplacée à la bonne position avec les méthodes de la classe de modèle de tableau, qui sera créée ultérieurement.

Méthodes surchargées pour définir des valeurs dans la cellule spécifiée :

//+------------------------------------------------------------------+
//| Set the double value to the specified cell                       |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const double value)
  {
//--- Get the required cell from the list and set a new value into it
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Set a long value to the specified cell                           |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const long value)
  {
//--- Get the required cell from the list and set a new value into it
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Set the datetime value to the specified cell                     |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const datetime value)
  {
//--- Get the required cell from the list and set a new value into it
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Set the color value to the specified cell                        |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const color value)
  {
//--- Get the required cell from the list and set a new value into it
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Set a string value to the specified cell                         |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const string value)
  {
//--- Get the required cell from the list and set a new value into it
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }

En utilisant l'index, nous obtenons la cellule requise dans la liste et lui attribuons une valeur. Si la cellule n'est pas modifiable, la méthode SetValue() de l'objet cellule ne définira pour la cellule que le type de valeur à définir. La valeur elle-même ne sera pas définie.

Méthode qui affecte un objet à une cellule :

//+------------------------------------------------------------------+
//| Assign an object to the cell                                     |
//+------------------------------------------------------------------+
void CTableRow::CellAssignObject(const uint index,CObject *object)
  {
//--- Get the required cell from the list and set a pointer to the object into it
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.AssignObject(object);
  }

Nous obtenons un objet cellule par son index et utilisons sa méthode AssignObject() pour assigner un pointeur sur l'objet à la cellule.

Méthode permettant d'annuler l'affectation d'un objet à une cellule :

//+------------------------------------------------------------------+
//| Cancel the assigned object for the cell                          |
//+------------------------------------------------------------------+
void CTableRow::CellUnassignObject(const uint index)
  {
//--- Get the required cell from the list and cancel the pointer to the object and its type in it
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.UnassignObject();
  }

Nous récupérons l'objet cellule par son index et utilisons sa méthode UnassignObject() pour supprimer le pointeur sur l'objet assigné à la cellule.

Méthode qui supprime une cellule :

//+------------------------------------------------------------------+
//| Delete a cell                                                    |
//+------------------------------------------------------------------+
bool CTableRow::CellDelete(const uint index)
  {
//--- Delete a cell in the list by index
   if(!this.m_list_cells.Delete(index))
      return false;
//--- Update the indices for the remaining cells in the list
   this.CellsPositionUpdate();
   return true;
  }

La méthode Delete() de la classe CList permet de supprimer la cellule de la liste. Une fois qu'une cellule a été supprimée de la liste, les index des cellules restantes sont modifiés. La méthode CellsPositionUpdate() permet de mettre à jour les index de toutes les cellules restantes de la liste.

Méthode qui déplace une cellule à la position spécifiée :

//+------------------------------------------------------------------+
//| Moves the cell to the specified position                         |
//+------------------------------------------------------------------+
bool CTableRow::CellMoveTo(const uint cell_index,const uint index_to)
  {
//--- Select the desired cell by index in the list, turning it into the current one
   CTableCell *cell=this.GetCell(cell_index);
//--- Move the current cell to the specified position in the list
   if(cell==NULL || !this.m_list_cells.MoveToIndex(index_to))
      return false;
//--- Update the indices of all cells in the list
   this.CellsPositionUpdate();
   return true;
  }

Pour que la classe CList puisse agir sur un objet, cet objet de la liste doit être l'objet courant. Il devient l’objet courant lorsqu'il est sélectionné par exemple. C'est pourquoi nous commençons par récupérer l'objet cellule de la liste par son index. La cellule devient la cellule courante, puis, en utilisant la méthode MoveToIndex() de la classe CList, nous déplaçons l'objet à la position requise dans la liste. Après avoir déplacé avec succès un objet vers une nouvelle position, les index des objets restants doivent être ajustés, ce qui est fait à l'aide de la méthode CellsPositionUpdate().

Méthode qui définit la position des lignes et des colonnes pour toutes les cellules de la liste :

//+------------------------------------------------------------------+
//| Set the row and column positions to all cells                    |
//+------------------------------------------------------------------+
void CTableRow::CellsPositionUpdate(void)
  {
//--- In the loop through all cells in the list
   for(int i=0;i<this.m_list_cells.Total();i++)
     {
      //--- get the next cell and set the row and column indices in it
      CTableCell *cell=this.GetCell(i);
      if(cell!=NULL)
         cell.SetPositionInTable(this.Index(),this.m_list_cells.IndexOf(cell));
     }
  }

La classe CList permet de trouver l'index de l'objet courant dans la liste. Pour ce faire, l'objet doit être sélectionné. Ici, nous parcourons en boucle tous les objets cellules de la liste, nous sélectionnons chacun d'entre eux et nous déterminons son indice à l'aide de la méthode IndexOf() de la classe CList. L'indice de la ligne et l'indice de la cellule trouvée sont attribués à l'objet cellule à l'aide de sa méthode SetPositionInTable().

Méthode qui réinitialise les données des cellules de la ligne :

//+------------------------------------------------------------------+
//| Reset the cell data of a row                                     |
//+------------------------------------------------------------------+
void CTableRow::ClearData(void)
  {
//--- In the loop through all cells in the list
   for(uint i=0;i<this.CellsTotal();i++)
     {
      //--- get the next cell and set an empty value to it
      CTableCell *cell=this.GetCell(i);
      if(cell!=NULL)
         cell.ClearData();
     }
  }

Dans la boucle, réinitialisez chaque cellule suivante de la liste à l'aide de la méthode ClearData() de l'objet cellule. Pour les données de type string, une ligne vide est écrite dans la cellule, et pour les données numériques, zéro est écrit.

Méthode qui renvoie la description de l'objet :

//+------------------------------------------------------------------+
//| Return the object description                                    |
//+------------------------------------------------------------------+
string CTableRow::Description(void)
  {
   return(::StringFormat("%s: Position %u, Cells total: %u",
                         TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.Index(),this.CellsTotal()));
  }

Une ligne est collectée à partir des propriétés et des données de l'objet et renvoyée dans le format suivant, par exemple :

Table Row: Position 1, Cells total: 4:

Méthode qui produit une description de l'objet dans le journal :

//+------------------------------------------------------------------+
//| Display the object description in the journal                    |
//+------------------------------------------------------------------+
void CTableRow::Print(const bool detail, const bool as_table=false, const int cell_width=10)
  {
      
//--- Number of cells
   int total=(int)this.CellsTotal();
   
//--- If the output is in tabular form
   string res="";
   if(as_table)
     {
      //--- create a table row from the values of all cells
      string head=" Row "+(string)this.Index();
      string res=::StringFormat("|%-*s |",cell_width,head);
      for(int i=0;i<total;i++)
        {
         CTableCell *cell=this.GetCell(i);
         if(cell==NULL)
            continue;
         res+=::StringFormat("%*s |",cell_width,cell.Value());
        }
      //--- Display a row in the journal
      ::Print(res);
      return;
     }
     
//--- Display the header as a row description
   ::Print(this.Description()+(detail ? ":" : ""));
   
//--- If detailed description
   if(detail)
     {
      
      //--- The output is not in tabular form
      //--- In the loop through the list of cells in the row
      for(int i=0; i<total; i++)
        {
         //--- get the current cell and add its description to the final row
         CTableCell *cell=this.GetCell(i);
         if(cell!=NULL)
            res+="  "+cell.Description()+(i<total-1 ? "\n" : "");
        }
      //--- Send the row created in the loop to the journal
      ::Print(res);
     }
  }

Pour l'affichage de données non tabulaires dans le journal, l'en-tête est d'abord affiché dans le journal en tant que description de ligne. Ensuite, si l'indicateur d'affichage détaillé est activé, les descriptions de chaque cellule sont affichées dans le journal, en boucle dans la liste des cellules.

Par conséquent, l'affichage détaillé d'une ligne de tableau dans le journal ressemble à ceci, par exemple (pour une vue non tabulaire) :

Table Row: Position 0, Cells total: 4:
  Table Cell: Row 0, Col 0, Editable <long>Value: 10
  Table Cell: Row 0, Col 1, Editable <long>Value: 21
  Table Cell: Row 0, Col 2, Editable <long>Value: 32
  Table Cell: Row 0, Col 3, Editable <long>Value: 43

Dans le cas d'un affichage sous forme de tableau, le résultat sera, par exemple, le suivant :

| Row 0     |         0 |         1 |         2 |         3 |

Méthode permettant d'enregistrer une ligne de tableau dans un fichier :

//+------------------------------------------------------------------+
//| Save to file                                                     |
//+------------------------------------------------------------------+
bool CTableRow::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long))
      return(false);
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return(false);

//--- Save the index
   if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE)
      return(false);
//--- Save the list of cells
   if(!this.m_list_cells.Save(file_handle))
      return(false);
   
//--- Successful
   return true;
  }

Enregistrez les marqueurs de début de données, puis le type d'objet. Il s'agit de l'en-tête standard de chaque objet du fichier. Une entrée dans le fichier des propriétés de l'objet suit ensuite. Ici, l'index des lignes est enregistré dans le fichier, puis la liste des cellules.

Méthode de chargement de la ligne à partir d'un fichier :

//+------------------------------------------------------------------+
//| Load from file                                                   |
//+------------------------------------------------------------------+
bool CTableRow::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return(false);

//--- Load the index
   this.m_index=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the list of cells
   if(!this.m_list_cells.Load(file_handle))
      return(false);
   
//--- Successful
   return true;
  }

Ici, tout est dans le même ordre que lors de l'enregistrement. Tout d'abord, le marqueur de début de données et le type d'objet sont chargés et vérifiés. L'index de la ligne et la liste complète des cellules sont ensuite chargés.


4. Classe de modèles de tableaux

Dans sa forme la plus simple, le modèle de tableau est une liste chaînée de lignes, qui contiennent à leur tour des listes chaînées de cellules. Notre modèle, que nous créons aujourd'hui, recevra en entrée un tableau bidimensionnel de l'un des cinq types suivants (double, long, datetime, color, string) et construira un tableau virtuel à partir de ce tableau. Nous étendrons également cette classe afin d'accepter d'autres arguments pour créer des tableaux à partir d'autres données d'entrée. Le même modèle sera considéré comme le modèle par défaut.

Continuons à écrire le code dans le même fichier \MQL5\Scripts\TableModel\TableModelTest.mq5.

La classe de modèle de tableau est essentiellement une liste chaînée de lignes avec des méthodes pour gérer les lignes, les colonnes et les cellules. Ecrivez le corps de la classe avec toutes les variables et les méthodes, puis examinez les méthodes déclarées de la classe :

//+------------------------------------------------------------------+
//| Table model class                                                |
//+------------------------------------------------------------------+
class CTableModel : public CObject
  {
protected:
   CTableRow         m_row_tmp;                             // Row object to search in the list
   CListObj          m_list_rows;                           // List of table rows
//--- Create a table model from a two-dimensional array
template<typename T>
   void              CreateTableModel(T &array[][]);
//--- Return the correct data type
   ENUM_DATATYPE     GetCorrectDatatype(string type_name)
                       {
                        return
                          (
                           //--- Integer value
                           type_name=="bool" || type_name=="char"    || type_name=="uchar"   ||
                           type_name=="short"|| type_name=="ushort"  || type_name=="int"     ||
                           type_name=="uint" || type_name=="long"    || type_name=="ulong"   ?  TYPE_LONG      :
                           //--- Real value
                           type_name=="float"|| type_name=="double"                          ?  TYPE_DOUBLE    :
                           //--- Date/time value
                           type_name=="datetime"                                             ?  TYPE_DATETIME  :
                           //--- Color value
                           type_name=="color"                                                ?  TYPE_COLOR     :
                           /*--- String value */                                          TYPE_STRING    );
                       }
     
//--- Create and add a new empty string to the end of the list
   CTableRow        *CreateNewEmptyRow(void);
//--- Add a string to the end of the list
   bool              AddNewRow(CTableRow *row);
//--- Set the row and column positions to all table cells
   void              CellsPositionUpdate(void);
   
public:
//--- Return (1) cell, (2) row by index, number (3) of rows, cells (4) in the specified row and (5) in the table
   CTableCell       *GetCell(const uint row, const uint col);
   CTableRow        *GetRow(const uint index)                  { return this.m_list_rows.GetNodeAtIndex(index);   }
   uint              RowsTotal(void)                     const { return this.m_list_rows.Total();  }
   uint              CellsInRow(const uint index);
   uint              CellsTotal(void);

//--- Set (1) value, (2) precision, (3) time display flags and (4) color name display flag to the specified cell
template<typename T>
   void              CellSetValue(const uint row, const uint col, const T value);
   void              CellSetDigits(const uint row, const uint col, const int digits);
   void              CellSetTimeFlags(const uint row, const uint col, const uint flags);
   void              CellSetColorNamesFlag(const uint row, const uint col, const bool flag);
//--- (1) Assign and (2) cancel the object in the cell
   void              CellAssignObject(const uint row, const uint col,CObject *object);
   void              CellUnassignObject(const uint row, const uint col);
//--- (1) Delete and (2) move the cell
   bool              CellDelete(const uint row, const uint col);
   bool              CellMoveTo(const uint row, const uint cell_index, const uint index_to);
   
//--- (1) Return and (2) display the cell description and (3) the object assigned to the cell
   string            CellDescription(const uint row, const uint col);
   void              CellPrint(const uint row, const uint col);
   CObject          *CellGetObject(const uint row, const uint col);

public:
//--- Create a new string and (1) add it to the end of the list, (2) insert to the specified list position
   CTableRow        *RowAddNew(void);
   CTableRow        *RowInsertNewTo(const uint index_to);
//--- (1) Remove or (2) relocate the row, (3) clear the row data
   bool              RowDelete(const uint index);
   bool              RowMoveTo(const uint row_index, const uint index_to);
   void              RowResetData(const uint index);
//--- (1) Return and (2) display the row description in the journal
   string            RowDescription(const uint index);
   void              RowPrint(const uint index,const bool detail);
   
//--- (1) Remove or (2) relocate the column, (3) clear the column data
   bool              ColumnDelete(const uint index);
   bool              ColumnMoveTo(const uint row_index, const uint index_to);
   void              ColumnResetData(const uint index);
   
//--- (1) Return and (2) display the table description in the journal
   string            Description(void);
   void              Print(const bool detail);
   void              PrintTable(const int cell_width=10);
   
//--- (1) Clear the data, (2) destroy the model
   void              ClearData(void);
   void              Destroy(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_MODEL);  }
   
//--- Constructors/destructor
                     CTableModel(void){}
                     CTableModel(double &array[][])   { this.CreateTableModel(array); }
                     CTableModel(long &array[][])     { this.CreateTableModel(array); }
                     CTableModel(datetime &array[][]) { this.CreateTableModel(array); }
                     CTableModel(color &array[][])    { this.CreateTableModel(array); }
                     CTableModel(string &array[][])   { this.CreateTableModel(array); }
                    ~CTableModel(void){}
  };

En principe, il n'existe qu'un seul objet pour une liste chaînée de lignes de tableau et des méthodes pour gérer les lignes, les cellules et les colonnes. Et des constructeurs qui acceptent différents types de tableaux bidimensionnels.

Un tableau est transmis au constructeur de la classe et une méthode de création d'un modèle de table est appelée :

//+------------------------------------------------------------------+
//| Create the table model from a two-dimensional array              |
//+------------------------------------------------------------------+
template<typename T>
void CTableModel::CreateTableModel(T &array[][])
  {
//--- Get the number of table rows and columns from the array properties
   int rows_total=::ArrayRange(array,0);
   int cols_total=::ArrayRange(array,1);
//--- In a loop by row indices
   for(int r=0; r<rows_total; r++)
     {
      //--- create a new empty row and add it to the end of the list of rows
      CTableRow *row=this.CreateNewEmptyRow();
      //--- If a row is created and added to the list,
      if(row!=NULL)
        {
         //--- In the loop by the number of cells in a row, 
         //--- create all the cells, adding each new one to the end of the list of cells in the row
         for(int c=0; c<cols_total; c++)
            row.CreateNewCell(array[r][c]);
        }
     }
  }

La logique de la méthode est expliquée dans les commentaires. La première dimension du tableau correspond aux lignes du tableau, la seconde aux cellules de chaque ligne. Lors de la création de cellules, celles-ci utilisent le même type de données que le type de tableau transmis à la méthode.

Nous pouvons créer plusieurs modèles de tableaux, dont les cellules stockent initialement différents types de données (double, long, datetime, color , et string). Plus tard, après avoir créé le modèle de tableau, les types de données stockées dans les cellules peuvent être modifiés.

Méthode qui crée une nouvelle ligne vide et l'ajoute à la fin de la liste :

//+------------------------------------------------------------------+
//| Create a new empty string and add it to the end of the list      |
//+------------------------------------------------------------------+
CTableRow *CTableModel::CreateNewEmptyRow(void)
  {
//--- Create a new row object
   CTableRow *row=new CTableRow(this.m_list_rows.Total());
   if(row==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create new row at position %u",__FUNCTION__, this.m_list_rows.Total());
      return NULL;
     }
//--- If failed to add the row to the list, remove the newly created object and return NULL
   if(!this.AddNewRow(row))
     {
      delete row;
      return NULL;
     }
   
//--- Success - return the pointer to the created object
   return row;
  }

La méthode crée un nouvel objet de la classe CTableRow et l'ajoute à la fin de la liste des lignes à l'aide de la méthode AddNewRow(). En cas d'erreur, le nouvel objet créé est supprimé et NULL est renvoyé. En cas de succès, la méthode renvoie un pointeur sur la ligne nouvellement ajoutée à la liste.

Méthode qui ajoute un objet ligne à la fin de la liste :

//+------------------------------------------------------------------+
//| Add a row to the end of the list                                 |
//+------------------------------------------------------------------+
bool CTableModel::AddNewRow(CTableRow *row)
  {
//--- If an empty object is passed, report this and return 'false'
   if(row==NULL)
     {
      ::PrintFormat("%s: Error. Empty CTableRow object passed",__FUNCTION__);
      return false;
     }
//--- Set the row index in the list and add it to the end of the list
   row.SetIndex(this.RowsTotal());
   if(this.m_list_rows.Add(row)==WRONG_VALUE)
     {
      ::PrintFormat("%s: Error. Failed to add row (%u) to list",__FUNCTION__,row.Index());
      return false;
     }

//--- Successful
   return true;
  }

Les deux méthodes mentionnées ci-dessus se trouvent dans la section protégée de la classe, fonctionnent par paires et sont utilisées en interne lors de l'ajout de nouvelles lignes au tableau.

Méthode permettant de créer une nouvelle ligne et de l'ajouter à la fin de la liste :

//+------------------------------------------------------------------+
//| Create a new string and add it to the end of the list            |
//+------------------------------------------------------------------+
CTableRow *CTableModel::RowAddNew(void)
  {
//--- Create a new empty row and add it to the end of the list of rows
   CTableRow *row=this.CreateNewEmptyRow();
   if(row==NULL)
      return NULL;
      
//--- Create cells equal to the number of cells in the first row
   for(uint i=0;i<this.CellsInRow(0);i++)
      row.CreateNewCell(0.0);
   row.ClearData();
   
//--- Success - return the pointer to the created object
   return row;
  }

Il s'agit d'une méthode publique. Elle est utilisée pour ajouter une nouvelle ligne avec des cellules au tableau. Le nombre de cellules pour la ligne créée est tiré de la toute première ligne du tableau.

Méthode permettant de créer et d'ajouter une nouvelle ligne à la position spécifiée dans la liste :

//+------------------------------------------------------------------+
//| Create and add a new string to the specified position in the list|
//+------------------------------------------------------------------+
CTableRow *CTableModel::RowInsertNewTo(const uint index_to)
  {
//--- Create a new empty row and add it to the end of the list of rows
   CTableRow *row=this.CreateNewEmptyRow();
   if(row==NULL)
      return NULL;
     
//--- Create cells equal to the number of cells in the first row
   for(uint i=0;i<this.CellsInRow(0);i++)
      row.CreateNewCell(0.0);
   row.ClearData();
   
//--- Shift the row to index_to
   this.RowMoveTo(this.m_list_rows.IndexOf(row),index_to);
   
//--- Success - return the pointer to the created object
   return row;
  }

Il est parfois nécessaire d'insérer une nouvelle ligne non pas à la fin de la liste des lignes, mais entre les lignes existantes. Cette méthode crée d'abord une nouvelle ligne à la fin de la liste, la remplit de cellules, les efface, puis déplace la ligne à la position souhaitée.

Méthode qui définit des valeurs dans la cellule spécifiée :

//+------------------------------------------------------------------+
//| Set the value to the specified cell                              |
//+------------------------------------------------------------------+
template<typename T>
void CTableModel::CellSetValue(const uint row,const uint col,const T value)
  {
//--- Get a cell by row and column indices
   CTableCell *cell=this.GetCell(row,col);
   if(cell==NULL)
      return;
//--- Get the correct type of the data being set (double, long, datetime, color, string)
   ENUM_DATATYPE type=this.GetCorrectDatatype(typename(T));
//--- Depending on the data type, we call the corresponding
//--- cell method for setting the value, explicitly specifying the required type
   switch(type)
     {
      case TYPE_DOUBLE  :  cell.SetValue((double)value);    break;
      case TYPE_LONG    :  cell.SetValue((long)value);      break;
      case TYPE_DATETIME:  cell.SetValue((datetime)value);  break;
      case TYPE_COLOR   :  cell.SetValue((color)value);     break;
      case TYPE_STRING  :  cell.SetValue((string)value);    break;
      default           :  break;
     }
  }

Il faut d'abord obtenir un pointeur sur la cellule souhaitée grâce aux coordonnées de sa ligne et de sa colonne, puis lui attribuer une valeur. Quelle que soit la valeur transmise à la méthode pour l'installer dans la cellule, la méthode ne sélectionnera que le type correct pour l'installation : double, long, datetime, color ou string.

Méthode qui définit la précision de l'affichage des données dans la cellule spécifiée :

//+------------------------------------------------------------------+
//| Set the precision of data display in the specified cell          |
//+------------------------------------------------------------------+
void CTableModel::CellSetDigits(const uint row,const uint col,const int digits)
  {
//--- Get a cell by row and column indices and
//--- call its corresponding method to set the value
   CTableCell *cell=this.GetCell(row,col);
   if(cell!=NULL)
      cell.SetDigits(digits);
  }

La méthode n'est pertinente que pour les cellules qui stockent le type de valeur réelle. Elle permet de spécifier le nombre de décimales de la valeur affichée par la cellule.

Méthode qui définit les drapeaux d'affichage de l'heure dans la cellule spécifiée :

//+------------------------------------------------------------------+
//| Set the time display flags to the specified cell                 |
//+------------------------------------------------------------------+
void CTableModel::CellSetTimeFlags(const uint row,const uint col,const uint flags)
  {
//--- Get a cell by row and column indices and
//--- call its corresponding method to set the value
   CTableCell *cell=this.GetCell(row,col);
   if(cell!=NULL)
      cell.SetDatetimeFlags(flags);
  }

Concerne les cellules affichant des valeurs datetime. Définissez le format d'affichage de l'heure par cellule (l'un des formats TIME_DATE|TIME_MINUTES|TIME_SECONDS, ou une combinaison de ceux-ci).

TIME_DATE donne le résultat " yyyy.mm.dd "
TIME_MINUTES donne le résultat " hh:mi "
TIME_SECONDS donne le résultat " hh:mi:ss "

Méthode qui définit les drapeaux d'affichage du nom de la couleur à la cellule spécifiée :

//+------------------------------------------------------------------+
//| Set the flag for displaying color names in the specified cell    |
//+------------------------------------------------------------------+
void CTableModel::CellSetColorNamesFlag(const uint row,const uint col,const bool flag)
  {
//--- Get a cell by row and column indices and
//--- call its corresponding method to set the value
   CTableCell *cell=this.GetCell(row,col);
   if(cell!=NULL)
      cell.SetColorNameFlag(flag);
  }

Ne concerne que les cellules affichant des valeurs de type color. Elle indique la nécessité d'afficher les noms des couleurs si la couleur stockée dans la cellule est présente dans la table des couleurs.

Méthode permettant d'affecter un objet à une cellule :

//+------------------------------------------------------------------+
//| Assign an object to a cell                                       |
//+------------------------------------------------------------------+
void CTableModel::CellAssignObject(const uint row,const uint col,CObject *object)
  {
//--- Get a cell by row and column indices and
//--- call its corresponding method to set the value
   CTableCell *cell=this.GetCell(row,col);
   if(cell!=NULL)
      cell.AssignObject(object);
  }

Méthode qui annule l'affectation d'un objet dans une cellule :

//+------------------------------------------------------------------+
//| Unassign an object from a cell                                   |
//+------------------------------------------------------------------+
void CTableModel::CellUnassignObject(const uint row,const uint col)
  {
//--- Get a cell by row and column indices and
//--- call its corresponding method to set the value
   CTableCell *cell=this.GetCell(row,col);
   if(cell!=NULL)
      cell.UnassignObject();
  }

Les deux méthodes présentées ci-dessus permettent d'affecter un objet à une cellule ou de supprimer son affectation. L'objet doit être un descendant de la classe CObject. Dans le contexte des articles sur les tables, un objet peut être, par exemple, l'un des objets connus de l'énumération ENUM_OBJECT_TYPE. Pour l'instant, la liste ne contient que des cellules, des lignes et des modèles de tableaux. Les affecter à une cellule n'a pas de sens. Mais l'énumération s'étendra au fur et à mesure que nous écrirons des articles sur le composant View, où les contrôles seront créés. Il est alors opportun d'attribuer à une cellule, par exemple, le contrôle "liste déroulante".

Méthode qui supprime la cellule spécifiée :

//+------------------------------------------------------------------+
//| Delete a cell                                                    |
//+------------------------------------------------------------------+
bool CTableModel::CellDelete(const uint row,const uint col)
  {
//--- Get the row by index and return the result of deleting the cell from the list
   CTableRow *row_obj=this.GetRow(row);
   return(row_obj!=NULL ? row_obj.CellDelete(col) : false);
  }

La méthode récupère l'objet ligne par son index et appelle sa méthode pour supprimer la cellule spécifiée. Pour une seule cellule dans une seule ligne, la méthode n'a pas encore de sens, car elle réduira le nombre de cellules dans une seule ligne du tableau. Cela entraîne une désynchronisation des cellules par rapport aux lignes voisines. Jusqu'à présent, il n'existe pas de traitement d'une telle suppression, où il est nécessaire de "développer" la cellule voisine de la cellule supprimée jusqu'à la taille de deux cellules afin de ne pas perturber la structure du tableau. Toutefois, cette méthode est utilisée dans le cadre de la méthode de suppression des colonnes d'un tableau, où les cellules de toutes les lignes sont supprimées en une seule fois sans violer l'intégrité de l'ensemble du tableau.

Méthode de déplacement d'une cellule de tableau :

//+------------------------------------------------------------------+
//| Move the cell                                                    |
//+------------------------------------------------------------------+
bool CTableModel::CellMoveTo(const uint row,const uint cell_index,const uint index_to)
  {
//--- Get the row by index and return the result of moving the cell to a new position
   CTableRow *row_obj=this.GetRow(row);
   return(row_obj!=NULL ? row_obj.CellMoveTo(cell_index,index_to) : false);
  }

L'objet ligne est récupéré par son index et sa méthode est appelée pour supprimer la cellule spécifiée.

Méthode qui renvoie le nombre de cellules dans la ligne spécifiée :

//+------------------------------------------------------------------+
//| Return the number of cells in the specified row                  |
//+------------------------------------------------------------------+
uint CTableModel::CellsInRow(const uint index)
  {
   CTableRow *row=this.GetRow(index);
   return(row!=NULL ? row.CellsTotal() : 0);
  }

L’objet ligne est récupéré par son index et le nombre de cellules qu'elle contient est renvoyé en appelant la méthode CellsTotal() sur la ligne.

Méthode qui renvoie le nombre de cellules dans le tableau :

//+------------------------------------------------------------------+
//| Return the number of cells in the table                          |
//+------------------------------------------------------------------+
uint CTableModel::CellsTotal(void)
  {
//--- count cells in a loop by rows (slow with a large number of rows)
   uint res=0, total=this.RowsTotal();
   for(int i=0; i<(int)total; i++)
     {
      CTableRow *row=this.GetRow(i);
      res+=(row!=NULL ? row.CellsTotal() : 0);
     }
   return res;
  }

La méthode parcourt toutes les lignes du tableau et ajoute le nombre de cellules de chaque ligne au résultat total, qui est renvoyé. Avec un grand nombre de lignes dans le tableau, ce comptage peut être lent. Une fois que toutes les méthodes affectant le nombre de cellules du tableau ont été créées, elles ne sont prises en compte que lorsque leur nombre change.

Méthode qui renvoie la cellule de tableau spécifiée :

//+------------------------------------------------------------------+
//| Return the specified table cell                                  |
//+------------------------------------------------------------------+
CTableCell *CTableModel::GetCell(const uint row,const uint col)
  {
//--- get the row by index row and return the row cell by 'row' index by 'col' index
   CTableRow *row_obj=this.GetRow(row);
   return(row_obj!=NULL ? row_obj.GetCell(col) : NULL);
  }

La ligne est récupérée par son index et le pointeur sur la cellule par son index col est renvoyé en utilisant la méthode GetCell() de l'objet ligne.

Méthode qui renvoie la description de la cellule :

//+------------------------------------------------------------------+
//| Return the cell description                                      |
//+------------------------------------------------------------------+
string CTableModel::CellDescription(const uint row,const uint col)
  {
   CTableCell *cell=this.GetCell(row,col);
   return(cell!=NULL ? cell.Description() : "");
  }

La ligne est récupérée par son index, puis la cellule est récupérée et sa description est renvoyée.

Méthode permettant d'afficher la description de la cellule dans le journal :

//+------------------------------------------------------------------+
//| Display a cell description in the journal                        |
//+------------------------------------------------------------------+
void CTableModel::CellPrint(const uint row,const uint col)
  {
//--- Get a cell by row and column index and return its description
   CTableCell *cell=this.GetCell(row,col);
   if(cell!=NULL)
      cell.Print();
  }

Un pointeur sur la cellule est récupéré par les index de ligne et de colonne et, à l'aide de la méthode Print() de l'objet cellule, sa description est écrite dans le journal.

Méthode qui supprime la ligne spécifiée :

//+------------------------------------------------------------------+
//| Delete a row                                                     |
//+------------------------------------------------------------------+
bool CTableModel::RowDelete(const uint index)
  {
//--- Remove a string from the list by index
   if(!this.m_list_rows.Delete(index))
      return false;
//--- After deleting a row, be sure to update all indices of all table cells
   this.CellsPositionUpdate();
   return true;
  }

La méthode Delete() de la classe CList permet de supprimer de la liste l'objet ligne par index. Après la suppression de la ligne, les index des lignes restantes et des cellules qu'elles contiennent ne correspondent pas à la réalité et doivent être ajustés à l'aide de la méthode CellsPositionUpdate().

Méthode qui déplace une ligne à la position spécifiée :

//+------------------------------------------------------------------+
//| Move a string to a specified position                            |
//+------------------------------------------------------------------+
bool CTableModel::RowMoveTo(const uint row_index,const uint index_to)
  {
//--- Get the row by index, turning it into the current one
   CTableRow *row=this.GetRow(row_index);
//--- Move the current string to the specified position in the list
   if(row==NULL || !this.m_list_rows.MoveToIndex(index_to))
      return false;
//--- After moving a row, it is necessary to update all indices of all table cells
   this.CellsPositionUpdate();
   return true;
  }

Dans la classe CList, de nombreuses méthodes travaillent avec l'objet liste en cours. Obtention d’un pointeur sur la ligne requise, en fait la ligne courante, et le déplace à la position requise à l'aide de la méthode MoveToIndex() de la classe CList. Après avoir déplacé la ligne vers une nouvelle position, il est nécessaire de mettre à jour les index des lignes restantes, ce que nous faisons à l'aide de la méthode CellsPositionUpdate().

Méthode qui définit la position des lignes et des colonnes pour toutes les cellules :

//+------------------------------------------------------------------+
//| Set the row and column positions to all cells                    |
//+------------------------------------------------------------------+
void CTableModel::CellsPositionUpdate(void)
  {
//--- In the loop by the list of rows
   for(int i=0;i<this.m_list_rows.Total();i++)
     {
      //--- get the next row
      CTableRow *row=this.GetRow(i);
      if(row==NULL)
         continue;
      //--- set the index, found by the IndexOf() method of the list, to the row
      row.SetIndex(this.m_list_rows.IndexOf(row));
      //--- Update the row cell position indices
      row.CellsPositionUpdate();
     }
  }

Parcours de la liste de toutes les lignes du tableau, sélection de chaque ligne suivante et définition correcte de son index, trouvé à l'aide de la méthode IndexOf() de la classe CList. Appel ensuite de la méthode CellsPositionUpdate() de la ligne, qui définit l'index correct pour chaque cellule de la ligne.

Méthode qui efface les données de toutes les cellules d'une ligne :

//+------------------------------------------------------------------+
//| Clear the row (only the data in the cells)                       |
//+------------------------------------------------------------------+
void CTableModel::RowResetData(const uint index)
  {
//--- Get a row from the list and clear the data of the row cells using the ClearData() method
   CTableRow *row=this.GetRow(index);
   if(row!=NULL)
      row.ClearData();
  }

Chaque cellule de la ligne est réinitialisée à une valeur "vide". Pour l'instant, à des fins de simplification, la valeur vide pour les cellules numériques est zéro, mais cela sera modifié ultérieurement, car zéro est également une valeur qui doit être affichée dans la cellule. Et la réinitialisation d'une valeur implique l'affichage d'un champ cellulaire vide.

Méthode qui efface les données de toutes les cellules du tableau :

//+------------------------------------------------------------------+
//| Clear the table (data of all cells)                              |
//+------------------------------------------------------------------+
void CTableModel::ClearData(void)
  {
//--- Clear the data of each row in the loop through all the table rows
   for(uint i=0;i<this.RowsTotal();i++)
      this.RowResetData(i);
  }

Parcours de toutes les lignes du tableau et, pour chacune d'elles, appel de la méthode RowResetData() décrite ci-dessus.

Méthode qui renvoie la description de la ligne :

//+------------------------------------------------------------------+
//| Return the row description                                       |
//+------------------------------------------------------------------+
string CTableModel::RowDescription(const uint index)
  {
//--- Get a row by index and return its description
   CTableRow *row=this.GetRow(index);
   return(row!=NULL ? row.Description() : "");
  }

Un pointeur sur la ligne est récupéré par son index et sa description est retournée.

Méthode permettant d'afficher la description des lignes dans le journal :

//+------------------------------------------------------------------+
//| Display the row description in the journal                       |
//+------------------------------------------------------------------+
void CTableModel::RowPrint(const uint index,const bool detail)
  {
   CTableRow *row=this.GetRow(index);
   if(row!=NULL)
      row.Print(detail);
  }

Un pointeur sur la ligne est récupéré et la méthode Print() de l'objet reçu est appelée.

Méthode qui supprime une colonne de tableau :

//+------------------------------------------------------------------+
//| Remove the column                                                |
//+------------------------------------------------------------------+
bool CTableModel::ColumnDelete(const uint index)
  {
   bool res=true;
   for(uint i=0;i<this.RowsTotal();i++)
     {
      CTableRow *row=this.GetRow(i);
      if(row!=NULL)
         res &=row.CellDelete(index);
     }
   return res;
  }

Dans une boucle parcourant toutes les lignes du tableau, chaque ligne est récupérée et la cellule demandée est supprimée en fonction de l'indice de la colonne. Cette opération supprime toutes les cellules du tableau qui ont les mêmes index de colonne.

Méthode permettant de déplacer une colonne de tableau :

//+------------------------------------------------------------------+
//| Move the column                                                  |
//+------------------------------------------------------------------+
bool CTableModel::ColumnMoveTo(const uint col_index,const uint index_to)
  {
   bool res=true;
   for(uint i=0;i<this.RowsTotal();i++)
     {
      CTableRow *row=this.GetRow(i);
      if(row!=NULL)
         res &=row.CellMoveTo(col_index,index_to);
     }
   return res;
  }

Dans une boucle parcourant toutes les lignes du tableau, chaque ligne est récupérée et la cellule demandée est déplacée vers une nouvelle position. Cette opération permet de déplacer toutes les cellules du tableau qui ont les mêmes index de colonne.

Méthode qui efface les données des cellules de la colonne :

//+------------------------------------------------------------------+
//| Clear the column data                                            |
//+------------------------------------------------------------------+
void CTableModel::ColumnResetData(const uint index)
  {
//--- In a loop through all table rows
   for(uint i=0;i<this.RowsTotal();i++)
     {
      //--- get the cell with the column index from each row and clear it
      CTableCell *cell=this.GetCell(i, index);
      if(cell!=NULL)
         cell.ClearData();
     }
  }

Dans une boucle parcourant toutes les lignes du tableau, chaque ligne est récupérée et les données dans la cellule voulue sont effacées. Cette opération permet d'effacer toutes les cellules du tableau qui ont les mêmes index de colonne.

Méthode qui renvoie la description de l'objet :

//+------------------------------------------------------------------+
//| Return the object description                                    |
//+------------------------------------------------------------------+
string CTableModel::Description(void)
  {
   return(::StringFormat("%s: Rows %u, Cells in row %u, Cells Total %u",
                         TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.RowsTotal(),this.CellsInRow(0),this.CellsTotal()));
  }

Une ligne est créée et renvoyée à partir de certains paramètres du modèle de tableau dans ce format :

Table Model: Rows 4, Cells in row 4, Cells Total 16:

Méthode qui produit une description de l'objet dans le journal :

//+------------------------------------------------------------------+
//| Display the object description in the journal                    |
//+------------------------------------------------------------------+
void CTableModel::Print(const bool detail)
  {
//--- Display the header in the journal
   ::Print(this.Description()+(detail ? ":" : ""));
//--- If detailed description,
   if(detail)
     {
      //--- In a loop through all table rows
      for(uint i=0; i<this.RowsTotal(); i++)
        {
         //--- get the next row and display its detailed description to the journal
         CTableRow *row=this.GetRow(i);
         if(row!=NULL)
            row.Print(true,false);
        }
     }
  }

Tout d'abord, l'en-tête est imprimé en tant que description du modèle, puis, si l'indicateur de sortie détaillée est activé, les descriptions détaillées de toutes les lignes du modèle de tableau sont imprimées dans la boucle.

Méthode permettant d'afficher la description de l'objet dans le journal sous forme de tableau :

//+------------------------------------------------------------------+
//| Display the object description as a table in the journal         |
//+------------------------------------------------------------------+
void CTableModel::PrintTable(const int cell_width=10)
  {
//--- Get the pointer to the first row (index 0)
   CTableRow *row=this.GetRow(0);
   if(row==NULL)
      return;
   //--- Create a table header row based on the number of cells in the first row of the table
   uint total=row.CellsTotal();
   string head=" n/n";
   string res=::StringFormat("|%*s |",cell_width,head);
   for(uint i=0;i<total;i++)
     {
      if(this.GetCell(0, i)==NULL)
         continue;
      string cell_idx=" Column "+(string)i;
      res+=::StringFormat("%*s |",cell_width,cell_idx);
     }
   //--- Display the header row in the journal
   ::Print(res);
   
   //--- Iterate through all table rows and display them in tabular form
   for(uint i=0;i<this.RowsTotal();i++)
     {
      CTableRow *row=this.GetRow(i);
      if(row!=NULL)
         row.Print(true,true,cell_width);
     }
  }

Tout d'abord, en fonction du nombre de cellules dans la toute première ligne du tableau, l'en-tête du tableau est créé avec les noms des colonnes du tableau et imprimé dans le journal. Ensuite, toutes les lignes du tableau sont parcourues dans une boucle et chacune d'entre elles est imprimée sous forme de tableau.

Méthode qui détruit le modèle de tableau :

//+------------------------------------------------------------------+
//| Destroy the model                                                |
//+------------------------------------------------------------------+
void CTableModel::Destroy(void)
  {
//--- Clear cell list
   m_list_rows.Clear();
  }

La liste des lignes du tableau est simplement effacée et tous les objets sont détruits à l'aide de la méthode Clear() de la classe CList.

Méthode d'enregistrement du modèle de tableau dans un fichier :

//+------------------------------------------------------------------+
//| Save to file                                                     |
//+------------------------------------------------------------------+
bool CTableModel::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long))
      return(false);
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return(false);

   //--- Save the list of rows
   if(!this.m_list_rows.Save(file_handle))
      return(false);
   
//--- Successful
   return true;
  }

Après avoir enregistré le marqueur de début de données et le type de liste, la liste des lignes est enregistrée dans le fichier à l'aide de la méthode Save() de la classe CList.

Méthode de chargement d'un modèle de tableau à partir d'un fichier :

//+------------------------------------------------------------------+
//| Load from file                                                   |
//+------------------------------------------------------------------+
bool CTableModel::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return(false);

   //--- Load the list of rows
   if(!this.m_list_rows.Load(file_handle))
      return(false);
   
//--- Successful
   return true;
  }

Après avoir chargé et vérifié le marqueur de début de données et le type de liste, la liste des lignes est chargé du fichier à l'aide de la méthode Load() de la classe CListObj, décrite au début de l'article.

Toutes les classes permettant de créer un modèle de tableau sont prêtes. Écrivons maintenant un script pour tester le fonctionnement du modèle.


Test du résultat

Continuez à écrire le code dans le même fichier. Ecrivez un script dans lequel nous créerons un tableau 4x4 bidimensionnel (4 lignes de 4 cellules chacune) avec le type long par exemple. Ensuite, ouvrez un fichier pour y écrire les données du tableau et chargez les données dans le tableau à partir du fichier. Créez un modèle de tableau et vérifiez le fonctionnement de certaines de ses méthodes.

Chaque fois que le tableau est modifié, nous enregistrons le résultat reçu à l'aide de la fonction TableModelPrint(), qui sélectionne la manière d'imprimer le modèle de tableau. Si la valeur de la macro PRINT_AS_TABLE est true, l'enregistrement est effectué à l'aide de la méthode PrintTable() de la classe CTableModel, si la valeur est false — à l'aide de la méthode Print() de la même classe.

Dans un script, créez un modèle de tableau, imprimez-le sous forme de tableau et enregistrez le modèle dans un fichier. Ensuite, ajoutez des lignes, supprimez des colonnes et modifiez les droits d'édition...

Téléchargez ensuite à nouveau la version initiale du tableau à partir du fichier et imprimez le résultat.

#define  PRINT_AS_TABLE    true  // Display the model as a table
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Declare and fill the 4x4 array
//--- Acceptable array types: double, long, datetime, color, string
   long array[4][4]={{ 1,  2,  3,  4},
                     { 5,  6,  7,  8},
                     { 9, 10, 11, 12},
                     {13, 14, 15, 16}};
     
//--- Create a table model from the 4x4 long array created above
   CTableModel *tm=new CTableModel(array);
   
//--- Leave if the model is not created
   if(tm==NULL)
      return;

//--- Print the model in tabular form
   Print("The table model has been successfully created:");
   tm.PrintTable();
   
//--- Delete the table model object
   delete tm;
  }

Nous obtenons ainsi le résultat suivant du script dans le journal :

The table model has been successfully created:
|       n/n |  Column 0 |  Column 1 |  Column 2 |  Column 3 |
| Row 0     |         1 |         2 |         3 |         4 |
| Row 1     |         5 |         6 |         7 |         8 |
| Row 2     |         9 |        10 |        11 |        12 |
| Row 3     |        13 |        14 |        15 |        16 |

Pour tester l'utilisation du modèle de tableau, l'ajout, la suppression et le déplacement de lignes et de colonnes, ainsi que l'utilisation d'un fichier, complétez le script :

#define  PRINT_AS_TABLE    true  // Display the model as a table
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Declare and fill the 4x4 array
//--- Acceptable array types: double, long, datetime, color, string
   long array[4][4]={{ 1,  2,  3,  4},
                     { 5,  6,  7,  8},
                     { 9, 10, 11, 12},
                     {13, 14, 15, 16}};
     
//--- Create a table model from the 4x4 long array created above
   CTableModel *tm=new CTableModel(array);
   
//--- Leave if the model is not created
   if(tm==NULL)
      return;

//--- Print the model in tabular form
   Print("The table model has been successfully created:");
   tm.PrintTable();
   
   
//--- Check handling files and the functionality of the table model
//--- Open a file to write table model data into it
   int handle=FileOpen(MQLInfoString(MQL_PROGRAM_NAME)+".bin",FILE_READ|FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
      return;
      
   //--- Save the original created table to the file
   if(tm.Save(handle))
      Print("\nThe table model has been successfully saved to file.");
   
//--- Now insert a new row into the table at position 2
//--- Get the last cell of the created row and make it non-editable
//--- Print the modified table model in the journal
   if(tm.RowInsertNewTo(2))
     {
      Print("\nInsert a new row at position 2 and set cell 3 to non-editable");
      CTableCell *cell=tm.GetCell(2,3);
      if(cell!=NULL)
         cell.SetEditable(false);
      TableModelPrint(tm);
     }
   
//--- Now delete the table column with index 1 and
//--- print the resulting table model in the journal
   if(tm.ColumnDelete(1))
     {
      Print("\nRemove column from position 1");
      TableModelPrint(tm);
     }
   
//--- When saving table data, the file pointer was shifted to the last set data
//--- Place the pointer at the beginning of the file, load the previously saved original table and print it
   if(FileSeek(handle,0,SEEK_SET) && tm.Load(handle))
     {
      Print("\nLoad the original table view from the file:");
      TableModelPrint(tm);
     }
   
//--- Close the open file and delete the table model object
   FileClose(handle);
   delete tm;
  }
//+------------------------------------------------------------------+
//| Display the table model                                          |
//+------------------------------------------------------------------+
void TableModelPrint(CTableModel *tm)
  {
   if(PRINT_AS_TABLE)
      tm.PrintTable();  // Print the model as a table
   else
      tm.Print(true);   // Print detailed table data
  }

Ce résultat apparaît dans le journal :

The table model has been successfully created:
|       n/n |  Column 0 |  Column 1 |  Column 2 |  Column 3 |
| Row 0     |         1 |         2 |         3 |         4 |
| Row 1     |         5 |         6 |         7 |         8 |
| Row 2     |         9 |        10 |        11 |        12 |
| Row 3     |        13 |        14 |        15 |        16 |

The table model has been successfully saved to file.

Insert a new row at position 2 and set cell 3 to non-editable
|       n/n |  Column 0 |  Column 1 |  Column 2 |  Column 3 |
| Row 0     |         1 |         2 |         3 |         4 |
| Row 1     |         5 |         6 |         7 |         8 |
| Row 2     |      0.00 |      0.00 |      0.00 |      0.00 |
| Row 3     |         9 |        10 |        11 |        12 |
| Row 4     |        13 |        14 |        15 |        16 |

Remove column from position 1
|       n/n |  Column 0 |  Column 1 |  Column 2 |
| Row 0     |         1 |         3 |         4 |
| Row 1     |         5 |         7 |         8 |
| Row 2     |      0.00 |      0.00 |      0.00 |
| Row 3     |         9 |        11 |        12 |
| Row 4     |        13 |        15 |        16 |

Load the original table view from the file:
|       n/n |  Column 0 |  Column 1 |  Column 2 |  Column 3 |
| Row 0     |         1 |         2 |         3 |         4 |
| Row 1     |         5 |         6 |         7 |         8 |
| Row 2     |         9 |        10 |        11 |        12 |
| Row 3     |        13 |        14 |        15 |        16 |

Si vous souhaitez éditer dans le journal les données qui ne sont pas sous forme de tableau à partir du modèle de tableau, utilisez false dans la macro PRINT_AS_TABLE :

#define  PRINT_AS_TABLE    false  // Display the model as a table

Dans ce cas, le texte suivant s'affiche dans le journal :

The table model has been successfully created:
|       n/n |  Column 0 |  Column 1 |  Column 2 |  Column 3 |
| Row 0     |         1 |         2 |         3 |         4 |
| Row 1     |         5 |         6 |         7 |         8 |
| Row 2     |         9 |        10 |        11 |        12 |
| Row 3     |        13 |        14 |        15 |        16 |

The table model has been successfully saved to file.

Insert a new row at position 2 and set cell 3 to non-editable
Table Model: Rows 5, Cells in row 4, Cells Total 20:
Table Row: Position 0, Cells total: 4:
  Table Cell: Row 0, Col 0, Editable <long>Value: 1
  Table Cell: Row 0, Col 1, Editable <long>Value: 2
  Table Cell: Row 0, Col 2, Editable <long>Value: 3
  Table Cell: Row 0, Col 3, Editable <long>Value: 4
Table Row: Position 1, Cells total: 4:
  Table Cell: Row 1, Col 0, Editable <long>Value: 5
  Table Cell: Row 1, Col 1, Editable <long>Value: 6
  Table Cell: Row 1, Col 2, Editable <long>Value: 7
  Table Cell: Row 1, Col 3, Editable <long>Value: 8
Table Row: Position 2, Cells total: 4:
  Table Cell: Row 2, Col 0, Editable <double>Value: 0.00
  Table Cell: Row 2, Col 1, Editable <double>Value: 0.00
  Table Cell: Row 2, Col 2, Editable <double>Value: 0.00
  Table Cell: Row 2, Col 3, Uneditable <double>Value: 0.00
Table Row: Position 3, Cells total: 4:
  Table Cell: Row 3, Col 0, Editable <long>Value: 9
  Table Cell: Row 3, Col 1, Editable <long>Value: 10
  Table Cell: Row 3, Col 2, Editable <long>Value: 11
  Table Cell: Row 3, Col 3, Editable <long>Value: 12
Table Row: Position 4, Cells total: 4:
  Table Cell: Row 4, Col 0, Editable <long>Value: 13
  Table Cell: Row 4, Col 1, Editable <long>Value: 14
  Table Cell: Row 4, Col 2, Editable <long>Value: 15
  Table Cell: Row 4, Col 3, Editable <long>Value: 16

Remove column from position 1
Table Model: Rows 5, Cells in row 3, Cells Total 15:
Table Row: Position 0, Cells total: 3:
  Table Cell: Row 0, Col 0, Editable <long>Value: 1
  Table Cell: Row 0, Col 1, Editable <long>Value: 3
  Table Cell: Row 0, Col 2, Editable <long>Value: 4
Table Row: Position 1, Cells total: 3:
  Table Cell: Row 1, Col 0, Editable <long>Value: 5
  Table Cell: Row 1, Col 1, Editable <long>Value: 7
  Table Cell: Row 1, Col 2, Editable <long>Value: 8
Table Row: Position 2, Cells total: 3:
  Table Cell: Row 2, Col 0, Editable <double>Value: 0.00
  Table Cell: Row 2, Col 1, Editable <double>Value: 0.00
  Table Cell: Row 2, Col 2, Uneditable <double>Value: 0.00
Table Row: Position 3, Cells total: 3:
  Table Cell: Row 3, Col 0, Editable <long>Value: 9
  Table Cell: Row 3, Col 1, Editable <long>Value: 11
  Table Cell: Row 3, Col 2, Editable <long>Value: 12
Table Row: Position 4, Cells total: 3:
  Table Cell: Row 4, Col 0, Editable <long>Value: 13
  Table Cell: Row 4, Col 1, Editable <long>Value: 15
  Table Cell: Row 4, Col 2, Editable <long>Value: 16

Load the original table view from the file:
Table Model: Rows 4, Cells in row 4, Cells Total 16:
Table Row: Position 0, Cells total: 4:
  Table Cell: Row 0, Col 0, Editable <long>Value: 1
  Table Cell: Row 0, Col 1, Editable <long>Value: 2
  Table Cell: Row 0, Col 2, Editable <long>Value: 3
  Table Cell: Row 0, Col 3, Editable <long>Value: 4
Table Row: Position 1, Cells total: 4:
  Table Cell: Row 1, Col 0, Editable <long>Value: 5
  Table Cell: Row 1, Col 1, Editable <long>Value: 6
  Table Cell: Row 1, Col 2, Editable <long>Value: 7
  Table Cell: Row 1, Col 3, Editable <long>Value: 8
Table Row: Position 2, Cells total: 4:
  Table Cell: Row 2, Col 0, Editable <long>Value: 9
  Table Cell: Row 2, Col 1, Editable <long>Value: 10
  Table Cell: Row 2, Col 2, Editable <long>Value: 11
  Table Cell: Row 2, Col 3, Editable <long>Value: 12
Table Row: Position 3, Cells total: 4:
  Table Cell: Row 3, Col 0, Editable <long>Value: 13
  Table Cell: Row 3, Col 1, Editable <long>Value: 14
  Table Cell: Row 3, Col 2, Editable <long>Value: 15
  Table Cell: Row 3, Col 3, Editable <long>Value: 16

Cette sortie fournit davantage d'informations de débogage. Par exemple, nous voyons ici que l'activation du drapeau d'interdiction de modification d'une cellule est affichée dans les journaux du programme.

Toutes les fonctionnalités mentionnées fonctionnent comme prévu, les lignes sont ajoutées, déplacées, les colonnes sont supprimées, nous pouvons modifier les propriétés des cellules et travailler avec des fichiers.


Conclusion

Il s'agit de la première version d'un modèle de tableau simple qui permet de créer un tableau à partir d'un tableau de données à 2 dimensions. Nous créerons par la suite d'autres modèles de tableaux spécialisés, nous créerons le composant View et, enfin, nous travaillerons pleinement avec des données tabulaires comme l'un des contrôles de l'interface graphique de l'utilisateur.

Le fichier du script créé aujourd'hui avec les classes incluses est joint à l'article. Vous pouvez le télécharger pour l'étudier vous-même.

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

Fichiers joints |
TableModelTest.mq5 (136.43 KB)
Derniers commentaires | Aller à la discussion (9)
Alexey Viktorov
Alexey Viktorov | 4 avr. 2025 à 15:31
Artyom Trishkin #:

Lorsqu'une classe de SomeObject est chargée à partir d'un fichier en appelant la méthode Load() de ce même SomeObject, elle vérifie "est-ce que je me suis vraiment lu moi-même dans le fichier ? Si ce n'est pas le cas, cela signifie que quelque chose s'est mal passé et qu'il est inutile de poursuivre le chargement.

Ce que j'ai ici est une LISTE (CListObj) qui lit un type d'objet à partir d'un fichier. La liste ne sait pas ce qu'il y a (quel objet) dans le fichier. Mais elle doit connaître ce type d'objet pour le créer dans sa méthode CreateElement(). C'est pourquoi elle ne vérifie pas le type de l'objet chargé à partir du fichier. Après tout, il y aura une comparaison avec Type(), qui dans cette méthode renvoie le type d'une liste, pas d'un objet.

Merci, j'ai compris.

Maxim Kuznetsov
Maxim Kuznetsov | 5 avr. 2025 à 08:05

Je l'ai lu, puis relu.

c'est tout sauf un "modèle" en MVC. Certains ListStorage par exemple

Rashid Umarov
Rashid Umarov | 5 avr. 2025 à 08:37
Passons aux choses sérieuses. Gardez vos opinions pour vous.
Aleksey Nikolayev
Aleksey Nikolayev | 5 avr. 2025 à 09:38
Je m'interroge. Est-il possible d'obtenir un analogue des dataframes de python et de R de cette manière ? Il s'agit de tableaux dont les différentes colonnes peuvent contenir des données de différents types (à partir d'un ensemble limité de types, mais incluant les chaînes de caractères).
Artyom Trishkin
Artyom Trishkin | 5 avr. 2025 à 11:29
Aleksey Nikolayev #:
Je m'interroge. Est-il possible d'obtenir un analogue des dataframes de python et de R de cette manière ? Il s'agit de tableaux où les différentes colonnes peuvent contenir des données de différents types (à partir d'un ensemble limité de types, mais incluant les chaînes de caractères).

C'est possible. Si nous parlons de différentes colonnes d'un tableau, alors dans l'implémentation décrite, chaque cellule du tableau peut avoir un type de données différent.

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
Voici la deuxième partie de l'article consacré à l'implémentation du modèle de tableau dans MQL5 en utilisant le paradigme architectural MVC (Modèle-Vue-Contrôleur). L'article traite du développement des classes de tableau et de l'en-tête de tableau sur la base d'un modèle de tableau créé précédemment. Les classes développées constitueront la base de l'implémentation ultérieure des composants Vue et Contrôleur, qui seront abordés dans les articles suivants.
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.
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.
Passer à MQL5 Algo Forge (Partie 3) : Utiliser des dépôts externes dans vos propres projets Passer à MQL5 Algo Forge (Partie 3) : Utiliser des dépôts externes dans vos propres projets
Voyons comment vous pouvez commencer à intégrer du code externe à partir de n'importe quel dépôt du stockage MQL5 Algo Forge dans votre propre projet. Dans cet article, nous nous attaquons enfin à cette tâche prometteuse, mais plus complexe : comment connecter et utiliser de manière pratique des bibliothèques provenant de dépôts tiers dans MQL5 Algo Forge.