English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
Tabellen- und Kopfzeilen-Klassen auf der Grundlage eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts

Tabellen- und Kopfzeilen-Klassen auf der Grundlage eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts

MetaTrader 5Beispiele |
13 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Einführung

Im ersten Artikel über die Erstellung des Table Controls haben wir ein Tabellenmodell in MQL5 unter Verwendung der MVC-Architekturvorlage erstellt. Es wurden Klassen von Zellen, Zeilen und Tabellenmodellen entwickelt, die es ermöglichten, Daten in einer praktischen und strukturierten Form zu organisieren.

Wir gehen nun zur nächsten Phase über – der Entwicklung von Tabellenklassen und Tabellenköpfen. Die Spaltenüberschriften einer Tabelle sind nicht nur Spaltenbeschriftungen, sondern ein Werkzeug zur Verwaltung der Tabelle und ihrer Spalten. Mit ihnen können wir Spalten hinzufügen, löschen und umbenennen. Natürlich kann eine Tabelle auch ohne eine Kopfklasse funktionieren, aber dann sind ihre Funktionen eingeschränkt. Es wird eine einfache statische Tabelle ohne Spaltenüberschriften und dementsprechend ohne die Möglichkeit, Spalten zu kontrollieren, erstellt.

Zur Implementierung der Spaltenkontrollfunktion muss das Tabellenmodell verfeinert werden. Wir werden sie mit Methoden ergänzen, die es Ihnen ermöglichen, mit Spalten zu arbeiten: ihre Struktur zu ändern, neue hinzuzufügen oder bestehende zu löschen. Diese Methoden werden von der Tabellenkopfklasse verwendet, um eine bequeme Kontrolle über ihre Struktur zu ermöglichen.

Diese Entwicklungsphase bildet die Grundlage für die weitere Implementierung von View- und Controller-Komponenten, die in den folgenden Artikeln behandelt werden. Dieser Schritt ist ein wichtiger Meilenstein auf dem Weg zu einer vollwertigen Schnittstelle für Betriebsdaten.


Verfeinerung des Tabellenmodells

Im Moment wird das Tabellenmodell aus einem zweidimensionalen Array erstellt, aber um die Flexibilität und den Komfort bei der Arbeit mit der Tabelle zu erhöhen, werden wir zusätzliche Initialisierungsmethoden hinzufügen. Dies ermöglicht die Anpassung des Modells an verschiedene Nutzungsszenarien. Die folgenden Methoden werden in der aktualisierten Version der Tabellenmodellklasse erscheinen:

  • Erstellen eines Modells aus einem zweidimensionalen Array

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

    Mit dieser Methode können wir schnell ein Tabellenmodell auf der Grundlage eines vorhandenen zweidimensionalen Datenfeldes erstellen.

  • Erstellen eines leeren Modells mit einer bestimmten Anzahl von Zeilen und Spalten

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

    Diese Methode ist geeignet, wenn die Tabellenstruktur im Voraus bekannt ist, die Daten aber erst später hinzugefügt werden.

  • Erstellen eines Modells aus einer Datenmatrix

    void CreateTableModel(const matrix &row_data);

    Mit dieser Methode können wir eine Datenmatrix für die Initialisierung einer Tabelle verwenden, was für die Arbeit mit vorbereiteten Datensätzen praktisch ist.

  • Erstellen eines Modells aus einer verknüpften Liste

    void CreateTableModel(CList &list_param);

    In diesem Fall wird ein Array von Arrays für die Datenspeicherung verwendet, wobei das Objekt CList (Daten auf Tabellenzeilen) andere CList-Objekte enthält, die Daten auf Tabellenzellen enthalten. Dieser Ansatz ermöglicht es, die Struktur der Tabelle und ihren Inhalt dynamisch zu steuern.

Diese Änderungen machen das Tabellenmodell vielseitiger und bequemer für den Einsatz in verschiedenen Szenarien. So wird es zum Beispiel möglich sein, Tabellen sowohl aus vorbereiteten Datenfeldern als auch aus dynamisch generierten Listen zu erstellen.

Im letzten Artikel haben wir alle Klassen beschrieben, um ein Tabellenmodell direkt in der Testskriptdatei zu erstellen. Heute werden wir diese Klassen in unsere eigene Include-Datei übertragen.

Erstellen wir in dem Ordner, in dem das Skript aus dem vorigen Artikel gespeichert ist (standardmäßig: \MQL5\Scripts\TableModel\), eine neue Include-Datei mit dem Namen Tables.mqh und kopieren aus der Datei TableModelTest.mq5, die sich im selben Ordner befindet, alles vom Anfang der Datei bis zum Beginn des Testskript-Codes hinein – alle Klassen zum Erstellen eines Tabellenmodells. Jetzt haben wir eine separate Datei mit den Klassen des Tabellenmodells namens Tables.mqh. Nehmen wir Änderungen und Verbesserungen an dieser Datei vor.

Verschieben wir die erstellte Datei in einen neuen Ordner, MQL5\Scripts\Tables\ – wir werden dieses Projekt in diesem Ordner erstellen.

Im Abschnitt der enthaltenen Dateien/Bibliotheken fügen wir eine Vorwärtsdeklaration der neuen Klassen hinzu. Die Deklaration der Klassen ist notwendig, damit die im vorigen Artikel erstellte Klasse des Objekts CListObj in ihrer Methode CreateElement() Objekte dieser Klassen erstellen kann:

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

Im Abschnitt Makros fügen wir eine Bestimmung der Tabellenzellenbreite in Zeichen hinzu, die 19 beträgt. Dies ist die Mindestbreite, bei der der Datumstext vollständig in die Zelle des Protokolls passt, ohne dass sich der rechte Rand der Zelle verschiebt, wodurch die Größe aller Zellen der im Protokoll gezeichneten Tabelle aus dem Gleichgewicht gerät:

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

Im Abschnitt der Enumerationen werden neue Konstanten für die Enumerationen von Objekttypen hinzugefügt:

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

In der Klasse des Objekts CListObj in der Elementerstellungsmethode fügen wir neue Fälle für die Erstellung neuer Objekttypen hinzu:

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

Nach der Erstellung neuer Klassen kann die Objektliste CListObj Objekte dieser Typen erstellen. Dies ermöglicht das Speichern und Laden von Listen aus Dateien, die diese Arten von Objekten enthalten.

Um einen „leeren“ Wert in eine Zelle zu setzen, der als leere Zeile und nicht wie bisher als „0“-Wert angezeigt wird, muss festgelegt werden, welcher Wert als „leer“ betrachtet werden soll. Es ist klar, dass bei String-Werten eine leere Zeile ein solcher Wert ist. Während für numerische Werte DBL_MAX für reelle Typen und LONG_MAX für ganze Zahlen definiert werden.

Um einen solchen Wert zu setzen, schreiben wir in der Objektklasse einer Tabellenzelle im geschützten Bereich die folgende Methode:

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

Die Methode, die den in einer Zelle gespeicherten Wert als formatierte Zeichenkette zurückgibt, prüft nun , ob der Wert in der Zelle nicht „leer“ ist, und wenn der Wert „leer“ ist, gibt sie eine leere Zeile zurück:

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

Die Methode zum Löschen von Daten in einer Zelle setzt nun nicht mehr Null, sondern ruft eine Methode auf, um einen leeren Wert in der Zelle zu setzen.

Methoden aller Klassen, aus denen das Tabellenmodell-Objekt erzeugt wird, die die Beschreibung des Objekts zurückgeben, werden jetzt virtuell gemacht – im Falle der Vererbung von diesen Objekten:

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

In der Tabellenzeilenklasse wurden alle CreateNewCell()-Methoden, die eine neue Zelle erstellen und an das Ende der Liste anfügen, umbenannt:

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

Dies geschieht so, dass alle Methoden, die für den Zugriff auf Zellen zuständig sind, mit der Teilzeichenkette „Cell“ beginnen. In anderen Klassen beginnen die Methoden für den Zugriff auf z. B. eine Tabellenzeile mit dem Teilstring „Row“. Dies bringt Ordnung in die Methoden der Klassen.

Um ein Tabellenmodell zu erstellen, müssen wir einen universellen Ansatz entwickeln, der die Erstellung von Tabellen aus nahezu beliebigen Daten ermöglicht. Dies können z.B. Strukturen, Listen von Handelsgeschäften, Aufträgen, Positionen oder andere Daten sein. Die Idee ist, ein Toolkit zu erstellen, mit dem eine Liste von Zeilen erstellt werden kann, wobei jede Zeile eine Liste von Eigenschaften ist. Jede Eigenschaft in der Liste entspricht einer Zelle in der Tabelle.

Hier sollte auf die Struktur der Eingabeparameter von MqlParam geachtet werden. Es bietet die folgenden Funktionen:

  • Angabe des Datentyps, der in der Struktur gespeichert wird (ENUM_DATATYPE).
  • Speicherung von Werten in drei Feldern:
    1. integer_value – für ganzzahlige Daten,
    2. double_value – für reelle Zahlen,
    3. string_value – für Zeichenketten.

Diese Struktur ermöglicht es, mit verschiedenen Datentypen zu arbeiten, die es erlauben, beliebige Eigenschaften zu speichern, wie z.B. Parameter von Transaktionen, Aufträgen oder anderen Objekten.

Zur bequemen Datenspeicherung erstellen wir die Klasse CMqlParamObj, die von der Basisklasse CObject aus der Standardbibliothek geerbt wurde. Diese Klasse wird die Struktur MqlParam enthalten und Methoden zum Setzen und Abrufen von Daten bereitstellen. Durch Vererbung von CObject können solche Objekte in CList-Listen gespeichert werden.

Betrachten wir die gesamte Klasse:

//+------------------------------------------------------------------+
//| Structure parameter object class                                 |
//+------------------------------------------------------------------+
class CMqlParamObj : public CObject
  {
protected:
public:
   MqlParam          m_param;
//--- Set the parameters
   void              Set(const MqlParam &param)
                       {
                        this.m_param.type=param.type;
                        this.m_param.double_value=param.double_value;
                        this.m_param.integer_value=param.integer_value;
                        this.m_param.string_value=param.string_value;
                       }
//--- Return the parameters
   MqlParam          Param(void)       const { return this.m_param;              }
   ENUM_DATATYPE     Datatype(void)    const { return this.m_param.type;         }
   double            ValueD(void)      const { return this.m_param.double_value; }
   long              ValueL(void)      const { return this.m_param.integer_value;}
   string            ValueS(void)      const { return this.m_param.string_value; }
//--- Object description
   virtual string    Description(void)
                       {
                        string t=::StringSubstr(::EnumToString(this.m_param.type),5);
                        t.Lower();
                        string v="";
                        switch(this.m_param.type)
                          {
                           case TYPE_STRING  :  v=this.ValueS(); break;
                           case TYPE_FLOAT   :  case TYPE_DOUBLE : v=::DoubleToString(this.ValueD()); break;
                           case TYPE_DATETIME:  v=::TimeToString(this.ValueL(),TIME_DATE|TIME_MINUTES|TIME_SECONDS); break;
                           default           :  v=(string)this.ValueL(); break;
                          }
                        return(::StringFormat("<%s>%s",t,v));
                       }
   
//--- Constructors/destructor
                     CMqlParamObj(void){}
                     CMqlParamObj(const MqlParam &param) { this.Set(param);  }
                    ~CMqlParamObj(void){}
  };

