Discusión sobre el artículo "Cómo crear un panel informativo para mostrar datos en indicadores y asesores"

 

Artículo publicado Cómo crear un panel informativo para mostrar datos en indicadores y asesores:

En el presente artículo consideraremos la creación de una clase de panel informativo para utilizarla en indicadores y asesores. Este será el artículo introductorio de una pequeña serie de artículos con plantillas para la conexión y el uso de indicadores estándar en asesores. Empezaremos creando un panel, un análogo de la ventana de datos de MetaTrader 5.

Hoy comenzaremos creando un panel informativo que muestre los datos especificados por el desarrollador. Un panel de este tipo será cómodo para representar visualmente los datos en el gráfico y realizar la depuración visual, cuando resulta más rápido mirar los valores de interés de los datos en el panel que seguirlos en el depurador. Es decir, en aquellos casos en los que estamos depurando una estrategia que depende de los valores de algunos datos, y no cuando se depura código en el depurador.

Implementaremos el panel como un prototipo de la ventana de datos en el terminal y lo rellenaremos con los mismos datos:

Fig.1 Ventana de datos y panel informativo

Nuestro panel personalizado nos permitirá añadirle los datos que deseemos, firmarlos (ponerles un título) y mostrar y actualizar las métricas desde el código de nuestro programa.

Podremos mover el panel por el gráfico con el ratón, fijarlo en el lugar deseado del gráfico y minimizarlo/desplegarlo. También habrá una opción para mostrar una tabla con un número especificado de filas y columnas en el panel para facilitar la disposición de nuestros datos. Los datos de esta tabla se podrán imprimir en el registro (coordenadas X e Y de cada celda de la tabla) y obtenerlos mediante de forma programática, para especificar el número de fila y columna donde estos datos deben estar, o simplemente imprimir las coordenadas en el registro y escribir los necesarios en nuestro código desde ella. El primer método será más cómodo debido a su completa automatización. El panel también tendrá un botón de cierre activo, pero su procesamiento se delegará al programa de control, ya que solo el desarrollador del programa deberá decidir cómo reaccionar al pulsar el botón de cierre. Al clicar en el botón, se enviará un evento personalizado al manejador de eventos del programa, que el desarrollador podrá procesar como considere oportuno.

Autor: Artyom Trishkin

 

Gracias por compartirlo.

Esta implementación de la clase tiene un gran inconveniente si hay necesidad de heredarla.

1. No hay constructor por defecto y el paramétrico establece parámetros importantes por tamaño. Por ejemplo, si necesitas calcular el tamaño del panel basándote en sus parámetros en el descendiente de la clase, entonces esta lógica tendrá que ser implementada o bien fuera de la clase, o bien en la clase, pero con el cálculo en una función separada, lo que en cierto modo vence a la lógica, porque la llamada del constructor paramétrico, sin el constructor por defecto, estará en la lista de inicialización. Quizás una mejor solución sería mover la inicialización del constructor a una función de la clase.

2. La sección con parámetros se declara como privada, lo que no permite al descendiente implementar, por ejemplo, un esquema de colores diferente o cambiar el tamaño de la cabecera

3. Entiendo que el trabajo no está terminado, pero algunas funciones no están implementadas, por ejemplo SetButtonClose(On/Off), SetButtonMinimize(On/Off).

Con la disponibilidad del código fuente, no es un problema finalizarlo, pero aún así...

 
Evgeny #:

Gracias por compartirlo.

Esta implementación de la clase tiene un gran inconveniente si hay una necesidad de heredar.

1. No hay constructor por defecto y el paramétrico establece parámetros importantes por tamaño. Por ejemplo, si necesitas calcular el tamaño del panel basándote en sus parámetros en el descendiente de la clase, entonces esta lógica tendrá que ser implementada o bien fuera de la clase, o bien en la clase, pero con el cálculo en una función separada, lo que en cierto modo vence a la lógica, porque la llamada del constructor paramétrico, sin el constructor por defecto, estará en la lista de inicialización. Quizás una mejor solución sería mover la inicialización del constructor a una función de la clase.

2. La sección con parámetros se declara como privada, lo que no permite al descendiente implementar, por ejemplo, un esquema de colores diferente o cambiar el tamaño de la cabecera

3. Me doy cuenta de que el trabajo no está terminado, pero algunas funciones no están implementadas, por ejemplo SetButtonClose(On/Off), SetButtonMinimize(On/Off).

Con la disponibilidad del código fuente, no es un problema para finalizarlo, pero aún así...

El artículo es un tutorial. En él, el panel cubre las necesidades mínimas.

1. El descendiente tiene su propio constructor, en su lista de inicialización debe estar el constructor del padre. En él se especifican las dimensiones necesarias.

2. Ni siquiera he pensado en los esquemas de color) Así como el tamaño de la cabecera.

3. Estos métodos se declaran en la sección pública. Es extraño que no estén implementados. Recuerdo exactamente que estaba probando su habilitación/deshabilitación.... Tuve que reescribir esta clase desde cero de memoria, porque su primera versión fue destruida por Windows cuando se quedó sin espacio en el disco. Debí olvidar restaurarlas entonces. Gracias, lo arreglaré.

Sí, con la disponibilidad del código fuente, todas las cartas están en manos de los lectores. Para eso están los artículos tutoriales.

 
Artyom Trishkin #:

El artículo es instructivo. En él, el panel cubre las necesidades mínimas.

1. El descendiente tiene su propio constructor, su lista de inicialización debe contener el constructor del padre. En ella se especifican las dimensiones requeridas.

2. Ni siquiera he pensado en los esquemas de color) Así como el tamaño de la cabecera.

3. Estos métodos se declaran en la sección pública. Es extraño que no estén implementados. Recuerdo exactamente que estaba probando su habilitación/deshabilitación.... Tuve que reescribir esta clase desde cero de memoria, porque su primera versión fue destruida por Windows cuando se quedó sin espacio en el disco. Debí olvidar restaurarlas entonces. Gracias, lo arreglaré.

Sí, con la disponibilidad del código fuente, todas las cartas están en manos de los lectores. Para eso están los artículos tutoriales.

1. Sí, pero simplemente cuando se llama al constructor ancestro en la lista de inicialización, el cálculo de los parámetros que se pasan en él debe ser trasladado a otro lugar. Sobre todo si no es el más primitivo.

Por ejemplo, el cálculo de X, Y posición para la visualización inicial en la esquina inferior derecha, el cálculo de la altura en función del número previsto de filas de la tabla.... Esto requerirá 3 funciones de clase adicionales, no todas en un trozo de código en su constructor con la posterior llamada del constructor ancestro. Parece más bien una muletilla, aunque funciona. Pues no es un código muy bonito cuando puedes hacer que la arquitectura de la clase se adapte mejor a cambios posteriores. (Por supuesto, este es un comentario sacado de las notas de un perfeccionista).

2. Los pequeños adornos hacen que el producto sea más cualitativo. Pero tu código en general es bonito y tus soluciones son interesantes, es agradable leerlo.

3. Me solidarizo, la pérdida de datos es siempre muy desagradable, por lo que una copia de seguridad fiable es nuestro todo.

Gracias, esperamos nuevos artículos.

[Eliminado]  
MetaQuotes:

Echa un vistazo al nuevo artículo: Cómo hacer un cuadro de mando para mostrar datos en indicadores y EAs.

Autor: Artyom Trishkin

Gracias por muy buen tutorial


Lo modifiqué a la mirada oscura y más lejos que mira para cambiar los colores del título y de los datos también

También he creado una función para añadir texto de manera fácil

void DrawData(int cell, string title, string data, color clr ) {
   dashboard.DrawText(title,  dashboard.CellX(cell,0)+2,   dashboard.CellY(cell,0)+2, clr);
   dashboard.DrawText(data,   dashboard.CellX(cell,1)+2,   dashboard.CellY(cell,1)+2,90, clr);
   ChartRedraw(ChartID());
}

pero sólo encontrar dificultades para cambiar los colores de estos textos, no estoy seguro de lo que está mal, como los colores se ve aburrido, aquí está modificado DrawText función

