Classes de table et d'en-tête basées sur un modèle de tableau dans MQL5 : Application du concept MVC
Sommaire
- Introduction
- Raffinement du modèle de tableau
- Classe d'en-tête de tableau Header
- Classe de tableau Table
- Test du résultat
- Conclusion
Introduction
Dans le premier article couvrant la création du Contrôle Tableau, nous avons créé un modèle de tableau dans MQL5 en utilisant le modèle d'architecture MVC. Des classes de cellules, de lignes et de modèles de tableaux ont été développées, ce qui a permis d'organiser les données sous une forme pratique et structurée.
Nous passons maintenant à l'étape suivante : le développement des classes de tableaux et des en-têtes de tableaux. Les en-têtes de colonne d'un tableau ne sont pas de simples étiquettes de colonne, mais un outil de gestion du tableau et de ses colonnes. Ils vous permettent d'ajouter, de supprimer et de renommer des colonnes. Bien entendu, un tableau peut fonctionner sans classe d'en-tête, mais ses fonctionnalités seront alors limitées. Un simple tableau statique sera créé sans en-tête de colonne et, par conséquent, sans la possibilité de contrôler les colonnes.
Pour mettre en œuvre la fonction de contrôle des colonnes, le modèle de tableau doit être affiné. Nous le compléterons par des méthodes permettant de travailler avec les colonnes : modifier leur structure, en ajouter de nouvelles ou en supprimer. Ces méthodes seront utilisées par la classe d'en-tête du tableau pour permettre un contrôle pratique de sa structure.
Cette phase de développement constituera la base de la mise en œuvre ultérieure des composants Vue et Contrôleur, qui seront abordés dans les articles suivants. Cette étape est un jalon important vers la création d'une interface à part entière pour les données d'exploitation.
Raffinement du modèle de tableau
Pour l'instant, le modèle de tableau est créé à partir d'un tableau à 2 dimensions. Mais pour améliorer la flexibilité et la facilité d'utilisation du tableau, nous allons ajouter des méthodes d'initialisation supplémentaires. Cela permettra d'adapter le modèle à différents scénarios d'utilisation. Les méthodes suivantes apparaîtront dans la version actualisée de la classe de modèle de tableau :
-
Création d'un modèle à partir d'un tableau à 2 dimensions
void CreateTableModel(T &array[][]);Cette méthode permet de créer rapidement un modèle de tableau basé sur un tableau de données bidimensionnel existant.
-
Création d'un modèle vide avec un nombre déterminé de lignes et de colonnes
void CreateTableModel(const uint num_rows, const uint num_columns);
Cette méthode convient lorsque la structure du tableau est connue à l'avance, mais que les données seront ajoutées ultérieurement.
-
Créer un modèle à partir d'une matrice de données
void CreateTableModel(const matrix &row_data);
Cette méthode permet d'utiliser une matrice de données pour initialiser un tableau, ce qui est pratique pour travailler avec des ensembles de données préparés à l'avance.
-
Création d'un modèle à partir d'une liste chaînée
void CreateTableModel(CList &list_param);Dans ce cas, un tableau de tableaux sera utilisé pour le stockage des données, où un objet CList (données sur les lignes du tableau) contient d'autres objets CList qui contiennent des données sur les cellules du tableau. Cette approche permet de contrôler dynamiquement la structure du tableau et son contenu.
Ces changements rendront le modèle de tableau plus polyvalent et plus pratique à utiliser dans différents scénarios. Par exemple, il sera possible de créer facilement des tableaux à partir de tableaux de données préparés à l'avance et de listes générées dynamiquement.
Dans le dernier article, nous avons décrit toutes les classes permettant de créer un modèle de tableau directement dans le fichier du script de test. Aujourd'hui, nous allons transférer ces classes dans notre propre fichier include.
Dans le dossier où est stocké le script de l'article précédent (par défaut : \MQL5\Scripts\TableModel\), créez un nouveau fichier include nommé Tables.mqh et copiez-y, à partir du fichier TableModelTest.mq5 situé dans le même dossier, tout ce qui se trouve entre le début du fichier et le début du code du script de test : toutes les classes pour la création d'un modèle de tableau. Nous disposons à présent d'un fichier distinct contenant les classes du modèle de tableau, nommé Tables.mqh. Apportons des modifications et des améliorations à ce fichier.
Déplacez le fichier créé dans un nouveau dossier, MQL5\Scripts\Tables\ — nous créerons ce projet dans ce dossier.
Dans la section des fichiers inclus/bibliothèques, ajouter une déclaration forward des nouvelles classes. Nous les ferons aujourd'hui, la déclaration des classes est nécessaire pour que la classe de liste d'objets CListObj créée dans l'article précédent puisse créer des objets de ces classes dans sa méthode CreateElement() :
//+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include <Arrays\List.mqh> //--- Forward declaration of classes class CTableCell; // Table cell class class CTableRow; // Table row class class CTableModel; // Table model class class CColumnCaption; // Table column header class class CTableHeader; // Table header class class CTable; // Table class class CTableByParam; // Table class based on parameter array //+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+
Dans la section macros , ajoutez une constante pour la largeur de la cellule du tableau, égale à 19 caractères. Il s'agit de la valeur de largeur minimale à laquelle le texte de la date et de l'heure tient entièrement dans l'espace de la cellule du journal, sans déplacer le bord droit de la cellule, ce qui désynchronise la taille de toutes les cellules du tableau dessiné dans le journal :
//+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+ #define MARKER_START_DATA -1 // Data start marker in a file #define MAX_STRING_LENGTH 128 // Maximum length of a string in a cell #define CELL_WIDTH_IN_CHARS 19 // Table cell width in characters //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+
Dans la section énumération, ajouter de nouvelles constantes à l'énumération des types d'objets :
//+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_OBJECT_TYPE // Enumeration of object types { OBJECT_TYPE_TABLE_CELL=10000, // Table cell OBJECT_TYPE_TABLE_ROW, // Table row OBJECT_TYPE_TABLE_MODEL, // Table model OBJECT_TYPE_COLUMN_CAPTION, // Table column header OBJECT_TYPE_TABLE_HEADER, // Table header OBJECT_TYPE_TABLE, // Table OBJECT_TYPE_TABLE_BY_PARAM, // Table based on the parameter array data };
Dans la classe de liste d'objets CListObj, dans la méthode de création d'éléments, ajouter de nouveaux cas de création de nouveaux types d'objets :
//+------------------------------------------------------------------+ //| List element creation method | //+------------------------------------------------------------------+ CObject *CListObj::CreateElement(void) { //--- Create a new object depending on the object type in m_element_type switch(this.m_element_type) { case OBJECT_TYPE_TABLE_CELL : return new CTableCell(); case OBJECT_TYPE_TABLE_ROW : return new CTableRow(); case OBJECT_TYPE_TABLE_MODEL : return new CTableModel(); case OBJECT_TYPE_COLUMN_CAPTION : return new CColumnCaption(); case OBJECT_TYPE_TABLE_HEADER : return new CTableHeader(); case OBJECT_TYPE_TABLE : return new CTable(); case OBJECT_TYPE_TABLE_BY_PARAM : return new CTableByParam(); default : return NULL; } }
Après avoir créé de nouvelles classes, la liste d'objets CListObj pourra créer des objets de ces types. Cela permettra d'enregistrer et de charger des listes à partir de fichiers contenant ces types d'objets.
Pour définir une valeur "vide" dans une cellule, qui sera affichée comme une ligne vide, et non comme une valeur "0", comme c'est le cas actuellement, il est nécessaire de déterminer quelle valeur doit être considérée comme "vide". Il est clair que pour les valeurs de type chaîne de caractères, une ligne vide correspondra à une telle valeur. Pour les valeurs numériques, définissez DBL_MAX pour les types réels et LONG_MAX pour les entiers.
Pour définir une telle valeur, dans la classe d'objets d'une cellule de tableau, dans la zone protégée, écrivez la méthode suivante :
class CTableCell : public CObject { protected: //--- Combining for storing cell values (double, long, string) union DataType { protected: double double_value; long long_value; ushort ushort_value[MAX_STRING_LENGTH]; public: //--- Set values void SetValueD(const double value) { this.double_value=value; } void SetValueL(const long value) { this.long_value=value; } void SetValueS(const string value) { ::StringToShortArray(value,ushort_value); } //--- Return values double ValueD(void) const { return this.double_value; } long ValueL(void) const { return this.long_value; } string ValueS(void) const { string res=::ShortArrayToString(this.ushort_value); res.TrimLeft(); res.TrimRight(); return res; } }; //--- Variables DataType m_datatype_value; // Value ENUM_DATATYPE m_datatype; // Data type CObject *m_object; // Cell object ENUM_OBJECT_TYPE m_object_type; // Object type in the cell int m_row; // Row index int m_col; // Column index int m_digits; // Data representation accuracy uint m_time_flags; // Date/time display flags bool m_color_flag; // Color name display flag bool m_editable; // Editable cell flag //--- Set "empty value" void SetEmptyValue(void) { switch(this.m_datatype) { case TYPE_LONG : case TYPE_DATETIME: case TYPE_COLOR : this.SetValue(LONG_MAX); break; case TYPE_DOUBLE : this.SetValue(DBL_MAX); break; default : this.SetValue(""); break; } } public: //--- Return cell coordinates and properties
La méthode qui renvoie la valeur stockée dans une cellule sous la forme d'une chaîne formatée vérifie maintenant que la valeur de la cellule n'est pas "vide". Si la valeur est "vide", elle renvoie une ligne vide :
public: //--- Return cell coordinates and properties uint Row(void) const { return this.m_row; } uint Col(void) const { return this.m_col; } ENUM_DATATYPE Datatype(void) const { return this.m_datatype; } int Digits(void) const { return this.m_digits; } uint DatetimeFlags(void) const { return this.m_time_flags; } bool ColorNameFlag(void) const { return this.m_color_flag; } bool IsEditable(void) const { return this.m_editable; } //--- Return (1) double, (2) long and (3) string value double ValueD(void) const { return this.m_datatype_value.ValueD(); } long ValueL(void) const { return this.m_datatype_value.ValueL(); } string ValueS(void) const { return this.m_datatype_value.ValueS(); } //--- Return the value as a formatted string string Value(void) const { switch(this.m_datatype) { case TYPE_DOUBLE : return(this.ValueD()!=DBL_MAX ? ::DoubleToString(this.ValueD(),this.Digits()) : ""); case TYPE_LONG : return(this.ValueL()!=LONG_MAX ? ::IntegerToString(this.ValueL()) : ""); case TYPE_DATETIME: return(this.ValueL()!=LONG_MAX ? ::TimeToString(this.ValueL(),this.m_time_flags) : ""); case TYPE_COLOR : return(this.ValueL()!=LONG_MAX ? ::ColorToString((color)this.ValueL(),this.m_color_flag) : ""); default : return this.ValueS(); } } //--- Return a description of the stored value type string DatatypeDescription(void) const { string type=::StringSubstr(::EnumToString(this.m_datatype),5); type.Lower(); return type; } //--- Clear data void ClearData(void) { this.SetEmptyValue(); }
La méthode d'effacement des données dans une cellule ne met plus zéro dans la cellule, mais appelle une méthode pour mettre une valeur vide dans la cellule.
Les méthodes de toutes les classes à partir desquelles l'objet modèle de tableau est créé, qui renvoient la description de l'objet, sont désormais virtuelles — en cas d'héritage de ces objets :
//--- (1) Return and (2) display the object description in the journal virtual string Description(void); void Print(void);
Dans la classe des lignes de tableau, toutes les méthodes CreateNewCell() qui créent une nouvelle cellule et l'ajoutent à la fin de la liste ont été renommées :
//+------------------------------------------------------------------+ //| Table row class | //+------------------------------------------------------------------+ class CTableRow : public CObject { protected: CTableCell m_cell_tmp; // Cell object to search in the list CListObj m_list_cells; // List of cells uint m_index; // Row index //--- Add the specified cell to the end of the list bool AddNewCell(CTableCell *cell); public: //--- (1) Set and (2) return the row index void SetIndex(const uint index) { this.m_index=index; } uint Index(void) const { return this.m_index; } //--- Set the row and column positions to all cells void CellsPositionUpdate(void); //--- Create a new cell and add it to the end of the list CTableCell *CellAddNew(const double value); CTableCell *CellAddNew(const long value); CTableCell *CellAddNew(const datetime value); CTableCell *CellAddNew(const color value); CTableCell *CellAddNew(const string value);
Nous pouvons voir que toutes les méthodes responsables de l'accès aux cellules commencent par la chaîne de caractères "Cell". Dans d'autres classes, les méthodes permettant d'accéder, par exemple, à une ligne de tableau commenceront par la chaîne de caractères "Row". Cela met de l'ordre dans les méthodes des classes.
Pour construire un modèle de tableau, nous devons développer une approche universelle qui permettra de créer des tableaux à partir de presque toutes les données. Il peut s'agir, par exemple, de structures, de listes de transactions, d'ordres, de positions ou de toute autre donnée. L'idée est de créer une boîte à outils qui permettra de créer une liste de lignes, où chaque ligne sera une liste de propriétés. Chaque propriété de la liste correspondra à une cellule du tableau.
Il convient ici de prêter attention à la structure des paramètres d'entrée de type MqlParam. Elle présente les caractéristiques suivantes :
- Spécification du type de données stockées dans la structure ENUM_DATATYPE.
- Stockage de valeurs dans 3 champs :
- integer_value — pour les données entières,
- double_value — pour les données réelles,
- string_value — pour les données de type chaîne de caractères.
Cette structure permet de travailler avec différents types de données, ce qui permet de stocker toutes les propriétés, telles que les paramètres des transactions, des ordres ou d'autres objets.
Pour un stockage pratique des données, créez la classe CMqlParamObj, héritée de la classe de base CObject de la bibliothèque standard. Cette classe comprendra la structure MqlParam et fournira des méthodes pour définir et récupérer des données. En héritant de CObject, ces objets peuvent être stockés dans des listes CList.
Considérez l'ensemble de la classe :
//+------------------------------------------------------------------+ //| Structure parameter object class | //+------------------------------------------------------------------+ class CMqlParamObj : public CObject { protected: public: MqlParam m_param; //--- Set the parameters void Set(const MqlParam ¶m) { this.m_param.type=param.type; this.m_param.double_value=param.double_value; this.m_param.integer_value=param.integer_value; this.m_param.string_value=param.string_value; } //--- Return the parameters MqlParam Param(void) const { return this.m_param; } ENUM_DATATYPE Datatype(void) const { return this.m_param.type; } double ValueD(void) const { return this.m_param.double_value; } long ValueL(void) const { return this.m_param.integer_value;} string ValueS(void) const { return this.m_param.string_value; } //--- Object description virtual string Description(void) { string t=::StringSubstr(::EnumToString(this.m_param.type),5); t.Lower(); string v=""; switch(this.m_param.type) { case TYPE_STRING : v=this.ValueS(); break; case TYPE_FLOAT : case TYPE_DOUBLE : v=::DoubleToString(this.ValueD()); break; case TYPE_DATETIME: v=::TimeToString(this.ValueL(),TIME_DATE|TIME_MINUTES|TIME_SECONDS); break; default : v=(string)this.ValueL(); break; } return(::StringFormat("<%s>%s",t,v)); } //--- Constructors/destructor CMqlParamObj(void){} CMqlParamObj(const MqlParam ¶m) { this.Set(param); } ~CMqlParamObj(void){} };
Il s'agit d'une enveloppe commune autour de la structure MqlParam, puisqu'elle nécessite un objet hérité de CObject afin de stocker ces objets dans une liste CList.
La structure des données créées sera la suivante :
- Un objet de la classe CMqlParamObj représente une propriété, par exemple le prix d'une transaction, son volume ou l'heure d'ouverture,
- Une seule liste CList représentera une ligne de tableau contenant toutes les propriétés d'une seule transaction,
- La liste CList principale contient un ensemble de lignes (listes CList), chacune correspondant à une transaction, un ordre, une position ou toute autre entité.
Nous obtiendrons ainsi une structure similaire à un tableau de tableaux :
- La liste principale CList est un "tableau de lignes",
- Chaque liste CList imbriquée est un "tableau de cellules" (propriétés d'un objet).
Voici un exemple d'une telle structure de données pour une liste de transactions historiques :
- La liste principale CList stocke les lignes d'un tableau. Chaque ligne est une liste CList distincte.
- Listes imbriquées CList — chaque liste imbriquée représente une ligne d'un tableau et contient des objets de la classe CMqlParamObj qui stockent des propriétés. Par exemple :
- première ligne : propriétés de la transaction n° 1 (prix, volume, heure d'ouverture, etc.),
- deuxième ligne : propriétés de la transaction n° 2 (prix, volume, heure d'ouverture, etc.),
- troisième ligne : propriétés de la transaction n° 3 (prix, volume, heure d'ouverture, etc.),
- etc.
- Objets de propriété (CMqlParamObj) — chaque objet stocke une propriété, par exemple le prix d'une transaction ou son volume.
Après avoir formé la structure de données (listes CList), celle-ci peut être transmise à la méthode CreateTableModel (CList &list_param) du modèle de table. Cette méthode permet d'interpréter les données comme suit :
- La liste principale CList est une liste de lignes du tableau,
- Chaque liste CList imbriquée est constituée des cellules de chaque ligne,
- Les objets CMqlParamObj à l'intérieur des listes imbriquées sont des valeurs de cellules.
Ainsi, sur la base de la liste transmise, un tableau correspondant entièrement aux données sources sera créé.
Pour faciliter la création de telles listes, une classe spéciale a été créée. Cette classe fournira des méthodes pour :
- Création d'une nouvelle ligne (de la liste CList) et ajout à la liste principale,
- Ajout d'une nouvelle propriété (l'objet CMqlParamObj) à la ligne.
//+------------------------------------------------------------------+ //| Class for creating lists of data | //+------------------------------------------------------------------+ class DataListCreator { public: //--- Add a new row to the CList list_data list static CList *AddNewRowToDataList(CList *list_data) { CList *row=new CList; if(row==NULL || list_data.Add(row)<0) return NULL; return row; } //--- Create a new CMqlParamObj parameter object and add it to CList static bool AddNewCellParamToRow(CList *row,MqlParam ¶m) { CMqlParamObj *cell=new CMqlParamObj(param); if(cell==NULL) return false; if(row.Add(cell)<0) { delete cell; return false; } return true; } };
Il s'agit d'une classe statique qui permet de créer des listes avec la structure correcte pour les transmettre aux méthodes de création de modèles de tableaux :
- Spécifier la propriété nécessaire (par exemple, le prix de l'échange),
- La classe crée automatiquement un objet CMqlParamObj, y inscrit la valeur de la propriété et l'ajoute à la ligne,
- La ligne est ajoutée à la liste principale.
Ensuite, la liste terminée peut être transmise au modèle de tableau pour être développée.
Par conséquent, l'approche développée permet de convertir des données provenant de n'importe quelle structure ou objet dans un format adapté à la construction d'un tableau. L'utilisation de listes CList et d'objets CMqlParamObj offre souplesse et commodité d'utilisation, et la classe auxiliaire DataListCreator simplifie le processus de création de ces listes. Ce modèle servira de base à la construction d'un modèle de tableau universel créé à partir de n'importe quelles données et pour n'importe quelle tâche.
Dans la classe des modèles de tableau, ajoutez 3 nouvelles méthodes pour la création d'un modèle de tableau, 3 méthodes pour la manipulation des colonnes. Au lieu de cinq constructeurs paramétriques surchargés, ajoutez un constructeur modèle et 3 nouveaux constructeurs basés sur les types de paramètres d'entrée des nouvelles méthodes de création d'un modèle de tableau:
//+------------------------------------------------------------------+ //| Table model class | //+------------------------------------------------------------------+ class CTableModel : public CObject { protected: CTableRow m_row_tmp; // Row object to search in the list CListObj m_list_rows; // List of table rows //--- Create a table model from a two-dimensional array template<typename T> void CreateTableModel(T &array[][]); void CreateTableModel(const uint num_rows,const uint num_columns); void CreateTableModel(const matrix &row_data); void CreateTableModel(CList &list_param); //--- Return the correct data type ENUM_DATATYPE GetCorrectDatatype(string type_name) { return ( //--- Integer value type_name=="bool" || type_name=="char" || type_name=="uchar" || type_name=="short"|| type_name=="ushort" || type_name=="int" || type_name=="uint" || type_name=="long" || type_name=="ulong" ? TYPE_LONG : //--- Real value type_name=="float"|| type_name=="double" ? TYPE_DOUBLE : //--- Date/time value type_name=="datetime" ? TYPE_DATETIME : //--- Color value type_name=="color" ? TYPE_COLOR : /*--- String value */ TYPE_STRING ); } //--- Create and add a new empty string to the end of the list CTableRow *CreateNewEmptyRow(void); //--- Add a string to the end of the list bool AddNewRow(CTableRow *row); //--- Set the row and column positions to all table cells void CellsPositionUpdate(void); public: //--- Return (1) cell, (2) row by index, number (3) of rows, cells (4) in the specified row and (5) in the table CTableCell *GetCell(const uint row, const uint col); CTableRow *GetRow(const uint index) { return this.m_list_rows.GetNodeAtIndex(index); } uint RowsTotal(void) const { return this.m_list_rows.Total(); } uint CellsInRow(const uint index); uint CellsTotal(void); //--- Set (1) value, (2) precision, (3) time display flags and (4) color name display flag to the specified cell template<typename T> void CellSetValue(const uint row, const uint col, const T value); void CellSetDigits(const uint row, const uint col, const int digits); void CellSetTimeFlags(const uint row, const uint col, const uint flags); void CellSetColorNamesFlag(const uint row, const uint col, const bool flag); //--- (1) Assign and (2) cancel the object in the cell void CellAssignObject(const uint row, const uint col,CObject *object); void CellUnassignObject(const uint row, const uint col); //--- (1) Delete and (2) move the cell bool CellDelete(const uint row, const uint col); bool CellMoveTo(const uint row, const uint cell_index, const uint index_to); //--- (1) Return and (2) display the cell description and (3) the object assigned to the cell string CellDescription(const uint row, const uint col); void CellPrint(const uint row, const uint col); CObject *CellGetObject(const uint row, const uint col); public: //--- Create a new string and (1) add it to the end of the list, (2) insert to the specified list position CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Remove or (2) relocate the row, (3) clear the row data bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Return and (2) display the row description in the journal string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Add, (2) remove, (3) relocate a column, (4) clear data, set the column data (5) type and (6) accuracy bool ColumnAddNew(const int index=-1); bool ColumnDelete(const uint index); bool ColumnMoveTo(const uint col_index, const uint index_to); void ColumnClearData(const uint index); void ColumnSetDatatype(const uint index,const ENUM_DATATYPE type); void ColumnSetDigits(const uint index,const int digits); //--- (1) Return and (2) display the table description in the journal virtual string Description(void); void Print(const bool detail); void PrintTable(const int cell_width=CELL_WIDTH_IN_CHARS); //--- (1) Clear the data, (2) destroy the model void ClearData(void); void Destroy(void); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_TABLE_MODEL); } //--- Constructors/destructor template<typename T> CTableModel(T &array[][]) { this.CreateTableModel(array); } CTableModel(const uint num_rows,const uint num_columns) { this.CreateTableModel(num_rows,num_columns); } CTableModel(const matrix &row_data) { this.CreateTableModel(row_data); } CTableModel(CList &row_data) { this.CreateTableModel(row_data); } CTableModel(void){} ~CTableModel(void){} };
Envisageons de nouvelles méthodes.
Une méthode qui crée un modèle de tableau à partir du nombre de lignes et de colonnes spécifié.
//+------------------------------------------------------------------+ //| Create a table model with specified number of rows and columns | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(const uint num_rows,const uint num_columns) { //--- In the loop based on the number of rows for(uint r=0; r<num_rows; r++) { //--- create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); //--- If a row is created and added to the list, if(row!=NULL) { //--- In a loop by the number of columns //--- create all the cells, adding each new one to the end of the list of cells in the row for(uint c=0; c<num_columns; c++) { CTableCell *cell=row.CellAddNew(0.0); if(cell!=NULL) cell.ClearData(); } } } }
La méthode crée un modèle vide avec le nombre de lignes et de colonnes spécifié. Elle convient lorsque la structure du tableau est connue à l'avance, mais que les données sont censées être ajoutées ultérieurement.
Une méthode qui crée un modèle de tableau à partir de la matrice spécifiée
//+------------------------------------------------------------------+ //| Create a table model from the specified matrix | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(const matrix &row_data) { //--- The number of rows and columns ulong num_rows=row_data.Rows(); ulong num_columns=row_data.Cols(); //--- In the loop based on the number of rows for(uint r=0; r<num_rows; r++) { //--- create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); //--- If a row is created and added to the list, if(row!=NULL) { //--- In the loop by the number of columns, //--- create all the cells, adding each new one to the end of the list of cells in the row for(uint c=0; c<num_columns; c++) row.CellAddNew(row_data[r][c]); } } }
Cette méthode permet d'utiliser une matrice de données pour initialiser un tableau, ce qui est pratique pour exploiter des ensembles de données préparés à l'avance.
Une méthode qui crée un modèle de tableau à partir d’une liste de paramètres
//+------------------------------------------------------------------+ //| Create a table model from the list of parameters | //+------------------------------------------------------------------+ void CTableModel::CreateTableModel(CList &list_param) { //--- If an empty list is passed, report this and leave if(list_param.Total()==0) { ::PrintFormat("%s: Error. Empty list passed",__FUNCTION__); return; } //--- Get the pointer to the first row of the table to determine the number of columns //--- If the first row could not be obtained, or there are no cells in it, report this and leave CList *first_row=list_param.GetFirstNode(); if(first_row==NULL || first_row.Total()==0) { if(first_row==NULL) ::PrintFormat("%s: Error. Failed to get first row of list",__FUNCTION__); else ::PrintFormat("%s: Error. First row does not contain data",__FUNCTION__); return; } //--- The number of rows and columns ulong num_rows=list_param.Total(); ulong num_columns=first_row.Total(); //--- In the loop based on the number of rows for(uint r=0; r<num_rows; r++) { //--- get the next table row from list_param CList *col_list=list_param.GetNodeAtIndex(r); if(col_list==NULL) continue; //--- create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); //--- If a row is created and added to the list, if(row!=NULL) { //--- In the loop by the number of columns, //--- create all the cells, adding each new one to the end of the list of cells in the row for(uint c=0; c<num_columns; c++) { CMqlParamObj *param=col_list.GetNodeAtIndex(c); if(param==NULL) continue; //--- Declare the pointer to a cell and the type of data to be contained in it CTableCell *cell=NULL; ENUM_DATATYPE datatype=param.Datatype(); //--- Depending on the data type switch(datatype) { //--- real data type case TYPE_FLOAT : case TYPE_DOUBLE : cell=row.CellAddNew((double)param.ValueD()); // Create a new cell with double data and if(cell!=NULL) cell.SetDigits((int)param.ValueL()); // set the precision of the displayed data break; //--- datetime data type case TYPE_DATETIME: cell=row.CellAddNew((datetime)param.ValueL()); // Create a new cell with datetime data and if(cell!=NULL) cell.SetDatetimeFlags((int)param.ValueD()); // set date/time display flags break; //--- color data type case TYPE_COLOR : cell=row.CellAddNew((color)param.ValueL()); // Create a new cell with color data and if(cell!=NULL) cell.SetColorNameFlag((bool)param.ValueD()); // set the flag for displaying the names of known colors break; //--- string data type case TYPE_STRING : cell=row.CellAddNew((string)param.ValueS()); // Create a new cell with string data break; //--- integer data type default : cell=row.CellAddNew((long)param.ValueL()); // Create a new cell with long data break; } } } } }
Cette méthode permet de créer un modèle de tableau basé sur une liste chaînée, ce qui peut être utile pour exploiter des structures de données dynamiques.
Il convient de noter que lors de la création de cellules avec un type de données, par exemple double, la précision de l'affichage est tirée de la valeur longue de l'objet param de la classe CMqlParamObj. Cela signifie que lors de la création d'une structure de tableau à l'aide de la classe DataListCreator, comme nous l'avons vu plus haut, nous pouvons également transmettre les informations nécessaires à l'objet paramètre. Pour le résultat financier d'une transaction, on peut procéder comme suit :
//--- Financial result of a trade param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_PROFIT); param.integer_value=(param.double_value!=0 ? 2 : 1); DataListCreator::AddNewCellParamToRow(row,param);
De la même manière, on peut passer des drapeaux d'affichage de l'heure pour les cellules de tableau de type datetime et un drapeau d'affichage du nom de la couleur pour une cellule de type color.
Le tableau affiche les types de cellules de tableau et les types de paramètres qui leur sont transmis via l'objet CMqlParamObj :
| Type dans CMqlParamObj | Type de cellule double | Type de cellule long | Type de cellule datetime | Type de cellule couleur | Type de cellule string |
|---|---|---|---|---|---|
| double_value | valeur de la cellule | non utilisé | drapeaux d'affichage de la date et de l'heure | drapeau d’affichage du nom de la couleur | non utilisé |
| integer_value | précision de la valeur de la cellule | valeur de la cellule | valeur de la cellule | valeur de la cellule | non utilisé |
| string_value | non utilisé | non utilisé | non utilisé | non utilisé | valeur de la cellule |
Le tableau montre que lors de la création d'une structure de tableau à partir de certaines données, si ces données sont de type réel (écrites dans le champ de la structure MqlParam double_value), vous pouvez également écrire dans le champ integer_value la valeur de la précision avec laquelle les données seront affichées dans la cellule du tableau. Il en va de même pour les données de type datetime et color, mais les drapeaux sont écrits dans le champ double_value, puisque le champ integer est occupé par la valeur de la propriété elle-même.
Cette option est facultative. En même temps, les valeurs des drapeaux et de la précision dans la cellule seront mises à zéro. Cette valeur peut ensuite être modifiée à la fois pour une cellule spécifique et pour l'ensemble de la colonne du tableau.
Une méthode qui ajoute une nouvelle colonne à un tableau
//+------------------------------------------------------------------+ //| Add a column | //+------------------------------------------------------------------+ bool CTableModel::ColumnAddNew(const int index=-1) { //--- Declare the variables CTableCell *cell=NULL; bool res=true; //--- In the loop based on the number of rows for(uint i=0;i<this.RowsTotal();i++) { //--- get the next row CTableRow *row=this.GetRow(i); if(row!=NULL) { //--- add a cell of double type to the end of the row cell=row.CellAddNew(0.0); if(cell==NULL) res &=false; //--- clear the cell else cell.ClearData(); } } //--- If the column index passed is not negative, shift the column to the specified position if(res && index>-1) res &=this.ColumnMoveTo(this.CellsInRow(0)-1,index); //--- Return the result return res; }
L'index de la nouvelle colonne est transmis à la méthode. Tout d'abord, de nouvelles cellules sont ajoutées à la fin de la ligne dans toutes les lignes du tableau, puis, si l'indice transmis n'est pas négatif, toutes les nouvelles cellules sont décalées de l'indice spécifié.
Une méthode qui définit le type de données de la colonne
//+------------------------------------------------------------------+ //| Set the column data type | //+------------------------------------------------------------------+ void CTableModel::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- In a loop through all table rows for(uint i=0;i<this.RowsTotal();i++) { //--- get the cell with the column index from each row and set the data type CTableCell *cell=this.GetCell(i, index); if(cell!=NULL) cell.SetDatatype(type); } }
Dans une boucle parcourant toutes les lignes du tableau, récupérez une cellule de chaque ligne par index et définissez un type de données pour cette cellule. Par conséquent, une valeur identique est définie dans les cellules de la colonne entière.
Une méthode qui définit la précision des données de la colonne
//+------------------------------------------------------------------+ //| Set the accuracy of the column data | //+------------------------------------------------------------------+ void CTableModel::ColumnSetDigits(const uint index,const int digits) { //--- In a loop through all table rows for(uint i=0;i<this.RowsTotal();i++) { //--- get the cell with the column index from each row and set the data accuracy CTableCell *cell=this.GetCell(i, index); if(cell!=NULL) cell.SetDigits(digits); } }
Dans une boucle parcourant toutes les lignes du tableau, récupérez une cellule de chaque ligne par index et définissez un type de données pour cette cellule. Par conséquent, la même valeur est définie dans les cellules de la colonne entière.
Les méthodes ajoutées à la classe de modèle de tableau permettent d'utiliser un ensemble de cellules comme une colonne de tableau entière. Les colonnes d'un tableau ne peuvent être contrôlées que si le tableau possède un en-tête. Le tableau sera statique s’il n’a pas d'en-tête.
Classe d'en-tête de tableau Header
L'en-tête du tableau est une liste d'objets d'en-tête de colonne avec une valeur de chaîne. Ils sont situés dans une liste dynamique CListObj. La liste dynamique constitue la base de la classe d'en-tête de tableau.
Sur cette base, deux classes devront être créées :
- Classe de l'objet d'en-tête de la colonne du tableau.
Il contient la valeur textuelle de l'en-tête, le numéro de la colonne, le type de données pour l'ensemble de la colonne et les méthodes de contrôle des cellules de la colonne. - La classe d'en-tête du tableau.
Il contient une liste d'objets d'en-tête de colonne et de méthodes d'accès pour contrôler les colonnes du tableau.
Continuez à écrire le code dans le même fichier \MQL5\Scripts\Tables\Tables.mqh et écrivez la classe d'en-tête de colonne de table :
//+------------------------------------------------------------------+ //| Table column header class | //+------------------------------------------------------------------+ class CColumnCaption : public CObject { protected: //--- Variables ushort m_ushort_array[MAX_STRING_LENGTH]; // Array of header symbols uint m_column; // Column index ENUM_DATATYPE m_datatype; // Data type public: //--- (1) Set and (2) return the column index void SetColumn(const uint column) { this.m_column=column; } uint Column(void) const { return this.m_column; } //--- (1) Set and (2) return the column data type ENUM_DATATYPE Datatype(void) const { return this.m_datatype; } void SetDatatype(const ENUM_DATATYPE datatype) { this.m_datatype=datatype;} //--- Clear data void ClearData(void) { this.SetValue(""); } //--- Set the header void SetValue(const string value) { ::StringToShortArray(value,this.m_ushort_array); } //--- Return the header text string Value(void) const { string res=::ShortArrayToString(this.m_ushort_array); res.TrimLeft(); res.TrimRight(); return res; } //--- (1) Return and (2) display the object description in the journal virtual string Description(void); void Print(void); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_COLUMN_CAPTION); } //--- Constructors/destructor CColumnCaption(void) : m_column(0) { this.SetValue(""); } CColumnCaption(const uint column,const string value) : m_column(column) { this.SetValue(value); } ~CColumnCaption(void) {} };
Il s'agit d'une version très simplifiée de la classe des cellules de tableau. Considérons quelques méthodes de la classe.
Méthode virtuelle de comparaison de deux objets
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ int CColumnCaption::Compare(const CObject *node,const int mode=0) const { const CColumnCaption *obj=node; return(this.Column()>obj.Column() ? 1 : this.Column()<obj.Column() ? -1 : 0); }
La comparaison est effectuée par l'index de la colonne pour laquelle l'en-tête a été créé.
Méthode d'enregistrement dans un fichier
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CColumnCaption::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the column index if(::FileWriteInteger(file_handle,this.m_column,INT_VALUE)!=INT_VALUE) return(false); //--- Save the value if(::FileWriteArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array)) return(false); //--- All is successful return true; }
Méthode de téléchargement à partir d'un fichier
//+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CColumnCaption::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the column index this.m_column=::FileReadInteger(file_handle,INT_VALUE); //--- Load the value if(::FileReadArray(file_handle,this.m_ushort_array)!=sizeof(this.m_ushort_array)) return(false); //--- All is successful return true; }
Des méthodes similaires ont été examinées en détail dans l'article précédent. La logique est exactement la même : les marqueurs de début de données et le type d'objet sont d'abord enregistrés. Puis, toutes ses propriétés, en termes d'éléments. La lecture se déroule dans le même ordre.
Une méthode qui renvoie la description d'un objet
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ string CColumnCaption::Description(void) { return(::StringFormat("%s: Column %u, Value: \"%s\"", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.Column(),this.Value())); }
Une chaîne de description est créée et renvoyée au format (Object Type : Colonne XX, Value "Valeur")
Méthode permettant d'afficher la description de l'objet dans le journal
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CColumnCaption::Print(void) { ::Print(this.Description()); }
Elle se contente d'imprimer la description de l'en-tête dans le journal.
Ces objets doivent maintenant être placés dans la liste qui constituera l'en-tête du tableau. Écrire la classe d'en-tête de tableau :
//+------------------------------------------------------------------+ //| Table header class | //+------------------------------------------------------------------+ class CTableHeader : public CObject { protected: CColumnCaption m_caption_tmp; // Column header object to search in the list CListObj m_list_captions; // List of column headers //--- Add the specified header to the end of the list bool AddNewColumnCaption(CColumnCaption *caption); //--- Create a table header from a string array void CreateHeader(string &array[]); //--- Set the column position of all column headers void ColumnPositionUpdate(void); public: //--- Create a new header and add it to the end of the list CColumnCaption *CreateNewColumnCaption(const string caption); //--- Return (1) the header by index and (2) the number of column headers CColumnCaption *GetColumnCaption(const uint index) { return this.m_list_captions.GetNodeAtIndex(index); } uint ColumnsTotal(void) const { return this.m_list_captions.Total(); } //--- Set the value of the specified column header void ColumnCaptionSetValue(const uint index,const string value); //--- (1) Set and (2) return the data type for the specified column header void ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type); ENUM_DATATYPE ColumnCaptionDatatype(const uint index); //--- (1) Remove and (2) relocate the column header bool ColumnCaptionDelete(const uint index); bool ColumnCaptionMoveTo(const uint caption_index, const uint index_to); //--- Clear column header data void ClearData(void); //--- Clear the list of column headers void Destroy(void) { this.m_list_captions.Clear(); } //--- (1) Return and (2) display the object description in the journal virtual string Description(void); void Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_TABLE_HEADER); } //--- Constructors/destructor CTableHeader(void) {} CTableHeader(string &array[]) { this.CreateHeader(array); } ~CTableHeader(void){} };
Considérons les méthodes de classe.
Une méthode qui crée un nouvel en-tête et l'ajoute à la fin de la liste des en-têtes de colonnes.
//+------------------------------------------------------------------+ //| Create a new header and add it to the end of the list | //+------------------------------------------------------------------+ CColumnCaption *CTableHeader::CreateNewColumnCaption(const string caption) { //--- Create a new header object CColumnCaption *caption_obj=new CColumnCaption(this.ColumnsTotal(),caption); if(caption_obj==NULL) { ::PrintFormat("%s: Error. Failed to create new column caption at position %u",__FUNCTION__, this.ColumnsTotal()); return NULL; } //--- Add the created header to the end of the list if(!this.AddNewColumnCaption(caption_obj)) { delete caption_obj; return NULL; } //--- Return the pointer to the object return caption_obj; }
Le texte de l'en-tête est transmis à la méthode. Un nouvel objet d'en-tête de colonne est créé avec le texte spécifié et un index égal au nombre d'en-têtes dans la liste. Il s'agit de l'indice du dernier en-tête. Ensuite, l'objet créé est placé à la fin de la liste des en-têtes de colonne et un pointeur sur l'en-tête créé est renvoyé.
Une méthode qui ajoute l'en-tête spécifié à la fin de la liste
//+------------------------------------------------------------------+ //| Add the header to the end of the list | //+------------------------------------------------------------------+ bool CTableHeader::AddNewColumnCaption(CColumnCaption *caption) { //--- If an empty object is passed, report it and return 'false' if(caption==NULL) { ::PrintFormat("%s: Error. Empty CColumnCaption object passed",__FUNCTION__); return false; } //--- Set the header index in the list and add the created header to the end of the list caption.SetColumn(this.ColumnsTotal()); if(this.m_list_captions.Add(caption)==WRONG_VALUE) { ::PrintFormat("%s: Error. Failed to add caption (%u) to list",__FUNCTION__,this.ColumnsTotal()); return false; } //--- Successful return true; }
Un pointeur sur l'objet d'en-tête de colonne est transmis à la méthode. Il doit être placé à la fin de la liste d'en-têtes. La méthode renvoie le résultat de l'ajout d'un objet à la liste.
Une méthode qui crée un en-tête de tableau à partir d'un tableau de chaînes de caractères.
//+------------------------------------------------------------------+ //| Create a table header from the string array | //+------------------------------------------------------------------+ void CTableHeader::CreateHeader(string &array[]) { //--- Get the number of table columns from the array properties uint total=array.Size(); //--- In a loop by array size, //--- create all the headers, adding each new one to the end of the list for(uint i=0; i<total; i++) this.CreateNewColumnCaption(array[i]); }
Un tableau d'en-têtes est transmis à la méthode. La taille du tableau détermine le nombre d'objets d'en-tête de colonne à créer, qui sont créés lors du passage en boucle des valeurs des textes d'en-tête dans le tableau.
Une méthode qui fixe une valeur à l'en-tête spécifié d'une colonne
//+------------------------------------------------------------------+ //| Set the value to the specified column header | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetValue(const uint index,const string value) { //--- Get the required header from the list and set a new value to it CColumnCaption *caption=this.GetColumnCaption(index); if(caption!=NULL) caption.SetValue(value); }
Cette méthode vous permet de définir une nouvelle valeur de texte telle que spécifiée par l'index de l'en-tête.
Une méthode qui définit un type de données pour l'en-tête de colonne spécifié
//+------------------------------------------------------------------+ //| Set the data type for the specified column header | //+------------------------------------------------------------------+ void CTableHeader::ColumnCaptionSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- Get the required header from the list and set a new value to it CColumnCaption *caption=this.GetColumnCaption(index); if(caption!=NULL) caption.SetDatatype(type); }
Cette méthode permet de définir une nouvelle valeur des données stockées dans la colonne indiquée par l'index de l'en-tête. Pour chaque colonne du tableau, vous pouvez définir le type de données stockées dans les cellules de la colonne. La définition d'un type de données pour l'objet d'en-tête permet de définir ultérieurement la même valeur pour l'ensemble de la colonne. Et vous pouvez lire la valeur des données de la colonne entière en lisant cette valeur dans l'en-tête de cette colonne.
Une méthode qui renvoie le type de données de l'en-tête de colonne spécifié.
//+------------------------------------------------------------------+ //| Return the data type of the specified column header | //+------------------------------------------------------------------+ ENUM_DATATYPE CTableHeader::ColumnCaptionDatatype(const uint index) { //--- Get the required header from the list and return the column data type from it CColumnCaption *caption=this.GetColumnCaption(index); return(caption!=NULL ? caption.Datatype() : (ENUM_DATATYPE)WRONG_VALUE); }
Cette méthode permet de récupérer une valeur des données stockées dans la colonne par l'index de l'en-tête. L'obtention de la valeur de l'en-tête permet de connaître le type de valeurs stockées dans toutes les cellules de cette colonne du tableau.
Une méthode qui supprime l'en-tête de la colonne spécifiée
//+------------------------------------------------------------------+ //| Remove the header of the specified column | //+------------------------------------------------------------------+ bool CTableHeader::ColumnCaptionDelete(const uint index) { //--- Remove the header from the list by index if(!this.m_list_captions.Delete(index)) return false; //--- Update the indices for the remaining headers in the list this.ColumnPositionUpdate(); return true; }
L'objet à l'index spécifié est supprimé de la liste des en-têtes. Après la suppression de l'objet "en-tête de colonne", il est nécessaire de mettre à jour les index des objets restants de la liste.
Une méthode qui déplace un en-tête de colonne à la position spécifiée
//+------------------------------------------------------------------+ //| Move the column header to the specified position | //+------------------------------------------------------------------+ bool CTableHeader::ColumnCaptionMoveTo(const uint caption_index,const uint index_to) { //--- Get the desired header by index in the list, turning it into the current one CColumnCaption *caption=this.GetColumnCaption(caption_index); //--- Move the current header to the specified position in the list if(caption==NULL || !this.m_list_captions.MoveToIndex(index_to)) return false; //--- Update the indices of all headers in the list this.ColumnPositionUpdate(); return true; }
Elle permet de déplacer l'en-tête de l'index spécifié vers une nouvelle position dans la liste.
Une méthode qui définit la position des colonnes pour tous les en-têtes
//+------------------------------------------------------------------+ //| Set the column positions of all headers | //+------------------------------------------------------------------+ void CTableHeader::ColumnPositionUpdate(void) { //--- In the loop through all the headings in the list for(int i=0;i<this.m_list_captions.Total();i++) { //--- get the next header and set the column index in it CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) caption.SetColumn(this.m_list_captions.IndexOf(caption)); } }
Après avoir supprimé ou déplacé un objet de la liste, il est nécessaire de réattribuer des index à tous les autres objets de la liste afin que leurs index correspondent à la position réelle dans la liste. La méthode passe en revue tous les objets de la liste dans une boucle, obtient l'indice réel de chaque objet et le définit comme une propriété de l'objet.
Une méthode qui efface les données d'en-tête de colonne dans la liste
//+------------------------------------------------------------------+ //| Clear column header data in the list | //+------------------------------------------------------------------+ void CTableHeader::ClearData(void) { //--- In the loop through all the headings in the list for(uint i=0;i<this.ColumnsTotal();i++) { //--- get the next header and set the empty value to it CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) caption.ClearData(); } }
Dans une boucle parcourant tous les objets d'en-tête de colonne de la liste, la méthode récupère chaque objet régulier et donne au texte de l'en-tête une valeur vide. Cela permet d'effacer complètement les en-têtes de chaque colonne du tableau.
Une méthode qui renvoie la description d'un objet
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ string CTableHeader::Description(void) { return(::StringFormat("%s: Captions total: %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.ColumnsTotal())); }
Une chaîne est créée et renvoyée au format (Object Type : Captions total: XX)
Méthode permettant d'afficher la description de l'objet dans le journal
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTableHeader::Print(const bool detail, const bool as_table=false, const int column_width=CELL_WIDTH_IN_CHARS) { //--- Number of headers int total=(int)this.ColumnsTotal(); //--- If the output is in tabular form string res=""; if(as_table) { //--- create a table row from the values of all headers res="|"; for(int i=0;i<total;i++) { CColumnCaption *caption=this.GetColumnCaption(i); if(caption==NULL) continue; res+=::StringFormat("%*s |",column_width,caption.Value()); } //--- Display a row in the journal and leave ::Print(res); return; } //--- Display the header as a row description ::Print(this.Description()+(detail ? ":" : "")); //--- If detailed description if(detail) { //--- In a loop by the list of row headers for(int i=0; i<total; i++) { //--- get the current header and add its description to the final row CColumnCaption *caption=this.GetColumnCaption(i); if(caption!=NULL) res+=" "+caption.Description()+(i<total-1 ? "\n" : ""); } //--- Send the row created in the loop to the journal ::Print(res); } }
La méthode permet d'enregistrer la description des en-têtes sous forme de tableau et de liste d'en-têtes de colonnes.
Méthodes d'enregistrement dans un fichier et de téléchargement d'un en-tête à partir d'un fichier
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CTableHeader::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the list of headers if(!this.m_list_captions.Save(file_handle)) return(false); //--- Successful return true; } //+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CTableHeader::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the list of headers if(!this.m_list_captions.Load(file_handle)) return(false); //--- Successful return true; }
La logique des méthodes est commentée dans le code et ne diffère en rien des méthodes similaires d'autres classes déjà créées pour la création de tableaux.
Nous avons tout ce qu'il faut pour commencer à assembler les classes de table. La classe de tableau doit être capable de construire une table basée sur son modèle et doit avoir un en-tête par lequel les colonnes de la table seront nommées. Si vous ne spécifiez pas l'en-tête du tableau, celui-ci sera construit uniquement en fonction du modèle, il sera statique et ses fonctionnalités seront limitées uniquement par la visualisation du tableau. Pour les tableaux simples, cela suffit amplement. Cependant, pour interagir avec l'utilisateur à l'aide du composant Contrôleur, l'en-tête du tableau doit être déterminé dans le tableau. Cela permettra de disposer d'un large éventail d'options pour contrôler les tableaux et leurs données. Mais nous ferons tout cela plus tard. Examinons maintenant les classes de tableaux.
Classes de tableaux
Continuez à écrire le code dans le même fichier et mettez en œuvre la classe tableau :
//+------------------------------------------------------------------+ //| Table class | //+------------------------------------------------------------------+ class CTable : public CObject { private: //--- Populate the array of column headers in Excel style bool FillArrayExcelNames(const uint num_columns); //--- Return the column name as in Excel string GetExcelColumnName(uint column_number); //--- Return the header availability bool HeaderCheck(void) const { return(this.m_table_header!=NULL && this.m_table_header.ColumnsTotal()>0); } protected: CTableModel *m_table_model; // Pointer to the table model CTableHeader *m_table_header; // Pointer to the table header CList m_list_rows; // List of parameter arrays from structure fields string m_array_names[]; // Array of column headers int m_id; // Table ID //--- Copy the array of header names bool ArrayNamesCopy(const string &column_names[],const uint columns_total); public: //--- (1) Set and (2) return the table model void SetTableModel(CTableModel *table_model) { this.m_table_model=table_model; } CTableModel *GetTableModel(void) { return this.m_table_model; } //--- (1) Set and (2) return the header void SetTableHeader(CTableHeader *table_header) { this.m_table_header=m_table_header; } CTableHeader *GetTableHeader(void) { return this.m_table_header; } //--- (1) Set and (2) return the table ID void SetID(const int id) { this.m_id=id; } int ID(void) const { return this.m_id; } //--- Clear column header data void HeaderClearData(void) { if(this.m_table_header!=NULL) this.m_table_header.ClearData(); } //--- Remove the table header void HeaderDestroy(void) { if(this.m_table_header==NULL) return; this.m_table_header.Destroy(); this.m_table_header=NULL; } //--- (1) Clear all data and (2) destroy the table model and header void ClearData(void) { if(this.m_table_model!=NULL) this.m_table_model.ClearData(); } void Destroy(void) { if(this.m_table_model==NULL) return; this.m_table_model.Destroy(); this.m_table_model=NULL; } //--- Return (1) the header, (2) cell, (3) row by index, number (4) of rows, (5) columns, cells (6) in the specified row, (7) in the table CColumnCaption *GetColumnCaption(const uint index) { return(this.m_table_header!=NULL ? this.m_table_header.GetColumnCaption(index) : NULL); } CTableCell *GetCell(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.GetCell(row,col) : NULL); } CTableRow *GetRow(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.GetRow(index) : NULL); } uint RowsTotal(void) const { return(this.m_table_model!=NULL ? this.m_table_model.RowsTotal() : 0); } uint ColumnsTotal(void) const { return(this.m_table_model!=NULL ? this.m_table_model.CellsInRow(0) : 0); } uint CellsInRow(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.CellsInRow(index) : 0); } uint CellsTotal(void) { return(this.m_table_model!=NULL ? this.m_table_model.CellsTotal() : 0); } //--- Set (1) value, (2) precision, (3) time display flags and (4) color name display flag to the specified cell template<typename T> void CellSetValue(const uint row, const uint col, const T value); void CellSetDigits(const uint row, const uint col, const int digits); void CellSetTimeFlags(const uint row, const uint col, const uint flags); void CellSetColorNamesFlag(const uint row, const uint col, const bool flag); //--- (1) Assign and (2) cancel the object in the cell void CellAssignObject(const uint row, const uint col,CObject *object); void CellUnassignObject(const uint row, const uint col); //--- Return the string value of the specified cell virtual string CellValueAt(const uint row, const uint col); protected: //--- (1) Delete and (2) move the cell bool CellDelete(const uint row, const uint col); bool CellMoveTo(const uint row, const uint cell_index, const uint index_to); public: //--- (1) Return and (2) display the cell description and (3) the object assigned to the cell string CellDescription(const uint row, const uint col); void CellPrint(const uint row, const uint col); //--- Return (1) the object assigned to the cell and (2) the type of the object assigned to the cell CObject *CellGetObject(const uint row, const uint col); ENUM_OBJECT_TYPE CellGetObjType(const uint row, const uint col); //--- Create a new string and (1) add it to the end of the list, (2) insert to the specified list position CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Remove or (2) relocate the row, (3) clear the row data bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Return and (2) display the row description in the journal string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Add new, (2) remove, (3) relocate the column and (4) clear the column data bool ColumnAddNew(const string caption,const int index=-1); bool ColumnDelete(const uint index); bool ColumnMoveTo(const uint index, const uint index_to); void ColumnClearData(const uint index); //--- Set (1) the value of the specified header and (2) data accuracy for the specified column void ColumnCaptionSetValue(const uint index,const string value); void ColumnSetDigits(const uint index,const int digits); //--- (1) Set and (2) return the data type for the specified column void ColumnSetDatatype(const uint index,const ENUM_DATATYPE type); ENUM_DATATYPE ColumnDatatype(const uint index); //--- (1) Return and (2) display the object description in the journal virtual string Description(void); void Print(const int column_width=CELL_WIDTH_IN_CHARS); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_TABLE); } //--- Constructors/destructor CTable(void) : m_table_model(NULL), m_table_header(NULL) { this.m_list_rows.Clear();} template<typename T> CTable(T &row_data[][],const string &column_names[]); CTable(const uint num_rows, const uint num_columns); CTable(const matrix &row_data,const string &column_names[]); ~CTable (void); };
Les pointeurs vers l'en-tête et la table des modèles sont déclarés dans la classe. Pour construire un tableau, vous devez d'abord créer un modèle de tableau à partir des données transmises aux constructeurs de la classe. Le tableau peut générer automatiquement un en-tête avec des noms de colonnes dans le style de MS Excel, où chaque colonne se voit attribuer un nom composé de lettres latines.
L'algorithme de calcul des noms est le suivant :
-
Noms à une lettre - les 26 premières colonnes sont indiquées par des lettres de "A" à "Z".
-
Noms à deux lettres - après "Z", les colonnes sont désignées par une combinaison de deux lettres. La première lettre change plus lentement, et la seconde itère sur l'ensemble de l'alphabet. Par exemple :
- "AA", "AB", "AC", ..., "AZ",
- puis "BA", "BB", ..., "BZ",
- etc.
-
Noms à trois lettres - après "ZZ", les colonnes sont désignées par une combinaison de 3 lettres. Le principe est le même :
- "AAA", "AAB", ..., "AAZ",
- puis "ABA", "ABB", ..., "ABZ",
- etc.
-
Le principe général est que les noms de colonnes peuvent être considérés comme des nombres dans le système numérique de base 26, où "A" correspond à 1, "B" correspond à 2, ..., "Z" - 26. Par exemple :
- "A" = 1,
- "Z" = 26,
- "AA" = 27 (1 * 26^1 + 1),
- "AB" = 28 (1 * 26^1 + 2),
- "BA" = 53 (2 * 26^1 + 1).
Ainsi, l'algorithme génère automatiquement des noms de colonnes, en les augmentant conformément au principe considéré. Le nombre maximal de colonnes dans Excel dépend de la version du programme (par exemple, dans Excel 2007 et les versions ultérieures, il y a 16 384 colonnes, se terminant par "XFD"). L'algorithme créé ici ne se limite pas à cette figure. Il peut donner des noms à un nombre de colonnes égal à INT_MAX :
//+------------------------------------------------------------------+ //| Return the column name as in Excel | //+------------------------------------------------------------------+ string CTable::GetExcelColumnName(uint column_number) { string column_name=""; uint index=column_number; //--- Check that the column index is greater than 0 if(index==0) return (__FUNCTION__+": Error. Invalid column number passed"); //--- Convert the index to the column name while(!::IsStopped() && index>0) { index--; // Decrease the index by 1 to make it 0-indexed uint remainder =index % 26; // Remainder after division by 26 uchar char_code ='A'+(uchar)remainder; // Calculate the symbol code (letters) column_name=::CharToString(char_code)+column_name; // Add a letter to the beginning of the string index/=26; // Move on to the next rank } return column_name; } //+------------------------------------------------------------------+ //| Populate the array of column headers in Excel style | //+------------------------------------------------------------------+ bool CTable::FillArrayExcelNames(const uint num_columns) { ::ResetLastError(); if(::ArrayResize(this.m_array_names,num_columns,num_columns)!=num_columns) { ::PrintFormat("%s: ArrayResize() failed. Error %d",__FUNCTION__,::GetLastError()); return false; } for(int i=0;i<(int)num_columns;i++) this.m_array_names[i]=this.GetExcelColumnName(i+1); return true; }
Les méthodes permettent de remplir un tableau de noms de colonnes de type Excel.
Considérons les constructeurs paramétriques d'une classe.
Un constructeur de modèle spécifiant un tableau de données à deux dimensions et un tableau d'en-têtes sous forme de chaîne de caractères.
//+-------------------------------------------------------------------+ //| Constructor specifying a table array and a header array. | //| Defines the index and names of columns according to column_names | //| The number of rows is determined by the size of the row_data array| //| also used to fill the table | //+-------------------------------------------------------------------+ template<typename T> CTable::CTable(T &row_data[][],const string &column_names[]) : m_id(-1) { this.m_table_model=new CTableModel(row_data); if(column_names.Size()>0) this.ArrayNamesCopy(column_names,row_data.Range(1)); else { ::PrintFormat("%s: An empty array names was passed. The header array will be filled in Excel style (A, B, C)",__FUNCTION__); this.FillArrayExcelNames((uint)::ArrayRange(row_data,1)); } this.m_table_header=new CTableHeader(this.m_array_names); }
Un tableau de données de n'importe quel type est transmis au constructeur du modèle à partir de l'énumération ENUM_DATATYPE. Ensuite, il sera converti dans le type de données utilisé par les tableaux(double, long, datetime, color, string) pour créer un modèle de tableau et un tableau d'en-têtes de colonnes. Si le tableau d'en-têtes est vide, des en-têtes de type MS Excel seront créés.
Constructeur avec indication du nombre de lignes et de colonnes du tableau
//+-----------------------------------------------------------------------+ //| Table constructor with definition of the number of columns and rows. | //| The columns will have Excel names "A", "B", "C", etc. | //+-----------------------------------------------------------------------+ CTable::CTable(const uint num_rows,const uint num_columns) : m_table_header(NULL), m_id(-1) { this.m_table_model=new CTableModel(num_rows,num_columns); if(this.FillArrayExcelNames(num_columns)) this.m_table_header=new CTableHeader(this.m_array_names); }
Le constructeur crée un modèle de tableau vide avec un en-tête de type MS Excel.
Le constructeur se base sur une matrice de données et un tableau d'en-têtes de colonnes.
//+-----------------------------------------------------------------------+ //| Table constructor with column initialization according to column_names| //| The number of rows is determined by row_data with matrix type | //+-----------------------------------------------------------------------+ CTable::CTable(const matrix &row_data,const string &column_names[]) : m_id(-1) { this.m_table_model=new CTableModel(row_data); if(column_names.Size()>0) this.ArrayNamesCopy(column_names,(uint)row_data.Cols()); else { ::PrintFormat("%s: An empty array names was passed. The header array will be filled in Excel style (A, B, C)",__FUNCTION__); this.FillArrayExcelNames((uint)row_data.Cols()); } this.m_table_header=new CTableHeader(this.m_array_names); }
Une matrice de données de type double est transmise au constructeur pour créer un modèle de tableau et un tableau d'en-têtes de colonnes. Si le tableau d'en-têtes est vide, des en-têtes de type MS Excel seront créés.
Dans le destructeur de la classe, le modèle et l'en-tête du tableau sont détruits.
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CTable::~CTable(void) { if(this.m_table_model!=NULL) { this.m_table_model.Destroy(); delete this.m_table_model; } if(this.m_table_header!=NULL) { this.m_table_header.Destroy(); delete this.m_table_header; } }
Méthode de comparaison de deux objets
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ int CTable::Compare(const CObject *node,const int mode=0) const { const CTable *obj=node; return(this.ID()>obj.ID() ? 1 : this.ID()<obj.ID() ? -1 : 0); }
Un identifiant peut être attribué à chaque table si le programme est censé créer plusieurs tables. Les tables du programme peuvent être identifiées par l'identifiant de l'ensemble, qui a par défaut la valeur -1. Si les tableaux créés sont placés dans des listes(CList, CArrayObj, etc.), la méthode de comparaison permet de comparer les tableaux par leurs identifiants pour les rechercher et les trier :
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ int CTable::Compare(const CObject *node,const int mode=0) const { const CTable *obj=node; return(this.ID()>obj.ID() ? 1 : this.ID()<obj.ID() ? -1 : 0); }
Une méthode qui copie un tableau de noms d'en-têtes
//+------------------------------------------------------------------+ //| Copy the array of header names | //+------------------------------------------------------------------+ bool CTable::ArrayNamesCopy(const string &column_names[],const uint columns_total) { if(columns_total==0) { ::PrintFormat("%s: Error. The table has no columns",__FUNCTION__); return false; } if(columns_total>column_names.Size()) { ::PrintFormat("%s: The number of header names is less than the number of columns. The header array will be filled in Excel style (A, B, C)",__FUNCTION__); return this.FillArrayExcelNames(columns_total); } uint total=::fmin(columns_total,column_names.Size()); return(::ArrayCopy(this.m_array_names,column_names,0,0,total)==total); }
Un tableau d'en-têtes et le nombre de colonnes du modèle de table créé sont transmis à la méthode. Si le tableau ne comporte aucune colonne, il n'est pas nécessaire de créer des en-têtes. Ce fait est noté et la méthode renvoie false. S'il y a plus de colonnes dans le modèle de tableau que d'en-têtes dans le tableau transmis, tous les en-têtes seront créés dans le style Excel afin que le tableau ne comporte pas de colonnes sans légende dans les en-têtes.
Une méthode qui fixe une valeur à la cellule spécifiée
//+------------------------------------------------------------------+ //| Set the value to the specified cell | //+------------------------------------------------------------------+ template<typename T> void CTable::CellSetValue(const uint row, const uint col, const T value) { if(this.m_table_model!=NULL) this.m_table_model.CellSetValue(row,col,value); }
Nous nous référons ici à la méthode portant le même nom pour l'objet modèle de table.
Dans cette classe, de nombreuses méthodes sont essentiellement dupliquées à partir de la classe de modèle de tableau. Si le modèle est créé, sa méthode similaire d'obtention ou de définition de la propriété est appelée.
Méthode de fonctionnement des cellules de tableau
//+------------------------------------------------------------------+ //| Set the accuracy to the specified cell | //+------------------------------------------------------------------+ void CTable::CellSetDigits(const uint row, const uint col, const int digits) { if(this.m_table_model!=NULL) this.m_table_model.CellSetDigits(row,col,digits); } //+------------------------------------------------------------------+ //| Set the time display flags to the specified cell | //+------------------------------------------------------------------+ void CTable::CellSetTimeFlags(const uint row, const uint col, const uint flags) { if(this.m_table_model!=NULL) this.m_table_model.CellSetTimeFlags(row,col,flags); } //+------------------------------------------------------------------+ //| Set the flag for displaying color names in the specified cell | //+------------------------------------------------------------------+ void CTable::CellSetColorNamesFlag(const uint row, const uint col, const bool flag) { if(this.m_table_model!=NULL) this.m_table_model.CellSetColorNamesFlag(row,col,flag); } //+------------------------------------------------------------------+ //| Assign an object to a cell | //+------------------------------------------------------------------+ void CTable::CellAssignObject(const uint row, const uint col,CObject *object) { if(this.m_table_model!=NULL) this.m_table_model.CellAssignObject(row,col,object); } //+------------------------------------------------------------------+ //| Cancel the object in the cell | //+------------------------------------------------------------------+ void CTable::CellUnassignObject(const uint row, const uint col) { if(this.m_table_model!=NULL) this.m_table_model.CellUnassignObject(row,col); } //+------------------------------------------------------------------+ //| Return the string value of the specified cell | //+------------------------------------------------------------------+ string CTable::CellValueAt(const uint row,const uint col) { CTableCell *cell=this.GetCell(row,col); return(cell!=NULL ? cell.Value() : ""); } //+------------------------------------------------------------------+ //| Delete a cell | //+------------------------------------------------------------------+ bool CTable::CellDelete(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellDelete(row,col) : false); } //+------------------------------------------------------------------+ //| Move the cell | //+------------------------------------------------------------------+ bool CTable::CellMoveTo(const uint row, const uint cell_index, const uint index_to) { return(this.m_table_model!=NULL ? this.m_table_model.CellMoveTo(row,cell_index,index_to) : false); } //+------------------------------------------------------------------+ //| Return the object assigned to the cell | //+------------------------------------------------------------------+ CObject *CTable::CellGetObject(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellGetObject(row,col) : NULL); } //+------------------------------------------------------------------+ //| Returns the type of the object assigned to the cell | //+------------------------------------------------------------------+ ENUM_OBJECT_TYPE CTable::CellGetObjType(const uint row,const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellGetObjType(row,col) : (ENUM_OBJECT_TYPE)WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return the cell description | //+------------------------------------------------------------------+ string CTable::CellDescription(const uint row, const uint col) { return(this.m_table_model!=NULL ? this.m_table_model.CellDescription(row,col) : ""); } //+------------------------------------------------------------------+ //| Display a cell description in the journal | //+------------------------------------------------------------------+ void CTable::CellPrint(const uint row, const uint col) { if(this.m_table_model!=NULL) this.m_table_model.CellPrint(row,col); }
Méthodes de travail avec les lignes de tableau
//+------------------------------------------------------------------+ //| Create a new string and add it to the end of the list | //+------------------------------------------------------------------+ CTableRow *CTable::RowAddNew(void) { return(this.m_table_model!=NULL ? this.m_table_model.RowAddNew() : NULL); } //+--------------------------------------------------------------------------------+ //| Create a new string and insert it into the specified position of the list | //+--------------------------------------------------------------------------------+ CTableRow *CTable::RowInsertNewTo(const uint index_to) { return(this.m_table_model!=NULL ? this.m_table_model.RowInsertNewTo(index_to) : NULL); } //+------------------------------------------------------------------+ //| Delete a row | //+------------------------------------------------------------------+ bool CTable::RowDelete(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.RowDelete(index) : false); } //+------------------------------------------------------------------+ //| Move the row | //+------------------------------------------------------------------+ bool CTable::RowMoveTo(const uint row_index, const uint index_to) { return(this.m_table_model!=NULL ? this.m_table_model.RowMoveTo(row_index,index_to) : false); } //+------------------------------------------------------------------+ //| Clear the row data | //+------------------------------------------------------------------+ void CTable::RowClearData(const uint index) { if(this.m_table_model!=NULL) this.m_table_model.RowClearData(index); } //+------------------------------------------------------------------+ //| Return the row description | //+------------------------------------------------------------------+ string CTable::RowDescription(const uint index) { return(this.m_table_model!=NULL ? this.m_table_model.RowDescription(index) : ""); } //+------------------------------------------------------------------+ //| Display the row description in the journal | //+------------------------------------------------------------------+ void CTable::RowPrint(const uint index,const bool detail) { if(this.m_table_model!=NULL) this.m_table_model.RowPrint(index,detail); }
Une méthode qui crée une nouvelle colonne et l'ajoute à la position spécifiée dans le tableau.
//+-----------------------------------------------------------------------+ //| Create a new column and adds it to the specified position in the table| //+-----------------------------------------------------------------------+ bool CTable::ColumnAddNew(const string caption,const int index=-1) { //--- If there is no table model, or there is an error adding a new column to the model, return 'false' if(this.m_table_model==NULL || !this.m_table_model.ColumnAddNew(index)) return false; //--- If there is no header, return 'true' (the column is added without a header) if(this.m_table_header==NULL) return true; //--- Check for the creation of a new column header and, if it has not been created, return 'false' CColumnCaption *caption_obj=this.m_table_header.CreateNewColumnCaption(caption); if(caption_obj==NULL) return false; //--- If a non-negative index has been passed, return the result of moving the header to the specified index //--- Otherwise, everything is ready - just return 'true' return(index>-1 ? this.m_table_header.ColumnCaptionMoveTo(caption_obj.Column(),index) : true); }
S'il n'y a pas de modèle de tableau, la méthode renvoie immédiatement une erreur. Si la colonne a été ajoutée avec succès au modèle de tableau, la méthode essaye d'ajouter l'en-tête approprié. Si le tableau n'a pas d'en-tête, la création d'une nouvelle colonne est renvoyée avec succès. S'il existe un en-tête, ajoute un nouvel en-tête de colonne et le déplace à la position spécifiée dans la liste.
Autres méthodes pour travailler avec des colonnes
//+------------------------------------------------------------------+ //| Remove the column | //+------------------------------------------------------------------+ bool CTable::ColumnDelete(const uint index) { if(!this.HeaderCheck() || !this.m_table_header.ColumnCaptionDelete(index)) return false; return this.m_table_model.ColumnDelete(index); } //+------------------------------------------------------------------+ //| Move the column | //+------------------------------------------------------------------+ bool CTable::ColumnMoveTo(const uint index, const uint index_to) { if(!this.HeaderCheck() || !this.m_table_header.ColumnCaptionMoveTo(index,index_to)) return false; return this.m_table_model.ColumnMoveTo(index,index_to); } //+------------------------------------------------------------------+ //| Clear the column data | //+------------------------------------------------------------------+ void CTable::ColumnClearData(const uint index) { if(this.m_table_model!=NULL) this.m_table_model.ColumnClearData(index); } //+------------------------------------------------------------------+ //| Set the value of the specified header | //+------------------------------------------------------------------+ void CTable::ColumnCaptionSetValue(const uint index,const string value) { CColumnCaption *caption=this.m_table_header.GetColumnCaption(index); if(caption!=NULL) caption.SetValue(value); } //+------------------------------------------------------------------+ //| Set the data type for the specified column | //+------------------------------------------------------------------+ void CTable::ColumnSetDatatype(const uint index,const ENUM_DATATYPE type) { //--- If the table model exists, set the data type for the column if(this.m_table_model!=NULL) this.m_table_model.ColumnSetDatatype(index,type); //--- If there is a header, set the data type for it if(this.m_table_header!=NULL) this.m_table_header.ColumnCaptionSetDatatype(index,type); } //+------------------------------------------------------------------+ //| Set the data accuracy for the specified column | //+------------------------------------------------------------------+ void CTable::ColumnSetDigits(const uint index,const int digits) { if(this.m_table_model!=NULL) this.m_table_model.ColumnSetDigits(index,digits); } //+------------------------------------------------------------------+ //| Return the data type for the specified column | //+------------------------------------------------------------------+ ENUM_DATATYPE CTable::ColumnDatatype(const uint index) { return(this.m_table_header!=NULL ? this.m_table_header.ColumnCaptionDatatype(index) : (ENUM_DATATYPE)WRONG_VALUE); }
Une méthode qui renvoie la description de l'objet
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ string CTable::Description(void) { return(::StringFormat("%s: Rows total: %u, Columns total: %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.RowsTotal(),this.ColumnsTotal())); }
Crée et renvoie la chaîne au format (Object Type : Rows total: XX, Columns total: XX)
Méthode permettant d'afficher la description de l'objet dans le journal
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTable::Print(const int column_width=CELL_WIDTH_IN_CHARS) { if(this.HeaderCheck()) { //--- Display the header as a row description ::Print(this.Description()+":"); //--- Number of headers int total=(int)this.ColumnsTotal(); string res=""; //--- create a table row from the values of all table column headers res="|"; for(int i=0;i<total;i++) { CColumnCaption *caption=this.GetColumnCaption(i); if(caption==NULL) continue; res+=::StringFormat("%*s |",column_width,caption.Value()); } //--- Add a header to the left row string hd="|"; hd+=::StringFormat("%*s ",column_width,"n/n"); res=hd+res; //--- Display the header row in the journal ::Print(res); } //--- Loop through all the table rows and print them out in tabular form for(uint i=0;i<this.RowsTotal();i++) { CTableRow *row=this.GetRow(i); if(row!=NULL) { //--- create a table row from the values of all cells string head=" "+(string)row.Index(); string res=::StringFormat("|%-*s |",column_width,head); for(int i=0;i<(int)row.CellsTotal();i++) { CTableCell *cell=row.GetCell(i); if(cell==NULL) continue; res+=::StringFormat("%*s |",column_width,cell.Value()); } //--- Display a row in the journal ::Print(res); } } }
La méthode produit une description dans le journal, et en-dessous un tableau avec l'en-tête et les données.
Méthode d'enregistrement du tableau dans un fichier
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CTable::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the ID if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return(false); //--- Check the table model if(this.m_table_model==NULL) return false; //--- Save the table model if(!this.m_table_model.Save(file_handle)) return(false); //--- Check the table header if(this.m_table_header==NULL) return false; //--- Save the table header if(!this.m_table_header.Save(file_handle)) return(false); //--- Successful return true; }
L'enregistrement n'aboutira que si le modèle de tableau et son en-tête ont été créés. L'en-tête peut être vide, c'est-à-dire qu'il ne peut avoir aucune colonne, mais l'objet doit être créé.
Méthode de téléchargement du tableau à partir d'un fichier
//+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CTable::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the ID this.m_id=::FileReadInteger(file_handle,INT_VALUE); //--- Check the table model if(this.m_table_model==NULL && (this.m_table_model=new CTableModel())==NULL) return(false); //--- Load the table model if(!this.m_table_model.Load(file_handle)) return(false); //--- Check the table header if(this.m_table_header==NULL && (this.m_table_header=new CTableHeader())==NULL) return false; //--- Load the table header if(!this.m_table_header.Load(file_handle)) return(false); //--- Successful return true; }
Étant donné que le tableau stocke à la fois les données du modèle et celles de l'en-tête, dans cette méthode (si le modèle ou l'en-tête n'est pas créé dans le tableau), ils sont pré-créés, puis leurs données sont chargées à partir du fichier.
La classe de tableau simple est prête.
Envisagez maintenant la possibilité d'hériter d'une classe de tableau simple - créez une classe de table qui s'appuie sur les données enregistrées dans la CList :
//+------------------------------------------------------------------+ //| Class for creating tables based on the array of parameters | //+------------------------------------------------------------------+ class CTableByParam : public CTable { public: virtual int Type(void) const { return(OBJECT_TYPE_TABLE_BY_PARAM); } //--- Constructor/destructor CTableByParam(void) { this.m_list_rows.Clear(); } CTableByParam(CList &row_data,const string &column_names[]); ~CTableByParam(void) {} };
Ici, le type de table est renvoyé sous la forme OBJECT_TYPE_TABLE_BY_PARAM, et le modèle de tableau et l'en-tête sont construits dans le constructeur de la classe :
//+------------------------------------------------------------------+ //| Constructor specifying a table array based on the row_data list | //| containing objects with structure field data. | //| Define the index and names of columns according to | //| column names in column_names | //+------------------------------------------------------------------+ CTableByParam::CTableByParam(CList &row_data,const string &column_names[]) { //--- Copy the passed list of data into a variable and //--- create a table model based on this list this.m_list_rows=row_data; this.m_table_model=new CTableModel(this.m_list_rows); //--- Copy the passed list of headers to m_array_names and //--- create a table header based on this list this.ArrayNamesCopy(column_names,column_names.Size()); this.m_table_header=new CTableHeader(this.m_array_names); }
Sur la base de cet exemple, il est possible de créer d'autres classes de tableaux, mais nous partons du principe que tout ce qui a été créé aujourd'hui est suffisant pour créer une grande variété de tableaux et un vaste ensemble de données possibles.
Testons tout ce que nous avons.
Test du résultat
Dans le dossier \MQL5\Scripts\Tables\, créez un nouveau script nommé TestEmptyTable.mq5, connectez-y le fichier de classe de tableau créé et créez un tableau 4x4 vide :
//+------------------------------------------------------------------+ //| TestEmptyTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Create an empty 4x4 table CTable *table=new CTable(4,4); if(table==NULL) return; //--- Display it in the journal and delete the created object table.Print(10); delete table; }
Le script produira la sortie suivante dans le journal :
Table: Rows total: 4, Columns total: 4: | n/n | A | B | C | D | | 0 | | | | | | 1 | | | | | | 2 | | | | | | 3 | | | | |
Ici, les en-têtes de colonne sont créés automatiquement dans le style MS Excel.
Ecrivez un autre script \MQL5\Scripts\Tables\TestTArrayTable.mq5 :
//+------------------------------------------------------------------+ //| TestTArrayTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare and initialize a 4x4 double array double array[4][4]={{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Declare and initialize the column header array string headers[]={"Column 1","Column 2","Column 3","Column 4"}; //--- Create a table based on the data array and the header array CTable *table=new CTable(array,headers); if(table==NULL) return; //--- Display the table in the journal and delete the created object table.Print(10); delete table; }
Suite à l'exécution du script, le tableau suivant s'affiche dans le journal :
Table: Rows total: 4, Columns total: 4: | n/n | Column 1 | Column 2 | Column 3 | Column 4 | | 0 | 1.00 | 2.00 | 3.00 | 4.00 | | 1 | 5.00 | 6.00 | 7.00 | 8.00 | | 2 | 9.00 | 10.00 | 11.00 | 12.00 | | 3 | 13.00 | 14.00 | 15.00 | 16.00 |
Ici, les colonnes sont déjà titrées à partir du tableau d'en-têtes transmis au constructeur de la classe. Un tableau à deux dimensions représentant les données pour la création d'un tableau peut être de n'importe quel type de l'énumération ENUM_DATATYPE.
Tous les types sont automatiquement convertis en 5 types utilisés dans la classe de modèle de tableau : double, long, datetime, color et string.
Ecrivez le script \MQL5\Scripts\Tables\TestTableMatrix.mq5 pour tester le tableau sur des données matricielles :
//+------------------------------------------------------------------+ //| TestMatrixTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare and initialize a 4x4 matrix matrix row_data = {{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Declare and initialize the column header array string headers[]={"Column 1","Column 2","Column 3","Column 4"}; //--- Create a table based on a matrix and an array of headers CTable *table=new CTable(row_data,headers); if(table==NULL) return; //--- Display the table in the journal and delete the created object table.Print(10); delete table; }
Le résultat sera un tableau similaire à celui construit sur la base d'un tableau bidimensionnel 4x4 :
Table: Rows total: 4, Columns total: 4: | n/n | Column 1 | Column 2 | Column 3 | Column 4 | | 0 | 1.00 | 2.00 | 3.00 | 4.00 | | 1 | 5.00 | 6.00 | 7.00 | 8.00 | | 2 | 9.00 | 10.00 | 11.00 | 12.00 | | 3 | 13.00 | 14.00 | 15.00 | 16.00 |
Maintenant, écrivez le script \MQL5\Scripts\Tables\TestDealsTable.mq5, dans lequel nous comptons toutes les transactions historiques, créons une table de transactions basée sur celles-ci et imprimons dans le journal :
//+------------------------------------------------------------------+ //| TestDealsTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include "Tables.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare a list of deals, the deal parameters object, and the structure of parameters CList rows_data; CMqlParamObj *cell=NULL; MqlParam param={}; //--- Select the entire history if(!HistorySelect(0,TimeCurrent())) return; //--- Create a list of deals in the array of arrays (CList in CList) //--- (one row is one deal, columns are deal property objects) int total=HistoryDealsTotal(); for(int i=0;i<total;i++) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) continue; //--- Add a new row of properties for the next deal to the list of deals CList *row=DataListCreator::AddNewRowToDataList(&rows_data); if(row==NULL) continue; //--- Create "cells" with the deal parameters and //--- add them to the created deal properties row string symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); //--- Deal time (column 0) param.type=TYPE_DATETIME; param.integer_value=HistoryDealGetInteger(ticket,DEAL_TIME); param.double_value=(TIME_DATE|TIME_MINUTES|TIME_SECONDS); DataListCreator::AddNewCellParamToRow(row,param); //--- Symbol name (column 1) param.type=TYPE_STRING; param.string_value=symbol; DataListCreator::AddNewCellParamToRow(row,param); //--- Deal ticket (column 2) param.type=TYPE_LONG; param.integer_value=(long)ticket; DataListCreator::AddNewCellParamToRow(row,param); //--- The order the performed deal is based on (column 3) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_ORDER); DataListCreator::AddNewCellParamToRow(row,param); //--- Position ID (column 4) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_POSITION_ID); DataListCreator::AddNewCellParamToRow(row,param); //--- Deal type (column 5) param.type=TYPE_STRING; ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE); param.integer_value=deal_type; string type=""; switch(deal_type) { case DEAL_TYPE_BUY : type="Buy"; break; case DEAL_TYPE_SELL : type="Sell"; break; case DEAL_TYPE_BALANCE : type="Balance"; break; case DEAL_TYPE_CREDIT : type="Credit"; break; case DEAL_TYPE_CHARGE : type="Charge"; break; case DEAL_TYPE_CORRECTION : type="Correction"; break; case DEAL_TYPE_BONUS : type="Bonus"; break; case DEAL_TYPE_COMMISSION : type="Commission"; break; case DEAL_TYPE_COMMISSION_DAILY : type="Commission daily"; break; case DEAL_TYPE_COMMISSION_MONTHLY : type="Commission monthly"; break; case DEAL_TYPE_COMMISSION_AGENT_DAILY : type="Commission agent daily"; break; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: type="Commission agent monthly"; break; case DEAL_TYPE_INTEREST : type="Interest"; break; case DEAL_TYPE_BUY_CANCELED : type="Buy canceled"; break; case DEAL_TYPE_SELL_CANCELED : type="Sell canceled"; break; case DEAL_DIVIDEND : type="Dividend"; break; case DEAL_DIVIDEND_FRANKED : type="Dividend franked"; break; case DEAL_TAX : type="Tax"; break; default : break; } param.string_value=type; DataListCreator::AddNewCellParamToRow(row,param); //--- Deal direction (column 6) param.type=TYPE_STRING; ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); param.integer_value=deal_entry; string entry=""; switch(deal_entry) { case DEAL_ENTRY_IN : entry="In"; break; case DEAL_ENTRY_OUT : entry="Out"; break; case DEAL_ENTRY_INOUT : entry="InOut"; break; case DEAL_ENTRY_OUT_BY : entry="OutBy"; break; default : break; } param.string_value=entry; DataListCreator::AddNewCellParamToRow(row,param); //--- Deal volume (column 7) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_VOLUME); param.integer_value=2; DataListCreator::AddNewCellParamToRow(row,param); //--- Deal price (column 8) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_PRICE); param.integer_value=(param.double_value>0 ? digits : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Stop Loss level (column 9) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_SL); param.integer_value=(param.double_value>0 ? digits : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Take Profit level (column 10) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_TP); param.integer_value=(param.double_value>0 ? digits : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Deal financial result (column 11) param.type=TYPE_DOUBLE; param.double_value=HistoryDealGetDouble(ticket,DEAL_PROFIT); param.integer_value=(param.double_value!=0 ? 2 : 1); DataListCreator::AddNewCellParamToRow(row,param); //--- Deal magic number (column 12) param.type=TYPE_LONG; param.integer_value=HistoryDealGetInteger(ticket,DEAL_MAGIC); DataListCreator::AddNewCellParamToRow(row,param); //--- Deal execution reason or source (column 13) param.type=TYPE_STRING; ENUM_DEAL_REASON deal_reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); param.integer_value=deal_reason; string reason=""; switch(deal_reason) { case DEAL_REASON_CLIENT : reason="Client"; break; case DEAL_REASON_MOBILE : reason="Mobile"; break; case DEAL_REASON_WEB : reason="Web"; break; case DEAL_REASON_EXPERT : reason="Expert"; break; case DEAL_REASON_SL : reason="SL"; break; case DEAL_REASON_TP : reason="TP"; break; case DEAL_REASON_SO : reason="StopOut"; break; case DEAL_REASON_ROLLOVER : reason="Rollover"; break; case DEAL_REASON_VMARGIN : reason="VMargin"; break; case DEAL_REASON_SPLIT : reason="Split"; break; case DEAL_REASON_CORPORATE_ACTION: reason="Corporate action"; break; default : break; } param.string_value=reason; DataListCreator::AddNewCellParamToRow(row,param); //--- Deal comment (column 14) param.type=TYPE_STRING; param.string_value=HistoryDealGetString(ticket,DEAL_COMMENT); DataListCreator::AddNewCellParamToRow(row,param); } //--- Declare and initialize the table header string headers[]={"Time","Symbol","Ticket","Order","Position","Type","Entry","Volume","Price","SL","TP","Profit","Magic","Reason","Comment"}; //--- Create a table based on the created list of parameters and the header array CTableByParam *table=new CTableByParam(rows_data,headers); if(table==NULL) return; //--- Display the table in the journal and delete the created object table.Print(); delete table; }
En conséquence, un tableau de toutes les transactions avec une largeur de cellule de 19 caractères sera imprimé (par défaut dans la méthode Print de la classe de tableau) :
Table By Param: Rows total: 797, Columns total: 15: | n/n | Time | Symbol | Ticket | Order | Position | Type | Entry | Volume | Price | SL | TP | Profit | Magic | Reason | Comment | | 0 |2025.01.01 10:20:10 | | 3152565660 | 0 | 0 | Balance | In | 0.00 | 0.0 | 0.0 | 0.0 | 100000.00 | 0 | Client | | | 1 |2025.01.02 00:01:31 | GBPAUD | 3152603334 | 3191672408 | 3191672408 | Sell | In | 0.25 | 2.02111 | 0.0 | 0.0 | 0.0 | 112 | Expert | | | 2 |2025.01.02 02:50:31 | GBPAUD | 3152749152 | 3191820118 | 3191672408 | Buy | Out | 0.25 | 2.02001 | 0.0 | 2.02001 | 17.04 | 112 | TP | [tp 2.02001] | | 3 |2025.01.02 04:43:43 | GBPUSD | 3152949278 | 3191671491 | 3191671491 | Sell | In | 0.10 | 1.25270 | 0.0 | 1.24970 | 0.0 | 12 | Expert | | ... ... | 793 |2025.04.18 03:22:11 | EURCAD | 3602552747 | 3652159095 | 3652048415 | Sell | Out | 0.25 | 1.57503 | 0.0 | 1.57503 | 12.64 | 112 | TP | [tp 1.57503] | | 794 |2025.04.18 04:06:52 | GBPAUD | 3602588574 | 3652200103 | 3645122489 | Sell | Out | 0.25 | 2.07977 | 0.0 | 2.07977 | 3.35 | 112 | TP | [tp 2.07977] | | 795 |2025.04.18 04:06:52 | GBPAUD | 3602588575 | 3652200104 | 3652048983 | Sell | Out | 0.25 | 2.07977 | 0.0 | 2.07977 | 12.93 | 112 | TP | [tp 2.07977] | | 796 |2025.04.18 05:57:48 | AUDJPY | 3602664574 | 3652277665 | 3652048316 | Buy | Out | 0.25 | 90.672 | 0.0 | 90.672 | 19.15 | 112 | TP | [tp 90.672] |
L'exemple ici montre les quatre premières et les quatre dernières transactions, mais il donne une idée du tableau imprimé dans le journal.
Tous les fichiers créés sont joints à l'article pour votre auto-apprentissage. Le fichier d'archive peut être décompressé dans le dossier du terminal et tous les fichiers seront placés dans le dossier souhaité : MQL5\Scripts\Tables.
Conclusion
Nous avons donc terminé le travail sur les composants de base du modèle de tableau (Model) dans le cadre de l'architecture MVC. Nous avons créé des classes pour travailler avec des tableaux et des en-têtes, et nous les avons également testées sur différents types de données : tableaux bidimensionnels, matrices et historique de trading.
Nous passons maintenant à la phase suivante, qui consiste à développer les composants Vue et Contrôleur. Dans MQL5, ces deux composantes sont étroitement liées grâce au système d'événements intégré qui permet aux objets de réagir aux actions de l'utilisateur.
Cela nous donne l'occasion de développer la visualisation du tableau (composant View) et son contrôle (composant Controller) en même temps. Cela simplifiera légèrement la mise en œuvre plutôt complexe et à plusieurs niveaux du composant View.
Tous les exemples et fichiers de l'article peuvent être téléchargés. Dans les prochains articles, nous créerons un composant Vue combiné à un Contrôleur pour mettre en œuvre un outil complet d'exploitation des tableaux dans MQL5.
Une fois le projet achevé, de nouvelles possibilités s'offriront à nous pour créer d'autres contrôles d'interface utilisateur à utiliser dans nos développements.
Programmes utilisés dans l'article :
| # | Nom | Type | Description |
|---|---|---|---|
| 1 | Tables.mqh | Bibliothèque de classe | Bibliothèque de classes pour la création de tableaux |
| 2 | TestEmptyTable.mq5 | Script | Un script pour tester la création d'un tableau vide avec un nombre défini de lignes et de colonnes |
| 3 | TestTArrayTable.mq5 | Script | Un script pour tester la création d'une table basée sur un tableau de données à deux dimensions |
| 4 | TestMatrixTable.mq5 | Script | Un script pour tester la création d'un tableau basé sur une matrice de données |
| 5 | TestDealsTable.mq5 | Script | Un script pour tester la création d'un tableau basé sur les données de l'utilisateur (transactions historiques) |
| 6 | MQL5.zip | Archive | Une archive des fichiers présentés ci-dessus, à décompresser dans le répertoire MQL5 du terminal client |
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/17803
Avertissement: Tous les droits sur ces documents sont réservés par MetaQuotes Ltd. La copie ou la réimpression de ces documents, en tout ou en partie, est interdite.
Cet article a été rédigé par un utilisateur du site et reflète ses opinions personnelles. MetaQuotes Ltd n'est pas responsable de l'exactitude des informations présentées, ni des conséquences découlant de l'utilisation des solutions, stratégies ou recommandations décrites.
Les méthodes de William Gann (première partie) : Création de l'indicateur Angles de Gann
Implémentation d'un modèle de table dans MQL5 : Application du concept MVC
Les méthodes de William Gann (Partie II) : Création de l'indicateur Carré de Gann (Gann Square)
Passer à MQL5 Algo Forge (Partie 4) : Travailler avec les versions et les mises à jour
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation