English Русский 中文 Español 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 |
856 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 <Arrays\List.mqh>

//--- Форвард-декларация классов
class CTableCell;                   // Класс ячейки таблицы
class CTableRow;                    // Класс строки таблицы
class CTableModel;                  // Класс модели таблицы

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 <Arrays\List.mqh>

//--- Форвард-декларация классов
class CTableCell;                   // Класс ячейки таблицы
class CTableRow;                    // Класс строки таблицы
class CTableModel;                  // Класс модели таблицы

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

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_TYPE               // Перечисление типов объектов
  {
   OBJECT_TYPE_TABLE_CELL=10000,    // Ячейка таблицы
   OBJECT_TYPE_TABLE_ROW,           // Строка таблицы
   OBJECT_TYPE_TABLE_MODEL,         // Модель таблицы
  };
  
enum ENUM_CELL_COMPARE_MODE         // Режимы сравнения ячеек таблицы
  {
   CELL_COMPARE_MODE_COL,           // Сравнение по номеру колонки
   CELL_COMPARE_MODE_ROW,           // Сравнение по номеру строки
   CELL_COMPARE_MODE_ROW_COL,       // Сравнение по строке и колонке
  };
  
//+------------------------------------------------------------------+
//| Функции                                                          |
//+------------------------------------------------------------------+
//--- Возвращает тип объекта как строку
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;
  }
//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+

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 :

//+------------------------------------------------------------------+
//| Классы                                                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс связанного списка объектов                                 |
//+------------------------------------------------------------------+
class CListObj : public CList
  {
protected:
   ENUM_OBJECT_TYPE  m_element_type;   // Тип создаваемого объекта в CreateElement()
public:
//--- Виртуальный метод (1) загрузки списка из файла, (2) создания элемента списка
   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 :

//+------------------------------------------------------------------+
//| Загрузка списка из файла                                         |
//+------------------------------------------------------------------+
bool CListObj::Load(const int file_handle)
  {
//--- Переменные
   CObject *node;
   bool     result=true;
//--- Проверяем хэндл
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- Загрузка и проверка маркера начала списка - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=MARKER_START_DATA)
      return(false);
//--- Загрузка и проверка типа списка
   if(::FileReadInteger(file_handle,INT_VALUE)!=Type())
      return(false);
//--- Чтение размера списка (количество объектов)
   uint num=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Последовательно заново создаём элементы списка с помощью вызова метода Load() объектов node
   this.Clear();
   for(uint i=0; i<num; i++)
     {
      //--- Читаем и проверяем маркер начала данных объекта - 0xFFFFFFFFFFFFFFFF
      if(::FileReadLong(file_handle)!=MARKER_START_DATA)
         return false;
      //--- Читаем тип объекта
      this.m_element_type=(ENUM_OBJECT_TYPE)::FileReadInteger(file_handle,INT_VALUE);
      node=this.CreateElement();
      if(node==NULL)
         return false;
      this.Add(node);
      //--- Сейчас файловый указатель смещён относительно начала маркера объекта на 12 байт (8 - маркер, 4 - тип)
      //--- Поставим указатель на начало данных объекта и загрузим свойства объекта из файла методом Load() элемента node.
      if(!::FileSeek(file_handle,-12,SEEK_CUR))
         return false;
      result &=node.Load(file_handle);
     }
//--- Результат
   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 :

//+------------------------------------------------------------------+
//| Метод создания элемента списка                                   |
//+------------------------------------------------------------------+
CObject *CListObj::CreateElement(void)
  {
//--- В зависимости от типа объекта в m_element_type, создаём новый объект
   switch(this.m_element_type)
     {
      case OBJECT_TYPE_TABLE_CELL   :  return new CTableCell();
      case OBJECT_TYPE_TABLE_ROW    :  return new CTableRow();
      case OBJECT_TYPE_TABLE_MODEL  :  return new CTableModel();
      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 :

//+------------------------------------------------------------------+
//| Класс ячейки таблицы                                             |
//+------------------------------------------------------------------+
class CTableCell : public CObject
  {
protected:
//--- Объединение для хранения значений ячейки (double, long, string)
   union DataType
     {
      protected:
      double         double_value;
      long           long_value;
      ushort         ushort_value[MAX_STRING_LENGTH];

      public:
      //--- Установка значений
      void           SetValueD(const double value) { this.double_value=value;                   }
      void           SetValueL(const long value)   { this.long_value=value;                     }
      void           SetValueS(const string value) { ::StringToShortArray(value,ushort_value);  }
      
      //--- Возврат значений
      double         ValueD(void) const { return this.double_value; }
      long           ValueL(void) const { return this.long_value; }
      string         ValueS(void) const
                       {
                        string res=::ShortArrayToString(this.ushort_value);
                        res.TrimLeft();
                        res.TrimRight();
                        return res;
                       }
     };
//--- Переменные
   DataType          m_datatype_value;                      // Значение
   ENUM_DATATYPE     m_datatype;                            // Тип данных
   CObject          *m_object;                              // Объект в ячейке
   ENUM_OBJECT_TYPE  m_object_type;                         // Тип объекта в ячейке
   int               m_row;                                 // Номер строки
   int               m_col;                                 // Номер столбца
   int               m_digits;                              // Точность представления данных
   uint              m_time_flags;                          // Флаги отображения даты/времени
   bool              m_color_flag;                          // Флаг отображения наименования цвета
   bool              m_editable;                            // Флаг редактируемой ячейки
   
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:
//--- Возврат координат и свойств ячейки
   uint              Row(void)                           const { return this.m_row;                            }
   uint              Col(void)                           const { return this.m_col;                            }
   ENUM_DATATYPE     Datatype(void)                      const { return this.m_datatype;                       }
   int               Digits(void)                        const { return this.m_digits;                         }
   uint              DatetimeFlags(void)                 const { return this.m_time_flags;                     }
   bool              ColorNameFlag(void)                 const { return this.m_color_flag;                     }
   bool              IsEditable(void)                    const { return this.m_editable;                       }
//--- Возвращает (1) double, (2) long, (3) string значение
   double            ValueD(void)                        const { return this.m_datatype_value.ValueD();        }
   long              ValueL(void)                        const { return this.m_datatype_value.ValueL();        }
   string            ValueS(void)                        const { return this.m_datatype_value.ValueS();        }
//--- Возвращает значение в виде форматированной строки
   string            Value(void) const
                       {
                        switch(this.m_datatype)
                          {
                           case TYPE_DOUBLE  :  return(::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;
                       }
//--- Установка значений переменных
   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);
                       }
//--- Назначает объект в ячейку
   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();
                       }
//--- Снимает назначение объекта
   void              UnassignObject(void)
                       {
                        this.m_object=NULL;
                        this.m_object_type=-1;
                       }
                       
//--- Устанавливает double-значение
   void              SetValue(const double value)
                       {
                        this.m_datatype=TYPE_DOUBLE;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueD(value);
                       }
//--- Устанавливает long-значение
   void              SetValue(const long value)
                       {
                        this.m_datatype=TYPE_LONG;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueL(value);
                       }
//--- Устанавливает datetime-значение
   void              SetValue(const datetime value)
                       {
                        this.m_datatype=TYPE_DATETIME;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueL(value);
                       }
//--- Устанавливает color-значение
   void              SetValue(const color value)
                       {
                        this.m_datatype=TYPE_COLOR;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueL(value);
                       }
//--- Устанавливает string-значение
   void              SetValue(const string value)
                       {
                        this.m_datatype=TYPE_STRING;
                        if(this.m_editable)
                           this.m_datatype_value.SetValueS(value);
                       }
//--- Очищает данные
   void              ClearData(void)
                       {
                        if(this.Datatype()==TYPE_STRING)
                           this.SetValue("");
                        else
                           this.SetValue(0.0);
                       }
//--- (1) Возвращает, (2) выводит в журнал описание объекта
   string            Description(void);
   void              Print(void);

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_CELL);}
   
   
//--- Конструкторы/деструктор
                     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);
                       }
                     //--- Принимает double-значение
                     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);
                       }
                     //--- Принимает long-значение
                     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);
                       }
                     //--- Принимает datetime-значение
                     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);
                       }
                     //--- Принимает color-значение
                     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);
                       }
                     //--- Принимает string-значение
                     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 :

//--- Устанавливает double-значение
   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 :