//+------------------------------------------------------------------+
//| Mostrar un mensaje de texto en las coordenadas especificadas |
//+------------------------------------------------------------------+
void CDashboard::DrawText(const string text,const int x,const int y,const color clr,const int width=WRONG_VALUE,const int height=WRONG_VALUE)
  {
//--- Declarar variables para registrar en ellas el ancho y alto del texto
   int w=width;
   int h=height;
//--- Si la anchura y la altura del texto pasado al método tienen valores cero,
//--- entonces todo el espacio de trabajo se limpia completamente usando el color transparente
   if(width==0 && height==0)
      this.m_workspace.Erase(0x00FFFFFF);
//--- De lo contrario
   else
     {
      //--- Si la anchura y altura pasadas tienen valores por defecto (-1), obtenemos su anchura y altura del texto
      if(width==WRONG_VALUE && height==WRONG_VALUE)
         this.m_workspace.TextSize(text,w,h);
      //--- de lo contrario,
      else
        {
         //--- si el ancho pasado al método tiene el valor por defecto (-1) - obtener el ancho del texto, o
         //--- si el ancho pasado al método tiene un valor mayor que cero, use el ancho pasado al método, o
         //--- si la anchura pasada al método tiene un valor cero, utilice el valor 1 para la anchura
         w=(width ==WRONG_VALUE ? this.m_workspace.TextWidth(text)  : width>0  ? width  : 1);
         //--- si la altura pasada al método tiene un valor por defecto (-1), obtener la altura del texto, o
         //--- si la altura pasada al método tiene un valor mayor que cero, usa la altura pasada al método, o
         //--- si la altura pasada al método tiene un valor cero, utilice el valor 1 para la altura
         h=(height==WRONG_VALUE ? this.m_workspace.TextHeight(text) : height>0 ? height : 1);
        }
      //--- Rellena el espacio según las coordenadas especificadas y la anchura y altura resultantes con un color transparente (borra la entrada anterior)
      this.m_workspace.FillRectangle(x,y,x+w,y+h,0x00FFFFFF);
     }
//--- Mostrar el texto en el espacio libre del texto anterior y actualizar el espacio de trabajo sin redibujar la pantalla
   this.m_workspace.TextOut(x,y,text,::ColorToARGB(clr,this.m_alpha));
   this.m_workspace.Update(false);
  }
 

Arpit T # :

//--- Mostrar el texto en el espacio libre del texto anterior y actualizar el espacio de trabajo sin redibujar la pantalla
   this .m_workspace. TextOut (x,y,text,:: ColorToARGB (clr, this .m_alpha ));
   this .m_workspace.Update( false );

El color resaltado es la transparencia del propio panel, no del texto. Para el texto, es mejor establecer la transparencia (o mejor dicho, la opacidad) al valor por defecto de 255 - texto completamente opaco. Pero puede "jugar" con el valor de opacidad introduciendo valores numéricos normales de 0 a 255 en lugar de this.m_alpha. 0 es un color completamente transparente, 255 es un color completamente opaco.

[Eliminado]  
Artyom Trishkin #:

El color resaltado es la transparencia del propio panel, no del texto. Para el texto, es mejor establecer la transparencia (o mejor dicho, la opacidad) al valor por defecto de 255 - texto completamente opaco. Pero puede "jugar" con el valor de opacidad introduciendo valores numéricos normales de 0 a 255 en lugar de this.m_alpha. 0 es un color completamente transparente, 255 es un color completamente opaco.

Gracias todo esta bien ahora


void DrawData(int cell, string title, string data, color clr ) {
   dashboard.DrawText(title,  dashboard.CellX(cell,0)+2,   dashboard.CellY(cell,0)+2, clr);
   dashboard.DrawText(data,   dashboard.CellX(cell,1)+2,   dashboard.CellY(cell,1)+2,90, clr);
   ChartRedraw(ChartID());
}

el código resaltado estaba dando problemas, lo quité y todo bien.

 
bien hecho trabajo oscuro es agradable
[Eliminado]  
VIKRAM SINGH #:
bien hecho el trabajo oscuro es agradable

Gracias

Aquí está el constructor modificado para el aspecto oscuro en dashboard.mqh si desea utilizar ese tema

//+------------------------------------------------------------------+
//| Constructor|
//+------------------------------------------------------------------+
CDashboard::CDashboard(const uint id,const int x,const int y, const int w,const int h,const int wnd=-1) : 
                        m_id(id),
                        m_chart_id(::ChartID()),
                        m_program_type((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)),
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)),
                        m_wnd(wnd==-1 ? GetSubWindow() : wnd),
                        m_chart_w((int)::ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS,m_wnd)),
                        m_chart_h((int)::ChartGetInteger(m_chart_id,CHART_HEIGHT_IN_PIXELS,m_wnd)),
                        m_mouse_state(MOUSE_STATE_NOT_PRESSED),
                        m_x(x),
                        m_y(::ChartGetInteger(m_chart_id,CHART_SHOW_ONE_CLICK) ? (y<79 ? 79 : y) : y),
                        m_w(w),
                        m_h(h),
                        m_x_dock(m_x),
                        m_y_dock(m_y),
                        m_header(true),
                        m_butt_close(true),
                        m_butt_minimize(true),
                        m_butt_pin(true),
                        m_header_h(18),
                        
                        //--- Implementación de la cabecera del panel
                        m_header_alpha(255),
                        m_header_alpha_c(m_header_alpha),
                        m_header_back_color(clrBlack),
                        m_header_back_color_c(m_header_back_color),
                        m_header_fore_color(clrSnow),
                        m_header_fore_color_c(m_header_fore_color),
                        m_header_border_color(clrSnow),
                        m_header_border_color_c(m_header_border_color),
                        m_title("Dashboard"),
                        m_title_font("Calibri"),
                        m_title_font_size(-100),
                        
                        //--- botón cerrar
                        m_butt_close_back_color(clrBlack),
                        m_butt_close_back_color_c(m_butt_close_back_color),
                        m_butt_close_fore_color(clrSnow),
                        m_butt_close_fore_color_c(m_butt_close_fore_color),
                        
                        //--- botón contraer/expandir
                        m_butt_min_back_color(clrBlack),
                        m_butt_min_back_color_c(m_butt_min_back_color),
                        m_butt_min_fore_color(clrSnow),
                        m_butt_min_fore_color_c(m_butt_min_fore_color),
                        
                        //--- botón pin
                        m_butt_pin_back_color(clrBlack),
                        m_butt_pin_back_color_c(m_butt_min_back_color),
                        m_butt_pin_fore_color(clrSnow),
                        m_butt_pin_fore_color_c(m_butt_min_fore_color),
                        
                        //--- Implementación del panel
                        m_alpha(255),
                        m_alpha_c(m_alpha),
                        m_fore_alpha(255),
                        m_fore_alpha_c(m_fore_alpha),
                        m_back_color(clrBlack),
                        m_back_color_c(m_back_color),
                        m_fore_color(clrSnow),
                        m_fore_color_c(m_fore_color),
                        m_border_color(clrSnow),
                        m_border_color_c(m_border_color),
                        m_font("Calibri"),
                        m_font_size(-100),
                        
                        m_minimized(false),
                        m_movable(true)
  {
//--- Establece el permiso para que el gráfico envíe mensajes sobre eventos de mover y pulsar botones del ratón,
//--- eventos de desplazamiento del ratón, así como creación/eliminación de objetos gráficos
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_MOUSE_WHEEL,true);
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_OBJECT_CREATE,true);
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_OBJECT_DELETE,true);
   
//--- Establece los nombres de las variables globales del terminal para almacenar las coordenadas del panel, el estado colapsado/expandido y el pinning.
   this.m_name_gv_x=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_X";
   this.m_name_gv_y=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Y";
   this.m_name_gv_m=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Minimize";
   this.m_name_gv_u=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Unpin";
   
//--- Si no existe una variable global, créala y escribe el valor actual,
//--- de lo contrario - leer el valor de la variable global terminal en él
//--- Coordenada X
   if(!::GlobalVariableCheck(this.m_name_gv_x))
      ::GlobalVariableSet(this.m_name_gv_x,this.m_x);
   else
      this.m_x=(int)::GlobalVariableGet(this.m_name_gv_x);
//--- Coordenada Y
   if(!::GlobalVariableCheck(this.m_name_gv_y))
      ::GlobalVariableSet(this.m_name_gv_y,this.m_y);
   else
      this.m_y=(int)::GlobalVariableGet(this.m_name_gv_y);
//--- Colapsado/expandido
   if(!::GlobalVariableCheck(this.m_name_gv_m))
      ::GlobalVariableSet(this.m_name_gv_m,this.m_minimized);
   else
      this.m_minimized=(int)::GlobalVariableGet(this.m_name_gv_m);
//--- Colapsado/no colapsado
   if(!::GlobalVariableCheck(this.m_name_gv_u))
      ::GlobalVariableSet(this.m_name_gv_u,this.m_movable);
   else
      this.m_movable=(int)::GlobalVariableGet(this.m_name_gv_u);

//--- Establece las banderas para que el tamaño del panel exceda el tamaño de la ventana del gráfico
   this.m_higher_wnd=this.HigherWnd();
   this.m_wider_wnd=this.WiderWnd();

