Implementierung eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts
Inhalt
- Einführung
- Ein wenig über das MVC-Konzept (Model-View-Controller)
- Schreiben von Klassen zum Aufbau eines Tabellenmodells
- Verknüpfte Listen als Grundlage für die Speicherung Tabellarischer Daten
- Klasse der Zellen der Tabelle
- Klasse der Zeilen der Tabelle
- Klasse des Modells der Tabelle
- Prüfung des Ergebnisses
- Schlussfolgerung
Einführung
Bei der Programmierung spielt die Anwendungsarchitektur eine Schlüsselrolle bei der Gewährleistung von Zuverlässigkeit, Skalierbarkeit und einfacher Unterstützung. Einer der Ansätze, der hilft, solche Ziele zu erreichen, ist die Nutzung eines Architekturmusters namens MVC (Model-View-Controller).
Das MVC-Konzept ermöglicht es, eine Anwendung in drei miteinander verbundene Komponenten zu unterteilen: model (Daten- und Logikverwaltung), View (Datenanzeige) und controller (Verarbeitung von Nutzeraktionen). Diese Trennung vereinfacht die Entwicklung, das Testen und die Wartung des Codes und macht ihn strukturierter und flexibler.
In diesem Artikel betrachten wir, wie man MVC-Prinzipien anwendet, um ein Tabellenmodell in der Sprache MQL5 zu implementieren. Tabellen sind ein wichtiges Hilfsmittel zum Speichern, Verarbeiten und Anzeigen von Daten, und ihre richtige Organisation kann die Arbeit mit Informationen erheblich erleichtern. Wir werden Klassen für die Arbeit mit Tabellen erstellen: Tabellenzellen, Zeilen und Tabellenmodell. Um Zellen innerhalb von Zeilen und Zeilen innerhalb des Tabellenmodells zu speichern, werden wir die Klassen der verknüpften Listen aus der MQL5-Standardbibliothek verwenden, die eine effiziente Speicherung und Verwendung von Daten ermöglichen.
Ein wenig über das MVC-Konzept: Was ist es und warum wollen wir es?
Stellen Sie sich die Bewerbung wie eine Theateraufführung vor. Es gibt ein Szenario, das beschreibt, was passieren sollte (dies ist das Modell). Da ist die Bühne – das, was der Betrachter sieht (das ist die View). Und schließlich gibt es den Regisseur, der den gesamten Prozess steuert und andere Elemente miteinander verbindet (dies ist der Controller). Auf diese Weise funktioniert das Architekturmuster MVC – Model-View-Controller.
Dieses Konzept hilft, die Verantwortlichkeiten innerhalb der Anwendung zu trennen. Das Modell ist für die Daten und die Logik zuständig, die Ansicht für die Anzeige und das Erscheinungsbild, und der Controller ist für die Verarbeitung der Nutzeraktionen verantwortlich. Eine solche Trennung macht den Code übersichtlicher, flexibler und bequemer für die Teamarbeit.
Angenommen, Sie erstellen eine Tabelle. Das Modell weiß, welche Zeilen und Zellen es enthält, und weiß, wie es diese ändern kann. Die Ansicht zeichnet eine Tabelle auf dem Bildschirm. Und der Controller reagiert, wenn der Nutzer auf „Zeile hinzufügen“ klickt, und übergibt die Aufgabe an das Modell, um dann die Ansicht zu aktualisieren.
MVC ist besonders nützlich, wenn die Anwendung komplexer wird: neue Funktionen kommen hinzu, die Schnittstelle ändert sich, und mehrere Entwickler arbeiten daran. Mit einer klaren Architektur ist es einfacher, Änderungen vorzunehmen, Komponenten einzeln zu testen und den Code wiederzuverwenden.
Dieser Ansatz hat auch einige Nachteile. Bei sehr einfachen Projekten kann MVC überflüssig sein – man muss sogar das trennen, was in ein paar Funktionen passen könnte. Für skalierbare, ernsthafte Anwendungen macht sich diese Struktur jedoch schnell bezahlt.
Zusammengefasst:
MVC ist eine leistungsstarke architektonische Vorlage, die dabei hilft, den Code zu organisieren und ihn verständlicher, testbarer und skalierbarer zu machen. Sie ist besonders nützlich für komplexe Anwendungen, bei denen eine Trennung von Datenlogik, Nutzeroberfläche und Verwaltung wichtig ist. Bei kleinen Projekten ist seine Verwendung überflüssig.
Das Model-View-Controller-Paradigma passt sehr gut zu unserer Aufgabe. Die Tabelle wird aus unabhängigen Objekten erstellt.:
- Tabellenzelle.
Ein Objekt, das einen Wert eines der Typen – Real, Integer oder String – speichert, ist mit Werkzeugen ausgestattet, um den Wert zu verwalten, zu setzen und abzurufen; - Tabellenzeile.
Ein Objekt, das eine Liste von Objekten in Tabellenzellen speichert, ist mit Werkzeugen zur Verwaltung von Zellen, ihrer Position, zum Hinzufügen und Löschen ausgestattet; - Ein Tabellenmodell.
Ein Objekt, das eine Liste von Tabellen-String-Objekten speichert, ist mit Werkzeugen zum Verwalten von Tabellen-Strings und Spalten, ihrer Position, zum Hinzufügen und Löschen ausgestattet und hat außerdem Zugriff auf String- und Zellsteuerungen.
Die folgende Abbildung zeigt schematisch den Aufbau eines 4x4-Tabellenmodells:

Abb.1 4x4 Tabellenmodell
Nun wollen wir von der Theorie zur Praxis übergehen.
Schreiben von Klassen zum Aufbau eines Tabellenmodells
Wir werden die MQL5-Standardbibliothek verwenden, um alle Objekte zu erstellen.
Jedes Objekt wird von der Basisklasse der Bibliothek abgeleitet. Dies ermöglicht es Ihnen, diese Objekte in Objektlisten zu speichern.
Wir werden alle Klassen in eine einzige Testskriptdatei schreiben, sodass alles in einer Datei steht, sichtbar und schnell zugänglich ist. In Zukunft werden wir die geschriebenen Klassen in separate Include-Dateien aufteilen.
1. Verknüpfte Listen als Grundlage für die Speicherung tabellarischer Daten
Die verknüpfte Liste CList ist sehr gut für die Speicherung von Tabellendaten geeignet. Anders als das ähnliche Listen-Objekt CArrayObj implementiert es Zugriffsmethoden auf benachbarte Listenobjekte, die sich links und rechts vom aktuellen befinden. Dies erleichtert das Verschieben von Zellen in einer Zeile oder von Zeilen in einer Tabelle sowie das Hinzufügen und Löschen von Zellen. Gleichzeitig kümmert sich die Liste selbst um die korrekte Indizierung von verschobenen, hinzugefügten oder gelöschten Objekten in der Liste.
Allerdings gibt es hier eine Nuance. Wenn Sie die Methoden zum Laden und Speichern einer Liste in eine Datei betrachten, können Sie sehen, dass die Listenklasse beim Laden aus einer Datei ein neues Objekt in der virtuellen Methode CreateElement() erstellen muss.
Diese Methode in dieser Klasse gibt einfach NULL zurück:
//--- method of creating an element of the list virtual CObject *CreateElement(void) { return(NULL); }
Das bedeutet, dass wir, um mit verknüpften Listen zu arbeiten, und vorausgesetzt, dass wir Datei-Operationen benötigen, von der Klasse CList erben und diese Methode in unserer Klasse implementieren müssen.
Wenn Sie sich die Methoden zum Speichern von Objekten der Standardbibliothek in einer Datei ansehen, können Sie den folgenden Algorithmus zum Speichern von Objekteigenschaften erkennen:
- Die Datenanfangsmarkierung (-1) wird in die Datei geschrieben,
- Der Objekttyp wird in die Datei geschrieben,
- Alle Objekteigenschaften werden nacheinander in die Datei geschrieben.
Der erste und der zweite Punkt sind allen implementierten Speichern/Laden-Methoden inhärent, die Objekte der Standardbibliothek besitzen. Dementsprechend wollen wir nach der gleichen Logik den Typ des in der Liste gespeicherten Objekts kennen, damit wir beim Lesen aus einer Datei ein Objekt mit diesem Typ in der virtuellen Methode CreateElement() der von CList geerbten Listenklasse erstellen können.
Außerdem müssen alle Objekte, die in die Liste geladen werden können, deklariert oder erstellt werden, bevor die Listenklasse implementiert wird. In diesem Fall „weiß“ die Liste, welche Objekte „in Frage“ sind und welche erstellt werden müssen.
Erstellen Sie im Terminalverzeichnis \MQL5\Scripts\ einen neuen Ordner TableModel\, und darin eine neue Datei des Testskripts TableModelTest.mq5.
Verbinden wir die verknüpfte Listendatei und deklarieren die zukünftige Tabellenmodellklassen:
//+------------------------------------------------------------------+ //| TableModelTest.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include <Arrays\List.mqh> //--- Forward declaration of classes class CTableCell; // Table cell class class CTableRow; // Table row class class CTableModel; // Table model class
Die Vorwärtsdeklaration zukünftiger Klassen ist hier notwendig, damit die Klasse der verknüpften Liste, die von CList erbt, über diese Arten von Klassen Bescheid weiß und auch über die Arten von Objekten, die sie erstellen muss. Zu diesem Zweck werden wir eine Enumeration von Objekttypen, Hilfsmakros und eine Enumeration von Möglichkeiten zur Sortierung von Listen schreiben:
//+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include <Arrays\List.mqh> //--- Forward declaration of classes class CTableCell; // Table cell class class CTableRow; // Table row class class CTableModel; // Table model class //+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+ #define MARKER_START_DATA -1 // Data start marker in a file #define MAX_STRING_LENGTH 128 // Maximum length of a string in a cell //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_OBJECT_TYPE // Enumeration of object types { OBJECT_TYPE_TABLE_CELL=10000, // Table cell OBJECT_TYPE_TABLE_ROW, // Table row OBJECT_TYPE_TABLE_MODEL, // Table model }; enum ENUM_CELL_COMPARE_MODE // Table cell comparison modes { CELL_COMPARE_MODE_COL, // Comparison by column number CELL_COMPARE_MODE_ROW, // Comparison by string number CELL_COMPARE_MODE_ROW_COL, // Comparison by row and column }; //+------------------------------------------------------------------+ //| Functions | //+------------------------------------------------------------------+ //--- Return the object type as a string string TypeDescription(const ENUM_OBJECT_TYPE type) { string array[]; int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array); string result=""; for(int i=2;i<total;i++) { array[i]+=" "; array[i].Lower(); array[i].SetChar(0,ushort(array[i].GetChar(0)-0x20)); result+=array[i]; } result.TrimLeft(); result.TrimRight(); return result; } //+------------------------------------------------------------------+ //| Classes | //+------------------------------------------------------------------+
Die Funktion, die die Beschreibung des Objekttyps zurückgibt, basiert auf der Annahme, dass alle Namen der Objekttypkonstanten mit der Teilzeichenkette „OBJECT_TYPE_“ beginnen. Dann können Sie die darauf folgende Teilzeichenkette nehmen, alle Zeichen der resultierenden Zeile in Kleinbuchstaben umwandeln, das erste Zeichen in Großbuchstaben umwandeln und alle Leer- und Steuerzeichen aus der endgültigen Zeichenkette links und rechts entfernen.
Wir wollen unsere eigene Klasse für verknüpfte Listen schreiben. Wir werden den Code in derselben Datei weiterschreiben:
//+------------------------------------------------------------------+ //| Classes | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Linked object list class | //+------------------------------------------------------------------+ class CListObj : public CList { protected: ENUM_OBJECT_TYPE m_element_type; // Created object type in CreateElement() public: //--- Virtual method (1) for loading a list from a file, (2) for creating a list element virtual bool Load(const int file_handle); virtual CObject *CreateElement(void); };
Die CListObj-Klasse ist unsere neue verknüpfte Listenklasse, die von der Klasse CList der Standardbibliothek geerbt wurde.
Die einzige Variable in der Klasse wird diejenige sein, in die der Typ des zu erstellenden Objekts geschrieben wird. Da die Methode CreateElement() virtuell ist und genau dieselbe Signatur wie die Methode der übergeordneten Klasse haben muss, können wir den Typ des zu erstellenden Objekts nicht an sie übergeben. Aber wir können diesen Typ in eine deklarierte Variable schreiben und den Typ des zu erstellenden Objekts daraus lesen.
Wir müssen zwei virtuelle Methoden der übergeordneten Klasse umdefinieren: die Methode zum Hochladen aus einer Datei und die Methode zum Erstellen eines neuen Objekts. Betrachten wir sie.
Dier Methode zum Hochladen der Liste aus einer Datei:
//+------------------------------------------------------------------+ //| Load a list from the file | //+------------------------------------------------------------------+ bool CListObj::Load(const int file_handle) { //--- Variables CObject *node; bool result=true; //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the list start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load and check the list type if(::FileReadInteger(file_handle,INT_VALUE)!=Type()) return(false); //--- Read the list size (number of objects) uint num=::FileReadInteger(file_handle,INT_VALUE); //--- Sequentially recreate the list elements by calling the Load() method of node objects this.Clear(); for(uint i=0; i<num; i++) { //--- Read and check the object data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return false; //--- Read the object type this.m_element_type=(ENUM_OBJECT_TYPE)::FileReadInteger(file_handle,INT_VALUE); node=this.CreateElement(); if(node==NULL) return false; this.Add(node); //--- Now the file pointer is offset relative to the beginning of the object marker by 12 bytes (8 - marker, 4 - type) //--- Set the pointer to the beginning of the object data and load the object properties from the file using the Load() method of the node element. if(!::FileSeek(file_handle,-12,SEEK_CUR)) return false; result &=node.Load(file_handle); } //--- Result return result; }
Hier wird zunächst der Anfang der Liste kontrolliert, ihr Typ und ihre Größe, d.h. die Anzahl der Elemente in der Liste; und dann werden in einer Schleife nach der Anzahl der Elemente die Anfangsdatenmarkierungen jedes Objekts und sein Typ aus der Datei gelesen. Der resultierende Typ wird in die Variable m_element_type geschrieben und eine Methode zur Erstellung eines neuen Elements aufgerufen. Bei dieser Methode wird ein neues Element mit dem empfangenen Typ erstellt und in eine Knotenzeigervariable geschrieben, die ihrerseits der Liste hinzugefügt wird. Die gesamte Logik der Methode wird in den Kommentaren ausführlich erläutert. Betrachten wir eine Methode zur Erstellung eines neuen Listeneintrags.
Die Methode zur Erstellung eines Listeneintrags:
//+------------------------------------------------------------------+ //| List element creation method | //+------------------------------------------------------------------+ CObject *CListObj::CreateElement(void) { //--- Create a new object depending on the object type in m_element_type switch(this.m_element_type) { case OBJECT_TYPE_TABLE_CELL : return new CTableCell(); case OBJECT_TYPE_TABLE_ROW : return new CTableRow(); case OBJECT_TYPE_TABLE_MODEL : return new CTableModel(); default : return NULL; } }
Dies bedeutet, dass vor dem Aufruf der Methode der Typ des zu erstellenden Objekts bereits in die Variable m_element_type geschrieben wird. Je nach Elementtyp wird ein neues Objekt des entsprechenden Typs erstellt und ein Zeiger darauf zurückgegeben. In Zukunft werden bei der Entwicklung neuer Steuerelemente deren Typen in die Aufzählung ENUM_OBJECT_TYPE geschrieben. Außerdem werden hier neue Fälle hinzugefügt, um neue Arten von Objekten zu schaffen. Die Klasse der verketteten Liste, die auf dem Standard CList basiert, ist fertig. Jetzt kann sie alle Objekte bekannter Typen speichern, Listen in einer Datei speichern und aus der Datei hochladen und korrekt wiederherstellen.
2. Tabelle Zellenklasse
Eine Tabellenzelle ist das einfachste Element einer Tabelle, das einen bestimmten Wert speichert. Zellen bilden Listen, die Tabellenzeilen darstellen. Jede Liste steht für eine Tabellenzeile. In unserer Tabelle können die Zellen jeweils nur einen Wert verschiedener Typen speichern – einen Real-, Integer- oder String-Wert.
Zusätzlich zu einem einfachen Wert kann einer Zelle ein Objekt eines bekannten Typs aus der Aufzählung ENUM_OBJECT_TYPE zugewiesen werden. In diesem Fall kann die Zelle einen Wert eines beliebigen der aufgeführten Typen sowie einen Zeiger auf ein Objekt speichern, dessen Typ in eine spezielle Variable geschrieben wird. So kann in Zukunft die View-Komponente angewiesen werden, ein solches Objekt in einer Zelle anzuzeigen, um mit ihm über die Controller-Komponente zu interagieren.
Da mehrere verschiedene Arten von Werten in einer Zelle gespeichert werden können, werden wir Union verwenden, um sie zu schreiben, zu speichern und zurückzugeben. Die Union ist ein spezieller Datentyp, der mehrere Felder im selben Speicherbereich speichert. Eine Vereinigung ähnelt einer Struktur, aber anders als bei einer Struktur gehören hier verschiedene Begriffe der Vereinigung zum selben Speicherbereich. In der Struktur wird jedem Feld ein eigener Speicherbereich zugewiesen.
Fahren wir mit dem Schreiben des Codes in der bereits erstellten Datei fort. Beginnen wir mit dem Schreiben einer neuen Klasse. Im geschützten Bereich schreiben wir eine Union und deklarieren Variablen:
//+------------------------------------------------------------------+ //| Table cell class | //+------------------------------------------------------------------+ class CTableCell : public CObject { protected: //--- Combining for storing cell values (double, long, string) union DataType { protected: double double_value; long long_value; ushort ushort_value[MAX_STRING_LENGTH]; public: //--- Set values void SetValueD(const double value) { this.double_value=value; } void SetValueL(const long value) { this.long_value=value; } void SetValueS(const string value) { ::StringToShortArray(value,ushort_value); } //--- Return values double ValueD(void) const { return this.double_value; } long ValueL(void) const { return this.long_value; } string ValueS(void) const { string res=::ShortArrayToString(this.ushort_value); res.TrimLeft(); res.TrimRight(); return res; } }; //--- Variables DataType m_datatype_value; // Value ENUM_DATATYPE m_datatype; // Data type CObject *m_object; // Cell object ENUM_OBJECT_TYPE m_object_type; // Object type in the cell int m_row; // Row index int m_col; // Column index int m_digits; // Data representation accuracy uint m_time_flags; // Date/time display flags bool m_color_flag; // Color name display flag bool m_editable; // Editable cell flag public:
Im öffentlichen Bereich schreiben wir die Zugriffsmethoden auf geschützte Variablen, virtuelle Methoden und Klassenkonstruktoren für verschiedene Arten von Daten, die in einer Zelle gespeichert sind:
public: //--- Return cell coordinates and properties uint Row(void) const { return this.m_row; } uint Col(void) const { return this.m_col; } ENUM_DATATYPE Datatype(void) const { return this.m_datatype; } int Digits(void) const { return this.m_digits; } uint DatetimeFlags(void) const { return this.m_time_flags; } bool ColorNameFlag(void) const { return this.m_color_flag; } bool IsEditable(void) const { return this.m_editable; } //--- Return (1) double, (2) long and (3) string value double ValueD(void) const { return this.m_datatype_value.ValueD(); } long ValueL(void) const { return this.m_datatype_value.ValueL(); } string ValueS(void) const { return this.m_datatype_value.ValueS(); } //--- Return the value as a formatted string string Value(void) const { switch(this.m_datatype) { case TYPE_DOUBLE : return(::DoubleToString(this.ValueD(),this.Digits())); case TYPE_LONG : return(::IntegerToString(this.ValueL())); case TYPE_DATETIME: return(::TimeToString(this.ValueL(),this.m_time_flags)); case TYPE_COLOR : return(::ColorToString((color)this.ValueL(),this.m_color_flag)); default : return this.ValueS(); } } string DatatypeDescription(void) const { string type=::StringSubstr(::EnumToString(this.m_datatype),5); type.Lower(); return type; } //--- Set variable values void SetRow(const uint row) { this.m_row=(int)row; } void SetCol(const uint col) { this.m_col=(int)col; } void SetDatatype(const ENUM_DATATYPE datatype) { this.m_datatype=datatype; } void SetDigits(const int digits) { this.m_digits=digits; } void SetDatetimeFlags(const uint flags) { this.m_time_flags=flags; } void SetColorNameFlag(const bool flag) { this.m_color_flag=flag; } void SetEditable(const bool flag) { this.m_editable=flag; } void SetPositionInTable(const uint row,const uint col) { this.SetRow(row); this.SetCol(col); } //--- Assign an object to a cell void AssignObject(CObject *object) { if(object==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return; } this.m_object=object; this.m_object_type=(ENUM_OBJECT_TYPE)object.Type(); } //--- Remove the object assignment void UnassignObject(void) { this.m_object=NULL; this.m_object_type=-1; } //--- Set double value void SetValue(const double value) { this.m_datatype=TYPE_DOUBLE; if(this.m_editable) this.m_datatype_value.SetValueD(value); } //--- Set long value void SetValue(const long value) { this.m_datatype=TYPE_LONG; if(this.m_editable) this.m_datatype_value.SetValueL(value); } //--- Set datetime value void SetValue(const datetime value) { this.m_datatype=TYPE_DATETIME; if(this.m_editable) this.m_datatype_value.SetValueL(value); } //--- Set color value void SetValue(const color value) { this.m_datatype=TYPE_COLOR; if(this.m_editable) this.m_datatype_value.SetValueL(value); } //--- Set string value void SetValue(const string value) { this.m_datatype=TYPE_STRING; if(this.m_editable) this.m_datatype_value.SetValueS(value); } //--- Clear data void ClearData(void) { if(this.Datatype()==TYPE_STRING) this.SetValue(""); else this.SetValue(0.0); } //--- (1) Return and (2) display the object description in the journal string Description(void); void Print(void); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_TABLE_CELL);} //--- Constructors/destructor CTableCell(void) : m_row(0), m_col(0), m_datatype(-1), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueD(0); } //--- Accept a double value CTableCell(const uint row,const uint col,const double value,const int digits) : m_row((int)row), m_col((int)col), m_datatype(TYPE_DOUBLE), m_digits(digits), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueD(value); } //--- Accept a long value CTableCell(const uint row,const uint col,const long value) : m_row((int)row), m_col((int)col), m_datatype(TYPE_LONG), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueL(value); } //--- Accept a datetime value CTableCell(const uint row,const uint col,const datetime value,const uint time_flags) : m_row((int)row), m_col((int)col), m_datatype(TYPE_DATETIME), m_digits(0), m_time_flags(time_flags), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueL(value); } //--- Accept color value CTableCell(const uint row,const uint col,const color value,const bool color_names_flag) : m_row((int)row), m_col((int)col), m_datatype(TYPE_COLOR), m_digits(0), m_time_flags(0), m_color_flag(color_names_flag), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueL(value); } //--- Accept string value CTableCell(const uint row,const uint col,const string value) : m_row((int)row), m_col((int)col), m_datatype(TYPE_STRING), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueS(value); } ~CTableCell(void) {} };
Bei den Methoden zum Setzen von Werten wird zuerst der Typ des in der Zelle gespeicherten Wertes gesetzt und dann das Flag der Funktion zum Bearbeiten von Werten in der Zelle überprüft. Und nur wenn das Flag gesetzt ist, wird der neue Wert in der Zelle gespeichert:
//--- Set double value void SetValue(const double value) { this.m_datatype=TYPE_DOUBLE; if(this.m_editable) this.m_datatype_value.SetValueD(value); }
Warum wird das so gemacht? Bei der Erstellung einer neuen Zelle wird diese mit dem tatsächlichen Typ des gespeicherten Wertes erstellt. Wenn Sie den Typ des Wertes ändern wollen, die Zelle aber gleichzeitig nicht editierbar ist, können Sie die Methode aufrufen, um den Wert des gewünschten Typs mit einem beliebigen Wert zu belegen. Der Typ des gespeicherten Wertes wird geändert, aber der Wert in der Zelle selbst wird nicht beeinflusst.
Die Datenbereinigungsmethode setzt numerische Werte auf Null und fügt in String-Werte ein Leerzeichen ein:
//--- Clear data void ClearData(void) { if(this.Datatype()==TYPE_STRING) this.SetValue(""); else this.SetValue(0.0); }
Später werden wir es anders machen – damit keine Daten in gelöschten Zellen angezeigt werden – um die Zelle leer zu halten, werden wir einen „leeren“ Wert für die Zelle machen, und dann, wenn die Zelle gelöscht wird, werden alle Werte, die in ihr aufgezeichnet und angezeigt werden, gelöscht werden. Schließlich ist auch die Null ein vollwertiger Wert, und wenn nun die Zelle gelöscht wird, werden die digitalen Daten mit Nullen aufgefüllt. Dies ist falsch.
In den parametrischen Konstruktoren der Klasse werden die Koordinaten der Zellen in der Tabelle übergeben – die Anzahl der Zeilen und Spalten und der Wert des gewünschten Typs (double, long, datetime, color, string). Einige Arten von Werten erfordern zusätzliche Informationen:
- double – Genauigkeit des Ausgabewerts (Anzahl der Dezimalstellen),
- datetime – Flag zur Zeitangabe (Datum/Stunden-Minuten/Sekunden),
- color – Flag zur Anzeige von Namen bekannter Standardfarben.
In Konstruktoren mit diesen Typen von in Zellen gespeicherten Werten werden zusätzliche Parameter übergeben, um das Format der in den Zellen angezeigten Werte festzulegen:
//--- Accept a double value CTableCell(const uint row,const uint col,const double value,const int digits) : m_row((int)row), m_col((int)col), m_datatype(TYPE_DOUBLE), m_digits(digits), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueD(value); } //--- Accept a long value CTableCell(const uint row,const uint col,const long value) : m_row((int)row), m_col((int)col), m_datatype(TYPE_LONG), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueL(value); } //--- Accept a datetime value CTableCell(const uint row,const uint col,const datetime value,const uint time_flags) : m_row((int)row), m_col((int)col), m_datatype(TYPE_DATETIME), m_digits(0), m_time_flags(time_flags), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueL(value); } //--- Accept color value CTableCell(const uint row,const uint col,const color value,const bool color_names_flag) : m_row((int)row), m_col((int)col), m_datatype(TYPE_COLOR), m_digits(0), m_time_flags(0), m_color_flag(color_names_flag), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueL(value); } //--- Accept string value CTableCell(const uint row,const uint col,const string value) : m_row((int)row), m_col((int)col), m_datatype(TYPE_STRING), m_digits(0), m_time_flags(0), m_color_flag(false), m_editable(true), m_object(NULL), m_object_type(-1) { this.m_datatype_value.SetValueS(value); }
Die Methode zum Vergleich zweier Objekte:
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ int CTableCell::Compare(const CObject *node,const int mode=0) const { const CTableCell *obj=node; switch(mode) { case CELL_COMPARE_MODE_COL : return(this.Col()>obj.Col() ? 1 : this.Col()<obj.Col() ? -1 : 0); case CELL_COMPARE_MODE_ROW : return(this.Row()>obj.Row() ? 1 : this.Row()<obj.Row() ? -1 : 0); //---CELL_COMPARE_MODE_ROW_COL default : return ( this.Row()>obj.Row() ? 1 : this.Row()<obj.Row() ? -1 : this.Col()>obj.Col() ? 1 : this.Col()<obj.Col() ? -1 : 0 ); } }
Die Methode ermöglicht den Vergleich von Parametern zweier Objekte nach einem von drei Vergleichskriterien – nach Spaltennummer, nach Zeilennummer oder gleichzeitig nach Zeilen- und Spaltennummer.
Diese Methode ist notwendig, um Tabellenzeilen nach Werten von Tabellenspalten sortieren zu können. Dies wird von Controller in späteren Artikeln behandelt.
Die Methode zum Speichern von Zelleigenschaften in einer Datei:
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CTableCell::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the data type if(::FileWriteInteger(file_handle,this.m_datatype,INT_VALUE)!=INT_VALUE) return(false); //--- Save the object type in the cell if(::FileWriteInteger(file_handle,this.m_object_type,INT_VALUE)!=INT_VALUE) return(false); //--- Save the row index if(::FileWriteInteger(file_handle,this.m_row,INT_VALUE)!=INT_VALUE) return(false); //--- Save the column index if(::FileWriteInteger(file_handle,this.m_col,INT_VALUE)!=INT_VALUE) return(false); //--- Maintain the accuracy of data representation if(::FileWriteInteger(file_handle,this.m_digits,INT_VALUE)!=INT_VALUE) return(false); //--- Save date/time display flags if(::FileWriteInteger(file_handle,this.m_time_flags,INT_VALUE)!=INT_VALUE) return(false); //--- Save the color name display flag if(::FileWriteInteger(file_handle,this.m_color_flag,INT_VALUE)!=INT_VALUE) return(false); //--- Save the edited cell flag if(::FileWriteInteger(file_handle,this.m_editable,INT_VALUE)!=INT_VALUE) return(false); //--- Save the value if(::FileWriteStruct(file_handle,this.m_datatype_value)!=sizeof(this.m_datatype_value)) return(false); //--- All is successful return true; }
Nach dem Schreiben der Startdatenmarkierung und des Objekttyps in die Datei werden nacheinander alle Zelleneigenschaften gespeichert. Die Vereinigung muss als Struktur mit FileWriteStruct() gespeichert werden.
Die Methode zum Laden von Zelleigenschaften aus einer Datei:
//+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CTableCell::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the data type this.m_datatype=(ENUM_DATATYPE)::FileReadInteger(file_handle,INT_VALUE); //--- Load the object type in the cell this.m_object_type=(ENUM_OBJECT_TYPE)::FileReadInteger(file_handle,INT_VALUE); //--- Load the row index this.m_row=::FileReadInteger(file_handle,INT_VALUE); //--- Load the column index this.m_col=::FileReadInteger(file_handle,INT_VALUE); //--- Load the precision of the data representation this.m_digits=::FileReadInteger(file_handle,INT_VALUE); //--- Load date/time display flags this.m_time_flags=::FileReadInteger(file_handle,INT_VALUE); //--- Load the color name display flag this.m_color_flag=::FileReadInteger(file_handle,INT_VALUE); //--- Load the edited cell flag this.m_editable=::FileReadInteger(file_handle,INT_VALUE); //--- Load the value if(::FileReadStruct(file_handle,this.m_datatype_value)!=sizeof(this.m_datatype_value)) return(false); //--- All is successful return true; }
Nach dem Lesen und Prüfen der Datenanfangsmarkierungen und des Objekttyps werden alle Eigenschaften des Objekts der Reihe nach in der gleichen Reihenfolge geladen, in der sie gespeichert wurden. Unions werden mit FileReadStruct() gelesen.
Die Methode, die eine Beschreibung des Objekts liefert:
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ string CTableCell::Description(void) { return(::StringFormat("%s: Row %u, Col %u, %s <%s>Value: %s", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.Row(),this.Col(), (this.m_editable ? "Editable" : "Uneditable"),this.DatatypeDescription(),this.Value())); }
Hier wird eine Zeile aus einigen Zellparametern erstellt und z. B. für double in diesem Format zurückgegeben:
Table Cell: Row 2, Col 2, Uneditable <double>Value: 0.00
Die Methode, die eine Objektbeschreibung in das Protokoll ausgibt:
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTableCell::Print(void) { ::Print(this.Description()); }
Hier wird die Objektbeschreibung einfach in das Protokoll gedruckt.
//+------------------------------------------------------------------+ //| Table row class | //+------------------------------------------------------------------+ class CTableRow : public CObject { protected: CTableCell m_cell_tmp; // Cell object to search in the list CListObj m_list_cells; // List of cells uint m_index; // Row index //--- Add the specified cell to the end of the list bool AddNewCell(CTableCell *cell); public: //--- (1) Set and (2) return the row index void SetIndex(const uint index) { this.m_index=index; } uint Index(void) const { return this.m_index; } //--- Set the row and column positions to all cells void CellsPositionUpdate(void); //--- Create a new cell and add it to the end of the list CTableCell *CreateNewCell(const double value); CTableCell *CreateNewCell(const long value); CTableCell *CreateNewCell(const datetime value); CTableCell *CreateNewCell(const color value); CTableCell *CreateNewCell(const string value); //--- Return (1) the cell by index and (2) the number of cells CTableCell *GetCell(const uint index) { return this.m_list_cells.GetNodeAtIndex(index); } uint CellsTotal(void) const { return this.m_list_cells.Total(); } //--- Set the value to the specified cell void CellSetValue(const uint index,const double value); void CellSetValue(const uint index,const long value); void CellSetValue(const uint index,const datetime value); void CellSetValue(const uint index,const color value); void CellSetValue(const uint index,const string value); //--- (1) assign to a cell and (2) remove an assigned object from the cell void CellAssignObject(const uint index,CObject *object); void CellUnassignObject(const uint index); //--- (1) Delete and (2) move the cell bool CellDelete(const uint index); bool CellMoveTo(const uint cell_index, const uint index_to); //--- Reset the data of the row cells void ClearData(void); //--- (1) Return and (2) display the object description in the journal string Description(void); void Print(const bool detail, const bool as_table=false, const int cell_width=10); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_TABLE_ROW); } //--- Constructors/destructor CTableRow(void) : m_index(0) {} CTableRow(const uint index) : m_index(index) {} ~CTableRow(void){} };
3. Klasse der Tabellenzeile
Eine Tabellenzeile ist im Wesentlichen eine verknüpfte Liste von Zellen. Die Zeilenklasse muss das Hinzufügen, Löschen und Umordnen von Zellen in der Liste an eine neue Position unterstützen. Die Klasse muss über ein Mindestmaß an Methoden zur Verwaltung der Zellen in der Liste verfügen.
Schreiben wir den Code weiter in dieselbe Datei. In den Klassenparametern ist nur eine Variable verfügbar – der Zeilenindex in der Tabelle. Alle anderen sind Methoden für die Arbeit mit Zeileneigenschaften und mit einer Liste ihrer Zellen.
Betrachten wir die Methoden der Klasse.
Die Methode zum Vergleich zweier Tabellenzeilen:
//+------------------------------------------------------------------+ //| Compare two objects | //+------------------------------------------------------------------+ int CTableRow::Compare(const CObject *node,const int mode=0) const { const CTableRow *obj=node; return(this.Index()>obj.Index() ? 1 : this.Index()<obj.Index() ? -1 : 0); }
Da Zeilen nur über ihren einzigen Parameter – den Zeilenindex – verglichen werden können, wird dieser Vergleich hier durchgeführt. Diese Methode wird benötigt, um die Tabellenzeilen zu sortieren.
Überladene Methoden zur Erstellung von Zellen mit verschiedenen Arten von gespeicherten Daten:
//+------------------------------------------------------------------+ //| Create a new double cell and add it to the end of the list | //+------------------------------------------------------------------+ CTableCell *CTableRow::CreateNewCell(const double value) { //--- Create a new cell object storing a value of double type CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value,2); if(cell==NULL) { ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal()); return NULL; } //--- Add the created cell to the end of the list if(!this.AddNewCell(cell)) { delete cell; return NULL; } //--- Return the pointer to the object return cell; } //+------------------------------------------------------------------+ //| Create a new long cell and add it to the end of the list | //+------------------------------------------------------------------+ CTableCell *CTableRow::CreateNewCell(const long value) { //--- Create a new cell object storing a long value CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value); if(cell==NULL) { ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal()); return NULL; } //--- Add the created cell to the end of the list if(!this.AddNewCell(cell)) { delete cell; return NULL; } //--- Return the pointer to the object return cell; } //+------------------------------------------------------------------+ //| Create a new datetime cell and add it to the end of the list | //+------------------------------------------------------------------+ CTableCell *CTableRow::CreateNewCell(const datetime value) { //--- Create a new cell object storing a value of datetime type CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value,TIME_DATE|TIME_MINUTES|TIME_SECONDS); if(cell==NULL) { ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal()); return NULL; } //--- Add the created cell to the end of the list if(!this.AddNewCell(cell)) { delete cell; return NULL; } //--- Return the pointer to the object return cell; } //+------------------------------------------------------------------+ //| Create a new color cell and add it to the end of the list | //+------------------------------------------------------------------+ CTableCell *CTableRow::CreateNewCell(const color value) { //--- Create a new cell object storing a value of color type CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value,true); if(cell==NULL) { ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal()); return NULL; } //--- Add the created cell to the end of the list if(!this.AddNewCell(cell)) { delete cell; return NULL; } //--- Return the pointer to the object return cell; } //+------------------------------------------------------------------+ //| Create a new string cell and add it to the end of the list | //+------------------------------------------------------------------+ CTableCell *CTableRow::CreateNewCell(const string value) { //--- Create a new cell object storing a value of string type CTableCell *cell=new CTableCell(this.m_index,this.CellsTotal(),value); if(cell==NULL) { ::PrintFormat("%s: Error. Failed to create new cell in row %u at position %u",__FUNCTION__, this.m_index, this.CellsTotal()); return NULL; } //--- Add the created cell to the end of the list if(!this.AddNewCell(cell)) { delete cell; return NULL; } //--- Return the pointer to the object return cell; }
Fünf Methoden, um eine neue Zelle zu erstellen (double, long, datetime, color, string) und sie am Ende der Listen hinzuzufügen. Zusätzliche Parameter des Datenausgabeformats in die Zelle werden mit Standardwerten eingestellt. Sie können nach der Erstellung der Zelle geändert werden. Zunächst wird ein neues Zellenobjekt erstellt und dann dem Listenende hinzugefügt. Wenn das Objekt nicht erstellt wurde, wird es gelöscht, um Speicherlecks zu vermeiden.
Die Methode, die dem Listenende eine Zelle hinzufügt:
//+------------------------------------------------------------------+ //| Add a cell to the end of the list | //+------------------------------------------------------------------+ bool CTableRow::AddNewCell(CTableCell *cell) { //--- If an empty object is passed, report it and return 'false' if(cell==NULL) { ::PrintFormat("%s: Error. Empty CTableCell object passed",__FUNCTION__); return false; } //--- Set the cell index in the list and add the created cell to the end of the list cell.SetPositionInTable(this.m_index,this.CellsTotal()); if(this.m_list_cells.Add(cell)==WRONG_VALUE) { ::PrintFormat("%s: Error. Failed to add cell (%u,%u) to list",__FUNCTION__,this.m_index,this.CellsTotal()); return false; } //--- Successful return true; }
Jede neu erstellte Zelle wird immer an das Listenende angehängt. Anschließend kann sie mit Hilfe von Methoden der später zu erstellenden Klasse des Tabellenmodells an die entsprechende Stelle verschoben werden.
Überladene Methoden zum Setzen von Werten in der angegebenen Zelle:
//+------------------------------------------------------------------+ //| Set the double value to the specified cell | //+------------------------------------------------------------------+ void CTableRow::CellSetValue(const uint index,const double value) { //--- Get the required cell from the list and set a new value into it CTableCell *cell=this.GetCell(index); if(cell!=NULL) cell.SetValue(value); } //+------------------------------------------------------------------+ //| Set a long value to the specified cell | //+------------------------------------------------------------------+ void CTableRow::CellSetValue(const uint index,const long value) { //--- Get the required cell from the list and set a new value into it CTableCell *cell=this.GetCell(index); if(cell!=NULL) cell.SetValue(value); } //+------------------------------------------------------------------+ //| Set the datetime value to the specified cell | //+------------------------------------------------------------------+ void CTableRow::CellSetValue(const uint index,const datetime value) { //--- Get the required cell from the list and set a new value into it CTableCell *cell=this.GetCell(index); if(cell!=NULL) cell.SetValue(value); } //+------------------------------------------------------------------+ //| Set the color value to the specified cell | //+------------------------------------------------------------------+ void CTableRow::CellSetValue(const uint index,const color value) { //--- Get the required cell from the list and set a new value into it CTableCell *cell=this.GetCell(index); if(cell!=NULL) cell.SetValue(value); } //+------------------------------------------------------------------+ //| Set a string value to the specified cell | //+------------------------------------------------------------------+ void CTableRow::CellSetValue(const uint index,const string value) { //--- Get the required cell from the list and set a new value into it CTableCell *cell=this.GetCell(index); if(cell!=NULL) cell.SetValue(value); }
Anhand des Indexes holen wir die gewünschte Zelle aus der Liste und setzen den Wert für sie. Wenn die Zelle nicht editierbar ist, setzt die Methode SetValue() des Zellenobjekts für die Zelle nur den Typ des zu setzenden Wertes. Der Wert selbst wird nicht gesetzt.
Eine Methode, die einer Zelle ein Objekt zuweist:
//+------------------------------------------------------------------+ //| Assign an object to the cell | //+------------------------------------------------------------------+ void CTableRow::CellAssignObject(const uint index,CObject *object) { //--- Get the required cell from the list and set a pointer to the object into it CTableCell *cell=this.GetCell(index); if(cell!=NULL) cell.AssignObject(object); }
Wir erhalten ein Zellobjekt über seinen Index und verwenden seine Methode AssignObject(), um der Zelle einen Zeiger auf das Objekt zuzuweisen.
Die Methode, die ein zugewiesenes Objekt für eine Zelle aufhebt:
//+------------------------------------------------------------------+ //| Cancel the assigned object for the cell | //+------------------------------------------------------------------+ void CTableRow::CellUnassignObject(const uint index) { //--- Get the required cell from the list and cancel the pointer to the object and its type in it CTableCell *cell=this.GetCell(index); if(cell!=NULL) cell.UnassignObject(); }
Wir holen uns das Zellenobjekt über seinen Index und verwenden seine Methode UnassignObject(), um den Zeiger auf das der Zelle zugewiesene Objekt zu entfernen.
Die Methode, die eine Zelle löscht:
//+------------------------------------------------------------------+ //| Delete a cell | //+------------------------------------------------------------------+ bool CTableRow::CellDelete(const uint index) { //--- Delete a cell in the list by index if(!this.m_list_cells.Delete(index)) return false; //--- Update the indices for the remaining cells in the list this.CellsPositionUpdate(); return true; }
Mit der Methode Delete() der Klasse CList löschen wir die Zelle aus der Liste. Nachdem eine Zelle aus der Liste gelöscht wurde, werden die Indizes der verbleibenden Zellen geändert. Mit der Methode CellsPositionUpdate() aktualisieren wir die Indizes aller verbleibenden Zellen in der Liste.
Die Methode, die eine Zelle an die angegebene Position verschiebt:
//+------------------------------------------------------------------+ //| Moves the cell to the specified position | //+------------------------------------------------------------------+ bool CTableRow::CellMoveTo(const uint cell_index,const uint index_to) { //--- Select the desired cell by index in the list, turning it into the current one CTableCell *cell=this.GetCell(cell_index); //--- Move the current cell to the specified position in the list if(cell==NULL || !this.m_list_cells.MoveToIndex(index_to)) return false; //--- Update the indices of all cells in the list this.CellsPositionUpdate(); return true; }
Damit die Klasse CList mit einem Objekt arbeiten kann, muss dieses Objekt in der Liste das aktuelle sein. Sie wird z. B. aktuell, wenn sie ausgewählt wird. Deshalb holen wir hier zunächst das Zellenobjekt aus der Liste nach Index. Die Zelle wird zur aktuellen Zelle, und mit der Methode MoveToIndex() der Klasse CList verschieben wir das Objekt an die gewünschte Position in der Liste. Nachdem ein Objekt erfolgreich an eine neue Position verschoben wurde, müssen die Indizes der verbleibenden Objekte angepasst werden, was mit der Methode CellsPositionUpdate() geschieht.
Die Methode, die Zeilen- und Spaltenpositionen für alle Zellen in der liste festlegt:
//+------------------------------------------------------------------+ //| Set the row and column positions to all cells | //+------------------------------------------------------------------+ void CTableRow::CellsPositionUpdate(void) { //--- In the loop through all cells in the list for(int i=0;i<this.m_list_cells.Total();i++) { //--- get the next cell and set the row and column indices in it CTableCell *cell=this.GetCell(i); if(cell!=NULL) cell.SetPositionInTable(this.Index(),this.m_list_cells.IndexOf(cell)); } }
Die Klasse CList ermöglicht es Ihnen, den aktuellen Objektindex in der Liste zu finden. Dazu muss das Objekt ausgewählt sein. Hier durchlaufen wir eine Schleife durch alle Zellenobjekte in der Liste, wählen jedes einzelne aus und ermitteln seinen Index mit der Methode IndexOf() der Klasse CList. Der Zeilenindex und der gefundene Zellenindex werden mit der Methode SetPositionInTable() auf das Zellenobjekt gesetzt.
Die Methode zum Zurücksetzen der Daten von Zeilen:
//+------------------------------------------------------------------+ //| Reset the cell data of a row | //+------------------------------------------------------------------+ void CTableRow::ClearData(void) { //--- In the loop through all cells in the list for(uint i=0;i<this.CellsTotal();i++) { //--- get the next cell and set an empty value to it CTableCell *cell=this.GetCell(i); if(cell!=NULL) cell.ClearData(); } }
Setzen Sie in der Schleife jede nächste Zelle in der Liste mit der Zellobjektmethode ClearData() zurück. Bei Zeichenkettendaten wird eine leere Zeile in die Zelle geschrieben, bei numerischen Daten wird eine Null geschrieben.
Die Methode, die eine Beschreibung des Objekts liefert:
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ string CTableRow::Description(void) { return(::StringFormat("%s: Position %u, Cells total: %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.Index(),this.CellsTotal())); }
Eine Zeile wird aus den Eigenschaften und Daten des Objekts zusammengestellt und beispielsweise im folgenden Format zurückgegeben:
Table Row: Position 1, Cells total: 4:
Die Methode, die eine Objektbeschreibung in das Protokoll ausgibt:
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTableRow::Print(const bool detail, const bool as_table=false, const int cell_width=10) { //--- Number of cells int total=(int)this.CellsTotal(); //--- If the output is in tabular form string res=""; if(as_table) { //--- create a table row from the values of all cells string head=" Row "+(string)this.Index(); string res=::StringFormat("|%-*s |",cell_width,head); for(int i=0;i<total;i++) { CTableCell *cell=this.GetCell(i); if(cell==NULL) continue; res+=::StringFormat("%*s |",cell_width,cell.Value()); } //--- Display a row in the journal ::Print(res); return; } //--- Display the header as a row description ::Print(this.Description()+(detail ? ":" : "")); //--- If detailed description if(detail) { //--- The output is not in tabular form //--- In the loop through the list of cells in the row for(int i=0; i<total; i++) { //--- get the current cell and add its description to the final row CTableCell *cell=this.GetCell(i); if(cell!=NULL) res+=" "+cell.Description()+(i<total-1 ? "\n" : ""); } //--- Send the row created in the loop to the journal ::Print(res); } }
Bei nicht-tabellarischer Datenanzeige im Protokoll wird die Überschrift zunächst als Zeilenbeschreibung im Protokoll angezeigt. Wenn das Flag für die detaillierte Anzeige gesetzt ist, werden die Beschreibungen der einzelnen Zellen im Protokoll in einer Schleife durch die Liste der Zellen angezeigt.
Die detaillierte Anzeige einer Tabellenzeile im Protokoll sieht dann z.B. so aus (bei einer nicht-tabellarischen Ansicht):
Table Row: Position 0, Cells total: 4: Table Cell: Row 0, Col 0, Editable <long>Value: 10 Table Cell: Row 0, Col 1, Editable <long>Value: 21 Table Cell: Row 0, Col 2, Editable <long>Value: 32 Table Cell: Row 0, Col 3, Editable <long>Value: 43
Bei einer tabellarischen Darstellung sieht das Ergebnis zum Beispiel wie folgt aus:
| Row 0 | 0 | 1 | 2 | 3 |
Die Methode zum Speichern einer Tabellenzeile in einer Datei:
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CTableRow::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the index if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE) return(false); //--- Save the list of cells if(!this.m_list_cells.Save(file_handle)) return(false); //--- Successful return true; }
Speichern Sie die Datenanfangsmarkierungen, dann den Objekttyp. Dies ist der Standard-Header für jedes Objekt in der Datei. Danach folgt ein Eintrag in die Eigenschaftsdatei des Objekts. Hier wird der Zeilenindex in der Datei gespeichert und dann die Liste der Zellen.
Die Methode zum Hochladen der Zeile aus einer Datei:
//+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CTableRow::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the index this.m_index=::FileReadInteger(file_handle,INT_VALUE); //--- Load the list of cells if(!this.m_list_cells.Load(file_handle)) return(false); //--- Successful return true; }
Hier ist alles in der gleichen Reihenfolge wie beim Speichern. Zunächst werden die Datenanfangsmarkierung und der Objekttyp geladen und geprüft. Dann werden der Zeilenindex und die gesamte Liste der Zellen geladen.
4. Die Klasse des Tabellenmodels
In seiner einfachsten Form ist das Tabellenmodell eine verknüpfte Liste von Zeilen, die ihrerseits verknüpfte Listen von Zellen enthalten. Unser Modell, das wir heute erstellen, empfängt ein zweidimensionales Array von einem der fünf Typen (double, long, datetime, color, string) als Eingabe und erstellt daraus eine virtuelle Tabelle. Außerdem werden wir diese Klasse erweitern, um andere Argumente für die Erstellung von Tabellen aus anderen Eingabedaten zu akzeptieren. Das gleiche Modell wird als Standardmodell betrachtet.
Schreiben wir den Code weiter in dieselbe Datei \MQL5\Scripts\TableModel\TableModelTest.mq5.
Die Klasse des Tabellenmodells ist im Wesentlichen eine verknüpfte Liste von Zeilen mit Methoden zur Verwaltung von Zeilen, Spalten und Zellen. Wir schreiben den Klassenkörper mit allen Variablen und Methoden und berücksichtigen dann die deklarierten Methoden der Klasse:
//+------------------------------------------------------------------+ //| Table model class | //+------------------------------------------------------------------+ class CTableModel : public CObject { protected: CTableRow m_row_tmp; // Row object to search in the list CListObj m_list_rows; // List of table rows //--- Create a table model from a two-dimensional array template<typename T> void CreateTableModel(T &array[][]); //--- Return the correct data type ENUM_DATATYPE GetCorrectDatatype(string type_name) { return ( //--- Integer value type_name=="bool" || type_name=="char" || type_name=="uchar" || type_name=="short"|| type_name=="ushort" || type_name=="int" || type_name=="uint" || type_name=="long" || type_name=="ulong" ? TYPE_LONG : //--- Real value type_name=="float"|| type_name=="double" ? TYPE_DOUBLE : //--- Date/time value type_name=="datetime" ? TYPE_DATETIME : //--- Color value type_name=="color" ? TYPE_COLOR : /*--- String value */ TYPE_STRING ); } //--- Create and add a new empty string to the end of the list CTableRow *CreateNewEmptyRow(void); //--- Add a string to the end of the list bool AddNewRow(CTableRow *row); //--- Set the row and column positions to all table cells void CellsPositionUpdate(void); public: //--- Return (1) cell, (2) row by index, number (3) of rows, cells (4) in the specified row and (5) in the table CTableCell *GetCell(const uint row, const uint col); CTableRow *GetRow(const uint index) { return this.m_list_rows.GetNodeAtIndex(index); } uint RowsTotal(void) const { return this.m_list_rows.Total(); } uint CellsInRow(const uint index); uint CellsTotal(void); //--- Set (1) value, (2) precision, (3) time display flags and (4) color name display flag to the specified cell template<typename T> void CellSetValue(const uint row, const uint col, const T value); void CellSetDigits(const uint row, const uint col, const int digits); void CellSetTimeFlags(const uint row, const uint col, const uint flags); void CellSetColorNamesFlag(const uint row, const uint col, const bool flag); //--- (1) Assign and (2) cancel the object in the cell void CellAssignObject(const uint row, const uint col,CObject *object); void CellUnassignObject(const uint row, const uint col); //--- (1) Delete and (2) move the cell bool CellDelete(const uint row, const uint col); bool CellMoveTo(const uint row, const uint cell_index, const uint index_to); //--- (1) Return and (2) display the cell description and (3) the object assigned to the cell string CellDescription(const uint row, const uint col); void CellPrint(const uint row, const uint col); CObject *CellGetObject(const uint row, const uint col); public: //--- Create a new string and (1) add it to the end of the list, (2) insert to the specified list position CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Remove or (2) relocate the row, (3) clear the row data bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowResetData(const uint index); //--- (1) Return and (2) display the row description in the journal string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Remove or (2) relocate the column, (3) clear the column data bool ColumnDelete(const uint index); bool ColumnMoveTo(const uint row_index, const uint index_to); void ColumnResetData(const uint index); //--- (1) Return and (2) display the table description in the journal string Description(void); void Print(const bool detail); void PrintTable(const int cell_width=10); //--- (1) Clear the data, (2) destroy the model void ClearData(void); void Destroy(void); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(OBJECT_TYPE_TABLE_MODEL); } //--- Constructors/destructor CTableModel(void){} CTableModel(double &array[][]) { this.CreateTableModel(array); } CTableModel(long &array[][]) { this.CreateTableModel(array); } CTableModel(datetime &array[][]) { this.CreateTableModel(array); } CTableModel(color &array[][]) { this.CreateTableModel(array); } CTableModel(string &array[][]) { this.CreateTableModel(array); } ~CTableModel(void){} };
Im Grunde gibt es nur ein Objekt für eine verknüpfte Liste von Tabellenzeilen und Methoden zur Verwaltung von Zeilen, Zellen und Spalten. Und Konstruktoren, die verschiedene Typen von zweidimensionalen Arrays akzeptieren.
Dem Klassenkonstruktor wird ein Array übergeben, und es wird eine Methode zur Erstellung eines Tabellenmodells aufgerufen:
//+------------------------------------------------------------------+ //| Create the table model from a two-dimensional array | //+------------------------------------------------------------------+ template<typename T> void CTableModel::CreateTableModel(T &array[][]) { //--- Get the number of table rows and columns from the array properties int rows_total=::ArrayRange(array,0); int cols_total=::ArrayRange(array,1); //--- In a loop by row indices for(int r=0; r<rows_total; r++) { //--- create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); //--- If a row is created and added to the list, if(row!=NULL) { //--- In the loop by the number of cells in a row, //--- create all the cells, adding each new one to the end of the list of cells in the row for(int c=0; c<cols_total; c++) row.CreateNewCell(array[r][c]); } } }
Die Logik der Methode wird in den Kommentaren erläutert. Die erste Dimension des Arrays sind die Tabellenzeilen, die zweite die Zellen der einzelnen Zeilen. Bei der Erstellung von Zellen wird derselbe Datentyp verwendet, der als Array an die Methode übergeben wird.
So können wir mehrere Tabellenmodelle erstellen, deren Zellen zunächst unterschiedliche Datentypen speichern (double, long, datetime, color und string). Später, nach der Erstellung des Tabellenmodells, können die in den Zellen gespeicherten Datentypen geändert werden.
Die Methode, die eine neue leere Zeile erstellt und an das Ende der Liste anfügt:
//+------------------------------------------------------------------+ //| Create a new empty string and add it to the end of the list | //+------------------------------------------------------------------+ CTableRow *CTableModel::CreateNewEmptyRow(void) { //--- Create a new row object CTableRow *row=new CTableRow(this.m_list_rows.Total()); if(row==NULL) { ::PrintFormat("%s: Error. Failed to create new row at position %u",__FUNCTION__, this.m_list_rows.Total()); return NULL; } //--- If failed to add the row to the list, remove the newly created object and return NULL if(!this.AddNewRow(row)) { delete row; return NULL; } //--- Success - return the pointer to the created object return row; }
Die Methode erstellt ein neues Objekt der Klasse CTableRow und fügt es mit der Methode AddNewRow() an das Ende der Zeilenliste an. Tritt ein Additionsfehler auf, wird das neu erstellte Objekt gelöscht und NULL zurückgegeben. Bei Erfolg gibt die Methode einen Zeiger auf die Zeile zurück, die der Liste neu hinzugefügt wurde.
Die Methode, die dem Listenende ein Zeilenobjekt hinzufügt:
//+------------------------------------------------------------------+ //| Add a row to the end of the list | //+------------------------------------------------------------------+ bool CTableModel::AddNewRow(CTableRow *row) { //--- If an empty object is passed, report this and return 'false' if(row==NULL) { ::PrintFormat("%s: Error. Empty CTableRow object passed",__FUNCTION__); return false; } //--- Set the row index in the list and add it to the end of the list row.SetIndex(this.RowsTotal()); if(this.m_list_rows.Add(row)==WRONG_VALUE) { ::PrintFormat("%s: Error. Failed to add row (%u) to list",__FUNCTION__,row.Index()); return false; } //--- Successful return true; }
Die beiden oben beschriebenen Methoden befinden sich im geschützten Bereich der Klasse, arbeiten paarweise und werden intern verwendet, wenn der Tabelle neue Zeilen hinzugefügt werden.
Methode zur Erstellung einer neuen Zeile und deren Hinzufügung zum Listenende:
//+------------------------------------------------------------------+ //| Create a new string and add it to the end of the list | //+------------------------------------------------------------------+ CTableRow *CTableModel::RowAddNew(void) { //--- Create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); if(row==NULL) return NULL; //--- Create cells equal to the number of cells in the first row for(uint i=0;i<this.CellsInRow(0);i++) row.CreateNewCell(0.0); row.ClearData(); //--- Success - return the pointer to the created object return row; }
Dies ist eine öffentliche Methode. Sie wird verwendet, um der Tabelle eine neue Zeile mit Zellen hinzuzufügen. Die Anzahl der Zellen für die erstellte Zeile wird aus der allerersten Zeile der Tabelle übernommen.
Die Methode zum Erstellen und Hinzufügen einer neuen Zeile an einer bestimmten Listenposition:
//+------------------------------------------------------------------+ //| Create and add a new string to the specified position in the list| //+------------------------------------------------------------------+ CTableRow *CTableModel::RowInsertNewTo(const uint index_to) { //--- Create a new empty row and add it to the end of the list of rows CTableRow *row=this.CreateNewEmptyRow(); if(row==NULL) return NULL; //--- Create cells equal to the number of cells in the first row for(uint i=0;i<this.CellsInRow(0);i++) row.CreateNewCell(0.0); row.ClearData(); //--- Shift the row to index_to this.RowMoveTo(this.m_list_rows.IndexOf(row),index_to); //--- Success - return the pointer to the created object return row; }
Manchmal müssen Sie eine neue Zeile nicht am Ende der Liste von Zeilen einfügen, sondern zwischen den vorhandenen Zeilen. Diese Methode erstellt zunächst eine neue Zeile am Ende der Liste, füllt sie mit Zellen, löscht sie und verschiebt die Zeile dann an die gewünschte Position.
Die Methode, die Werte in die angegebene Zelle setzt:
//+------------------------------------------------------------------+ //| Set the value to the specified cell | //+------------------------------------------------------------------+ template<typename T> void CTableModel::CellSetValue(const uint row,const uint col,const T value) { //--- Get a cell by row and column indices CTableCell *cell=this.GetCell(row,col); if(cell==NULL) return; //--- Get the correct type of the data being set (double, long, datetime, color, string) ENUM_DATATYPE type=this.GetCorrectDatatype(typename(T)); //--- Depending on the data type, we call the corresponding //--- cell method for setting the value, explicitly specifying the required type switch(type) { case TYPE_DOUBLE : cell.SetValue((double)value); break; case TYPE_LONG : cell.SetValue((long)value); break; case TYPE_DATETIME: cell.SetValue((datetime)value); break; case TYPE_COLOR : cell.SetValue((color)value); break; case TYPE_STRING : cell.SetValue((string)value); break; default : break; } }
Holen Sie sich zunächst einen Zeiger auf die gewünschte Zelle anhand der Koordinaten ihrer Zeile und Spalte, und setzen Sie dann einen Wert darauf. Unabhängig von dem Wert, der an die Methode zur Installation in der Zelle übergeben wird, wählt die Methode nur den richtigen Typ für die Installation aus – double, long, datetime, color oder string.
Methode zur Einstellung der Genauigkeit der Anzeige von Daten in der angegebenen Zelle:
//+------------------------------------------------------------------+ //| Set the precision of data display in the specified cell | //+------------------------------------------------------------------+ void CTableModel::CellSetDigits(const uint row,const uint col,const int digits) { //--- Get a cell by row and column indices and //--- call its corresponding method to set the value CTableCell *cell=this.GetCell(row,col); if(cell!=NULL) cell.SetDigits(digits); }
Die Methode ist nur für Zellen relevant, die den Typ Realwert speichern. Er wird verwendet, um die Anzahl der Dezimalstellen für den von der Zelle angezeigten Wert anzugeben.
Die Methode, die Zeitanzeigeflags für die angegebene Zelle setzt :
//+------------------------------------------------------------------+ //| Set the time display flags to the specified cell | //+------------------------------------------------------------------+ void CTableModel::CellSetTimeFlags(const uint row,const uint col,const uint flags) { //--- Get a cell by row and column indices and //--- call its corresponding method to set the value CTableCell *cell=this.GetCell(row,col); if(cell!=NULL) cell.SetDatetimeFlags(flags); }
Das ist relevant für die Zellen, die Datumswerte anzeigen. Es Legt das Format der Zeitanzeige in der Zelle fest (eines von TIME_DATE|TIME_MINUTES|TIME_SECONDS oder Kombinationen davon).
TIME_DATE liefert das Ergebnis als „jjjj.mm.tt“,
TIME_MINUTES liefert das Ergebnis als „hh:mi“,
TIME_SECONDS liefert das Ergebnis als „hh:mi:ss“.
Die Methode zum Setzen von Farbnamen-Anzeigeflags für die angegebene Zelle setzt:
//+------------------------------------------------------------------+ //| Set the flag for displaying color names in the specified cell | //+------------------------------------------------------------------+ void CTableModel::CellSetColorNamesFlag(const uint row,const uint col,const bool flag) { //--- Get a cell by row and column indices and //--- call its corresponding method to set the value CTableCell *cell=this.GetCell(row,col); if(cell!=NULL) cell.SetColorNameFlag(flag); }
Ist nur für die Zellen relevant, die Farbwerte anzeigen. Es zeigt an, dass die Farbnamen angezeigt werden müssen, wenn die in der Zelle gespeicherte Farbe in der Farbtabelle vorhanden ist.
Die Methode, die einer Zelle ein Objekt zuweist:
//+------------------------------------------------------------------+ //| Assign an object to a cell | //+------------------------------------------------------------------+ void CTableModel::CellAssignObject(const uint row,const uint col,CObject *object) { //--- Get a cell by row and column indices and //--- call its corresponding method to set the value CTableCell *cell=this.GetCell(row,col); if(cell!=NULL) cell.AssignObject(object); }
Die Methode, die die Zuordnung eines Objekts in einer Zelle aufhebt:
//+------------------------------------------------------------------+ //| Unassign an object from a cell | //+------------------------------------------------------------------+ void CTableModel::CellUnassignObject(const uint row,const uint col) { //--- Get a cell by row and column indices and //--- call its corresponding method to set the value CTableCell *cell=this.GetCell(row,col); if(cell!=NULL) cell.UnassignObject(); }
Mit den beiden oben vorgestellten Methoden können Sie ein Objekt einer Zelle zuordnen oder seine Zuordnung aufheben. Das Objekt muss von der Klasse CObject abgeleitet werden. Im Zusammenhang mit Artikeln über Tabellen kann ein Objekt z. B. ein Objekt aus der Liste der bekannten Objekte aus der Aufzählung ENUM_OBJECT_TYPE sein. Im Moment enthält die Liste nur Zellobjekte, Zeilen und Tabellenmodelle. Es macht keinen Sinn, sie einer Zelle zuzuordnen. Die Enumeration wird jedoch erweitert, wenn wir Artikel über die View-Komponente schreiben, in der die Steuerelemente erstellt werden. Es wäre sinnvoll, einer Zelle z. B. das Steuerelement „Dropdown-Liste“ zuzuweisen.
Die Methode, die die angegebene Zelle löscht:
//+------------------------------------------------------------------+ //| Delete a cell | //+------------------------------------------------------------------+ bool CTableModel::CellDelete(const uint row,const uint col) { //--- Get the row by index and return the result of deleting the cell from the list CTableRow *row_obj=this.GetRow(row); return(row_obj!=NULL ? row_obj.CellDelete(col) : false); }
Die Methode holt sich das Zeilenobjekt über seinen Index und ruft seine Methode zum Löschen der angegebenen Zelle auf. Für eine einzelne Zelle in einer einzigen Zeile ist die Methode noch nicht sinnvoll, da sie die Anzahl der Zellen in nur einer Zeile der Tabelle reduziert. Dies führt dazu, dass die Zellen nicht mehr mit den benachbarten Zeilen synchronisiert sind. Bisher gibt es keine Verarbeitung einer solchen Löschung, bei der es notwendig ist, die Zelle neben der gelöschten auf die Größe von zwei Zellen zu „erweitern“, damit die Tabellenstruktur nicht gestört wird. Diese Methode wird jedoch als Teil der Methode zum Löschen von Tabellenspalten verwendet, bei der Zellen in allen Zeilen auf einmal gelöscht werden, ohne die Integrität der gesamten Tabelle zu verletzen.
Die Methode zum Verschieben einer Tabellenzelle:
//+------------------------------------------------------------------+ //| Move the cell | //+------------------------------------------------------------------+ bool CTableModel::CellMoveTo(const uint row,const uint cell_index,const uint index_to) { //--- Get the row by index and return the result of moving the cell to a new position CTableRow *row_obj=this.GetRow(row); return(row_obj!=NULL ? row_obj.CellMoveTo(cell_index,index_to) : false); }
Abrufen des Zeilenobjekts anhand seines Index und Aufrufen seiner Methode zum Löschen der angegebenen Zelle.
Die Methode, die die Anzahl der Zellen in der angegebenen Zeile zurückgibt:
//+------------------------------------------------------------------+ //| Return the number of cells in the specified row | //+------------------------------------------------------------------+ uint CTableModel::CellsInRow(const uint index) { CTableRow *row=this.GetRow(index); return(row!=NULL ? row.CellsTotal() : 0); }
Holen Sie die Zeile nach Index und geben Sie die Anzahl der Zellen darin zurück, indem Sie die Methode CellsTotal() der Zeile aufrufen.
Methode zur Rückgabe der Anzahl der Zellen in der Tabelle zurückgibt:
//+------------------------------------------------------------------+ //| Return the number of cells in the table | //+------------------------------------------------------------------+ uint CTableModel::CellsTotal(void) { //--- count cells in a loop by rows (slow with a large number of rows) uint res=0, total=this.RowsTotal(); for(int i=0; i<(int)total; i++) { CTableRow *row=this.GetRow(i); res+=(row!=NULL ? row.CellsTotal() : 0); } return res; }
Die Methode durchläuft alle Zeilen der Tabelle und addiert die Anzahl der Zellen jeder Zeile zum Gesamtergebnis, das zurückgegeben wird. Bei einer großen Anzahl von Zeilen in der Tabelle kann eine solche Zählung langsam sein. Nachdem alle Methoden, die sich auf die Anzahl der Zellen in der Tabelle auswirken, erstellt wurden, sind sie nur dann zu berücksichtigen, wenn sich ihre Anzahl ändert.
Die Methode, die die angegebene Tabellenzelle zurückgibt:
//+------------------------------------------------------------------+ //| Return the specified table cell | //+------------------------------------------------------------------+ CTableCell *CTableModel::GetCell(const uint row,const uint col) { //--- get the row by index row and return the row cell by 'row' index by 'col' index CTableRow *row_obj=this.GetRow(row); return(row_obj!=NULL ? row_obj.GetCell(col) : NULL); }
Abrufen der Zeile nach Zeilenindex und Rückgabe des Zeigers auf das Zellenobjekt nach Spaltenindex mit der Methode GetCell() des Zeilenobjekts.
Die Methode, die dieBeschreibung der Zelle zurückgibt:
//+------------------------------------------------------------------+ //| Return the cell description | //+------------------------------------------------------------------+ string CTableModel::CellDescription(const uint row,const uint col) { CTableCell *cell=this.GetCell(row,col); return(cell!=NULL ? cell.Description() : ""); }
Abrufen der Zeile nach Index, Abrufen der Zelle aus der Zeile und Rückgabe ihrer Beschreibung.
Die Methode, die eine Zellenbeschreibung im Protokoll anzeigt:
//+------------------------------------------------------------------+ //| Display a cell description in the journal | //+------------------------------------------------------------------+ void CTableModel::CellPrint(const uint row,const uint col) { //--- Get a cell by row and column index and return its description CTableCell *cell=this.GetCell(row,col); if(cell!=NULL) cell.Print(); }
Holen Sie sich einen Zeiger auf die Zelle durch Zeilen- und Spaltenindizes und zeigen Sie mit der Print()-Methode des Zellenobjekts dessen Beschreibung im Protokoll an.
Die Methode, die die angegebene Zeile löscht:
//+------------------------------------------------------------------+ //| Delete a row | //+------------------------------------------------------------------+ bool CTableModel::RowDelete(const uint index) { //--- Remove a string from the list by index if(!this.m_list_rows.Delete(index)) return false; //--- After deleting a row, be sure to update all indices of all table cells this.CellsPositionUpdate(); return true; }
Mit der Methode Delete() der Klasse CList wird das Zeilenobjekt nach Index aus der Liste gelöscht. Nach dem Löschen der Zeile entsprechen die Indizes der verbleibenden Zeilen und der darin enthaltenen Zellen nicht der Realität und müssen mit der Methode CellsPositionUpdate() angepasst werden.
Die Methode, die eine Zeile an die angegebene Position verschiebt:
//+------------------------------------------------------------------+ //| Move a string to a specified position | //+------------------------------------------------------------------+ bool CTableModel::RowMoveTo(const uint row_index,const uint index_to) { //--- Get the row by index, turning it into the current one CTableRow *row=this.GetRow(row_index); //--- Move the current string to the specified position in the list if(row==NULL || !this.m_list_rows.MoveToIndex(index_to)) return false; //--- After moving a row, it is necessary to update all indices of all table cells this.CellsPositionUpdate(); return true; }
In der Klasse CList arbeiten viele Methoden mit dem aktuellen Listenobjekt. Holen Sie sich einen Zeiger auf die gewünschte Zeile, machen Sie sie zur aktuellen Zeile und verschieben Sie sie mit der Methode MoveToIndex() der Klasse CList an die gewünschte Position. Nachdem die Zeile an eine neue Position verschoben wurde, müssen die Indizes für die verbleibenden Zeilen aktualisiert werden, was mit der Methode CellsPositionUpdate() geschieht.
Die Methode, die Zeilen- und Spaltenpositionen für alle Zellen festlegt:
//+------------------------------------------------------------------+ //| Set the row and column positions to all cells | //+------------------------------------------------------------------+ void CTableModel::CellsPositionUpdate(void) { //--- In the loop by the list of rows for(int i=0;i<this.m_list_rows.Total();i++) { //--- get the next row CTableRow *row=this.GetRow(i); if(row==NULL) continue; //--- set the index, found by the IndexOf() method of the list, to the row row.SetIndex(this.m_list_rows.IndexOf(row)); //--- Update the row cell position indices row.CellsPositionUpdate(); } }
Gehen Sie durch die Liste aller Zeilen in der Tabelle, wählen Sie jede nachfolgende Zeile aus und setzen Sie einen korrekten Index für sie, der mit der Methode IndexOf() der Klasse CList gefunden wurde. Rufen Sie dann die Zeilenmethode CellsPositionUpdate() auf, die den richtigen Index für jede Zelle in der Zeile festlegt.
Die Methode, die die daten aller Zellen in einer Zeile löscht:
//+------------------------------------------------------------------+ //| Clear the row (only the data in the cells) | //+------------------------------------------------------------------+ void CTableModel::RowResetData(const uint index) { //--- Get a row from the list and clear the data of the row cells using the ClearData() method CTableRow *row=this.GetRow(index); if(row!=NULL) row.ClearData(); }
Jede Zelle in der Zeile wird auf einen „leeren“ Wert zurückgesetzt. Zur Vereinfachung ist der leere Wert für numerische Zellen zunächst Null, aber das wird später geändert, da Null auch ein Wert ist, der in der Zelle angezeigt werden muss. Und das Zurücksetzen eines Wertes bedeutet, dass ein leeres Zellenfeld angezeigt wird.
Die Methode zum Löschen der Daten von aller Zellen in der Tabelle:
//+------------------------------------------------------------------+ //| Clear the table (data of all cells) | //+------------------------------------------------------------------+ void CTableModel::ClearData(void) { //--- Clear the data of each row in the loop through all the table rows for(uint i=0;i<this.RowsTotal();i++) this.RowResetData(i); }
Gehen Sie durch alle Zeilen der Tabelle und rufen Sie für jede Zeile die oben beschriebene Methode RowResetData() auf.
Die Methode, die die Beschreibung der Zeile zurückgibt:
//+------------------------------------------------------------------+ //| Return the row description | //+------------------------------------------------------------------+ string CTableModel::RowDescription(const uint index) { //--- Get a row by index and return its description CTableRow *row=this.GetRow(index); return(row!=NULL ? row.Description() : ""); }
Holt einen Zeiger auf die Zeile nach Index und gibt ihre Beschreibung zurück.
Methode zur Anzeige der Zeilenbeschreibung im Protokoll:
//+------------------------------------------------------------------+ //| Display the row description in the journal | //+------------------------------------------------------------------+ void CTableModel::RowPrint(const uint index,const bool detail) { CTableRow *row=this.GetRow(index); if(row!=NULL) row.Print(detail); }
Holen Sie sich einen Zeiger auf die Zeile und rufen Sie die Methode Print() des empfangenen Objekts auf.
Die Methode zum Löschen von Tabellenspalten:
//+------------------------------------------------------------------+ //| Remove the column | //+------------------------------------------------------------------+ bool CTableModel::ColumnDelete(const uint index) { bool res=true; for(uint i=0;i<this.RowsTotal();i++) { CTableRow *row=this.GetRow(i); if(row!=NULL) res &=row.CellDelete(index); } return res; }
Gehen Sie in einer Schleife durch alle Zeilen der Tabelle, holen Sie sich die jeweils nächste Zeile und löschen Sie darin eine gewünschte Zelle nach Spaltenindex. Dadurch werden alle Zellen in der Tabelle gelöscht, die die gleichen Spaltenindizes haben.
Die Methode, die eine Tabellenspalte verschiebt:
//+------------------------------------------------------------------+ //| Move the column | //+------------------------------------------------------------------+ bool CTableModel::ColumnMoveTo(const uint col_index,const uint index_to) { bool res=true; for(uint i=0;i<this.RowsTotal();i++) { CTableRow *row=this.GetRow(i); if(row!=NULL) res &=row.CellMoveTo(col_index,index_to); } return res; }
Gehen Sie in einer Schleife durch alle Zeilen der Tabelle, holen Sie die jeweils nächste Zeile und verschieben Sie die gewünschte Zelle an eine neue Position. Dadurch werden alle Zellen in der Tabelle verschoben, die die gleichen Spaltenindizes haben.
Die Methode, die Daten von Spaltenzellen löscht:
//+------------------------------------------------------------------+ //| Clear the column data | //+------------------------------------------------------------------+ void CTableModel::ColumnResetData(const uint index) { //--- In a loop through all table rows for(uint i=0;i<this.RowsTotal();i++) { //--- get the cell with the column index from each row and clear it CTableCell *cell=this.GetCell(i, index); if(cell!=NULL) cell.ClearData(); } }
Wir gehen in einer Schleife durch alle Zeilen der Tabelle, rufen wir jede Zeile auf und löschen die Daten in der gewünschten Zelle. Dadurch werden alle Zellen in der Tabelle gelöscht, die die gleichen Spaltenindizes haben.
Die Methode, die eine Beschreibung des Objekts liefert:
//+------------------------------------------------------------------+ //| Return the object description | //+------------------------------------------------------------------+ string CTableModel::Description(void) { return(::StringFormat("%s: Rows %u, Cells in row %u, Cells Total %u", TypeDescription((ENUM_OBJECT_TYPE)this.Type()),this.RowsTotal(),this.CellsInRow(0),this.CellsTotal())); }
In diesem Format wird aus einigen Parametern des Tabellenmodells eine Zeile erstellt und zurückgegeben:
Table Model: Rows 4, Cells in row 4, Cells Total 16:
Die Methode, die eine Objektbeschreibung in das Protokoll ausgibt:
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTableModel::Print(const bool detail) { //--- Display the header in the journal ::Print(this.Description()+(detail ? ":" : "")); //--- If detailed description, if(detail) { //--- In a loop through all table rows for(uint i=0; i<this.RowsTotal(); i++) { //--- get the next row and display its detailed description to the journal CTableRow *row=this.GetRow(i); if(row!=NULL) row.Print(true,false); } } }
Zunächst wird die Kopfzeile als Beschreibung des Modells gedruckt, und dann, wenn das Flag für detaillierte Ausgabe gesetzt ist, werden in der Schleife detaillierte Beschreibungen aller Zeilen des Tabellenmodells gedruckt.
Methode zur Ausgabe der Objektbeschreibung in Tabellenform in das Protokoll ausgibt:
//+------------------------------------------------------------------+ //| Display the object description as a table in the journal | //+------------------------------------------------------------------+ void CTableModel::PrintTable(const int cell_width=10) { //--- Get the pointer to the first row (index 0) CTableRow *row=this.GetRow(0); if(row==NULL) return; //--- Create a table header row based on the number of cells in the first row of the table uint total=row.CellsTotal(); string head=" n/n"; string res=::StringFormat("|%*s |",cell_width,head); for(uint i=0;i<total;i++) { if(this.GetCell(0, i)==NULL) continue; string cell_idx=" Column "+(string)i; res+=::StringFormat("%*s |",cell_width,cell_idx); } //--- Display the header row in the journal ::Print(res); //--- Iterate through all table rows and display them in tabular form for(uint i=0;i<this.RowsTotal();i++) { CTableRow *row=this.GetRow(i); if(row!=NULL) row.Print(true,true,cell_width); } }
Wir erstellen zunächst auf der Grundlage der Anzahl der Zellen in der allerersten Zeile der Tabelle den Tabellenkopf mit den Namen der Tabellenspalten und drucken ihn im Protokoll aus. Gehen Sie dann in einer Schleife durch alle Zeilen der Tabelle und drucken Sie jede Zeile in Tabellenform aus.
Die Methode, die das Tabellenmodell vernichtet:
//+------------------------------------------------------------------+ //| Destroy the model | //+------------------------------------------------------------------+ void CTableModel::Destroy(void) { //--- Clear cell list m_list_rows.Clear(); }
Die Liste der Tabellenzeilen wird einfach geleert und alle Objekte werden mit der Klassenmethode Clear() von CList zerstört.
Die Methode zum Speichern des Tabellenmodells in einer Datei:
//+------------------------------------------------------------------+ //| Save to file | //+------------------------------------------------------------------+ bool CTableModel::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,MARKER_START_DATA)!=sizeof(long)) return(false); //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return(false); //--- Save the list of rows if(!this.m_list_rows.Save(file_handle)) return(false); //--- Successful return true; }
Nach dem Speichern der Datenanfangsmarkierung und des Typs der Liste wird die Liste der Zeilen mit der Klassenmethode Save() von CList in der Datei gespeichert.
Die Methode zum Laden eines Tabellenmodells aus einer Datei:
//+------------------------------------------------------------------+ //| Load from file | //+------------------------------------------------------------------+ bool CTableModel::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return(false); //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Load the list of rows if(!this.m_list_rows.Load(file_handle)) return(false); //--- Successful return true; }
Nach dem Laden und Überprüfen der Datenanfangsmarkierung und des Listentyps laden Sie die Liste der Zeilen aus der Datei mit der Load()-Methode der Klasse CListObj, die am Anfang des Artikels beschrieben wurde.
Alle Klassen zur Erstellung eines Tabellenmodells sind fertig. Lassen Sie uns nun ein Skript schreiben, um die Funktionsweise des Modells zu testen.
Testen Sie das Ergebnis.
Schreiben Sie den Code weiter in dieselbe Datei. Schreiben Sie ein Skript, das ein zweidimensionales 4x4-Array (4 Zeilen mit je 4 Zellen) mit dem Typ long erstellt. Öffnen Sie dann eine Datei, um die Daten des Tabellenmodells in diese zu schreiben, und laden Sie die Daten aus der Datei in die Tabelle. Erstellen Sie ein Tabellenmodell und überprüfen Sie die Funktionsweise einiger seiner Methoden.
Jedes Mal, wenn die Tabelle geändert wird, protokollieren wir das erhaltene Ergebnis mit der Funktion TableModelPrint(), die festlegt, wie das Tabellenmodell zu drucken ist. Wenn der Wert des Makros PRINT_AS_TABLE true ist, erfolgt die Protokollierung mit der Methode PrintTable() der Klasse CTableModel, wenn der Wert false ist – mit der Methode Print() derselben Klasse.
Erstellen Sie in einem Script ein Tabellenmodell, drucken Sie es in Tabellarischer Form aus und speichern Sie das Modell in einer Datei. Fügen Sie dann Zeilen hinzu, löschen Sie Spalten, und ändern Sie die Bearbeitungsberechtigung...
Laden Sie dann die ursprüngliche Version der Tabelle erneut aus der Datei herunter und drucken Sie das Ergebnis aus.
#define PRINT_AS_TABLE true // Display the model as a table //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare and fill the 4x4 array //--- Acceptable array types: double, long, datetime, color, string long array[4][4]={{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Create a table model from the 4x4 long array created above CTableModel *tm=new CTableModel(array); //--- Leave if the model is not created if(tm==NULL) return; //--- Print the model in tabular form Print("The table model has been successfully created:"); tm.PrintTable(); //--- Delete the table model object delete tm; }
Als Ergebnis erhalten wir das folgende Ergebnis des Skripts im Protokoll:
The table model has been successfully created: | n/n | Column 0 | Column 1 | Column 2 | Column 3 | | Row 0 | 1 | 2 | 3 | 4 | | Row 1 | 5 | 6 | 7 | 8 | | Row 2 | 9 | 10 | 11 | 12 | | Row 3 | 13 | 14 | 15 | 16 |
Um das Arbeiten mit dem Tabellenmodell, das Hinzufügen, Löschen und Verschieben von Zeilen und Spalten sowie das Arbeiten mit einer Datei zu testen, vervollständigen Sie das Skript:
#define PRINT_AS_TABLE true // Display the model as a table //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Declare and fill the 4x4 array //--- Acceptable array types: double, long, datetime, color, string long array[4][4]={{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}}; //--- Create a table model from the 4x4 long array created above CTableModel *tm=new CTableModel(array); //--- Leave if the model is not created if(tm==NULL) return; //--- Print the model in tabular form Print("The table model has been successfully created:"); tm.PrintTable(); //--- Check handling files and the functionality of the table model //--- Open a file to write table model data into it int handle=FileOpen(MQLInfoString(MQL_PROGRAM_NAME)+".bin",FILE_READ|FILE_WRITE|FILE_BIN|FILE_COMMON); if(handle==INVALID_HANDLE) return; //--- Save the original created table to the file if(tm.Save(handle)) Print("\nThe table model has been successfully saved to file."); //--- Now insert a new row into the table at position 2 //--- Get the last cell of the created row and make it non-editable //--- Print the modified table model in the journal if(tm.RowInsertNewTo(2)) { Print("\nInsert a new row at position 2 and set cell 3 to non-editable"); CTableCell *cell=tm.GetCell(2,3); if(cell!=NULL) cell.SetEditable(false); TableModelPrint(tm); } //--- Now delete the table column with index 1 and //--- print the resulting table model in the journal if(tm.ColumnDelete(1)) { Print("\nRemove column from position 1"); TableModelPrint(tm); } //--- When saving table data, the file pointer was shifted to the last set data //--- Place the pointer at the beginning of the file, load the previously saved original table and print it if(FileSeek(handle,0,SEEK_SET) && tm.Load(handle)) { Print("\nLoad the original table view from the file:"); TableModelPrint(tm); } //--- Close the open file and delete the table model object FileClose(handle); delete tm; } //+------------------------------------------------------------------+ //| Display the table model | //+------------------------------------------------------------------+ void TableModelPrint(CTableModel *tm) { if(PRINT_AS_TABLE) tm.PrintTable(); // Print the model as a table else tm.Print(true); // Print detailed table data }
Dieses Ergebnis wird in das Protokoll aufgenommen:
The table model has been successfully created: | n/n | Column 0 | Column 1 | Column 2 | Column 3 | | Row 0 | 1 | 2 | 3 | 4 | | Row 1 | 5 | 6 | 7 | 8 | | Row 2 | 9 | 10 | 11 | 12 | | Row 3 | 13 | 14 | 15 | 16 | The table model has been successfully saved to file. Insert a new row at position 2 and set cell 3 to non-editable | n/n | Column 0 | Column 1 | Column 2 | Column 3 | | Row 0 | 1 | 2 | 3 | 4 | | Row 1 | 5 | 6 | 7 | 8 | | Row 2 | 0.00 | 0.00 | 0.00 | 0.00 | | Row 3 | 9 | 10 | 11 | 12 | | Row 4 | 13 | 14 | 15 | 16 | Remove column from position 1 | n/n | Column 0 | Column 1 | Column 2 | | Row 0 | 1 | 3 | 4 | | Row 1 | 5 | 7 | 8 | | Row 2 | 0.00 | 0.00 | 0.00 | | Row 3 | 9 | 11 | 12 | | Row 4 | 13 | 15 | 16 | Load the original table view from the file: | n/n | Column 0 | Column 1 | Column 2 | Column 3 | | Row 0 | 1 | 2 | 3 | 4 | | Row 1 | 5 | 6 | 7 | 8 | | Row 2 | 9 | 10 | 11 | 12 | | Row 3 | 13 | 14 | 15 | 16 |
Wenn Sie die Daten, die nicht in Tabellenform vorliegen, aus dem Tabellenmodell in das Protokoll ausgeben wollen, setzen Sie im Makro PRINT_AS_TABLE den Wert false:
#define PRINT_AS_TABLE false // Display the model as a table
In diesem Fall wird im Protokoll Folgendes angezeigt:
The table model has been successfully created: | n/n | Column 0 | Column 1 | Column 2 | Column 3 | | Row 0 | 1 | 2 | 3 | 4 | | Row 1 | 5 | 6 | 7 | 8 | | Row 2 | 9 | 10 | 11 | 12 | | Row 3 | 13 | 14 | 15 | 16 | The table model has been successfully saved to file. Insert a new row at position 2 and set cell 3 to non-editable Table Model: Rows 5, Cells in row 4, Cells Total 20: Table Row: Position 0, Cells total: 4: Table Cell: Row 0, Col 0, Editable <long>Value: 1 Table Cell: Row 0, Col 1, Editable <long>Value: 2 Table Cell: Row 0, Col 2, Editable <long>Value: 3 Table Cell: Row 0, Col 3, Editable <long>Value: 4 Table Row: Position 1, Cells total: 4: Table Cell: Row 1, Col 0, Editable <long>Value: 5 Table Cell: Row 1, Col 1, Editable <long>Value: 6 Table Cell: Row 1, Col 2, Editable <long>Value: 7 Table Cell: Row 1, Col 3, Editable <long>Value: 8 Table Row: Position 2, Cells total: 4: Table Cell: Row 2, Col 0, Editable <double>Value: 0.00 Table Cell: Row 2, Col 1, Editable <double>Value: 0.00 Table Cell: Row 2, Col 2, Editable <double>Value: 0.00 Table Cell: Row 2, Col 3, Uneditable <double>Value: 0.00 Table Row: Position 3, Cells total: 4: Table Cell: Row 3, Col 0, Editable <long>Value: 9 Table Cell: Row 3, Col 1, Editable <long>Value: 10 Table Cell: Row 3, Col 2, Editable <long>Value: 11 Table Cell: Row 3, Col 3, Editable <long>Value: 12 Table Row: Position 4, Cells total: 4: Table Cell: Row 4, Col 0, Editable <long>Value: 13 Table Cell: Row 4, Col 1, Editable <long>Value: 14 Table Cell: Row 4, Col 2, Editable <long>Value: 15 Table Cell: Row 4, Col 3, Editable <long>Value: 16 Remove column from position 1 Table Model: Rows 5, Cells in row 3, Cells Total 15: Table Row: Position 0, Cells total: 3: Table Cell: Row 0, Col 0, Editable <long>Value: 1 Table Cell: Row 0, Col 1, Editable <long>Value: 3 Table Cell: Row 0, Col 2, Editable <long>Value: 4 Table Row: Position 1, Cells total: 3: Table Cell: Row 1, Col 0, Editable <long>Value: 5 Table Cell: Row 1, Col 1, Editable <long>Value: 7 Table Cell: Row 1, Col 2, Editable <long>Value: 8 Table Row: Position 2, Cells total: 3: Table Cell: Row 2, Col 0, Editable <double>Value: 0.00 Table Cell: Row 2, Col 1, Editable <double>Value: 0.00 Table Cell: Row 2, Col 2, Uneditable <double>Value: 0.00 Table Row: Position 3, Cells total: 3: Table Cell: Row 3, Col 0, Editable <long>Value: 9 Table Cell: Row 3, Col 1, Editable <long>Value: 11 Table Cell: Row 3, Col 2, Editable <long>Value: 12 Table Row: Position 4, Cells total: 3: Table Cell: Row 4, Col 0, Editable <long>Value: 13 Table Cell: Row 4, Col 1, Editable <long>Value: 15 Table Cell: Row 4, Col 2, Editable <long>Value: 16 Load the original table view from the file: Table Model: Rows 4, Cells in row 4, Cells Total 16: Table Row: Position 0, Cells total: 4: Table Cell: Row 0, Col 0, Editable <long>Value: 1 Table Cell: Row 0, Col 1, Editable <long>Value: 2 Table Cell: Row 0, Col 2, Editable <long>Value: 3 Table Cell: Row 0, Col 3, Editable <long>Value: 4 Table Row: Position 1, Cells total: 4: Table Cell: Row 1, Col 0, Editable <long>Value: 5 Table Cell: Row 1, Col 1, Editable <long>Value: 6 Table Cell: Row 1, Col 2, Editable <long>Value: 7 Table Cell: Row 1, Col 3, Editable <long>Value: 8 Table Row: Position 2, Cells total: 4: Table Cell: Row 2, Col 0, Editable <long>Value: 9 Table Cell: Row 2, Col 1, Editable <long>Value: 10 Table Cell: Row 2, Col 2, Editable <long>Value: 11 Table Cell: Row 2, Col 3, Editable <long>Value: 12 Table Row: Position 3, Cells total: 4: Table Cell: Row 3, Col 0, Editable <long>Value: 13 Table Cell: Row 3, Col 1, Editable <long>Value: 14 Table Cell: Row 3, Col 2, Editable <long>Value: 15 Table Cell: Row 3, Col 3, Editable <long>Value: 16
Diese Ausgabe liefert weitere Informationen zur Fehlersuche. Hier sehen wir zum Beispiel, dass das Setzen des Bearbeitungsverbotsflags für eine Zelle in den Programmprotokollen angezeigt wird.
Alle genannten Funktionen funktionieren wie erwartet: Zeilen werden hinzugefügt, verschoben, Spalten gelöscht, wir können Zelleigenschaften ändern und mit Dateien arbeiten.
Schlussfolgerung
Dies ist die erste Version eines einfachen Tabellenmodells, das die Erstellung einer Tabelle aus einem zweidimensionalen Datenfeld ermöglicht. Als Nächstes werden wir andere, spezialisierte Tabellenmodelle erstellen, die Komponente Ansichtstabelle erstellen, und dann werden wir mit Tabellendaten als eines der Steuerelemente der grafischen Nutzeroberfläche arbeiten können.
Die Datei des heute erstellten Skripts mit den darin enthaltenen Klassen ist dem Artikel beigefügt. Sie können es zum Selbststudium herunterladen.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/17653
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.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Vom Neuling zum Experten: Entwicklung eines geografischen Marktbewusstseins mit MQL5-Visualisierung
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Aufbau eines Remote-Forex-Risikomanagementsystems in Python
- 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.
Wenn eine Klasse von SomeObject aus einer Datei geladen wird, indem man die Load()-Methode von eben diesem SomeObject aufruft, wird geprüft, ob ich mich wirklich aus der Datei eingelesen habe (das ist die Frage, die Sie stellen). Wenn nicht, bedeutet das, dass etwas schief gelaufen ist und es keinen Sinn hat, weiter zu laden.
Was ich hier habe, ist eine LISTE (CListObj), die einen Objekttyp aus einer Datei liest. Die Liste weiß nicht, was sich in der Datei befindet (welches Objekt). Aber sie muss diesen Objekttyp kennen, um ihn in ihrer CreateElement()-Methode zu erzeugen. Deshalb prüft sie nicht den Typ des geladenen Objekts aus der Datei. Schließlich wird es einen Vergleich mit Type() geben, der in dieser Methode den Typ einer Liste und nicht eines Objekts zurückgibt.
Danke, ich habe es herausgefunden, ich verstehe.
Ich habe es gelesen, und dann habe ich es noch einmal gelesen.
ist etwas anderes als ein "Modell" in MVC. Einige ListStorage zum Beispiel
Ich frage mich. Ist es möglich, einige Analogon von Python und R dataframes auf diese Weise zu erhalten? Dies sind solche Tabellen, in denen verschiedene Spalten Daten verschiedener Typen enthalten können (aus einer begrenzten Anzahl von Typen, aber einschließlich String).
Sie können. Wenn wir über verschiedene Spalten einer Tabelle sprechen, dann kann in der beschriebenen Implementierung jede Zelle der Tabelle einen anderen Datentyp haben.