//--- Очищает данные
   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 :

 //--- Принимает double-значение
 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);
   }
 //--- Принимает long-значение
 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);
   }
 //--- Принимает datetime-значение
 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);
   }

 //--- Принимает color-значение
 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);
   }
 //--- Принимает string-значение
 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 :

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

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

   //--- Сохраняем тип данных
   if(::FileWriteInteger(file_handle,this.m_datatype,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем тип объекта в ячейке
   if(::FileWriteInteger(file_handle,this.m_object_type,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем номер строки
   if(::FileWriteInteger(file_handle,this.m_row,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем номер столбца
   if(::FileWriteInteger(file_handle,this.m_col,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем точность представления данных
   if(::FileWriteInteger(file_handle,this.m_digits,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем флаги отображения даты/времени
   if(::FileWriteInteger(file_handle,this.m_time_flags,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем флаг отображения наименования цвета
   if(::FileWriteInteger(file_handle,this.m_color_flag,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем флаг редактируемой ячейки
   if(::FileWriteInteger(file_handle,this.m_editable,INT_VALUE)!=INT_VALUE)
      return(false);
   //--- Сохраняем значение
   if(::FileWriteStruct(file_handle,this.m_datatype_value)!=sizeof(this.m_datatype_value))
      return(false);
   
//--- Всё успешно
   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 :

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

   //--- Загружаем тип данных
   this.m_datatype=(ENUM_DATATYPE)::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем тип объекта в ячейке
   this.m_object_type=(ENUM_OBJECT_TYPE)::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем номер строки
   this.m_row=::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем номер столбца
   this.m_col=::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем точность представления данных
   this.m_digits=::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем флаги отображения даты/времени
   this.m_time_flags=::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем флаг отображения наименования цвета
   this.m_color_flag=::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем флаг редактируемой ячейки
   this.m_editable=::FileReadInteger(file_handle,INT_VALUE);
   //--- Загружаем значение
   if(::FileReadStruct(file_handle,this.m_datatype_value)!=sizeof(this.m_datatype_value))
      return(false);
   
//--- Всё успешно
   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 :

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

//+------------------------------------------------------------------+
//| Выводит в журнал описание объекта                                |
//+------------------------------------------------------------------+
void CTableCell::Print(void)
  {
   ::Print(this.Description());
  }

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

//+------------------------------------------------------------------+
//| Класс строки таблицы                                             |
//+------------------------------------------------------------------+
class CTableRow : public CObject
  {
protected:
   CTableCell        m_cell_tmp;                            // Объект ячейки для поиска в списке
   CListObj          m_list_cells;                          // Список ячеек
   uint              m_index;                               // Индекс строки
   
//--- Добавляет указанную ячейку в конец списка
   bool              AddNewCell(CTableCell *cell);
   
public:
//--- (1) Устанавливает, (2) возвращает индекс строки
   void              SetIndex(const uint index)                { this.m_index=index;  }
   uint              Index(void)                         const { return this.m_index; }
//--- Устанавливает позиции строки и колонки всем ячейкам
   void              CellsPositionUpdate(void);
   
//--- Создаёт новую ячейку и добавляет в конец списка
   CTableCell       *CreateNewCell(const double value);
   CTableCell       *CreateNewCell(const long value);
   CTableCell       *CreateNewCell(const datetime value);
   CTableCell       *CreateNewCell(const color value);
   CTableCell       *CreateNewCell(const string value);
   
//--- Возвращает (1) ячейку по индексу, (2) количество ячеек
   CTableCell       *GetCell(const uint index)                 { return this.m_list_cells.GetNodeAtIndex(index);  }
   uint              CellsTotal(void)                    const { return this.m_list_cells.Total();                }
   
//--- Устанавливает значение в указанную ячейку
   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) назначает в ячейку, (2) снимает с ячейки назначенный объект
   void              CellAssignObject(const uint index,CObject *object);
   void              CellUnassignObject(const uint index);
   
//--- (1) Удаляет (2) перемещает ячейку
   bool              CellDelete(const uint index);
   bool              CellMoveTo(const uint cell_index, const uint index_to);
   
//--- Обнуляет данные ячеек строки
   void              ClearData(void);

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

//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_ROW); }
   
//--- Конструкторы/деструктор
                     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 :

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

//+------------------------------------------------------------------+
//| Создаёт новую double-ячейку и добавляет в конец списка           |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const double value)
  {
//--- Создаём новый объект ячейки, хранящей значение с типом double
   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;
     }
//--- Добавляем созданную ячейку в конец списка
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Возвращаем указатель на объект
   return cell;
  }
//+------------------------------------------------------------------+
//| Создаёт новую long-ячейку и добавляет в конец списка             |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const long value)
  {
//--- Создаём новый объект ячейки, хранящей значение с типом long
   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;
     }
//--- Добавляем созданную ячейку в конец списка
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Возвращаем указатель на объект
   return cell;
  }
//+------------------------------------------------------------------+
//| Создаёт новую datetime-ячейку и добавляет в конец списка         |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const datetime value)
  {
//--- Создаём новый объект ячейки, хранящей значение с типом datetime
   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;
     }
//--- Добавляем созданную ячейку в конец списка
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Возвращаем указатель на объект
   return cell;
  }
//+------------------------------------------------------------------+
//| Создаёт новую color-ячейку и добавляет в конец списка            |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const color value)
  {
//--- Создаём новый объект ячейки, хранящей значение с типом color
   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;
     }
//--- Добавляем созданную ячейку в конец списка
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Возвращаем указатель на объект
   return cell;
  }
//+------------------------------------------------------------------+
//| Создаёт новую string-ячейку и добавляет в конец списка           |
//+------------------------------------------------------------------+
CTableCell *CTableRow::CreateNewCell(const string value)
  {
//--- Создаём новый объект ячейки, хранящей значение с типом string
   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;
     }
//--- Добавляем созданную ячейку в конец списка
   if(!this.AddNewCell(cell))
     {
      delete cell;
      return NULL;
     }
//--- Возвращаем указатель на объект
   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 :

//+------------------------------------------------------------------+
//| Добавляет ячейку в конец списка                                  |
//+------------------------------------------------------------------+
bool CTableRow::AddNewCell(CTableCell *cell)
  {
//--- Если передан пустой объект - сообщаем и возвращаем false
   if(cell==NULL)
     {
      ::PrintFormat("%s: Error. Empty CTableCell object passed",__FUNCTION__);
      return false;
     }
//--- Устанавливаем индекс ячейки в списке и добавляем созданную ячейку в конец списка
   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;
     }
//--- Успешно
   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 :

//+------------------------------------------------------------------+
//| Устанавливает double-значение в указанную ячейку                 |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const double value)
  {
//--- Получаем из списка нужную ячейку и записываем в неё новое значение
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Устанавливает long-значение в указанную ячейку                   |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const long value)
  {
//--- Получаем из списка нужную ячейку и записываем в неё новое значение
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Устанавливает datetime-значение в указанную ячейку               |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const datetime value)
  {
//--- Получаем из списка нужную ячейку и записываем в неё новое значение
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Устанавливает color-значение в указанную ячейку                  |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const color value)
  {
//--- Получаем из списка нужную ячейку и записываем в неё новое значение
   CTableCell *cell=this.GetCell(index);
   if(cell!=NULL)
      cell.SetValue(value);
  }
//+------------------------------------------------------------------+
//| Устанавливает string-значение в указанную ячейку                 |
//+------------------------------------------------------------------+
void CTableRow::CellSetValue(const uint index,const string value)
  {
//--- Получаем из списка нужную ячейку и записываем в неё новое значение
   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 :

//+------------------------------------------------------------------+
//| Назначает в ячейку объект                                        |
//+------------------------------------------------------------------+
void CTableRow::CellAssignObject(const uint index,CObject *object)
  {
//--- Получаем из списка нужную ячейку и записываем в неё указатель на объект
   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 :

//+------------------------------------------------------------------+
//| Отменяет для ячейки назначенный объект                           |
//+------------------------------------------------------------------+
void CTableRow::CellUnassignObject(const uint index)
  {
//--- Получаем из списка нужную ячейку и отменяем в ней указатель на объект и его тип
   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 :

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

//+------------------------------------------------------------------+
//| Перемещает ячейку на указанную позицию                           |
//+------------------------------------------------------------------+
bool CTableRow::CellMoveTo(const uint cell_index,const uint index_to)
  {
//--- Выбираем нужную ячейку по индексу в списке, делая её текущей
   CTableCell *cell=this.GetCell(cell_index);
//--- Перемещаем текущую ячейку на указанную позицию в списке
   if(cell==NULL || !this.m_list_cells.MoveToIndex(index_to))
      return false;
//--- Обновляем индексы всех ячеек в списке
   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 :

//+------------------------------------------------------------------+
//| Устанавливает позиции строки и колонки всем ячейкам              |
//+------------------------------------------------------------------+
void CTableRow::CellsPositionUpdate(void)
  {
//--- В цикле по всем ячейкам в списке
   for(int i=0;i<this.m_list_cells.Total();i++)
     {
      //--- получаем очередную ячейку и устанавливаем в неё индексы строки и столбца
      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 :

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

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

//+------------------------------------------------------------------+
//| Выводит в журнал описание объекта                                |
//+------------------------------------------------------------------+
void CTableRow::Print(const bool detail, const bool as_table=false, const int cell_width=10)
  {
      
//--- Количество ячеек
   int total=(int)this.CellsTotal();
   
//--- Если вывод в табличном виде
   string res="";
   if(as_table)
     {
      //--- создаём строку таблицы из значений всех ячеек
      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());
        }
      //--- Выводим строку в журнал
      ::Print(res);
      return;
     }
     
//--- Выводим заголовок в виде описания строки
   ::Print(this.Description()+(detail ? ":" : ""));
   
//--- Если детализированное описание
   if(detail)
     {
      
      //--- Вывод не в табличном виде
      //--- В цикле по спискук ячеек строки
      for(int i=0; i<total; i++)
        {
         //--- получаем текущую ячейку и добавляем в итоговую строку её описание
         CTableCell *cell=this.GetCell(i);
         if(cell!=NULL)
            res+="  "+cell.Description()+(i<total-1 ? "\n" : "");
        }
      //--- Выводим в журнал созданную в цикле строку
      ::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 :

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

//--- Сохраняем индекс
   if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE)
      return(false);
//--- Сохраняем список ячеек
   if(!this.m_list_cells.Save(file_handle))
      return(false);
   
//--- Успешно
   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 :

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

//--- Загружаем индекс
   this.m_index=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем список ячеек
   if(!this.m_list_cells.Load(file_handle))
      return(false);
   
//--- Успешно
   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 :

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

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

public:
//--- Создаёт новую строку и (1) добавляет в конец списка, (2) вставляет в указанную позицию списка
   CTableRow        *RowAddNew(void);
   CTableRow        *RowInsertNewTo(const uint index_to);
//--- (1) Удаляет (2) перемещает строку, (3) очищает данные строки
   bool              RowDelete(const uint index);
   bool              RowMoveTo(const uint row_index, const uint index_to);
   void              RowResetData(const uint index);
//--- (1) Возвращает, (2) выводит в журнал описание строки
   string            RowDescription(const uint index);
   void              RowPrint(const uint index,const bool detail);
   
//--- (1) Удаляет (2) перемещает столбец, (3) очищает данные столбца
   bool              ColumnDelete(const uint index);
   bool              ColumnMoveTo(const uint row_index, const uint index_to);
   void              ColumnResetData(const uint index);
   
//--- (1) Возвращает, (2) выводит в журнал описание таблицы
   string            Description(void);
   void              Print(const bool detail);
   void              PrintTable(const int cell_width=10);
   
//--- (1) Очищает данные, (2) уничтожает модель
   void              ClearData(void);
   void              Destroy(void);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(OBJECT_TYPE_TABLE_MODEL);  }
   
//--- Конструкторы/деструктор
                     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 :

//+------------------------------------------------------------------+
//| Создаёт модель таблицы из двумерного массива                     |
//+------------------------------------------------------------------+
template<typename T>
void CTableModel::CreateTableModel(T &array[][])
  {
//--- Получаем из свойств массива количество строк и столбцов таблицы
   int rows_total=::ArrayRange(array,0);
   int cols_total=::ArrayRange(array,1);
//--- В цикле по индексам строк
   for(int r=0; r<rows_total; r++)
     {
      //--- создаём новую пустую строку и добавляем её в конец списка строк
      CTableRow *row=this.CreateNewEmptyRow();
      //--- Если строка создана и добавлена в список,
      if(row!=NULL)
        {
         //--- В цикле по количеству ячеек в строке 
         //--- создаём все ячейки, добавляя каждую новую в конец списка ячеек строки
         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 :

//+------------------------------------------------------------------+
//| Создаёт новую пустую строку и добавляет в конец списка           |
//+------------------------------------------------------------------+
CTableRow *CTableModel::CreateNewEmptyRow(void)
  {
//--- Создаём новый объект строки
   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;
     }
//--- Если строку не удалось добавить в список - удаляем созданный новый объект и возвращаем NULL
   if(!this.AddNewRow(row))
     {
      delete row;
      return NULL;
     }
   
//--- Успешно - возвращаем указатель на созданный объект
   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 :

//+------------------------------------------------------------------+
//| Добавляет строку в конец списка                                  |
//+------------------------------------------------------------------+
bool CTableModel::AddNewRow(CTableRow *row)
  {
//--- Если передан пустой объект - сообщаем об этом и возвращаем false
   if(row==NULL)
     {
      ::PrintFormat("%s: Error. Empty CTableRow object passed",__FUNCTION__);
      return false;
     }
//--- Устанавливаем строке её индекс в списке и добавляем её в конец списка
   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;
     }

//--- Успешно
   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 :

//+------------------------------------------------------------------+
//| Создаёт новую строку и добавляет в конец списка                  |
//+------------------------------------------------------------------+
CTableRow *CTableModel::RowAddNew(void)
  {
//--- Создаём новую пустую строку и добавляем её в конец списка строк
   CTableRow *row=this.CreateNewEmptyRow();
   if(row==NULL)
      return NULL;
      
//--- Создаём ячейки по количеству ячеек первой строки
   for(uint i=0;i<this.CellsInRow(0);i++)
      row.CreateNewCell(0.0);
   row.ClearData();
   
//--- Успешно - возвращаем указатель на созданный объект
   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 :

//+------------------------------------------------------------------+
//| Создаёт и добавляет новую строку в указанную позицию списка      |
//+------------------------------------------------------------------+
CTableRow *CTableModel::RowInsertNewTo(const uint index_to)
  {
//--- Создаём новую пустую строку и добавляем её в конец списка строк
   CTableRow *row=this.CreateNewEmptyRow();
   if(row==NULL)
      return NULL;
     
//--- Создаём ячейки по количеству ячеек первой строки
   for(uint i=0;i<this.CellsInRow(0);i++)
      row.CreateNewCell(0.0);
   row.ClearData();
   
//--- Смещаем строку на позицию index_to
   this.RowMoveTo(this.m_list_rows.IndexOf(row),index_to);
   
//--- Успешно - возвращаем указатель на созданный объект
   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 :

//+------------------------------------------------------------------+
//| Устанавливает значение в указанную ячейку                        |
//+------------------------------------------------------------------+
template<typename T>
void CTableModel::CellSetValue(const uint row,const uint col,const T value)
  {
//--- Получаем ячейку по индексам строки и столбца
   CTableCell *cell=this.GetCell(row,col);
   if(cell==NULL)
      return;
//--- Получаем корректный тип устанавливаемых данных (double, long, datetime, color, string)
   ENUM_DATATYPE type=this.GetCorrectDatatype(typename(T));
//--- В зависимости от типа данных вызываем соответствующий типу данных
//--- метод ячейки для установки значения, явно указывая требуемый тип
   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 :

//+------------------------------------------------------------------+
//| Устанавливает точность отображения данных в указанную ячейку     |
//+------------------------------------------------------------------+
void CTableModel::CellSetDigits(const uint row,const uint col,const int digits)
  {
//--- Получаем ячейку по индексам строки и столбца и
//--- вызываем её соответствующий метод для установки значения
   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 :

//+------------------------------------------------------------------+
//| Устанавливает флаги отображения времени в указанную ячейку       |
//+------------------------------------------------------------------+
void CTableModel::CellSetTimeFlags(const uint row,const uint col,const uint flags)
  {
//--- Получаем ячейку по индексам строки и столбца и
//--- вызываем её соответствующий метод для установки значения
   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 :

//+------------------------------------------------------------------+
//| Устанавливает флаг отображения имён цветов в указанную ячейку    |
//+------------------------------------------------------------------+
void CTableModel::CellSetColorNamesFlag(const uint row,const uint col,const bool flag)
  {
//--- Получаем ячейку по индексам строки и столбца и
//--- вызываем её соответствующий метод для установки значения
   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 :

//+------------------------------------------------------------------+
//| Назначает объект в ячейку                                        |
//+------------------------------------------------------------------+
void CTableModel::CellAssignObject(const uint row,const uint col,CObject *object)
  {
//--- Получаем ячейку по индексам строки и столбца и
//--- вызываем её соответствующий метод для установки значения
   CTableCell *cell=this.GetCell(row,col);
   if(cell!=NULL)
      cell.AssignObject(object);
  }

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

//+------------------------------------------------------------------+
//| Отменяет назначение объекта в ячейке                             |
//+------------------------------------------------------------------+
void CTableModel::CellUnassignObject(const uint row,const uint col)
  {
//--- Получаем ячейку по индексам строки и столбца и
//--- вызываем её соответствующий метод для установки значения
   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 :

//+------------------------------------------------------------------+
//| Удаляет ячейку                                                   |
//+------------------------------------------------------------------+
bool CTableModel::CellDelete(const uint row,const uint col)
  {
//--- Получаем строку по индексу и возвращаем результат удаления ячейки из списка
   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 :

//+------------------------------------------------------------------+
//| Перемещает ячейку                                                |
//+------------------------------------------------------------------+
bool CTableModel::CellMoveTo(const uint row,const uint cell_index,const uint index_to)
  {
//--- Получаем строку по индексу и возвращаем результат перемещения ячейки на новую позицию
   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 :

//+------------------------------------------------------------------+
//| Возвращает количество ячеек в указанной строке                   |
//+------------------------------------------------------------------+
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 :

//+------------------------------------------------------------------+
//| Возвращает количество ячеек в таблице                            |
//+------------------------------------------------------------------+
uint CTableModel::CellsTotal(void)
  {
//--- подсчёт ячеек в цикле по строкам (медленно при большом количестве строк)
   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 :

//+------------------------------------------------------------------+
//| Возвращает указанную ячейку таблицы                              |
//+------------------------------------------------------------------+
CTableCell *CTableModel::GetCell(const uint row,const uint col)
  {
//--- Получаем строку по индексу row и возвращаем по индексу col ячейку строки
   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 :

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

//+------------------------------------------------------------------+
//| Выводит в журнал описание ячейки                                 |
//+------------------------------------------------------------------+
void CTableModel::CellPrint(const uint row,const uint col)
  {
//--- Получаем ячейку по индексу строки и колонки и возвращаем её описание
   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 :

//+------------------------------------------------------------------+
//| Удаляет строку                                                   |
//+------------------------------------------------------------------+
bool CTableModel::RowDelete(const uint index)
  {
//--- Удаляем строку из списка по индексу
   if(!this.m_list_rows.Delete(index))
      return false;
//--- После удаления строки необходимо обновить все индексы всех ячеек таблицы
   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 :

//+------------------------------------------------------------------+
//| Перемещает строку на указанную позицию                           |
//+------------------------------------------------------------------+
bool CTableModel::RowMoveTo(const uint row_index,const uint index_to)
  {
//--- Получаем строку по индексу, делая её текущей
   CTableRow *row=this.GetRow(row_index);
//--- Перемещаем текущую строку на указанную позицию в списке
   if(row==NULL || !this.m_list_rows.MoveToIndex(index_to))
      return false;
//--- После перемещения строки необходимо обновить все индексы всех ячеек таблицы
   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 :

//+------------------------------------------------------------------+
//| Устанавливает позиции строки и колонки всем ячейкам              |
//+------------------------------------------------------------------+
void CTableModel::CellsPositionUpdate(void)
  {
//--- В цикле по списку строк
   for(int i=0;i<this.m_list_rows.Total();i++)
     {
      //--- получаем очередную строку
      CTableRow *row=this.GetRow(i);
      if(row==NULL)
         continue;
      //--- устанавливаем строке индекс, найденный методом IndexOf() списка
      row.SetIndex(this.m_list_rows.IndexOf(row));
      //--- Обновляем индексы позиций ячеек строки
      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 :

//+------------------------------------------------------------------+
//| Очищает строку (только данные в ячйках)                          |
//+------------------------------------------------------------------+
void CTableModel::RowResetData(const uint index)
  {
//--- Получаем строку из списка и очищаем данные ячеек строки методом ClearData()
   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 :

//+------------------------------------------------------------------+
//| Очищает таблицу (данные всех ячеек)                              |
//+------------------------------------------------------------------+
void CTableModel::ClearData(void)
  {
//--- В цикле по всем строкам таблицы очищаем данные каждой строки
   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 :

//+------------------------------------------------------------------+
//| Возвращает описание строки                                       |
//+------------------------------------------------------------------+
string CTableModel::RowDescription(const uint index)
  {
//--- Получаем строку по индексу и возвращаем её описание
   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 :

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

//+------------------------------------------------------------------+
//| Удаляет столбец                                                  |
//+------------------------------------------------------------------+
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 :

//+------------------------------------------------------------------+
//| Перемещает столбец                                               |
//+------------------------------------------------------------------+
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 :

//+------------------------------------------------------------------+
//| Очищает данные столбца                                           |
//+------------------------------------------------------------------+
void CTableModel::ColumnResetData(const uint index)
  {
//--- В цикле по всем строкам таблицы
   for(uint i=0;i<this.RowsTotal();i++)
     {
      //--- получаем из каждой строки ячейку с индексом столбца и очищаем её
      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 :

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

//+------------------------------------------------------------------+
//| Выводит в журнал описание объекта                                |
//+------------------------------------------------------------------+
void CTableModel::Print(const bool detail)
  {
//--- Выводим в журнал заголовок
   ::Print(this.Description()+(detail ? ":" : ""));
//--- Если детализированное описание,
   if(detail)
     {
      //--- В цикле по всем строкам таблицы
      for(uint i=0; i<this.RowsTotal(); i++)
        {
         //--- получаем очередную строку и выводим в журнал её детализированное описание
         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 :

//+------------------------------------------------------------------+
//| Выводит в журнал описание объекта в табличном виде               |
//+------------------------------------------------------------------+
void CTableModel::PrintTable(const int cell_width=10)
  {
//--- Получаем указатель на первую строку (индекс 0)
   CTableRow *row=this.GetRow(0);
   if(row==NULL)
      return;
   //--- По количеству ячеек первой строки таблицы создаём строку заголовка таблицы
   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);
     }
   //--- Выводим строку заголовка в журнал
   ::Print(res);
   
   //--- Пройдём в цикле по всем строкам таблицы и распечатаем их в табличном виде
   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 :

//+------------------------------------------------------------------+
//| Уничтожает модель                                                |
//+------------------------------------------------------------------+
void CTableModel::Destroy(void)
  {
//--- Очищаем список строк
   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 :

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

   //--- Сохраняем список строк
   if(!this.m_list_rows.Save(file_handle))
      return(false);
   
//--- Успешно
   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 :

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

   //--- Загружаем список строк
   if(!this.m_list_rows.Load(file_handle))
      return(false);
   
//--- Успешно
   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  // Распечатывать модель как таблицу
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Объявляем и заполняем массив с размерностью 4x4
//--- Тип массива может быть 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}};
     
//--- Создаём модель таблицы из вышесозданного long-массива array 4x4
   CTableModel *tm=new CTableModel(array);
   
//--- Если модель не создана - уходим
   if(tm==NULL)
      return;

//--- Распечатаем модель в табличном виде
   Print("The table model has been successfully created:");
   tm.PrintTable();
   
//--- Удалим объект модели таблицы
   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  // Распечатывать модель как таблицу
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Объявляем и заполняем массив с размерностью 4x4
//--- Тип массива может быть 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}};
     
//--- Создаём модель таблицы из вышесозданного long-массива array 4x4
   CTableModel *tm=new CTableModel(array);
   
//--- Если модель не создана - уходим
   if(tm==NULL)
      return;

//--- Распечатаем модель в табличном виде
   Print("The table model has been successfully created:");
   tm.PrintTable();
   
   
//--- Проверим работу с файлами и функционал модели таблицы
//--- Открываем файл для записи в него данных модели таблицы
   int handle=FileOpen(MQLInfoString(MQL_PROGRAM_NAME)+".bin",FILE_READ|FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
      return;
      
   //--- Сохраним в файл оригинальную созданную таблицу
   if(tm.Save(handle))
      Print("\nThe table model has been successfully saved to file.");
   
//--- Теперь вставим в таблицу новую строку в позицию 2
//--- Получим последнюю ячейку созданной строки и сделаем её нередактируемой
//--- Распечатаем в журнале изменённую модель таблицы
   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);
     }
   
//--- Теперь удалим столбец таблицы с индексом 1 и
//--- распечатаем в журнале полученную модель таблицы
   if(tm.ColumnDelete(1))
     {
      Print("\nRemove column from position 1");
      TableModelPrint(tm);
     }
   
//--- При сохранении данных таблицы файловый указатель был смещён на последние записанные данные
//--- Поставим указатель в начало файла, загрузим ранее сохранённую оригинальную таблицу и распечатаем её
   if(FileSeek(handle,0,SEEK_SET) && tm.Load(handle))
     {
      Print("\nLoad the original table view from the file:");
      TableModelPrint(tm);
     }
   
//--- Закроем открытый файл и удалим объект модели таблицы
   FileClose(handle);
   delete tm;
  }
//+------------------------------------------------------------------+
//| Распечатывает модель таблицы                                     |
//+------------------------------------------------------------------+
void TableModelPrint(CTableModel *tm)
  {
   if(PRINT_AS_TABLE)
      tm.PrintTable();  // Распечатать модель как таблицу
   else
      tm.Print(true);   // Распечатать детализированные данные таблицы
  }

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  // Распечатывать модель как таблицу

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.