//--- Si se crea el recurso gráfico del panel,
   if(this.m_canvas.CreateBitmapLabel(this.m_chart_id,this.m_wnd,"P"+(string)this.m_id,this.m_x,this.m_y,this.m_w,this.m_h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      //--- establece la fuente del lienzo y rellena el lienzo con el color transparente
      this.m_canvas.FontSet(this.m_title_font,this.m_title_font_size,FW_BOLD);
      this.m_canvas.Erase(0x00FFFFFF);
     }
//--- en caso contrario - informar al diario de la creación fallida del objeto
   else
      ::PrintFormat("%s: Error. CreateBitmapLabel for canvas failed",(string)__FUNCTION__);

//--- Si se crea un espacio de trabajo de un recurso gráfico,
   if(this.m_workspace.CreateBitmapLabel(this.m_chart_id,this.m_wnd,"W"+(string)this.m_id,this.m_x+1,this.m_y+this.m_header_h,this.m_w-2,this.m_h-this.m_header_h-1,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      //--- establece la fuente para el área de trabajo y rellénala con el color transparente
      this.m_workspace.FontSet(this.m_font,this.m_font_size);
      this.m_workspace.Erase(0x00FFFFFF);
     }
//--- en caso contrario - informar al diario de la creación fallida del objeto
   else
      ::PrintFormat("%s: Error. CreateBitmapLabel for workspace failed",(string)__FUNCTION__);
  }
 
Artem hola! Estoy aprendiendo a crear un panel de información, no puedo entender una cosa! Ya que no soy realmente un profesional en este..... En general mira, CDashboard * dashboard = NULL; Creamos una instancia de la clase ... ¿verdad? y luego le asignamos NULL ...? verdad? luego en oninit, le asignamos el descriptor... dashboard = new CDashboard(InpUniqID, InpPanelX, InpPanelY, 500, 700); ¿Por qué así? y no como este CDashboard dashboard; ¿O funciona así con objetos? ¡Tengo la cabeza hecha un lío! Si no te importa... explicarme de forma sencilla ... ¡gracias!
 
Igor Bakhrushen NULL; Creamos una instancia de la clase ... ¿verdad? y luego le asignamos NULL ...? verdad? luego en oninit, le asignamos el descriptor... dashboard = new CDashboard(InpUniqID, InpPanelX, InpPanelY, 500, 700); ¿Por qué así? y no como este CDashboard dashboard; ¿O funciona así con objetos? ¡Tengo la cabeza hecha un lío! Si no te importa... explicarme de forma sencilla ... ¡ gracias!

Hola.

De esta manera

CDashboard  *dashboard = NULL;

se declara una variable puntero a un futuro objeto nuevo, creado dinámicamente, de la clase y se inicializa inmediatamente con el valor NULL.


Y sólo una instancia de la clase se declara de esta manera:

CDashboard   dashboard;

Pero en este caso, no se puede declarar y crear una instancia de esta manera - la clase no tiene constructor sin parámetros formales.

Por lo tanto, al declarar una instancia de esta manera, debes especificar todos los parámetros necesarios del objeto de la clase, que deben pasarse al constructor de la clase:

CDashboard   dashboard(InpUniqID, InpPanelX, InpPanelY, 200, 250);

------------------------

En el ejemplo de trabajo con la clase, primero se crea un puntero vacío al futuro objeto en el indicador, y luego, en OnInit(), se crea un objeto panel, donde el puntero al objeto creado se escribe en la variable puntero:

//--- Crear objeto panel
   dashboard=new CDashboard(InpUniqID,InpPanelX,InpPanelY,200,250);
   if(dashboard==NULL)
     {
      Print("Error. Failed to create dashboard object");
      return INIT_FAILED;
     }


Luego, en OnDeinit(), se borra el objeto en memoria utilizando este puntero:

//--- Si el objeto panel existe - elimínelo
   if(dashboard!=NULL)
      delete dashboard;

Si simplemente creáramos un nuevo objeto mediante el operador new sin escribir el puntero al objeto creado en una variable, no podríamos borrarlo después, lo que provocaría una fuga de memoria.

Así que, en resumen, en el ejemplo del artículo

  1. declaramos un puntero-variable al futuro objeto de clase y lo inicializamos con valor NULL,
  2. creamos un nuevo objeto de clase y escribimos el puntero al mismo en la variable dashboard creada anteriormente,
  3. al acceder al objeto creado utilizamos la variable-puntero y un punto ( dashboard.AnyMethod() )
  4. al final del trabajo eliminar el objeto de clase creado dinámicamente por el puntero a la misma.

Si la instancia necesaria de la clase (dashboard CDashboard) se hubiera creado inmediatamente, no habría sido necesario ningún puntero a la misma - se habría accedido a ella de la misma manera utilizando el operador "point". Y no habría necesidad de borrarlo cuando el trabajo hubiera terminado - el subsistema terminal lo hace por sí mismo. Pero sería la única instancia de la clase en el programa.

La creación dinámica permite crear sobre la marcha nuevos objetos de la clase deseada y referirse a ellos mediante punteros. Por eso en el ejemplo se utilizó la creación dinámica de un objeto de clase. Simplificado, sin cubrir algunos puntos.