Tabellen- und Kopfzeilen-Klassen auf der Grundlage eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts
Inhalt
- Einführung
- Verfeinerung des Tabellenmodells
- Header-Klasse der Tabelle
- Klasse der Tabelle
- Testen des Ergebnisses
- Schlussfolgerung
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:
- integer_value – für ganzzahlige Daten,
- double_value – für reelle Zahlen,
- 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 ¶m) { this.m_param.type=param.type; this.m_param.double_value=param.double_value; this.m_param.integer_value=param.integer_value; this.m_param.string_value=param.string_value; } //--- Return the parameters MqlParam Param(void) const { return this.m_param; } ENUM_DATATYPE Datatype(void) const { return this.m_param.type; } double ValueD(void) const { return this.m_param.double_value; } long ValueL(void) const { return this.m_param.integer_value;} string ValueS(void) const { return this.m_param.string_value; } //--- Object description virtual string Description(void) { string t=::StringSubstr(::EnumToString(this.m_param.type),5); t.Lower(); string v=""; switch(this.m_param.type) { case TYPE_STRING : v=this.ValueS(); break; case TYPE_FLOAT : case TYPE_DOUBLE : v=::DoubleToString(this.ValueD()); break; case TYPE_DATETIME: v=::TimeToString(this.ValueL(),TIME_DATE|TIME_MINUTES|TIME_SECONDS); break; default : v=(string)this.ValueL(); break; } return(::StringFormat("<%s>%s",t,v)); } //--- Constructors/destructor CMqlParamObj(void){} CMqlParamObj(const MqlParam ¶m) { this.Set(param); } ~CMqlParamObj(void){} };
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:
- Die Hauptliste CList speichert Zeilen einer Tabelle. Jede Zeile ist eine eigene CList-Liste.
- 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.
- 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:
- Erstellen einer neuen Zeile (der Liste CList) und Hinzufügen zur Hauptliste,
- 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 ¶m) { 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:
- Wir geben die erforderliche Eigenschaft an (z. B. den Handelspreis).
- Die Klasse erstellt automatisch ein CMqlParamObj-Objekt, schreibt den Eigenschaftswert in dieses Objekt und fügt es der Zeile hinzu.
- 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:
- 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. - 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:
-
Namen mit einem Buchstaben – die ersten 26 Spalten sind mit Buchstaben von „A“ bis „Z“ gekennzeichnet.
-
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.
-
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.
-
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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Vom Neuling zum Experten: Automatisierung der Handelsdisziplin mit einem MQL5 Risk Enforcement EA
Vom Neuling zum Experten: Handel mit dem RSI unter Berücksichtigung der Struktur des Marktes
Die View Komponente für Tabellen im MQL5 MVC Paradigma: Grafisches Basiselement
Implementierung eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.