Implementazione di un modello di tabella in MQL5: Applicazione del concetto MVC
Contenuto
- Introduzione
- Un po' di cose sul concetto MVC (Model-View-Controller)
- Scrivere classi per costruire un modello di tabella
- Elenchi collegati come base per l'archiviazione di dati tabellari
- Classe della Cella della Tabella
- Classe della Riga della Tabella
- Classe del Modello della Tabella
- Verifica del risultato
- Conclusioni
Introduzione
Nella programmazione, l'architettura delle applicazioni svolge un ruolo fondamentale nel garantire affidabilità, scalabilità e facilità di supporto. Uno degli approcci che aiuta a raggiungere tali obiettivi è quello di sfruttare il modello di architettura chiamato MVC (Model-View-Controller).
Il concetto MVC consente di dividere un'applicazione in tre componenti interconnessi: model (gestione dei dati e della logica), view (visualizzazione dei dati) e controller (elaborazione delle azioni dell'utente). Questa separazione semplifica lo sviluppo, il test e la manutenzione del codice, rendendolo più strutturato e flessibile.
In questo articolo, consideriamo come applicare i principi MVC per implementare un modello di tabella nel linguaggio MQL5. Le tabelle sono uno strumento importante per l'archiviazione, l'elaborazione e la visualizzazione dei dati e la loro corretta organizzazione può facilitare il lavoro con le informazioni. Creeremo classi per lavorare con le tabelle: celle, righe e modello di tabella. Per memorizzare le celle all'interno delle righe e le righe all'interno del modello di tabella, utilizzeremo le classi degli elenchi collegati della Libreria Standard MQL5, che consentono di memorizzare e utilizzare i dati in modo efficiente.
Un po' di cose sul concetto MVC: cos'è e perché lo vogliamo?
Immaginate l'applicazione come una produzione teatrale. Esiste uno scenario che descrive ciò che dovrebbe accadere (questo è il modello). C'è il palcoscenico - ciò che lo spettatore vede (questa è la vista). Infine, c'è il regista che gestisce l'intero processo e collega gli altri elementi (è il controllore). Questo è il modo in cui opera il modello architettonico MVC — Model-View-Controller.
Questo concetto aiuta a separare le responsabilità all'interno dell'applicazione. Il modello è responsabile dei dati e della logica, la vista è responsabile della visualizzazione e dell'aspetto e il controllore è responsabile dell'elaborazione delle azioni dell'utente. Questa separazione rende il codice più chiaro, più flessibile e più comodo per il lavoro di squadra.
Supponiamo di creare una tabella. Il modello sa quali righe e celle contiene e sa come modificarle. La vista disegna una tabella sullo schermo. Il controllore reagisce quando l'utente fa clic su "Aggiungi riga" e passa il compito al modello, per poi dire alla vista di aggiornare.
MVC è particolarmente utile quando l'applicazione diventa più complessa: vengono aggiunte nuove funzionalità, l'interfaccia cambia e lavorano diversi sviluppatori. Con un'architettura chiara, è più facile apportare modifiche, testare i singoli componenti e riutilizzare il codice.
Questo approccio presenta anche alcuni svantaggi. Per progetti molto semplici, MVC potrebbe essere superfluo: si dovrà separare anche ciò che potrebbe rientrare in un paio di funzioni. Tuttavia, per le applicazioni scalabili e serie, questa struttura si ripaga rapidamente.
In sintesi:
MVC è un potente modello di architettura che aiuta a organizzare il codice, a renderlo più comprensibile, testabile e scalabile. È particolarmente utile per le applicazioni complesse in cui è importante la separazione tra logica dei dati, interfaccia utente e gestione. Per i progetti di piccole dimensioni, il suo utilizzo è superfluo.
Il paradigma Model-View-Controller si adatta molto bene al nostro compito. La tabella verrà creata da oggetti indipendenti:
- Cella della tabella.
Un oggetto che memorizza un valore di uno dei tipi - reale, intero o stringa - è dotato di strumenti per gestire il valore, impostarlo e recuperarlo; - Riga della tabella.
Un oggetto che memorizza un elenco di oggetti nelle celle della tabella è dotato di strumenti per la gestione delle celle, la loro posizione, l'aggiunta e la cancellazione; - Un modello di tabella.
Un oggetto che memorizza un elenco di oggetti stringa della tabella è dotato di strumenti per gestire le stringhe e le colonne della tabella, la loro posizione, l'aggiunta e l'eliminazione e ha anche accesso ai controlli delle stringhe e delle celle.
La figura seguente mostra schematicamente la struttura di un modello di tabella 4x4:

Fig.1 Modello di tabella 4x4
Passiamo ora dalla teoria alla pratica.
Scrivere classi per costruire un modello di tabella
Utilizzeremo la Libreria Standard MQL5 per creare tutti gli oggetti.
Ogni oggetto sarà un erede della classe base della libreria. Ciò consente di memorizzare questi oggetti in elenchi di oggetti.
Scriveremo tutte le classi in un unico file di script di test, in modo che tutto sia in un unico file, visibile e rapidamente accessibile. In futuro, distribuiremo le classi scritte nei loro file include separatamente.
1. Elenchi collegati come base per l'archiviazione di dati tabellari
L'elenco collegato CList è molto adatto alla memorizzazione di dati tabellari. A differenza dell'analogo elenco CArrayObj, implementa metodi di accesso agli oggetti elenco vicini, situati a sinistra e a destra di quello corrente. In questo modo sarà facile spostare le celle di una riga o le righe di una tabella, aggiungerle ed eliminarle. Allo stesso tempo, l'elenco stesso si occuperà della corretta indicizzazione degli oggetti spostati, aggiunti o cancellati nell'elenco.
Ma qui c'è una sfumatura. Se si fa riferimento ai metodi per caricare e salvare un elenco in un file, si può notare che quando si carica da un file, la classe elenco deve creare un nuovo oggetto nel metodo virtuale CreateElement().
Questo metodo in questa classe restituisce semplicemente NULL:
//--- method of creating an element of the list virtual CObject *CreateElement(void) { return(NULL); }
Ciò significa che per lavorare con gli elenchi collegati e a condizione che abbiamo bisogno di operazioni sui file, dobbiamo ereditare dalla classe CList e implementare questo metodo nella nostra classe.
Se si esaminano i metodi di salvataggio degli oggetti della Libreria Standard in un file, possiamo vedere il seguente algoritmo per il salvataggio delle proprietà degli oggetti:
- Il marcatore di inizio dati (-1) viene scritto nel file,
- Il tipo di oggetto viene scritto nel file,
- Tutte le proprietà dell'oggetto vengono scritte nel file una per una.
Il primo e il secondo punto sono inerenti a tutti i metodi di salvataggio/caricamento implementati che gli oggetti della Libreria Standard possiedono. Di conseguenza, seguendo la stessa logica, vogliamo conoscere il tipo dell'oggetto salvato nell'elenco, in modo che, leggendo da un file, possiamo creare un oggetto con questo tipo nel metodo virtuale CreateElement() della classe elenco ereditata da CList.
Inoltre, tutti gli oggetti che possono essere caricati nell'elenco - le loro classi devono essere dichiarate o create prima che la classe dell'elenco sia implementata. In questo caso, l'elenco "saprà" quali oggetti sono "in questione" e quali devono essere creati.
Nella directory del terminale \MQL5\Scripts\, create una nuova cartella TableModel\, e in essa un nuovo file dello script di test TableModelTest.mq5.
Includere il file dell'elenco collegato e dichiarare le future classi del modello di tabella:
//+------------------------------------------------------------------+ //| 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
La dichiarazione di classi future è necessaria per far sì che la classe degli elenchi collegati che eredita da CList conosca questi tipi di classi e i tipi di oggetti che dovrà creare. Per farlo, scriveremo enumerazioni di tipi di oggetti, macro ausiliarie e enumerazioni di modi per ordinare gli elenchi:
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+
La funzione che restituisce la descrizione del tipo di oggetto si basa sull'ipotesi che tutti i nomi delle costanti del tipo di oggetto inizino con la sottostringa "OBJECT_TYPE_". Quindi è possibile prendere la sottostringa successiva a questa, convertire tutti i caratteri della riga risultante in minuscolo, convertire il primo carattere in maiuscolo e cancellare tutti gli spazi e controllare i caratteri dalla stringa finale a sinistra e a destra.
Scriviamo la nostra classe di liste collegate. Continueremo a scrivere il codice nello stesso file:
//+------------------------------------------------------------------+ //| 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); };
La classe CListObj è la nostra nuova classe di elenchi collegati, ereditata dalla classe CList della Libreria Standard.
L'unica variabile della classe sarà quella in cui verrà scritto il tipo dell'oggetto creato. Poiché il metodo CreateElement() è virtuale e deve avere esattamente la stessa firma del metodo della classe madre, non possiamo passargli il tipo dell'oggetto che viene creato. Ma possiamo scrivere questo tipo, in una variabile dichiarata e leggere da essa il tipo dell'oggetto che viene creato.
Dobbiamo ridefinire due metodi virtuali della classe madre: il metodo di caricamento da un file e il metodo di creazione di un nuovo oggetto. Prendiamoli in considerazione.
Metodo di caricamento dell'elenco da un file:
//+------------------------------------------------------------------+ //| 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; }
In questo caso, viene prima controllato l'inizio dell'elenco, il suo tipo e la sua dimensione, cioè il numero di elementi nell'elenco; quindi, in un ciclo per il numero di elementi, vengono letti dal file i marcatori di inizio dati di ogni oggetto e il suo tipo. Il tipo risultante viene scritto nella variabile m_element_type e viene richiamato un metodo per creare un nuovo elemento. In questo metodo, un nuovo elemento con il tipo ricevuto viene creato e scritto nella variabile puntatore node, che a sua volta viene aggiunta all'elenco. L'intera logica del metodo è spiegata in dettaglio nei commenti. Consideriamo un metodo per creare un nuovo elemento dell'elenco.
Metodo di creazione di un elemento dell'elenco:
//+------------------------------------------------------------------+ //| 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; } }
Ciò significa che prima di chiamare il metodo, il tipo dell'oggetto che viene creato è già scritto nella variabile m_element_type . In base al tipo di elemento, viene creato un nuovo oggetto del tipo appropriato e viene restituito un puntatore ad esso. In futuro, quando si svilupperanno nuovi controlli, i loro tipi saranno scritti nell'enumerazione ENUM_OBJECT_TYPE. E qui verranno aggiunti nuovi casi per creare nuovi tipi di oggetti. La classe di elenchi collegati basata su CList standard è pronta. Ora è in grado di memorizzare tutti gli oggetti di tipo conosciuti, salvare gli elenchi in un file, caricarli dal file e ripristinarli correttamente.
2. Classe della Cella della Tabella
La cella di una tabella è l'elemento più semplice di una tabella che memorizza un determinato valore. Le celle compongono elenchi, rappresentando le righe della tabella. Ogni elenco rappresenta una riga della tabella. Nella nostra tabella, le celle potranno memorizzare solo un valore di diversi tipi alla volta - un valore reale, intero o stringa.
Oltre a un semplice valore a una cella può essere assegnato un oggetto di un tipo conosciuto dall'enumerazione ENUM_OBJECT_TYPE. In questo caso, la cella può memorizzare un valore di uno qualsiasi dei tipi elencati, più un puntatore a un oggetto, il cui tipo viene scritto in una variabile speciale. In futuro, quindi, il componente View potrà essere istruito a visualizzare tale oggetto in una cella per interagire con esso tramite il componente Controller.
Poiché in una cella possono essere memorizzati diversi tipi di valori, utilizzeremo union per scriverli, memorizzarli e restituirli. Union è un tipo speciale di dati che memorizza diversi campi nella stessa area di memoria. L’unione è simile a una struttura, ma in questo caso, a differenza di una struttura, i diversi termini dell'unione appartengono alla stessa area di memoria. Mentre nella struttura, a ogni campo viene allocata una propria area di memoria.
Continuiamo a scrivere il codice nel file già creato. Iniziamo a scrivere una nuova classe. Nella sezione protected, scriviamo un'unione e dichiariamo le variabili:
//+------------------------------------------------------------------+ //| 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:
Nella sezione public, scrivete i metodi di accesso alle variabili protette, i metodi virtuali e i costruttori di classe per i vari tipi di dati memorizzati in una cella:
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) {} };
Nei metodi di impostazione dei valori, si imposta prima il tipo di valore memorizzato nella cella e poi si controlla il flag della funzione di modifica dei valori nella cella. Solo quando il flag è impostato, il nuovo valore viene salvato nella cella:
//--- Set double value void SetValue(const double value) { this.m_datatype=TYPE_DOUBLE; if(this.m_editable) this.m_datatype_value.SetValueD(value); }
Perché si fa così? Quando si crea una nuova cella, questa viene creata con il tipo reale del valore memorizzato. Se si desidera cambiare il tipo di valore, ma allo stesso tempo la cella non è modificabile, è possibile richiamare il metodo per impostare il valore del tipo desiderato con un valore qualsiasi. Il tipo di valore memorizzato viene modificato, ma il valore della cella stessa non sarà interessato.
Il metodo di pulizia dei dati imposta i valori numerici a zero e inserisce uno spazio nei valori stringa:
//--- Clear data void ClearData(void) { if(this.Datatype()==TYPE_STRING) this.SetValue(""); else this.SetValue(0.0); }
In seguito, faremo diversamente, in modo che nessun dato venga visualizzato nelle celle pulite. Per mantenere la cella vuota, creeremo un valore "vuoto" per la cella e poi, quando la cella verrà pulita, tutti i valori registrati e visualizzati al suo interno verranno cancellati. Dopo tutto, anche lo zero è un valore a tutti gli effetti e ora, quando la cella viene pulita, i dati digitali vengono riempiti di zeri. Questo non è corretto.
Nei costruttori parametrici della classe, vengono passate le coordinate delle celle della tabella - numero di riga e colonna e il valore del tipo richiesto (double, long, datetime, color, string). Alcuni tipi di valori richiedono informazioni aggiuntive:
- double - precisione del valore di uscita (numero di cifre decimali),
- datetime - flag di uscita dell'ora (data/ore-minuti/secondi),
- color- flag per visualizzare i nomi dei colori standard conosciuti.
Nei costruttori con questi tipi di valori memorizzati nelle celle vengono passati parametri aggiuntivi per impostare il formato dei valori visualizzati nelle celle:
//--- 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); }
Metodo per il confronto di due oggetti:
//+------------------------------------------------------------------+ //| 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 ); } }
Il metodo consente di confrontare i parametri di due oggetti in base a uno dei tre criteri di confronto - per numero di colonna, per numero di riga e contemporaneamente per numero di riga e di colonna.
Questo metodo è necessario per poter ordinare le righe della tabella in base ai valori delle colonne della tabella. Questo aspetto sarà trattato dal Controller negli articoli successivi.
Metodo per salvare le proprietà delle celle in un file:
//+------------------------------------------------------------------+ //| 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; }
Dopo aver scritto nel file il marcatore dei dati di partenza e il tipo di oggetto, vengono salvate a turno tutte le proprietà delle celle. L'unione deve essere salvata come struttura utilizzando FileWriteStruct().
Metodo per caricare le proprietà delle celle da un file:
//+------------------------------------------------------------------+ //| 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; }
Dopo aver letto e controllato i marcatori di inizio dati e il tipo di oggetto, tutte le proprietà dell'oggetto vengono caricate a turno nello stesso ordine in cui sono state salvate. Le unioni vengono lette usando FileReadStruct().
Metodo che restituisce la descrizione dell'oggetto:
//+------------------------------------------------------------------+ //| 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())); }
Qui viene creata una riga a partire da alcuni parametri della cella e restituita, ad esempio, per double, in questo formato:
Table Cell: Row 2, Col 2, Uneditable <double>Value: 0.00
Metodo che restituisce la descrizione dell'oggetto al log:
//+------------------------------------------------------------------+ //| Display the object description in the journal | //+------------------------------------------------------------------+ void CTableCell::Print(void) { ::Print(this.Description()); }
In questo caso, la descrizione dell'oggetto viene semplicemente stampata nel log.
//+------------------------------------------------------------------+ //| 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. Classe della Riga della Tabella
Una riga di tabella è essenzialmente un elenco collegato di celle. La classe della riga deve supportare l'aggiunta, la cancellazione e il riordino delle celle dell'elenco in una nuova posizione. La classe deve avere un insieme di metodi minimo-sufficiente per gestire le celle dell'elenco.
Continuiamo a scrivere il codice nello stesso file. Solo una variabile è disponibile nei parametri della classe - l'indice della riga della tabella. Tutti gli altri sono metodi per lavorare con le proprietà delle righe e con un elenco delle sue celle.
Consideriamo i metodi della classe.
Metodo per confrontare due righe di tabella:
//+------------------------------------------------------------------+ //| 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); }
Poiché le righe possono essere confrontate solo in base al loro singolo parametro - l'indice della riga - questo confronto viene implementato qui. Questo metodo è necessario per ordinare le righe della tabella.
Metodi di sovraccarico per creare celle con diversi tipi di dati memorizzati:
//+------------------------------------------------------------------+ //| 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; }
Cinque metodi per creare una nuova cella (double, long, datetime, color, string) e aggiungerla alla fine dell'elenco. I parametri aggiuntivi del formato di output dei dati nella cella sono impostati con valori predefiniti. Possono essere modificati dopo la creazione della cella. Innanzitutto, viene creato un nuovo oggetto cella, che viene poi aggiunto alla fine dell'elenco. Se l'oggetto non è stato creato, viene eliminato per evitare perdite di memoria.
Metodo che aggiunge una cella alla fine dell'elenco:
//+------------------------------------------------------------------+ //| 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; }
Ogni nuova cella creata viene sempre aggiunta alla fine dell'elenco. Quindi, può essere spostata nella posizione appropriata utilizzando i metodi della classe del modello della tabella, che verrà creata in seguito.
Metodi sovraccaricati per impostare i valori nella cella specificata:
//+------------------------------------------------------------------+ //| 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); }
Utilizzando l'indice, otteniamo la cella desiderata dall'elenco e se ne imposta il valore. Se la cella non è modificabile, il metodo SetValue() dell'oggetto cella imposterà solo il tipo di valore che è stato impostato. Il valore stesso non verrà impostato.
Un metodo che assegna un oggetto a una cella:
//+------------------------------------------------------------------+ //| 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); }
Otteniamo un oggetto cella in base al suo indice e utilizziamo il metodo AssignObject() per assegnare un puntatore all'oggetto cella.
Metodo che annulla un oggetto assegnato per una cella:
//+------------------------------------------------------------------+ //| 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(); }
Otteniamo l'oggetto cella in base al suo indice e utilizziamo il metodo UnassignObject() per rimuovere il puntatore all'oggetto assegnato alla cella.
Metodo che elimina una cella:
//+------------------------------------------------------------------+ //| 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; }
Utilizzando il metodo Delete() della classe CList, cancelliamo la cella dall'elenco. Dopo l'eliminazione di una cella dall'elenco, gli indici delle celle rimanenti vengono modificati. Utilizzando il metodo CellsPositionUpdate(), aggiorniamo gli indici di tutte le celle rimanenti nell'elenco.
Metodo che sposta una cella nella posizione specificata:
//+------------------------------------------------------------------+ //| 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; }
Affinché la classe CList possa operare su un oggetto, questo oggetto dell'elenco deve essere quello attuale. Diventa attuale, ad esempio, quando viene selezionato. Pertanto, per prima cosa si ottiene l'oggetto cella dall'elenco in base all'indice. La cella diventa quella corrente e quindi, utilizzando il metodo MoveToIndex() della classe CList, si sposta l'oggetto nella posizione desiderata dell'elenco. Dopo aver spostato con successo un oggetto in una nuova posizione, è necessario regolare gli indici degli oggetti rimanenti, utilizzando il metodo CellsPositionUpdate().
Metodo che imposta le posizioni di riga e colonna per tutte le celle dell'elenco:
//+------------------------------------------------------------------+ //| 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)); } }
La classe CList consente di trovare l'indice dell'oggetto corrente nell'elenco. A tal fine, l'oggetto deve essere selezionato. In questo caso, cicliamo tutti gli oggetti cella dell'elenco, si seleziona ciascuno di essi e si scopre il suo indice utilizzando il metodo IndexOf() della classe CList. L'indice della riga e l'indice della cella trovata vengono impostati sull'oggetto cella utilizzando il metodo SetPositionInTable().
Metodo che reimposta i dati delle celle della riga:
//+------------------------------------------------------------------+ //| 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(); } }
Nel ciclo, azzerare ogni cella successiva dell'elenco utilizzando il metodo dell'oggetto cella ClearData(). Per i dati stringa, nella cella viene scritta una riga vuota, mentre per i dati numerici viene scritto zero.
Metodo che restituisce la descrizione dell'oggetto:
//+------------------------------------------------------------------+ //| 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())); }
Una riga viene raccolta dalle proprietà e dai dati dell'oggetto e restituita nel seguente formato, ad esempio:
Table Row: Position 1, Cells total: 4:
Metodo che restituisce la descrizione dell'oggetto al log:
//+------------------------------------------------------------------+ //| 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); } }
Per la visualizzazione di dati non tabellari nel log, l'intestazione viene visualizzata per prima nel log come descrizione della riga. Quindi, se il flag di visualizzazione dettagliata è impostato, le descrizioni di ciascuna cella vengono visualizzate nel log in un ciclo attraverso l'elenco delle celle.
Di conseguenza, la visualizzazione dettagliata di una riga della tabella nel log appare, ad esempio (per una vista non tabellare):
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
Per la visualizzazione tabellare, il risultato sarà, ad esempio, il seguente:
| Row 0 | 0 | 1 | 2 | 3 |
Metodo che salva una riga della tabella in un file:
//+------------------------------------------------------------------+ //| 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; }
Salvare i marcatori di inizio dati, quindi il tipo di oggetto. È l'intestazione standard di ogni oggetto nel file. Segue nel file una voce delle proprietà dell'oggetto. In questo caso, viene salvato nel file l'indice della riga e poi l'elenco delle celle.
Metodo di caricamento della riga da un file:
//+------------------------------------------------------------------+ //| 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; }
Qui tutto è nello stesso ordine in cui è stato salvato. Innanzitutto, vengono caricati e controllati il marcatore di inizio dati e il tipo di oggetto. Quindi vengono caricati l'indice di riga e l'intero elenco di celle.
4. Classe modelli di tabella
Nella sua forma più semplice, il modello di tabella è un elenco collegato di righe, che a loro volta contengono elenchi collegati di celle. Il nostro modello, che creeremo oggi, riceverà in ingresso un array bidimensionale di uno dei cinque tipi (double, long, datetime, color, string) e costruirà una tabella virtuale a partire da esso. Inoltre, estenderemo questa classe per accettare altri argomenti per creare tabelle da altri dati di input. Lo stesso modello sarà considerato il modello predefinito.
Continuiamo a scrivere il codice nello stesso file \MQL5\Scripts\TableModel\TableModelTest.mq5.
La classe del modello della tabella è essenzialmente un elenco collegato di righe con metodi per la gestione delle righe, colonne e celle. Scrivere il corpo della classe con tutte le variabili e i metodi, quindi considerare i metodi dichiarati della classe:
//+------------------------------------------------------------------+ //| 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){} };
In pratica, esiste un solo oggetto per un elenco collegato di righe di tabelle e metodi per gestire righe, celle e colonne. E costruttori che accettano diversi tipi di array bidimensionali.
Al costruttore della classe viene passato un array e viene richiamato un metodo per creare un modello di tabella:
//+------------------------------------------------------------------+ //| 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]); } } }
La logica del metodo è spiegata nei commenti. La prima dimensione dell'array è costituita dalle righe della tabella, la seconda dalle celle di ciascuna riga. Quando si creano le celle, esse utilizzano lo stesso tipo di dati che viene passato al metodo.
In questo modo, possiamo creare diversi modelli di tabella, le cui celle inizialmente memorizzano diversi tipi di dati (double, long, datetime, color e string). In seguito, dopo aver creato il modello di tabella, è possibile modificare i tipi di dati memorizzati nelle celle.
Un metodo che crea una nuova riga vuota e la aggiunge alla fine dell'elenco:
//+------------------------------------------------------------------+ //| 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; }
Il metodo crea un nuovo oggetto della classe CTableRow e lo aggiunge alla fine dell'elenco delle righe utilizzando il metodo AddNewRow(). Se si verifica un errore di aggiunta, il nuovo oggetto creato viene eliminato e viene restituito NULL. In caso di successo, il metodo restituisce un puntatore alla riga appena aggiunta all'elenco.
Metodo che aggiunge un oggetto riga alla fine dell'elenco:
//+------------------------------------------------------------------+ //| 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; }
Entrambi i metodi discussi sopra si trovano nella sezione protected della classe, funzionano in coppia e sono utilizzati internamente quando si aggiungono nuove righe alla tabella.
Metodo per creare una nuova riga e aggiungerla alla fine dell'elenco:
//+------------------------------------------------------------------+ //| 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; }
Questo è un metodo pubblico. Viene utilizzato per aggiungere una nuova riga con celle alla tabella. Il numero di celle per la riga creata viene preso dalla prima riga della tabella.
Metodo per la creazione e l'aggiunta di una nuova riga in una posizione specifica dell'elenco:
//+------------------------------------------------------------------+ //| 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; }
A volte è necessario inserire una nuova riga non alla fine dell'elenco, ma tra quelle esistenti. Questo metodo crea prima una nuova riga alla fine dell'elenco, la riempie con le celle, le pulisce e quindi sposta la riga nella posizione desiderata.
Metodo che imposta i valori nella cella specificata:
//+------------------------------------------------------------------+ //| 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; } }
Per prima cosa, si ottiene un puntatore alla cella desiderata tramite le coordinate della riga e della colonna, quindi si imposta un valore su di essa. Qualunque sia il valore passato al metodo per installarlo nella cella, il metodo selezionerà solo il tipo corretto per l'installazione - double, long, datetime, color o string.
Metodo che imposta la precisione della visualizzazione dei dati nella cella specificata:
//+------------------------------------------------------------------+ //| 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); }
Il metodo è rilevante solo per le celle che memorizzano il tipo di valore reale. Viene utilizzato per specificare il numero di cifre decimali per il valore visualizzato dalla cella.
Metodo che imposta i flag di visualizzazione dell'ora nella cella specificata:
//+------------------------------------------------------------------+ //| 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); }
Rilevante per le celle che visualizzano i valori datetime. Imposta il formato di visualizzazione dell'ora per la cella (uno tra TIME_DATE | TIME_MINUTES | TIME_SECONDS, o una loro combinazione).
TIME_DATE ottiene il risultato come " yyyy.mm.dd " ,
TIME_MINUTES ottiene il risultato come " hh:mi " ,
TIME_SECONDS ottiene il risultato come " hh:mi:ss ".
Metodo che imposta i flag di visualizzazione del nome del colore alla cella specificata:
//+------------------------------------------------------------------+ //| 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); }
Rilevante solo per le celle che visualizzano valori color. Indica la necessità di visualizzare i nomi dei colori se il colore memorizzato nella cella è presente nella tabella dei colori.
Metodo che assegna un oggetto a una cella:
//+------------------------------------------------------------------+ //| 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); }
Metodo che annulla l'assegnazione di un oggetto ad una cella:
//+------------------------------------------------------------------+ //| 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(); }
I due metodi presentati sopra consentono di assegnare un oggetto a una cella o di rimuoverne l'assegnazione. L'oggetto deve essere un discendente della classe CObject. Nel contesto degli articoli sulle tabelle, un oggetto può essere, ad esempio, uno degli oggetti noti dell'enumerazione ENUM_OBJECT_TYPE. Al momento, l'elenco contiene solo oggetti cella, righe e modelli di tabella. Assegnarli a una cella non ha senso. Ma l'enumerazione si espanderà quando scriveremo articoli sul componente View, dove verranno creati i controlli. È opportuno assegnare a una cella, ad esempio, il controllo "elenco a discesa".
Metodo che elimina la cella specificata:
//+------------------------------------------------------------------+ //| 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); }
Il metodo ottiene l'oggetto riga in base al suo indice e chiama il suo metodo per eliminare la cella specificata. Per una singola cella in una singola riga, il metodo non ha ancora senso, poiché ridurrà il numero di celle in una sola riga della tabella. In questo modo le celle non saranno più sincronizzate con le righe vicine. Finora non esiste un'elaborazione di questo tipo di cancellazione, in cui è necessario "espandere" la cella accanto a quella cancellata fino alla dimensione di due celle, in modo da non interrompere la struttura della tabella. Tuttavia, questo metodo viene utilizzato come parte del metodo di eliminazione delle colonne della tabella, in cui le celle di tutte le righe vengono eliminate in una sola volta senza violare l'integrità dell'intera tabella.
Metodo per spostare una cella di tabella:
//+------------------------------------------------------------------+ //| 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); }
Ottenere l'oggetto riga in base al suo indice e richiamare il suo metodo per eliminare la cella specificata.
Metodo che restituisce il numero di celle nella riga specificata:
//+------------------------------------------------------------------+ //| 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); }
Ottenere la riga per indice e restituire il numero di celle in essa contenute richiamando il metodo CellsTotal() row.
Metodo che restituisce il numero di celle della tabella:
//+------------------------------------------------------------------+ //| 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; }
Il metodo passa in rassegna tutte le righe della tabella e aggiunge il numero di celle di ogni riga al risultato totale, che viene restituito. Con un numero elevato di righe nella tabella, tale conteggio può essere lento. Dopo aver creato tutti i metodi che influenzano il numero di celle nella tabella, tenerli in considerazione solo quando il loro numero cambia.
Metodo che restituisce la cella di tabella specificata:
//+------------------------------------------------------------------+ //| 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); }
Ottenere la riga in base all'indice di riga e restituire il puntatore all'oggetto cella in base all'indice di colonna utilizzando il metodo GetCell() dell'oggetto riga.
Metodo che restituisce la descrizione della cella:
//+------------------------------------------------------------------+ //| Return the cell description | //+------------------------------------------------------------------+ string CTableModel::CellDescription(const uint row,const uint col) { CTableCell *cell=this.GetCell(row,col); return(cell!=NULL ? cell.Description() : ""); }
Ottenere la riga in base all'indice, ricava la cella dalla riga e restituisce la sua descrizione.
Metodo che visualizza la descrizione della cella nel log:
//+------------------------------------------------------------------+ //| 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(); }
Ottenere un puntatore alla cella tramite gli indici di riga e colonna e, utilizzando il metodo Print() dell'oggetto cella, visualizzarne la descrizione nel log.
Metodo che elimina la riga specificata:
//+------------------------------------------------------------------+ //| 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; }
Utilizzando il metodo Delete() della classe CList, si elimina dall'elenco l'oggetto riga per indice. Dopo l'eliminazione della riga, gli indici delle righe rimanenti e delle celle in esse contenute non corrispondono alla realtà e devono essere modificati con il metodo CellsPositionUpdate().
Metodo che sposta una riga nella posizione specificata:
//+------------------------------------------------------------------+ //| 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; }
Nella classe CList, molti metodi lavorano con l'oggetto dell’elenco corrente. Ottenere un puntatore alla riga desiderata, rendendola quella corrente e spostarla nella posizione desiderata utilizzando il metodo MoveToIndex() della classe CList. Dopo aver spostato la riga in una nuova posizione, è necessario aggiornare gli indici per le righe rimanenti, che viene fatto con il metodo CellsPositionUpdate().
Metodo che imposta le posizioni di riga e colonna per tutte le celle:
//+------------------------------------------------------------------+ //| 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(); } }
Scorrere l'elenco di tutte le righe della tabella, selezionare ogni riga successiva e impostare un indice corretto, trovato con il metodo IndexOf() della classe CList. Richiamare quindi il metodo di riga CellsPositionUpdate(), che imposta l'indice corretto per ogni cella della riga.
Metodo che cancella i dati di tutte le celle di una riga:
//+------------------------------------------------------------------+ //| 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(); }
Ogni cella della riga viene reimpostata su un valore "vuoto". Per ora, a scopo di semplificazione, il valore vuoto per le celle numeriche è zero, ma questo verrà modificato in seguito, poiché anche lo zero è un valore che deve essere visualizzato nella cella. E la reimpostazione di un valore implica la visualizzazione di un campo di cella vuoto.
Metodo che cancella i dati di tutte le celle della tabella:
//+------------------------------------------------------------------+ //| 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); }
Passare attraverso tutte le righe della tabella e per ogni riga richiamare il metodo RowResetData() discusso in precedenza.
Metodo che restituisce la descrizione della riga:
//+------------------------------------------------------------------+ //| 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() : ""); }
Ottenere un puntatore alla riga per indice e restituire la sua descrizione.
Metodo che visualizza la descrizione della riga nel log:
//+------------------------------------------------------------------+ //| 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); }
Ottenere un puntatore alla riga e chiamare il metodo Print() dell'oggetto ricevuto.
Metodo che elimina le colonne della tabella:
//+------------------------------------------------------------------+ //| 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; }
In un ciclo che attraversa tutte le righe della tabella, ottenere ogni riga successiva ed eliminare la cella richiesta in essa in base all'indice colonna. Questo elimina tutte le celle della tabella che hanno gli stessi indici colonna.
Metodo che sposta una colonna della tabella:
//+------------------------------------------------------------------+ //| 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; }
In un ciclo che attraversa tutte le righe della tabella, si ottiene ogni riga successiva e si sposta la cella richiesta in una nuova posizione. Questo sposta tutte le celle della tabella che hanno lo stesso indice colonna.
Metodo che cancella i dati delle celle delle colonne:
//+------------------------------------------------------------------+ //| 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(); } }
In un ciclo che attraversa tutte le righe della tabella, ottenere ogni riga e cancellare i dati nella cella richiesta. Questo cancella tutte le celle della tabella che hanno lo stesso indice colonna.
Metodo che restituisce la descrizione dell'oggetto:
//+------------------------------------------------------------------+ //| 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())); }
Una riga viene creata e restituisce alcuni parametri del modello della tabella in questo formato:
Table Model: Rows 4, Cells in row 4, Cells Total 16:
Metodo che restituisce la descrizione dell'oggetto al log:
//+------------------------------------------------------------------+ //| 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); } } }
In primo luogo, viene stampata l'intestazione come descrizione del modello e poi, se il flag di output è impostato su dettagliato, nel ciclo vengono stampate le descrizioni dettagliate di tutte le righe del modello di tabella.
Metodo che restituisce la descrizione dell'oggetto nel registro sotto forma di tabella:
//+------------------------------------------------------------------+ //| 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); } }
Per prima cosa, in base al numero di celle della prima riga della tabella, creare e stampare l'intestazione della tabella con i nomi delle colonne della tabella nel log. Quindi, passare in rassegna tutte le righe della tabella in un ciclo e stampare ciascuna di esse in forma tabellare.
Metodo che distrugge il modello di tabella:
//+------------------------------------------------------------------+ //| Destroy the model | //+------------------------------------------------------------------+ void CTableModel::Destroy(void) { //--- Clear cell list m_list_rows.Clear(); }
L'elenco delle righe della tabella viene semplicemente cancellato e tutti gli oggetti vengono distrutti con il metodo Clear() della classe CList.
Metodo per salvare il modello di tabella su file:
//+------------------------------------------------------------------+ //| 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; }
Dopo aver salvato il marcatore di inizio dati e il tipo di elenco, salvare l'elenco di righe nel file utilizzando il metodo Save() della classe CList.
Metodo per caricare il modello di tabella da un file:
//+------------------------------------------------------------------+ //| 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; }
Dopo aver caricato e controllato il marcatore di inizio dati e il tipo di elenco, caricare l'elenco di righe dal file usando il metodo Load() della classe CListObj, discusso all'inizio dell'articolo.
Tutte le classi per la creazione di un modello di tabella sono pronte. Ora scriviamo uno script per verificare il funzionamento del modello.
Verifica del risultato.
Continuare a scrivere il codice nello stesso file. Scrivete uno script in cui creeremo un array bidimensionale 4x4 (4 righe di 4 celle ciascuna) con il tipo, ad esempio, long. Quindi, aprire un file per scrivervi i dati del modello di tabella e caricare i dati nella tabella dal file. Creare un modello di tabella e verificare il funzionamento di alcuni dei suoi metodi.
Ogni volta che la tabella viene modificata, si registrerà il risultato ricevuto utilizzando la funzione TableModelPrint(), che seleziona come stampare il modello della tabella. Se il valore della macro PRINT_AS_TABLE è true, la registrazione viene eseguita con il metodo PrintTable() della classe CTableModel, se il valore è false - con il metodo Print() della stessa classe.
In uno script, creare un modello di tabella, stamparlo in forma tabellare e salvare il modello in un file. Quindi aggiungere righe, eliminare colonne e modificare i permessi di modifica...
Quindi scaricare nuovamente dal file la versione originale della tabella e stampare il risultato.
#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; }
Di conseguenza, nel log si ottiene il seguente risultato dello script:
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 |
Per testare il lavoro con il modello di tabella, l'aggiunta, l'eliminazione e lo spostamento di righe e colonne, il lavoro con un file, completate lo script:
#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 }
Otterrete questo risultato nel log:
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 |
Se si desidera inviare al log i dati non in forma tabellare dal modello di tabella, impostare false nella macro PRINT_AS_TABLE :
#define PRINT_AS_TABLE false // Display the model as a table
In questo caso, nel log viene visualizzato quanto segue:
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
Questo output fornisce ulteriori informazioni di debug. Ad esempio, qui vediamo che l'impostazione del flag di divieto di modifica di una cella viene visualizzata nei registri del programma.
Tutte le funzionalità indicate funzionano come previsto, le righe vengono aggiunte, spostate, le colonne eliminate, possiamo modificare le proprietà delle celle e lavorare con i file.
Conclusioni
Questa è la prima versione di un semplice modello di tabella che consente di creare una tabella da un array bidimensionale di dati. In seguito, creeremo altri modelli di tabelle specializzate, creeremo il componente View della tabella e, infine, lavoreremo a pieno titolo con i dati tabellari come uno dei controlli dell'interfaccia grafica dell'utente.
Il file dello script creato oggi con le classi incluse è allegato all'articolo. È possibile scaricarlo per lo studio autonomo.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/17653
Avvertimento: Tutti i diritti su questi materiali sono riservati a MetaQuotes Ltd. La copia o la ristampa di questi materiali in tutto o in parte sono proibite.
Questo articolo è stato scritto da un utente del sito e riflette le sue opinioni personali. MetaQuotes Ltd non è responsabile dell'accuratezza delle informazioni presentate, né di eventuali conseguenze derivanti dall'utilizzo delle soluzioni, strategie o raccomandazioni descritte.
Classi di Tabelle e Intestazioni basate su un modello di tabella in MQL5: Applicazione del concetto MVC
Passaggio a MQL5 Algo Forge (Parte 4): Lavorare con le Versioni e i Rilasci
I metodi di William Gann (Parte I): Creazione dell'indicatore Angoli di Gann
Previsioni economiche: Esplorare il potenziale di Python
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso
Quando una classe di SomeObject viene caricata da un file chiamando il metodo Load() di questo stesso SomeObject, esso controlla "mi sono davvero letto dal file?" (è quello che ci si chiede). In caso contrario, significa che qualcosa è andato storto, quindi è inutile continuare a caricare.
Si tratta di una LISTA (CListObj) che legge un tipo di oggetto da un file. La lista non sa cosa c'è (quale oggetto) nel file. Ma deve conoscere questo tipo di oggetto per crearlo nel suo metodo CreateElement(). Ecco perché non controlla il tipo dell'oggetto caricato dal file. Dopo tutto, ci sarà un confronto con Type(), che in questo metodo restituisce il tipo di una lista, non di un oggetto.
Grazie, ho capito, ho capito.
L'ho letto e poi riletto.
è tutto tranne che un "modello" in MVC. Alcuni ListStorage per esempio
Mi chiedo. È possibile ottenere un analogo dei dataframe di python e R in questo modo? Si tratta di tabelle in cui le diverse colonne possono contenere dati di tipo diverso (da un insieme limitato di tipi, ma che includono le stringhe).
È possibile. Se stiamo parlando di diverse colonne di una tabella, nell'implementazione descritta ogni cella della tabella può avere un tipo di dati diverso.