Dies ist ein üblicher Wrapper um die MqlParam-Struktur, da er ein von CObject geerbtes Objekt benötigt, um solche Objekte in der CList-Liste zu speichern.

Die Struktur der erstellten Daten wird wie folgt aussehen:

  • Ein Objekt der Klasse CMqlParamObj repräsentiert eine Eigenschaft, z. B. den Preis eines Handels, sein Volumen oder die Eröffnungszeit.
  • Eine einzelne CList-Liste stellt eine Tabellenzeile dar, die alle Eigenschaften eines einzelnen Geschäfts enthält.
  • Die CList-Hauptliste enthält eine Reihe von Zeilen (CList-Listen), von denen jede einem Geschäft, einem Auftrag, einer Position oder einer anderen Einheit entspricht.

So erhalten wir eine Struktur, die einem Array von Arrays ähnelt:

  • Die Hauptliste CList ist ein „Array von Zeilen“,
  • Jede verschachtelte CList-Liste ist ein „Array von Zellen“ (Eigenschaften eines Objekts).

Hier ist ein Beispiel für eine solche Datenstruktur für eine Liste historischer Abschlüsse:

  1. Die Hauptliste CList speichert Zeilen einer Tabelle. Jede Zeile ist eine eigene CList-Liste.
  2. Verschachtelte Listen CList – jede verschachtelte Liste stellt eine Zeile in einer Tabelle dar und enthält Objekte der Klasse CMqlParamObj, die Eigenschaften speichern. Zum Beispiel:
    • Zeile eins: Eigenschaften des Handelsgeschäfts Nr. 1 (Preis, Volumen, Eröffnungszeit usw.),
    • Zeile zwei: Eigenschaften des Handelsgeschäfts Nr. 2 (Preis, Volumen, Eröffnungszeit usw.),
    • Zeile drei: Eigenschaften des Handelsgeschäfts Nr. 3 (Preis, Volumen, Eröffnungszeit usw.),
    • usw.
  3. Eigenschaftsobjekte (CMqlParamObj) – jedes Objekt speichert eine Eigenschaft, z. B. den Preis eines Geschäfts oder sein Volumen.

Nachdem die Datenstruktur (CList-Listen) gebildet wurde, kann sie an die Methode CreateTableModel (CList &list_param) des Tabellenmodells übergeben werden. Mit dieser Methode werden die Daten wie folgt interpretiert:

  • Die Hauptliste CList ist eine Liste von Tabellenzeilen,
  • Jede verschachtelte CList-Liste besteht aus den Zellen der einzelnen Zeilen,
  • CMqlParamObj-Objekte innerhalb verschachtelter Listen sind Zellenwerte.

So wird auf der Grundlage der übergebenen Liste eine Tabelle erstellt, die den Quelldaten vollständig entspricht.

Für die Bequemlichkeit der Erstellung solcher Listen entwickeln eine spezielle Klasse. Diese Klasse bietet Methoden für:

  1. Erstellen einer neuen Zeile (der Liste CList) und Hinzufügen zur Hauptliste,
  2. Hinzufügen einer neuen Eigenschaft (das CMqlParamObj-Objekt) zu der Zeile.
//+------------------------------------------------------------------+
//| Class for creating lists of data                                 |
//+------------------------------------------------------------------+
class DataListCreator
  {
public:
//--- Add a new row to the CList list_data list
   static CList     *AddNewRowToDataList(CList *list_data)
                       {
                        CList *row=new CList;
                        if(row==NULL || list_data.Add(row)<0)
                           return NULL;
                        return row;
                       }
//--- Create a new CMqlParamObj parameter object and add it to CList
   static bool       AddNewCellParamToRow(CList *row,MqlParam &param)
                       {
                        CMqlParamObj *cell=new CMqlParamObj(param);
                        if(cell==NULL)
                           return false;
                        if(row.Add(cell)<0)
                          {
                           delete cell;
                           return false;
                          }
                        return true;
                       }
  };

Dies ist eine statische Klasse, die eine Funktion zur bequemen Erstellung von Listen mit der richtigen Struktur zur Übergabe an Methoden zur Erstellung von Tabellenmodellen bietet:

  1. Wir geben die erforderliche Eigenschaft an (z. B. den Handelspreis).
  2. Die Klasse erstellt automatisch ein CMqlParamObj-Objekt, schreibt den Eigenschaftswert in dieses Objekt und fügt es der Zeile hinzu.
  3. Die Zeile wird der Hauptliste hinzugefügt.

Danach kann die fertige Liste zur Entwicklung an das Tabellenmodell übergeben werden.

Der entwickelte Ansatz ermöglicht es, Daten aus beliebigen Strukturen oder Objekten in ein für den Aufbau einer Tabelle geeignetes Format zu konvertieren. Die Verwendung von CList-Listen und CMqlParamObj-Objekten bietet Flexibilität und Bequemlichkeit, und die Hilfsklasse DataListCreator vereinfacht den Prozess der Erstellung solcher Listen. Dies wird die Grundlage für den Aufbau eines universellen Tabellenmodells sein, das aus beliebigen Daten und für beliebige Aufgaben erstellt werden kann.

In der Klasse des Tabellenmodells ergänzen wir drei neue Methoden zur Erstellung eines Tabellenmodells und drei Methoden zur Spaltenmanipulation. Anstelle von fünf überladenen parametrischen Konstruktoren fügen wir einen Vorlagenkonstruktor und drei neue Konstruktoren hinzu, die auf den Typen der Eingabeparameter der neuen Methoden zur Erstellung eines Tabellenmodells basieren:

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

Denken wir über neue Methoden nach.

Eine Methode, die ein Tabellenmodell mit der angegebenen Anzahl von Zeilen und Spalten erstellt

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

Die Methode erstellt ein leeres Modell mit der angegebenen Anzahl von Zeilen und Spalten. Sie ist geeignet, wenn die Tabellenstruktur im Voraus bekannt ist, die Daten aber später hinzugefügt werden sollen.

Eine Methode, die ein ein Tabellenmodell aus der angegebenen Matrix

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

Die Methode ermöglicht die Verwendung einer Datenmatrix zur Initialisierung einer Tabelle, was für den Umgang mit vorbereiteten Datensätzen praktisch ist.

Eine Methode, die ein ein Tabellenmodell aus der Parameterliste

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

Diese Methode ermöglicht es, ein Tabellenmodell auf der Grundlage einer verknüpften Liste zu erstellen, was für den Betrieb dynamischer Datenstrukturen nützlich sein kann.

Es ist zu beachten, dass bei der Erstellung von Zellen mit einem Datentyp, z. B. double, die Anzeigegenauigkeit aus dem Long-Wert des param-Objekts der Klasse CMqlParamObj übernommen wird. Das bedeutet, dass wir beim Anlegen einer Tabellenstruktur mit der Klasse DataListCreator, wie oben besprochen, zusätzlich die notwendigen klärenden Informationen an das Parameterobjekt übergeben können. Für das finanzielle Ergebnis eines Handelsgeschäfts kann dies wie folgt geschehen:

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

Auf die gleiche Weise kann man Zeitanzeigeflags für Tabellenzellen mit dem Typ datetime und ein Flag zur Anzeige des Farbnamens für eine Zelle mit dem Typ color übergeben.

Die Tabelle zeigt die Typen der Tabellenzellen und die Typen der Parameter, die für sie über das CMqlParamObj-Objekt übergeben werden:

Typ in CMqlParamObj
Typ der Zelle „double“ Typ der Zelle „long“
Typ der Zelle „datetime“
Typ der Zelle „color“
Typ der Zelle „string“
  double_value Zellwert nicht verwendet Datum/Uhrzeit-Anzeige-Flags Farbname Anzeige-Flag nicht verwendet
  integer_value Dezimalstellen des Zellwerts Zellwert Zellwert Zellwert nicht verwendet
  string_value nicht verwendet nicht verwendet nicht verwendet nicht verwendet Zellwert

Die Tabelle zeigt, dass bei der Erstellung einer Tabellenstruktur aus einigen Daten, wenn diese Daten von einem reellen Typ sind (in das Feld der Struktur MqlParam double_value geschrieben), dann können wir zusätzlich in das Feld integer_value den Wert der Genauigkeit schreiben, mit dem die Daten in der Tabellenzelle angezeigt werden. Das Gleiche gilt für Daten mit den Typen datetime und color, aber Flags werden in das Feld double_value geschrieben, da das Feld integer durch den Eigenschaftswert selbst belegt ist.

Dies ist optional. Gleichzeitig werden die Werte der Merker und der Genauigkeit in der Zelle auf Null gesetzt. Dieser Wert kann dann sowohl für eine bestimmte Zelle als auch für die gesamte Spalte der Tabelle geändert werden.

Eine Methode, die eine neue Spalte zu einer Tabelle hinzufügt

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

Der Index der neuen Spalte wird an die Methode übergeben. Zunächst werden in allen Zeilen der Tabelle neue Zellen am Ende der Zeile hinzugefügt, und dann werden alle neuen Zellen, sofern der übergebene Index nicht negativ ist, um den angegebenen Index verschoben.

Eine Methode, die den Datentyp der Spalte festlegt

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

Holen wir in einer Schleife über alle Zeilen der Tabelle eine Zelle jeder Zeile nach Index und legen einen Datentyp für sie fest. Dies hat zur Folge, dass in den Zellen der gesamten Spalte ein identischer Wert gesetzt wird.

Eine Methode, die die Genauigkeit der Spaltendaten festlegt

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

Holen wir in einer Schleife über alle Zeilen der Tabelle eine Zelle jeder Zeile nach Index und legen einen Datentyp für sie fest. Dadurch wird in den Zellen der gesamten Spalte derselbe Wert eingestellt.

Neue Methoden in der Klasse Table Model ermöglichen die Arbeit mit einer Reihe von Zellen als ganze Tabellenspalte. Spalten einer Tabelle können nur gesteuert werden, wenn die Tabelle eine Kopfzeile hat. Die Tabelle wird ohne die Kopfzeile statisch sein.


Header-Klasse der Tabelle

Der Tabellenkopf ist eine Liste von Spaltenkopfobjekten mit einem String-Wert. Sie befinden sich in einer dynamischen Liste CListObj. Und die dynamische Liste bildet die Grundlage für die Tabellenkopfklasse.

Auf dieser Grundlage müssen zwei Klassen geschaffen werden:

  1. Die Klasse des Kopfobjekts der Tabellenspalte.
    Sie enthält den Textwert der Überschrift, die Spaltennummer, den Datentyp für die gesamte Spalte und die Methoden zur Steuerung der Spaltenzellen.
  2. Die Tabellenkopfklasse.
    Sie enthält eine Liste von Spaltentitelobjekten und Zugriffsmethoden zur Steuerung von Tabellenspalten.

Fahren wir mit dem Schreiben des Codes in derselben Datei \MQL5\Scripts\Tables\Tables.mqh fort und schreiben die Klasse der Tabellenspaltenüberschrift:

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

Dies ist eine stark vereinfachte Version der Klasse der Tabellenzellen. Betrachten wir einige Methoden der Klasse.

Ein virtuelles Verfahren zum Vergleich zweier Objekte

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

Der Vergleich wird anhand des Index der Spalte durchgeführt, für die die Überschrift erstellt wurde.

Ein Verfahren zum Speichern in einer Datei

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

Das Verfahren zum Herunterladen aus einer Datei

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

Ähnliche Methoden wurden in dem vorangegangenen Artikel ausführlich erörtert. Die Logik ist hier genau dieselbe: Zunächst werden die Datenanfangsmarkierungen und der Objekttyp erfasst. Und dann alle seine Eigenschaften, je nach Element. Die Lesung findet in der gleichen Reihenfolge statt.

Eine Methode, die die Beschreibung eines Objekts zurückgibt

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

Es wird eine Beschreibungszeichenfolge erstellt und im Format (Objekttyp: Spalte XX, Wert „Value“)

Methode zur Ausgabe der Objektbeschreibung in das Protokoll

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

Es wird lediglich die Kopfzeilenbeschreibung im Protokoll ausgedruckt.

Nun sollten diese Objekte in die Liste aufgenommen werden, die den Tabellenkopf bildet. Schreiben wir die Tabellenkopfklasse:

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

Betrachten wir die Methoden der Klasse.

Eine Methode, die eine neue Überschrift erstellt und diese am Ende der Liste der Spaltenüberschriften anfügt

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

Der Kopfzeilentext wird an die Methode übergeben. Es wird ein neues Spaltentitelobjekt mit dem angegebenen Text und einem Index erstellt, der der Anzahl der Titel in der Liste entspricht. Dies ist der Index der letzten Kopfzeile. Anschließend wird das erstellte Objekt an das Ende der Liste der Spaltenüberschriften gesetzt, und es wird ein Zeiger auf die erstellte Überschrift zurückgegeben.

Eine Methode, die die angegebene Überschrift zum Listenende hinzufügt

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

Der Methode wird ein Zeiger auf das Spaltentitelobjekt übergeben. Sie muss am Ende der Kopfliste stehen. Die Methode gibt das Ergebnis des Hinzufügens eines Objekts zur Liste zurück.

Eine Methode, die einen Tabellenkopf aus einem String-Array erzeugt

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

Der Methode wird ein Text-Array mit Kopfzeilen übergeben. Die Größe des Arrays bestimmt die Anzahl der zu erstellenden Spaltenüberschriftenobjekte, die beim Durchlaufen der Werte der Überschriftentexte im Array in einer Schleife erstellt werden.

Eine Methode, die einen Wert auf die angegebene Überschrift einer Spalte setzt

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

Mit dieser Methode können wir einen neuen Textwert festlegen, der durch den Kopfzeilenindex bestimmt wird.

Eine Methode, die einen Datentyp für die angegebene Spaltenüberschrift

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

Mit dieser Methode kann ein neuer Wert für die in der durch den Kopfzeilenindex angegebenen Spalte gespeicherten Daten festgelegt werden. Für jede Spalte der Tabelle können wir die Art der in den Spaltenzellen gespeicherten Daten festlegen. Das Festlegen eines Datentyps für das Header-Objekt ermöglicht es, später denselben Wert für die gesamte Spalte festzulegen. Und wir können den Datenwert der gesamten Spalte lesen, indem wir diesen Wert aus der Kopfzeile dieser Spalte lesen.

Eine Methode, die einen Datentyp der angegebenen Spaltenüberschrift zurückgibt

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

Mit dieser Methode kann ein Wert der in der Spalte gespeicherten Daten über den Kopfzeilenindex abgerufen werden. Die Abfrage des Wertes in der Kopfzeile ermöglicht es, die Art der in allen Zellen dieser Tabellenspalte gespeicherten Werte herauszufinden.

Eine Methode, die die Überschrift der angegebenen Spalte löscht.

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

Das Objekt mit dem angegebenen Index wird aus der Liste der Überschriften gelöscht. Nach erfolgreicher Löschung des Spaltentitelobjekts müssen die Indizes der übrigen Objekte in der Liste aktualisiert werden.

Eine Methode, die eine Spaltenüberschrift an die angegebene Position verschiebt

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

Sie ermöglicht es, die Kopfzeile vom angegebenen Index an eine neue Position in der Liste zu verschieben.

Eine Methode, die Spaltenpositionen für alle Überschriften festlegt

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

Nach dem Löschen oder Verschieben eines Objekts in der Liste an eine andere Stelle müssen die Indizes allen anderen Objekten in der Liste neu zugewiesen werden, damit ihre Indizes der tatsächlichen Position in der Liste entsprechen. Die Methode durchläuft alle Objekte in der Liste in einer Schleife, ermittelt den tatsächlichen Index jedes Objekts und legt ihn als Eigenschaft des Objekts fest.

Eine Methode, die die Daten der Spaltenüberschriften in der Liste löscht

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

Gehen wir in einer Schleife durch alle Spaltenüberschriften in der Liste, holen jedes reguläre Objekt und setzen den Überschriftentext auf einen leeren Wert. Dadurch werden die Kopfzeilen jeder Spalte der Tabelle vollständig gelöscht.

Eine Methode, die die Beschreibung eines Objekts zurückgibt

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

Es wird eine Zeichenkette erstellt und im Format (Object Type: Captions total: XX)

Methode zur Ausgabe der Objektbeschreibung in das Protokoll

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

