Progettare e implementare nuovi widget GUI basati sulla classe CChartObject
Introduzione
Dopo aver scritto un articolo sull’Expert Advisor semiautomatico con interfaccia GUI, ho realizzato che sarebbe stato opportuno migliorare l'interfaccia con alcune nuove funzionalità per gli indicatori e gli Expert Advisor più complessi. Dopo aver familiarizzato con le classi della libreria standard MQL5, ho implementato nuovi widget.
In questo articolo descriverò un processo per l’utilizzo delle classi della libreria standard MQL5 per gli oggetti GUI e spiegherò come implementare nuove classi derivate dalla classe CChartObjectEdit: CChartObjectProgressBar, CChartObjectSpinner e CChartEditTable. La classe CChartEditTable utilizza un array bidimensionale dinamico di oggetti. Questo è un esempio funzionante su come implementare un array 2D dinamico di oggetti in MQL5.
1. CChartObject e i suoi discendenti
Se non utilizziamo la classe della libreria MQL5 standard, dobbiamo utilizzare le Funzioni oggetto per creare e mantenere qualsiasi oggetto sul grafico.
Gli oggetti vengono creati con la funzione ObjectCreate() e il tipo di oggetto viene passato alla funzione ObjectCreate() come valore ENUM_OBJECT. Tutti gli oggetti nel grafico hanno le loro proprietà, le quali possono essere di tipo Integer, Double o String. Tutte le proprietà vengono impostate e recuperate tramite funzioni dedicate: ObjectGetInteger(), ObjectSetInteger(), ObjectGetDouble(), ObjectSetDouble(), ObjectGetString(), ObjectSetString(). Ci sono anche delle funzioni per eliminare, spostare e contareg li oggetti su un dato grafico.
A causa del paradigma OOP in MQL5, la gestione dei vari oggetti del grafico può essere eseguita utilizzando la classe CChartObject e i suoi discendenti.
La classe CChartObject è una classe base per qualsiasi oggetto grafico che può essere inserito all’interno di un grafico. Osserva il diagramma di ereditarietà di base per CChartObject riportato di seguito:
Figura 1. Diagramma di ereditarietà per la classe CChartObject
Come possiamo notare, ci sono alcune classi contrassegnate da un piccolo triangolo nell'angolo in basso a destra.
Queste sono classi che a loro volta sono genitori di altre classi. Fondamentalmente, le classi discendenti migliorano le possibilità di una classe base aggiungendo nuove variabili e nuovi metodi che operano sull'oggetto. Possono anche differire nei metodi Create() e Type() per creare un oggetto derivato e restituirne il tipo.
Lo dimostrerò con un esempio: La classe CChartObjectTrend è genitore delle classi CChartObjectTrendByAngle, CChartObjectChannel, CChartObjectStdDevChannel, CChartObjectRegression e CChartObjectPitchfork.
CChartObjectTrend è una classe base per gli oggetti che possiedono proprietà OBJPROP_RAY_RIGHT e OBJPROP_RAY_LEFT ed è definita come riportato di seguito:
class CChartObjectTrend : public CChartObject { public: //--- methods of access to properties of the object bool RayLeft() const; bool RayLeft(bool new_sel); bool RayRight() const; bool RayRight(bool new_sel); //--- method of creating the object bool Create(long chart_id,string name,int window, datetime time1,double price1,datetime time2,double price2); //--- method of identifying the object virtual int Type() const { return(OBJ_TREND); } //--- methods for working with files virtual bool Save(int file_handle); virtual bool Load(int file_handle); };
Ci sono dei commenti nella definizione che permettono di distinguere i diversi tipi di metodi.
I metodi di accesso alle proprietà dell'oggetto sono RayLeft() e RayRight(). La loro implementazione consiste nel chiamare i metodi ObjectGetInteger() e ObjectSetInteger() che operano sull'oggetto CChartObjectTrend.
bool CChartObjectTrend::RayLeft(bool new_ray) { //--- checking if(m_chart_id==-1) return(false); //--- return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT,new_ray)); }
Il metodo Create() è responsabile della creazione e del collegamento di oggetti sul grafico.
Chiama il metodo ObjectCreate() con OBJ_TREND come uno dei parametri:
bool CChartObjectTrend::Create(long chart_id,string name,int window, datetime time1,double price1,datetime time2,double price2) { bool result=ObjectCreate(chart_id,name,OBJ_TREND,window,time1,price1,time2,price2); if(result) result&=Attach(chart_id,name,window,2); //--- return(result); }
I metodi Save() e Load() archiviano e caricano i dati degli oggetti su un disco rigido utilizzando le funzioni FileWriteInteger() e FileLoadInteger():
bool CChartObjectTrend::Save(int file_handle) { bool result; //--- checking if(file_handle<=0) return(false); if(m_chart_id==-1) return(false); //--- writing result=CChartObject::Save(file_handle); if(result) { //--- writing value of the "Ray left" property if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name, OBJPROP_RAY_LEFT),CHAR_VALUE)!=sizeof(char)) return(false); //--- writing value of the "Ray right" property if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name, OBJPROP_RAY_RIGHT),CHAR_VALUE)!=sizeof(char)) return(false); } //--- return(result); } bool CChartObjectTrend::Load(int file_handle) { bool result; //--- checking if(file_handle<=0) return(false); if(m_chart_id==-1) return(false); //--- reading result=CChartObject::Load(file_handle); if(result) { //--- reading value of the "Ray left" property if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT, FileReadInteger(file_handle,CHAR_VALUE)))return(false); //--- reading value of the "Ray right" property if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_RIGHT, FileReadInteger(file_handle,CHAR_VALUE))) return(false); } //--- return(result); }
Esaminiamo rapidamente le definizioni di classi discendenti e di CChartObjectTrend.
La classe CChartObjectTrendByAngle aggiunge il modificatore di proprietà Angle() e restituisce il tipo di oggetto OBJ_TRENDBYANGLE:
class CChartObjectTrendByAngle : public CChartObjectTrend { public: //--- methods of access to properties of the object double Angle() const; bool Angle(double angle); //--- method of creating the object bool Create(long chart_id,string name,int window,datetime time1,double price1, datetime time2,double price2); //--- method of identifying the object virtual int Type() { return(OBJ_TRENDBYANGLE); } };
La classe CChartObjectChannel restituisce l’oggetto di tipo OBJ_CHANNEL e, poiché gestisce i canali, vengono passate tre coppie di parametri prezzo/data al metodo Create():
class CChartObjectChannel : public CChartObjectTrend { public: //--- method of creating the object bool Create(long chart_id,string name,int window,datetime time1,double price1, datetime time2,double price2,datetime time3,double price3); //--- method of identifying the object virtual int Type() const { return(OBJ_CHANNEL); } };
La classe CChartObjectStdDevChannel aggiunge il modificatore di proprietà Deviations() e il parametro di deviazione aggiuntivo nel metodo Create():
class CChartObjectStdDevChannel : public CChartObjectTrend { public: //--- methods of access to properties of the object double Deviations() const; bool Deviations(double deviation); //--- method of creating the object bool Create(long chart_id,string name,int window, datetime time1,datetime time2,double deviation); //--- method of identifying the object virtual int Type() const { return(OBJ_STDDEVCHANNEL); } //--- methods for working with files virtual bool Save(int file_handle); virtual bool Load(int file_handle); };
La classe CChartObjectRegression crea una linea di tendenza di regressione. Solo i metodi Create() e Type() vengono sovrascritti da quelli presenti nella classe CChartObjectTrend:
class CChartObjectRegression : public CChartObjectTrend { public: //--- method of creating the object bool Create(long chart_id,string name,int window,datetime time1,datetime time2); //--- method of identifying the object virtual int Type() const { return(OBJ_REGRESSION); } };
La classe CChartObjectPitchfork gestisce il tipo pitchfork. Inoltre vengono modificati solo i metodi Create() e Type():
class CChartObjectPitchfork : public CChartObjectTrend { public: //--- method of creating the object bool Create(long chart_id,string name,int window,datetime time1,double price1, datetime time2,double price2,datetime time3,double price3); //--- method of identifying the object virtual int Type() const { return(OBJ_CHANNEL); } };
Questa scansione rapida ha mostrato le regole base che vengono applicate quando si scrive una nuova classe di oggetti grafici basata su altre classi:
- modifica del metodo Create() per la creazione di oggetti
- modifica del metodo Type() per restituire il tipo di oggetto
- aggiunta di modificatori di accesso alle proprietà
Non si devono applicare tutte le regole. All’interno della classe si potrebbero anche solo aggiungere nuovi modificatori di accesso o nuove variabili e/o oggetti.
Prima di andare avanti, lascia che ti spieghi come utilizzare i metodi CChartObject sugli oggetti grafici.
Invece di utilizzare la famiglia di metodi ObjectSet e ObjectGet e utilizzare le proprietà dell'oggetto è sufficiente dichiarare CChartObject o un oggetto discendente e chiamare metodi che ne modificano le proprietà desiderate. Per facilitare il tutto, ti fornirò un esempio di etichetta ordinaria.
Invece di scrivere:
void OnStart() { //--- string label_name="my_OBJ_LABEL_object"; if(ObjectFind(0,label_name)<0) { Print("Object ",label_name," not found. Error code = ",GetLastError()); ObjectCreate(0,label_name,OBJ_LABEL,0,0,0); ObjectSetInteger(0,label_name,OBJPROP_XDISTANCE,200); ObjectSetInteger(0,label_name,OBJPROP_YDISTANCE,300); ObjectSetInteger(0,label_name,OBJPROP_COLOR,White); ObjectSetString(0,label_name,OBJPROP_TEXT,UP); ObjectSetString(0,label_name,OBJPROP_FONT,"Wingdings"); ObjectSetInteger(0,label_name,OBJPROP_FONTSIZE,10); ObjectSetDouble(0,label_name,OBJPROP_ANGLE,-45); ObjectSetInteger(0,label_name,OBJPROP_SELECTABLE,false); ChartRedraw(0); } }
Possiamo implementarlo usando il paradigma OOP:
1. Dichiara l'oggetto CChartObjectLabel:
CChartObjectLabel label;
2. Opera sull'oggetto:
int OnInit() { //--- label.Create(0, label_name, 0, 0); label.X_Distance(200); label.Y_Distance(300); label.Color(White); label.Description(UP); label.Font("Wingdings"); label.FontSize(10); label.Angle(-45); label.Selectable(false); //--- return(0); }
Come puoi vedere, la differenza principale è che non operiamo più su una stringa label_ name:
string label_name="my_OBJ_LABEL_object";
e chiamiamo le funzioni ObjectSetInteger(), ObjectGetInteger(), ObjectSetDouble(), ObjectGetDouble() con label_name come uno dei parametri, ma dichiariamo l'oggetto CChartObjectLabel e utilizziamo i suoi metodi. In questo modo non sarà solo più semplice da ricordare e logico da implementare, ma anche più veloce da scrivere.
L'editor di codice MQL5 fornisce funzionalità di completamento del codice quando si inserisce il punto (.) dopo l'istanza dell'oggetto. Non è necessario scorrere avanti e indietro la documentazione MQL5 per vedere quale proprietà OBJPROP inserire per impostare o ottenere una determinata proprietà.
Analogamente per la classe CChartObjectTrend descritta in precedenza, per ottenere o impostare un raggio sinistro o destro è sufficiente dichiarare l'oggetto CChartObjectTrend e chiamare il metodo RayRight() o RayLeft():
CChartObjectTrend trendline; trendline.RayRight(true);
2. ProgressBar
Il primo widget che implementeremo è il ProgressBar. Le barre di progresso mostrano l'avanzamento di alcune operazioni, da 0 a x percentuale.
Per renderlo più robusto, non limitiamo il valore massimo a 100, ma a qualsiasi valore intero positivo. Abbiamo bisogno di una striscia a colori che cambi le sue dimensioni in base al valore di avanzamento. La prima cosa che viene in mente è di usare due rettangoli, ma ho scelto un'altra opzione: usare due oggetti CChartObjectEdit, uno dentro l'altro, con colori di sfondo diversi.
Questo semplifica la codifica e aggiunge testo che può essere inserito nella barra di avanzamento per mostrarne il valore. Sarebbe bello se la nostra barra di progresso potesse essere orizzontale o verticale a seconda delle proprie esigenze.
2.1. Implementazione della ProgressBar
La classe CChartObjectProgress è derivata dalla classe CChartObjectEdit.
Ho aggiunto variabili interne private per mantenere il valore e i vincoli sul valore: m_value, m_min, m_max.
La direzione della barra di progresso è impostata come valore intero ed è mantenuta dalla variabile m_direction. Il colore è mantenuto dalla variabile m_color. Il metodo Type() restituisce il valore OBJ_EDIT, poiché non esiste comunque alcun valore riconosciuto per il nostro scopo. Si può notare la variabile m_bar di CChartObjectEdit all'interno della definizione della classe: questa è la barra interna che cambia le sue dimensioni a seconda di m_value. Le variabili aggiuntive m_name e m_chart contengono valori interni per la variabile m_bar.
class CChartObjectProgressBar : public CChartObjectEdit { private: int m_value; int m_min; int m_max; int m_direction; color m_color; CChartObjectEdit m_bar; string m_name; long m_chart_id; public: int GetValue(); int GetMin(); int GetMax(); void SetValue(int val); void SetMin(int val); void SetMax(int val); void SetColor(color bgcol,color fgcol); bool Create(long chart_id,string name,int window,int X,int Y, int sizeX,int sizeY,int direction); //--- method of identifying the object virtual int Type() const { return(OBJ_EDIT); } };
Il metodo Create() crea l'oggetto ProgressBar e lo collega al grafico.
Potrai notare che la variabile Y viene sottratta dalla variabile sizeY nel caso in cui venga disegnata una barra verticale. Questo perché normalmente la CChartObjectEdit viene disegnato dall'alto verso il basso mentre io volevo disegnare il rettangolo interno dal basso verso l'alto:
bool CChartObjectProgressBar::Create(long chart_id,string name,int window,int X,int Y, int sizeX,int sizeY,int direction=0) { bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0); m_name=name; m_chart_id=chart_id; m_direction=direction; if(direction!=0) { Y=Y-sizeY; } ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White); ObjectSetInteger(chart_id,name,OBJPROP_COLOR,White); ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true); result&=m_bar.Create(chart_id,name+"m_bar",window,X,Y,sizeX,sizeY); m_bar.Color(White); m_bar.ReadOnly(true); m_bar.Selectable(false); //--- if(result) result&=Attach(chart_id,name,window,1); result&=X_Distance(X); result&=Y_Distance(Y); result&=X_Size(sizeX); result&=Y_Size(sizeY); //--- return(result); }
Il metodo SetColor() imposta i colori di sfondo e in primo piano su entrambi i rettangoli:
void CChartObjectProgressBar::SetColor(color bgCol,color fgCol=White) { m_color=bgCol; m_bar.BackColor(m_color); m_bar.Color(fgCol); }
Il metodo SetValue() è responsabile sia dell'impostazione del valore m_val che del ricalcolo della dimensione dell'oggetto rettangolo interno.
La dimensione è calcolata in modo diverso per le barre orizzontali e verticali:
void CChartObjectProgressBar::SetValue(int val) { if(m_direction==0) // horizontal ProgressBar { double sizex=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_XSIZE,0); double stepSize=sizex/(m_max-m_min); m_value=val; m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(), m_bar.X_Distance(),m_bar.Y_Distance(),(int)MathFloor(stepSize*m_value),m_bar.Y_Size()); } else { double sizey=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_YSIZE,0); double stepSize=sizey/(m_max-m_min); m_value=val; m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(), m_bar.X_Distance(),(int)(this.Y_Distance()+sizey-MathFloor(stepSize*m_value)), m_bar.X_Size(),(int)MathFloor(stepSize*m_value)); } m_bar.Description(IntegerToString(m_value)); }
2.2. Demo sulla ProgressBar
Dato che abbiamo già implementato la classe CChartObjectProgressBar, è ora il momento di vederla in azione.
Per posizionare una nuova barra di progresso sul grafico è sufficiente dichiarare l'oggetto CChartObjectProgressBar e utilizzare Create() e i metodi di proprietà appropriati:
progressBar.Create(0, "progressBar1", 0, 10, 10, 200, 40); progressBar.SetColor(YellowGreen); progressBar.SetMin(0); progressBar.SetMax(100); progressBar.SetValue(0);
Ho realizzato una demo per l’Expert Advisor che posiziona sei barre di progresso diverse sul grafico e cambia il loro valore dopo che un oggetto viene cliccato sullo schermo.
Il codice sorgente completo per questa e altre demo si trova negli allegati. Guarda la seguente presentazione:
3. Spinner
Il widget Spinner è un widget che contiene un campo e due pulsanti. Viene utilizzato per aumentare o diminuire un valore nel campo di modifica cliccando su uno dei pulsanti.
Durante la progettazione dell'oggetto non volevo operare solo su valori interi, quindi Spinner è stato progettato per operare su un doppio tipo. Spinner ha anche la possibilità di definire la dimensione del passo, cioè il valore per aumentare o diminuire il valore corrente. Dovrebbe anche avere un valore minimo e massimo che non può essere attraversato.
3.1. Implementazione di Spinner
In MQL5 abbiamo le classi CChartObjectEdit e CChartObjectButton, le quali possono essere combinate in una classe CChartObjectSpinner. CChartObjectSpinner eredita da CChartObjectEdit e contiene due oggetti membri privati CChartObjectButton.
Sono presenti dei vincoli per m_value minimo e massimo archiviati nelle variabili membro m_min e m_max, mentre la variabile m_precision memorizza la precisione di calcolo all'ennesima cifra. I metodi necessari sono quelli per l'accesso al valore, l'impostazione della dimensione del passo di incremento e decremento e l'impostazione del valore.
class CChartObjectSpinner: public CChartObjectEdit { private: double m_value; double m_stepSize; double m_min; double m_max; int m_precision; string m_name; long m_chart_id; CChartObjectButton m_up,m_down; public: double GetValue(); double GetMin(); double GetMax(); void SetValue(double val); void SetMin(double val); void SetMax(double val); double Inc(); double Dec(); bool Create(long chart_id,string name,int window,int X,int Y, int sizeX,int sizeY,double val,double stepSize,int precision); //--- method of identifying the object virtual int Type() const { return(OBJ_EDIT); } };
Il metodo Create() crea un nuovo CChartObjectSpinner e lo collega al grafico.
Ci sono due CChartObjectButton creati sul lato destro di CChartObjectEdit, ognuno con un'altezza pari alla metà dell'altezza di CChartObjectEdit.
Il pulsante di incremento riporta il segno '+', mentre il pulsante di decremento riporta il segno '-'.
bool CChartObjectSpinner::Create(long chart_id,string name,int window,int X,int Y, int sizeX,int sizeY,double val=0.0,double stepSize=1.0,int precision=8) { bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0); m_name=name; m_chart_id=chart_id; m_value=val; m_stepSize=stepSize; m_precision=precision; ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White); ObjectSetInteger(chart_id,name,OBJPROP_COLOR,Black); ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true); result&=m_up.Create(chart_id, name+"_up", window, X+sizeX, Y, 15, sizeY/2); result&=m_down.Create(chart_id, name+"_down", window, X+sizeX, Y+sizeY/2, 15, sizeY/2); m_up.Description("+"); m_down.Description("-"); ObjectSetString(chart_id,name,OBJPROP_TEXT,0,(DoubleToString(m_value,precision))); //--- if(result) result&=Attach(chart_id,name,window,1); result&=X_Distance(X); result&=Y_Distance(Y); result&=X_Size(sizeX); result&=Y_Size(sizeY); //--- return(result); }
Il metodo SetValue() imposta la variabile privata m_value su un valore doppio a condizione che rientri nell'intervallo <m_min, m_max>.
void CChartObjectSpinner::SetValue(double val) { if(val>=m_min && val<=m_max) m_value=val; this.Description(DoubleToString(m_value)); }
Il metodo Inc() incrementa il valore in base alla dimensione del passo, ma non più del valore m_max.
Da notare che ho dovuto usare la funzione NormalizeDouble() per confrontare i valori doppi con una specifica precisione.
double CChartObjectSpinner::Inc(void) { if(NormalizeDouble(m_max-m_value-m_stepSize,m_precision)>0.0) m_value+=m_stepSize; else m_value=m_max; this.Description(DoubleToString(m_value, m_precision)); m_up.State(false); return m_value; }
Il metodo Dec() decrementa il valore in base alla dimensione del passo data, ma non meno del valore m_min.
double CChartObjectSpinner::Dec(void) { if(NormalizeDouble(m_value-m_stepSize-m_min,m_precision)>0.0) m_value-=m_stepSize; else m_value=m_min; this.Description(DoubleToString(m_value,m_precision)); m_down.State(false); return m_value; }
3.2. Demo su Spinner
È il momento di testare gli oggetti Spinner. Per utilizzarli è sufficiente dichiarare l'oggetto CChartObjectSpinner e utilizzare i metodi Create(), SetMin() e SetMax().
spinner.Create(0, "spinner1", 0, 10, 10, 200, 40, 0.0, 0.4); spinner.SetMin(0); spinner.SetMax(100);
Ho preparato una demo che utilizza tre widget Spinner e aggiunge tutti i valori dopo aver cliccato su qualsiasi pulsante Spinner.
Questo avviene all'interno della funzione OnChartEvent();
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Check the event by pressing a mouse button if(id==CHARTEVENT_OBJECT_CLICK) { if (sparam=="spinner1_up") spinner.Inc(); if (sparam=="spinner1_down") spinner.Dec(); if (sparam=="spinner2_up") spinner2.Inc(); if (sparam=="spinner2_down") spinner2.Dec(); if (sparam=="spinner3_up") spinner3.Inc(); if (sparam=="spinner3_down") spinner3.Dec(); label.Description(DoubleToString(NormalizeDouble(spinner.GetValue()+spinner2.GetValue()+spinner3.GetValue(),10),10)); ChartRedraw(); } }
Guarda la demo in allegato:
4. CChartObjectEditTable
In molti Expert Advisor multi time frame (MTF) ci sono valori di indicatore visualizzati separatamente per ogni time frame.
A volte, ciascun intervallo di tempo ha diverse impostazioni dell'indicatore visualizzate sotto forma di tabella 2D di rettangoli o quadrati di colori diversi. Ho progettato una tabella 2D universale per questi oggetti creando la classe CChartObjectEditTable. Questa classe può contenere una quantità arbitraria di righe e colonne dato che sto utilizzando un array dinamico di oggetti 2D.
Durante la progettazione, ho deciso di definire separatamente un colore per ogni cella e aggiungere anche la possibilità di inserire stringhe di testo diverse su qualsiasi cella. Le celle hanno le stesse dimensioni, ma ho voluto definirne l’altezza, la larghezza e lo spazio tra loro.
La classe CChartObjectEditTable contiene il puntatore CArrayObj a un array bidimensionale di oggetti e le variabili membro m_rows e m_columns contengono il numero di righe e colonne nella tabella.
Esiste una variabile membro m_baseName che contiene il prefisso di tutti gli oggetti CChartObjectEdit della cella all'interno della tabella. I metodi GetColor(), SetColor(), GetText(), SetText() servono per impostare e ottenere valori di colore e testo nella cella desiderata. Il metodo Delete() elimina tutti gli oggetti creati dal metodo Create().
class CChartObjectEditTable { private: CArrayObj *array2D; int m_rows; int m_cols; string m_baseName; public: bool Create(long chart_id,string name,int window,int rows,int cols,int startX,int startY, int sizeX,int sizeY,color Bg,int deltaX,int deltaY); bool Delete(); bool SetColor(int row,int col,color newColor); color GetColor(int row,int col); bool SetText(int row,int col,string newText); string GetText(int row,int col); };
Il metodo Create() crea due tabelle dinamiche bidimensionali di oggetti CChartObjectEdit.
Osserva come creare un array 2D di oggetti in MQL5: prima dichiariamo un puntatore all'array 2D e poi riempiamo l'array con un numero di oggetti CArrayObj(), ovvero creiamo degli array all'interno dell'array. Tutti gli array possono essere visti come contenitori per le colonne della tabella.
Ogni colonna contiene delle righe che a loro volta contengono gli oggetti CChartObjectEdit, dove ciascun oggetto è una singola cella da visualizzare.
bool CChartObjectEditTable::Create(long chart_id,string name,int window,int rows=1,int cols=1, int startX=0,int startY=0,int sizeX=15,int sizeY=15, color Bg=White,int deltaX=5,int deltaY=5) { m_rows=rows; m_cols=cols; m_baseName=name; int i=0,j=0; array2D=new CArrayObj(); if (array2D==NULL) return false; for(j=0; j<m_cols; j++) { CArrayObj *new_array=new CArrayObj(); if (array2D==NULL) return false; array2D.Add(new_array); for(i=0; i<m_rows; i++) { CChartObjectEdit *new_edit=new CChartObjectEdit(); new_edit.Create(chart_id, name+IntegerToString(i)+":"+IntegerToString(j), window, startX+j*(sizeX+deltaX), startY+i*(sizeY+deltaY), sizeX, sizeY); new_edit.BackColor(Bg); new_edit.Color(White); new_edit.Selectable(false); new_edit.ReadOnly(true); new_edit.Description(""); new_array.Add(new_edit); } } return true; }
Il metodo SetColor() imposta il colore di ogni cella. All'inizio trova l'array di colonne e poi l'ennesimo elemento nell'array di colonne.
Quindi il valore del colore dell'elemento viene modificato ricorrendo al metodo BackColor().
bool CChartObjectEditTable::SetColor(int row,int col,color newColor) { CArrayObj *sub_array; CChartObjectEdit *element; if((row>=0 && row<m_rows) && (col>=0 && col<m_cols)) { if(array2D!=NULL) { sub_array=array2D.At(col); element=(CChartObjectEdit*)sub_array.At(row); element.BackColor(newColor); return true; } } return false; }
Il metodo GetColor() ha lo stesso algoritmo per trovare la cella del metodo SetColor(), ma restituisce il valore del colore di una determinata cella.
color CChartObjectEditTable::GetColor(int row,int col) { CArrayObj *sub_array; CChartObjectEdit *element; if((row>=0 && row<m_rows) && (col>=0 && col<m_cols)) { if(array2D!=NULL) { sub_array=array2D.At(col); element=(CChartObjectEdit*)sub_array.At(row); return element.BackColor(); } } return NULL; }
Il metodo SetText() trova l'elemento e imposta il suo valore di testo ricorrendo al metodo Description().
bool CChartObjectEditTable::SetText(int row,int col,string newText) { CArrayObj *sub_array; CChartObjectEdit *element; if((row>=0 && row<m_rows) && (col>=0 && col<m_cols)) { if(array2D!=NULL) { sub_array=array2D.At(col); element=(CChartObjectEdit*)sub_array.At(row); element.Description(newText); return true; } } return false; }
Il metodo Delete() elimina tutti gli oggetti creati dal metodo Create().
All'inizio cancella tutti gli array di colonne e poi cancella l'oggetto array2D dalla memoria.
bool CChartObjectEditTable::Delete(void) { for(int j=0; j<m_cols; j++) { CArrayObj *column_array=array2D.At(j); column_array.Clear(); delete column_array; } delete array2D; return true; }
4.2. Demo su CChartObjectEditTable
Per utilizzare il widget CChartObjectEditTable è necessario dichiarare l'oggetto CChartEditTable e utilizzare il metodo Create() con parametri che indicano quante righe e colonne deve contenere la tabella.
Quindi, utilizzando i modificatori di proprietà, è possibile modificare semplicemente il colore e il testo su qualsiasi cella.
table.Create(0,"t",0,1,10,10,10,15,15,Yellow); table.SetColor(2,2,Red); table.SetText(2,2,"2");
Guarda lo script che ho preparato e che dimostra le possibilità di utilizzo dell'oggetto CChartObjectEditTable.
In allegato troverai il codice sorgente dello script.
Conclusione
Nell’articolo ho descritto e introdotto un processo di creazione di nuovi widget grafici derivati dalla classe CChartObject.
Il processo di utilizzo dei widget implementati è molto semplice e richiede solo poche righe di codice.
Per utilizzare i widget, includi il file ChartObjectsExtControls.mqh sull’Expert Advisor o il codice dell'indicatore.
Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/196
- 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