English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Progettare e implementare nuovi widget GUI basati sulla classe CChartObject

Progettare e implementare nuovi widget GUI basati sulla classe CChartObject

MetaTrader 5Indicatori | 17 dicembre 2021, 14:41
69 0
investeo
investeo

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:

 

Diagramma di ereditarietà per la classe CChartObjectDiagramma 

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.


4.1. Implementazione di CChartObjectEditTable

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

File allegati |
progressbarea.mq5 (3.67 KB)
spinnerdemoea.mq5 (2.83 KB)
edittabledemo.mq5 (4.15 KB)
Calcoli paralleli su MetaTrader 5 Calcoli paralleli su MetaTrader 5
Il tempo ha sempre avuto un grande valore in tutta la storia dell'umanità e noi ci sforziamo di non sprecarlo inutilmente. Questo articolo ti dirà come accelerare il lavoro del tuo Expert Advisor se il tuo computer ha un processore multi-core. Inoltre, l'implementazione del metodo proposto non richiede la conoscenza di altri linguaggi oltre a MQL5.
Creare un Expert Advisor interattivo semiautomatico drag-and-drop basato su rischio predefinito e rapporto R/R Creare un Expert Advisor interattivo semiautomatico drag-and-drop basato su rischio predefinito e rapporto R/R
Alcuni trader eseguono automaticamente tutte le loro operazioni, mentre alcuni combinano operazioni automatiche e manuali in base all'output dei diversi indicatori. Facendo parte di quest'ultimo gruppo, avevo bisogno di uno strumento interattivo per valutare dinamicamente il rischio e i livelli di prezzo del rendimento direttamente dal grafico. Questo articolo presenterà un modo per implementare un Expert Advisor interattivo semiautomatico con rischio azionario predefinito e rapporto R/R. I parametri di rischio, R/R e dimensione del lotto dell’Expert Advisor possono essere modificati durante l’esecuzione sul pannello EA.
Disegnare i canali - Vista interna ed esterna Disegnare i canali - Vista interna ed esterna
Suppongo che non risulterà esagerato dire che i canali sono lo strumento più popolare per l'analisi del mercato e per prendere decisioni di trading dopo le medie mobili. Senza andare a fondo nella moltitudine delle strategie di trading che utilizzano i canali e i loro componenti, discuteremo le basi matematiche e l'implementazione pratica di un indicatore, il quale disegna un canale determinato da tre estremi sullo schermo del client terminal.
Un Avvio Rapido o una Breve Guida per Principianti Un Avvio Rapido o una Breve Guida per Principianti
Ciao caro lettore! In questo articolo, cercherò di spiegarti e mostrarti come puoi imparare facilmente e rapidamente i principi della creazione dell’Expert Advisor, lavorare con gli indicatori, ecc. È rivolto ai principianti e non presenterà alcun esempio difficile o astruso.