Die Methode kann Überschriftenbeschreibungen in tabellarischer Form und als Liste von Spaltenüberschriften protokollieren.

Methoden zum Speichern in einer Datei und zum Herunterladen einer Kopfzeile aus einer Datei

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

Die Logik der Methoden ist im Code auskommentiert und unterscheidet sich in keiner Weise von ähnlichen Methoden anderer bereits erstellter Klassen zur Erstellung von Tabellen.

Wir haben alles vorbereitet, um mit dem Zusammenbau der Tabellenklassen zu beginnen. Die Table-Klasse sollte in der Lage sein, eine Tabelle auf der Grundlage ihres Modells zu erstellen, und sie sollte eine Überschrift haben, nach der die Tabellenspalten benannt werden. Wenn wir in der Tabelle keinen Tabellenkopf angeben, wird sie nur entsprechend dem Modell erstellt, sie ist statisch und ihre Funktionen werden nur durch die Ansicht der Tabelle eingeschränkt. Für einfache Tabellen ist das völlig ausreichend. Um jedoch mit Hilfe der Controller-Komponente mit dem Nutzer interagieren zu können, muss der Tabellenkopf in der Tabelle festgelegt werden. Dies bietet eine breite Palette von Möglichkeiten zur Steuerung von Tabellen und deren Daten. Aber das werden wir alles später machen. Betrachten wir nun die Tabellenklassen.


Tabelle Klassen

Schreiben wir den Code in derselben Datei weiter und implementieren die Tabellenklasse:

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

Zeiger auf den Kopf und die Modelltabelle werden in der Klasse deklariert. Um eine Tabelle zu erstellen, müssen wir zunächst ein Tabellenmodell aus den Daten erstellen, die den Klassenkonstruktoren übergeben werden. Die Tabelle kann automatisch eine Kopfzeile mit Spaltennamen im Stil von MS Excel erzeugen, wobei jeder Spalte ein aus lateinischen Buchstaben bestehender Name zugewiesen wird.

Der Algorithmus zur Berechnung der Namen lautet wie folgt:

  1. Namen mit einem Buchstaben – die ersten 26 Spalten sind mit Buchstaben von „A“ bis „Z“ gekennzeichnet.

  2. Zweibuchstabige Namen – nach dem „Z“ werden die Spalten durch eine Kombination von zwei Buchstaben bezeichnet. Der erste Buchstabe ändert sich langsamer, und der zweite wiederholt das gesamte Alphabet. Zum Beispiel:

    • "AA", "AB", "AC", ..., "AZ",
    • dann "BA", "BB", ..., "BZ",
    • usw.
  3. Namen mit drei Buchstaben – nach „ZZ“ werden die Spalten durch eine Kombination von drei Buchstaben bezeichnet. Das Prinzip ist das gleiche:

    • "AAA", "AAB", ..., "AAZ",
    • dann "ABA", "ABB", ..., "ABZ",
    • usw.
  4. Das allgemeine Prinzip ist, dass Spaltennamen als Zahlen im Zahlensystem zur Basis 26 betrachtet werden können, wobei „A“ der Zahl 1 entspricht, „B“ der Zahl 2, ..., „Z“ – 26. Zum Beispiel:

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

Auf diese Weise erzeugt der Algorithmus automatisch Spaltennamen und erhöht sie entsprechend dem betrachteten Prinzip. Die maximale Anzahl von Spalten in Excel hängt von der Programmversion ab (in Excel 2007 und späteren Versionen sind es beispielsweise 16.384, die mit „XFD“ enden). Der hier entwickelte Algorithmus ist nicht auf diese Abbildung beschränkt. Sie kann der Anzahl der Spalten, die INT_MAX entspricht, Namen geben:

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

Die Methoden ermöglichen die Eingabe eines Arrays von Spaltennamen im Excel-Stil.

Betrachten wir die parametrischen Konstruktoren einer Klasse.

Ein Template-Konstruktor, der ein zweidimensionales Array von Daten und ein String-Array von Headern angibt

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

Dem Template-Konstruktor wird ein Array mit Daten beliebigen Typs aus der Enumeration ENUM_DATATYPE übergeben. Anschließend werden sie in den von Tabellen verwendeten Datentyp (double, long, datetime, color, string) konvertiert, um ein Tabellenmodell und ein Array von Spaltenüberschriften zu erstellen. Wenn das Kopfzeilenfeld leer ist, werden Kopfzeilen im Stil von MS Excel erstellt.

Konstruktor mit Angabe der Anzahl der Zeilen und Spalten der Tabelle

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

Der Konstruktor erstellt ein leeres Tabellenmodell mit einer Kopfzeile im Stil von MS Excel.

Der Konstruktor basiert auf einer Datenmatrix und einem Array mit Spaltenüberschriften

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

Dem Konstruktor wird eine Datenmatrix vom Typ double übergeben, um ein Tabellenmodell und ein Array mit Spaltenüberschriften zu erstellen. Wenn das Kopfzeilenfeld leer ist, werden Kopfzeilen im Stil von MS Excel erstellt.

Im Destruktor der Klasse werden das Modell und der Tabellenkopf zerstört.

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

Verfahren zum Vergleich zweier Objekte

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

Jeder Tabelle kann ein Bezeichner zugewiesen werden, wenn das Programm mehrere Tabellen erstellen soll. Tabellen im Programm können durch den Set-Identifikator identifiziert werden, der standardmäßig den Wert -1 hat. Wenn die erstellten Tabellen in Listen (CList, CArrayObj, etc.) platziert werden, dann erlaubt die Vergleichsmethode den Vergleich von Tabellen anhand ihrer Bezeichner, um sie zu suchen und zu sortieren:

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

