Diseñar e implementar nuevos widgets GUI basados en la clase CChartObject
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:
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_RIGHT y OBJPROP_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.
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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso