English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Diseñar e implementar nuevos widgets GUI basados en la clase CChartObject

Diseñar e implementar nuevos widgets GUI basados en la clase CChartObject

MetaTrader 5Indicadores | 1 abril 2014, 15:36
860 0
investeo
investeo

Introducción

Tras haber escrito antes un artículo sobre un Expert Advisor semiautomático con un interfaz GUI, me he dado cuenta de la conveniencia de mejorar el interfaz con algunas características nuevas para indicadores y Expert Advisors más complejos. Y después de familiarizarme con las clases de la librería estándar de MQL5, he implementado unos widgets nuevos.

En este artículo voy a describir el proceso de uso de las clases de la Librería estándar de MQL5 para los objetos GUI y el modo de implementar nuevas clases derivadas de la clase CChartObjectEdit: CChartObjectProgressBar, CChartObjectSpinner y CChartEditTable. La clase CChartEditTable usa una matriz de objetos dinámica de dos dimensiones, este es un ejemplo práctico de cómo implementar una matriz de objetos dinámica 2D en MQL5.

 

1. CChartObject y sus descendientes

Si no usamos la clase de la librería estándar de MQL5, tenemos que utilizar Funciones de objetos para crear y mantener los objetos en el gráfico.

Se crean los objetos mediante la función ObjectCreate(), y se pasa el tipo de objeto a la función ObjectCreate() como un valor ENUM_OBJECT. Todos los objetos en el gráfico tienen sus propias propiedades, que pueden ser del tipo Integer (entero), Double (doble precisión), o String (cadena). Se establecen y se recuperan todas las propiedades mediante funciones dedicadas: ObjectGetInteger(), ObjectSetInteger(), ObjectGetDouble(), ObjectSetDouble(), ObjectGetString(), ObjectSetString(). También hay funciones para eliminar, mover y contar objetos de un gráfico determinado. 

Debido al paradigma de la Programación orientada a objetos (POO) en MQL5, se pueden manejar varios objetos mediante la clase CChartObject y sus descendientes.

La clase CChartObject es una clase base para cualquiera de los objetos gráficos que se puedan colocar en el gráfico. Observe el siguiente diagrama básico de herencia de CChartObject:

 

Diagrama de herencia de la clase CChartObject 

Figura 1. Diagrama de herencia de la clase CChartObject


Como podemos apreciar, hay pocas clases marcadas con un pequeño triangulo en la esquina inferior derecha.

Se trata de las clases padres de otras clases.  Básicamente, las clases descendientes mejoran las posibilidades de una clase base agregando nuevas variables y métodos que actúan sobre el objeto. También pueden diferenciarse en los métodos Create() y Type() para crear un objeto derivado y devolver su tipo.

Permítame que lo muestre mediante un ejemplo: CChartObjectTrend es una clase padre de CChartObjectTrendByAngle, CChartObjectChannel, CChartObjectStdDevChannel, CChartObjectRegression y de las clases CChartObjectPitchfork.

CChartObjectTrend es una clase base para los objetos que tengan las propiedades OBJPROP_RAY_RIGHTOBJPROP_RAY_LEFT y se define a continuación:

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);
  };

Hay comentarios en la definición que permiten distinguir entre los distintos tipos de métodos.

Los métodos de acceso a las propiedades del objeto son RayLeft() y RayRight(). Su implementación sirve para llamar a los métodos ObjectGetInteger() y ObjectSetInteger() que actúan sobre un objeto 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));
  }

El método Create() se encarga de crear y adjuntar el objeto al gráfico.

Llama al método ObjectCreate() mediante OBJ_TREND como uno de los parámetros:

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);
  }

Los métodos Save() y Load() almacenan y cargan los datos del objeto en el disco duro mediante las funciones FileWriteInteger() y 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);
  }

Vamos a ver rápidamente las definiciones de las clases descendientes de CChartObjectTrend.

La clase CChartObjectTrendByAngle añade el modificador de propiedad Angle(), y devuelve el tipo de objeto 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 clase CChartObjectChannel devuelve el tipo de objeto OBJ_CHANNEL y puesto que se encarga de los canales, se envían al método Create() tres pares de parámetros precio/datos:

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 clase CChartObjectStdDevChannel añade el modificador de propiedad Deviations() y un parámetro de desviación adicional al método 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);
  };

Las clase CChartObjectRegression crea la línea de regresión de la tendencia, solo cambian los métodos Create() y Type() en comparación con aquellos de la clase 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 clase CChartObjectPitchfork maneja el tipo de tridente (pitchfork), además, solo cambian los métodos Create() and 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); }
  };

 Este análisis rápido muestra las reglas básicas aplicadas a la escritura de una nueva clase de objetos gráficos basada en otra clase:

  • cambiar el método Create() para crear un objeto
  • cambiar el método Type() para devolver el tipo de objeto
  • añadir modificadores de acceso a las propiedades 

No se deben aplicar todas las reglas, solo podemos añadir nuevos modificadores de acceso o añadir nuevas variable y/o objetos a la clase.

Antes de continuar, permítame explicar cómo usar los métodos CChartObject con objetos gráficos.

En lugar de utilizar la familia de métodos ObjectSet y ObjectGet y utilizar las propiedades de objetos, basta con declarar CChartObject o un objeto descendiente y llamar a los métodos que modifican las propiedades requeridas del mismo. Para facilitar las cosas, proporciono el ejemplo de una etiqueta común. 

En lugar de escribir:

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);                                      
     }
  }

Podemos implementarlo mediante un modelo de POO:

1. Declaramos el objeto CChartObjectLabel:

CChartObjectLabel label;

2. Manejamos el objeto: 

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);
  }

Como puede observar, la principal diferencia es que ya no estamos operando sobre cadenas label_name: 

string label_name="my_OBJ_LABEL_object";

y llamamos a las funciones ObjectSetInteger(), ObjectGetInteger(), ObjectSetDouble(), ObjectGetDouble() con label_name como uno de los parámetros, pero declaramos el objeto CChartObjectLabel y usamos sus métodos. Esto no es solo más sencillo de recordar y más lógico de implementar, sino también que se escribe más rápidamente.

El editor de código de MQL5 nos proporciona la funcionalidad de completar el código al poner un punto (.) después de la instancia del objeto. No hay ninguna necesidad de recorrer la documentación de MQL5 repetidamente para ver qué propiedad OBJPROP colocar en la orden para definir u obtener una propiedad determinada.

Del mismo modo para la clase CChartObjectTrend, descrita anteriormente, para obtener o definir una línea hacia la derecha o hacia la izquierda, basta con declarar el objeto CChartObjectTrend y llamar al método RayRight() o RayLeft():

CChartObjectTrend trendline;
trendline.RayRight(true); 


2. ProgressBar

El primer widget que vamos a implementar es ProgressBar. Las barras de progreso muestran el progreso de alguna operación, desde 0 hasta x por ciento.

Para que el widget sea más robusto, no vamos a limitar el valor máximo a 100, sino a cualquier valor entero positivo. Necesitamos una raya de color que va cambiando de tamaño en función de la evolución del valor. Lo primero que se me ocurrió es el uso de dos rectángulos, pero escogí otra vía: usar dos objetos CChartObjectEdit, el uno dentro del otro, con distintos colores de fondo.

Simplifica la codificación y añade textos que pueden colocarse en la barra de progreso para mostrar sus valores. Estaría bien si la barra pudiera ser horizontal o vertical según las necesidades de cada uno.


2.1. Implementación de ProgressBar

La clase CChartObjectProgress es una derivada de la clase CChartObjectEdit.

He añadido unas variables internas privadas para contener el valor y sus límites: m_value, m_min, m_max.

Se establece la dirección de la barra de progreso como un valor entero y se almacena en la variable m_direction. Se almacena el color en la variable m_color. El método Type() devuelve el valor OBJ_EDIT, ya que en cualquier caso, no hay ningún valor registrado para nuestro objetivo. Se puede apreciar la variable m_bar CChartObjectEdit en la definición de la clase; esta es la barra interna que cambia su tamaño en función de m_value. Las variables adicionales m_name y m_chart guardan internamente los valores de la variable 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); }
};

El método Create() crea el objeto ProgressBar y lo adjunta al gráfico.

Puede apreciar que la variable Y se resta de la variable sizeY en el caso de dibujar una barra vertical, esto se debe a que en general, CChartObjectEdit se dibuja desde arriba hacia abajo, y que quería dibujar el rectángulo interior desde abajo hacia arriba:

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);
  }

El método SetColor() establece los colores del fondo y del frente de ambos rectángulos:

void CChartObjectProgressBar::SetColor(color bgCol,color fgCol=White)
  {
   m_color=bgCol;
   m_bar.BackColor(m_color);
   m_bar.Color(fgCol);
  }

El método SetValue() se encarga a la vez de establecer el valor de m_val y de volver a calcular el tamaño del objeto del rectángulo interior.

Se calcula el tamaño de manera distinta para las barras horizontales y verticales:

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. Demostración de ProgressBar

Ahora que hemos implementado la clase CChartObjectProgressBar, es el momento de ver su funcionamiento.

Para colocar una nueva barra de progreso en el gráfico, basta con declarar el objeto CChartObjectProgressBar y utilizar Create() y los métodos apropiados de propiedades:

progressBar.Create(0, "progressBar1", 0, 10, 10, 200, 40);
progressBar.SetColor(YellowGreen);
progressBar.SetMin(0);
progressBar.SetMax(100);
progressBar.SetValue(0);

Implementé un Expert Advisor de demostración que coloca seis barras de progreso distintas en el gráfico y cambia sus valores al hacer un clic encima de cualquier objeto de la pantalla.

El código fuente completo de esta y otras demostraciones están adjuntos a este artículo, observe la siguiente demostración: 

 


3. Spinner

El widget Spinner contiene un campo y dos botones. Se usa para aumentar o disminuir el valor en el campo de edición haciendo clic en uno de los botones.

Al diseñar el objeto, no quería trabajar solo con valores enteros (integer), así que Spinner fue diseñado para trabajar con valores de tipo doble precisión (double). Además, Spinner ofrece la posibilidad de definir el tamaño de los pasos, es decir, el valor a incrementar o reducir del valor actual. También debe tener un valor mínimo y máximo que no se puede cruzar.


3.1. Implementación de Spinner

En MQL5 tenemos las clases CChartObjectEdit y CChartObjectButton, que se pueden combinar en la clase CChartObjectSpinner. CChartObjectSpinner se hereda de CChartObjectEdit y contiene dos objetos CChartObjectButton miembro privados.

Hay restricciones para los valores mínimo y máximo m_value almacenados en las variables miembro m_min y m_max, y las variables m_precision almacenan la precisión del cálculo en el enésimo valor del dígito. Se necesitan métodos para acceder al valor, definir el tamaño del paso del aumento y la disminución y definir el valor.

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); }
   
  };

El método Create() crea un nuevo CChartObjectSpinner y lo adjunta al gráfico.

Hay dos CChartObjectButtons creados en el lado derecho de CChartObjectEdit, cada uno tiene la mitad de la altura de CChartObjectEdit.

El botón de incremento tiene el signo "+" y el botón de disminución el signo "-".

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);
  }

El método SetValue() establece la variable privada m_value a un valor de doble precisión (double) con la condición de que se encuentre en el rango <m_min, m_max>.

void CChartObjectSpinner::SetValue(double val)
  {
   if(val>=m_min && val<=m_max) m_value=val;
   this.Description(DoubleToString(m_value));
  }

El método Inc() incrementa el valor con el tamaño del paso indicado, pero sin superar el valor de m_max.

Tome nota que tengo que usar la función NormalizeDouble() para comparar los valores de tipo double con la precisión indicada.

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;
  }

El método Dec() disminuye el valor con el tamaño del paso indicado, pero sin ser inferior al valor de 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. Demostración de Spinner

Es la hora de probar los objetos Spinner. Para poder utilizarlos, basta con declarar el objeto CChartObjectSpinner y usar los métodos Create(), SetMin() y SetMax().

   spinner.Create(0, "spinner1", 0, 10, 10, 200, 40, 0.0, 0.4);
   spinner.SetMin(0);
   spinner.SetMax(100);

He preparado una demostración que usa tres widgets Spinner y añade todos los valores después de hacer clic en cualquier botón.

Esto se hace dentro de la función 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();
     }
  }


 Consulte la siguiente demostración:

 

 

4. CChartObjectEditTable

En muchos Expert Advisors con múltiples períodos temporales (MTF), se muestran los valores del indicador para cada período de tiempo por separado.

A veces, en cada período de tiempo hay una configuración distinta del indicador, que se muestra en forma de tabla 2D o rectángulos o cuadrados o varios colores. He diseñado una tabla 2D universal de dichos objetos mediante la creación de la clase CChartObjectEditTable. Esta clase puede tener un número de filas y columnas aleatorio, puesto que estoy utilizando una matriz de objetos dinámica 2D.

Durante el diseño he decidido definir el color de cada celda por separado y también añadir la posibilidad de colocar distintas cadenas de texto en cualquier celda. Las celdas tienen el mismo tamaño, pero he querido definir su altura y su anchura además del espacio entre las mismas.


4.1. Implementación de CChartObjectEditTable

La clase CChartObjectEditTable contiene el puntero CArrayObj para una matriz de objetos de dos dimensiones, y las variables miembro m_rows y m_columns contienen el número de filas y columnas en la tabla.

Hay una variable miembro m_baseName que contiene el prefijo de todas las celdas de los objetos CChartObjectEdit en la tabla. Los métodos GetColor(), SetColor(), GetText(), SetText() son para definir y obtener los valores del color y del texto de cualquier celda. El método Delete() borra todos los objetos creados mediante el método 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);


  };

El método Create() crea una tabla dinámica de dos dimensiones de los objetos CChartObjectEdit.

Observe cómo crear una matriz de objetos 2D en MQL5: en primer lugar, declaramos un puntero para la matriz 2D y luego rellenamos la matriz con el número de objetos CArrayObj(), es decir, creamos matrices dentro de la matriz. Se pueden considerar todas las matrices como sustentadoras de las columnas de la tabla.

Cada columna tiene filas que contienen los objetos CChartObjectEdit, siendo cada objeto una celda única a mostrar. 

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;
  }

El método SetColor() define el color de cualquier celda única. Al principio, encuentra la matriz de la columna y luego el enésimo elemento en la matriz de la columna.

A continuación, se cambia el valor del color del elemento llamando al método 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;
  }

El método GetColor() tiene el mismo algoritmo que SetColor() para encontrar la celda, pero devuelve el valor del color de cualquier celda. 

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;
  }

El método SetText() encuentra el elemento y define el valor de su texto llamando al método 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;
  }

El método Delete() borra todos los objetos creados mediante el método Create().

En primer lugar, borra todas las matrices de columnas y luego elimina el objeto array2D de la 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. Demostración de CChartObjectEditTable

Para poder utilizar el widget CChartObjectEditTable hay que declarar el objeto CChartEditTable e usar el método Create() con los parámetros que indican el número de filas y columnas que debe contener la tabla.

Luego, mediante los modificadores de propiedades podemos simplemente cambiar el color y el texto de cualquier celda.

table.Create(0,"t",0,1,10,10,10,15,15,Yellow);
table.SetColor(2,2,Red);
table.SetText(2,2,"2");

Observe el script que he preparado y que demuestra las posibilidades que ofrece el uso del objeto CChartObjectEditTable.

El código fuente del script está en los adjuntos.

 


Conclusión

En este artículo, he descrito y presentado el proceso de creación de nuevos widgets del gráfico, derivados de la clase CChartObject

El proceso de uso de los widgets implementados es muy sencillo y solo ocupa unas pocas líneas de código.

Para usar los widgets, incluya el archivo ChartObjectsExtControls.mqh en el código del Expert Advisor o el indicador.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/196

Archivos adjuntos |
progressbarea.mq5 (3.67 KB)
spinnerdemoea.mq5 (2.83 KB)
edittabledemo.mq5 (4.15 KB)
Cálculos paralelos en MetaTrader 5 Cálculos paralelos en MetaTrader 5
El tiempo ha tenido un gran valor a lo largo de la historia de la humanidad, y nos esforzamos en no desperdiciarlo innecesariamente. En este artículo, se le va a mostrar cómo acelerar el funcionamiento de su Expert Advisor si su ordenador dispone de un procesador de núcleo múltiple. Además, la implementación del método propuesto no requiere el conocimiento de ningún otro lenguaje aparte de MQL5.
Implementación de un Expert Advisor tipo "arrastrar y soltar" semiautomático e interactivo basado en el riesgo predefinido y la relación R/R (riesgo/beneficio) Implementación de un Expert Advisor tipo "arrastrar y soltar" semiautomático e interactivo basado en el riesgo predefinido y la relación R/R (riesgo/beneficio)
Algunos operadores realizan todas sus operaciones de forma automática, y algunos hacen una mezcla de operaciones automáticas y manuales basadas ​​en las salidas de varios indicadores. Y como miembro de este último grupo, necesitaba una herramienta interactiva para poder evaluar de forma dinámica los niveles de riesgo y de beneficio, directamente desde el gráfico. En este artículo vamos a presentar una forma de implementación de un Expert Advisor con un riesgo de pérdida de patrimonio y relación R/R predefinidos. Se pueden modificar los parámetros de riesgo, R/R y el tamaño del lote durante la ejecución en el panel del EA.
Dibujar los canales; visión interna y externa Dibujar los canales; visión interna y externa
Supongo que no es ninguna exageraci&oacute;n decir que los canales representan la segunda herramienta m&aacute;s popular para el an&aacute;lisis del mercado y la toma de decisiones de trading por detr&aacute;s de los promedios m&oacute;viles. Sin profundizar demasiado en los detalles de las estrategias de trading que usan los canales y sus componentes, vamos a hablar de la base matem&aacute;tica y pr&aacute;ctica de la implementaci&oacute;n de un indicador, que dibuja un canal definido por tres extremos en la pantalla del terminal de cliente.
Análisis de las Características Principales de las Series Cronológicas Análisis de las Características Principales de las Series Cronológicas
Este artículo presenta una clase diseñada para dar un cálculo rápido preliminar de características de varias series cronológicas. Mientras esto se lleva a cabo se calculan parámetros estadísticos y la función de autocorrelación, se lleva a cabo un cálculo espectral de series cronológicas y se construye un histograma.