Eine Methode, die ein Array von Kopfzeilennamen kopiert

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

Der Methode wird ein Array von Überschriften und die Anzahl der Spalten im erstellten Tabellenmodell übergeben. Wenn die Tabelle keine Spalten enthält, müssen auch keine Kopfzeilen erstellt werden. Melden wir dies und geben false zurück. Wenn es mehr Spalten im Tabellenmodell als Kopfzeilen im übergebenen Array gibt, dann werden alle Kopfzeilen im Excel-Stil erstellt, sodass die Tabelle keine Spalten ohne Beschriftungen in den Kopfzeilen hat.

Eine Methode, die einen Wert auf die angegebene Zelle setzt

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

Hier beziehen wir uns auf die gleichnamige Methode für das Tabellenmodellobjekt.

Im Wesentlichen werden in dieser Klasse viele Methoden aus der Klasse Table Model dupliziert. Wenn das Modell erstellt wird, wird seine ähnliche Methode zum Abrufen oder Setzen der Eigenschaft aufgerufen.

Verfahren zum Betrieb von Tabellenzellen

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

Methoden für die Arbeit mit Tabellenzeilen

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

Eine Methode, die eine neue Spalte erstellt und sie der angegebenen Tabellenposition hinzufügt

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

Wenn kein Tabellenmodell vorhanden ist, gibt die Methode sofort einen Fehler zurück. Wenn die Spalte erfolgreich zum Tabellenmodell hinzugefügt wurde, versuchen Sie, die entsprechende Überschrift hinzuzufügen. Wenn die Tabelle keine Überschrift hat, wird der Erfolg der Erstellung einer neuen Spalte zurückgegeben. Wenn eine Überschrift vorhanden ist, fügen wir eine neue Spaltenüberschrift hinzu und verschieben sie an die angegebene Position in der Liste.

Weitere Methoden für die Arbeit mit Spalten

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

Eine Methode, die eine Objektbeschreibung zurückgibt

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

Erzeugt und liefert die Zeichenkette im Format (Object Type: Rows total: XX, Columns total: XX)

Methode zur Ausgabe der Objektbeschreibung in das Protokoll

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

Die Methode gibt eine Beschreibung in das Protokoll aus, und unten ist eine Tabelle mit der Kopfzeile und den Daten.

Eine Methode zum Speichern der Tabelle in einer Datei

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

Das Speichern ist nur dann erfolgreich, wenn sowohl das Tabellenmodell als auch sein Kopf erstellt wurden. Die Kopfzeile kann leer sein, d. h. sie kann keine Spalten enthalten, aber das Objekt muss erstellt werden.

Methode zum Hochladen der Tabelle aus einer Datei

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

Da die Tabelle sowohl Modell- als auch Kopfdaten speichert, werden sie bei dieser Methode (wenn das Modell oder die Kopfdaten nicht in der Tabelle erstellt werden) vorab erstellt, und danach werden ihre Daten aus der Datei geladen.

Die einfache Tabellenklasse ist fertig.

Betrachten wir nun die Möglichkeit, von einer einfachen Tabellenklasse zu erben – erstellen wir eine Tabellenklasse, die auf den in der CList gespeicherten Daten aufbaut:

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

Hier wird der Tabellentyp als OBJECT_TYPE_TABLE_BY_PARAM zurückgegeben, und das Tabellenmodell und die Kopfzeile werden im Klassenkonstruktor erstellt:

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

Ausgehend von diesem Beispiel kann man einige andere Klassen von Tabellen erstellen, aber wir gehen davon aus, dass alles, was heute erstellt wird, ausreicht, um eine große Vielfalt von Tabellen und einen umfangreichen Satz von möglichen Daten zu erstellen.

Testen wir alles, was wir haben.


Testen des Ergebnisses

Erstellen wir im Ordner \MQL5\Scripts\Tables\ ein neues Skript namens TestEmptyTable.mq5, verbinden die erstellte Tabellenklassendatei damit und erstellen eine leere 4x4-Tabelle:

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

Das Skript erzeugt die folgende Ausgabe im Protokoll:

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

Hier werden die Spaltenüberschriften automatisch im MS Excel-Stil erstellt.

Schreiben wir ein weiteres Skript \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;
  }

Als Ergebnis der Skriptoperation wird die folgende Tabelle im Protokoll angezeigt:

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 |

Hier sind die Spalten bereits aus dem Header-Array betitelt, das an den Klassenkonstruktor übergeben wird. Ein zweidimensionales Array, das die Daten für die Erstellung einer Tabelle darstellt, kann einen beliebigen Typ aus der Enumeration ENUM_DATATYPE haben.

Alle Typen werden automatisch in die fünf Typen konvertiert, die in der Tabellenmodellklasse verwendet werden: double, long, datetime, color und string.

Schreiben wir das Skript \MQL5\Scripts\Tables\TestMatrixTable.mq5, um die Tabelle mit Matrixdaten zu testen:

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

Das Ergebnis ist eine Tabelle, die derjenigen ähnelt, die auf der Grundlage eines zweidimensionalen 4x4-Arrays erstellt wurde:

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 |

Schreiben wir nun das Skript \MQL5\Scripts\Tables\TestDealsTable.mq5, in dem wir alle historischen Abschlüsse zählen, eine darauf basierende Abschlusstabelle erstellen und diese im Protokoll ausgeben:

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

Als Ergebnis wird eine Tabelle aller Handelsgeschäfte mit einer Zellenbreite von 19 Zeichen ausgedruckt (standardmäßig in der Methode Print der Klasse table):

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

Das Beispiel hier zeigt die ersten und letzten vier Abschlüsse, aber es gibt einen Eindruck von der Tabelle, die im Protokoll ausgedruckt wird.

Alle erstellten Dateien sind dem Artikel zum Selbststudium beigefügt. Die Archivdatei kann in den Terminal-Ordner entpackt werden, und alle Dateien befinden sich dann im gewünschten Ordner: MQL5\Scripts\Tables.


Schlussfolgerung

Wir haben also die Arbeit an den grundlegenden Komponenten des Tabellenmodells (Model) im Rahmen der MVC-Architektur abgeschlossen. Wir haben Klassen für die Arbeit mit Tabellen und Kopfzeilen erstellt und sie mit verschiedenen Datentypen getestet: zweidimensionale Arrays, Matrizen und Handelsverläufe.

Wir gehen nun zur nächsten Phase über – der Entwicklung der View- und Controller-Komponenten. In MQL5 sind diese beiden Komponenten aufgrund des eingebauten Ereignissystems, das es Objekten ermöglicht, auf Nutzeraktionen zu reagieren, eng miteinander verbunden.

Dies gibt uns die Möglichkeit, die Visualisierung der Tabelle (View-Komponente) und ihre Steuerung (Controller-Komponente) gleichzeitig zu entwickeln. Dadurch wird die recht komplexe und mehrstufige Implementierung der Komponente Ansicht etwas vereinfacht.

Alle Beispiele und Dateien aus dem Artikel stehen zum Download bereit. In zukünftigen Artikeln werden wir eine View-Komponente in Kombination mit einem Controller erstellen, um ein vollwertiges Werkzeug zur Bedienung von Tabellen in MQL5 zu implementieren.

Nach Abschluss des Projekts eröffnen sich neue Möglichkeiten für die Erstellung weiterer UI-Steuerelemente zur Verwendung in unseren Entwicklungen.


Die Programme dieses Artikels:

#
Name
Typ
Beschreibung
 1  Tables.mqh Klassenbibliothek Klassenbibliothek für die Tabellenerstellung
 2  TestEmptyTable.mq5 Skript Ein Skript zum Testen der Erstellung einer leeren Tabelle mit einer bestimmten Anzahl von Zeilen und Spalten
 3  TestTArrayTable.mq5 Skript Ein Skript zum Testen der Erstellung einer Tabelle auf der Grundlage eines zweidimensionalen Datenarrays
 4  TestMatrixTable.mq5 Skript Ein Skript zum Testen der Erstellung einer Tabelle auf der Grundlage einer Datenmatrix
 5  TestDealsTable.mq5 Skript Ein Skript zum Testen der Erstellung einer Tabelle auf der Grundlage von Nutzerdaten (historische Abschlüsse)
 6  MQL5.zip Archive Ein Archiv mit den oben genannten Dateien zum Entpacken in das MQL5-Verzeichnis des Client-Terminals

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/17803

Beigefügte Dateien |
Tables.mqh (261.24 KB)
TestEmptyTable.mq5 (3.22 KB)
TestDealsTable.mq5 (24.63 KB)
MQL5.zip (30.86 KB)
Vom Neuling zum Experten: Automatisierung der Handelsdisziplin mit einem MQL5 Risk Enforcement EA Vom Neuling zum Experten: Automatisierung der Handelsdisziplin mit einem MQL5 Risk Enforcement EA
Für viele Händler ist die Lücke zwischen der Kenntnis einer Risikoregel und deren konsequenter Befolgung der Punkt, an dem die Konten sterben. Emotionale Übertreibungen, Kompensationshandel und einfaches Versehen können selbst die beste Strategie zunichte machen. Heute werden wir die MetaTrader 5-Plattform in einen unnachgiebigen Vollstrecker Ihrer Handelsregeln verwandeln, indem wir einen Risk Enforcement Expert Advisor entwickeln. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.
Vom Neuling zum Experten: Handel mit dem RSI unter Berücksichtigung der Struktur des Marktes Vom Neuling zum Experten: Handel mit dem RSI unter Berücksichtigung der Struktur des Marktes
In diesem Artikel werden wir praktische Techniken für den Handel mit dem Relative Strength Index (RSI) Oszillator mit Marktstruktur untersuchen. Wir werden uns auf Kanal-Preisaktionsmuster konzentrieren, wie sie typischerweise gehandelt werden und wie MQL5 zur Verbesserung dieses Prozesses eingesetzt werden kann. Am Ende werden Sie über ein regelbasiertes, automatisiertes Channel-Trading-System verfügen, mit dem Sie Gelegenheiten zur Trendfortsetzung mit größerer Präzision und Beständigkeit nutzen können.
Die View Komponente für Tabellen im MQL5 MVC Paradigma: Grafisches Basiselement Die View Komponente für Tabellen im MQL5 MVC Paradigma: Grafisches Basiselement
Der Artikel behandelt den Prozess der Entwicklung eines grafischen Basiselements für die View-Komponente als Teil der Implementierung von Tabellen im MVC-Paradigma (Model-View-Controller) in MQL5. Dies ist der erste Artikel über die Komponente View und der dritte in einer Reihe von Artikeln über die Erstellung von Tabellen für das MetaTrader 5 Client Terminal.
Implementierung eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts Implementierung eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts
In diesem Artikel betrachten wir den Prozess der Entwicklung eines Tabellenmodells in MQL5 unter Verwendung des MVC-Architekturmusters (Model-View-Controller) zur Trennung der Logik, Darstellung und Steuerung der Daten, was strukturierten, flexiblen und skalierbaren Code ermöglicht. Wir betrachten die Implementierung von Klassen zum Aufbau eines Tabellenmodells, einschließlich der Verwendung von verknüpften Listen zur Speicherung von Daten.