Interfaces gráficas IV: Elementos informativos de la interfaz (Capítulo 1)

Anatoli Kazharski | 28 abril, 2016

Índice

 


Introducción

El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo. 

En este momento, la librería para la creación de las interfaces gráficas contiene el formulario y varios controles que pueden ser adjuntados a este formulario. Antes ya hemos mencionado que en uno de los próximos artículos vamos a analizar el modo de ventanas múltiples. Ahora tenemos todo preparado para dedicarnos a este asunto, y nos ocuparemos de ello en la segunda parte de este artículo. Pero antes, en el primer capítulo, vamos a escribir las clases que nos permitirán crear los elementos informativos de la interfaz, tales como: la “barra de estado” y la “descripción emergente”.


Elemento “Barra de estado” (Status Bar)

La “Barra de estado” pertenece a los elementos informativos de la interfaz gráfica. Este elemento se utiliza para visualizar rápidamente algunos datos importantes, detalles, valores, etc. Por ejemplo, en los terminales MetaTrader también hay una barra de estado. Se compone de varias secciones (puntos). En la primera de sección se muestra la información sobre la parte del terminal en la que se encuentra el cursor en este momento, o los nombres de los comandos del programa. Además, hay puntos donde se muestran las fechas y los precios, cuando el cursor se desplaza por el área del gráfico de precios. Algunos puntos contienen el menú contextual que se abre con el clic izquierdo. El editor del código MetaEditor también contiene la barra de estado. En sus puntos se muestran las descripciones de los comandos del programa, la posición del cursor (fila/columna) y el modo de introducción del texto (INS/OVR). Puede encontrar la información más detallada sobre las barras de estado del terminal y del editor en sus Ayudas correspondientes (F1).

En este artículo vamos a crear la versión simple de la barra de estado, sin posibilidad de adjuntar los menús contextuales a sus elementos. Igual que otros elementos de la interfaz, las barra de estado va a componerse de varios objetos primitivos:

  • Fondo.
  • Elementos.
  • Líneas separadoras.


Fig. 1. Partes integrantes del elemento “Barra de estado”.


Por favor, cree el archivo StatusBar.mqh e inclúyelo en el archivo WndContainer.mqh, así estará disponible en la librería entera para su uso posterior. En el código de abajo se muestra la clase CStatusBar con los métodos virtuales estándar que se utilizan en todas las clases de controles. 

//+------------------------------------------------------------------+
//|                                                    StatusBar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Clase para crear la barra de estado                              |
//+------------------------------------------------------------------+
class CStatusBar : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el control
   CWindow          *m_wnd;
   //---
public:
                     CStatusBar(void);
                    ~CStatusBar(void);
   //--- Guarda el puntero del formulario
   void              WindowPointer(CWindow &object)                   { m_wnd=::GetPointer(object);   }
   //---
public:
   //--- Manejador de eventos del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {}
   //--- Temporizador
   virtual void      OnEventTimer(void) {}
//--- Desplazamiento del control
   virtual void      Moving(const int x,const int y);
   //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Establecer, (2) poner a cero las prioridades para el clic izquierdo del ratón
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CStatusBar::CStatusBar(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CStatusBar::~CStatusBar(void)
  {
  }
//+------------------------------------------------------------------+

Los métodos para el procesamiento de eventos OnEvent() y OnEventTimer() no van a utilizarse en esta versión, los dejaremos para mantener la uniformidad con otras clases de los controles. 

A continuación, hablaremos de las propiedades de la barra de estado. Puesto que se supone el uso de los arrays de objetos, aquí habrán las propiedades comunes y generales para ellos. De las propiedades únicas se comprende sólo el ancho de los elementos. Lista de propiedades comunes:

  • Color del fondo y del marco del fondo;
  • Color del texto;
  • Colores para la línea separadora;
  • Prioridad para el clic izquierdo del ratón.

Inicializamos los campos de las propiedades dentro del constructor usando los valores predefinidos. En el momento de la creación del elemento, el usuario puede redefinirlos usando los métodos públicos de la clase. 

class CStatusBar : public CElement
  {
private:
  //--- Propiedades:
       Arrays para las propiedades únicas
   int               m_width[];
   //--- (1) Color del fondo y (2) del marco del fondo
   color             m_area_color;
   color             m_area_border_color;
   //--- Color del texto
   color             m_label_color;
   //--- Prioridad para el clic izquierdo del ratón
   int               m_zorder;
   //--- Colores para las líneas separadoras
   color             m_sepline_dark_color;
   color             m_sepline_light_color;
   //---
public:
   //--- Color del (1) fondo, (2) marco del fondo y (3) texto
   void              AreaColor(const color clr)                       { m_area_color=clr;             }
   void              AreaBorderColor(const color clr)                 { m_area_border_color=clr;      }
   void              LabelColor(const color clr)                      { m_label_color=clr;            }
   //--- Colores de las líneas separadoras
   void              SeparateLineDarkColor(const color clr)           { m_sepline_dark_color=clr;     }
   void              SeparateLineLightColor(const color clr)          { m_sepline_light_color=clr;    }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CStatusBar::CStatusBar(void) : m_area_color(C'240,240,240'),
                               m_area_border_color(clrSilver),
                               m_label_color(clrBlack),
                               m_sepline_dark_color(C'160,160,160'),
                               m_sepline_light_color(clrWhite)
  {
//--- Guardamos el nombre de la clase del control en la clase base  
   CElement::ClassName(CLASS_NAME);
//--- Establecemos las prioridades para el clic izquierdo del ratón
   m_zorder=2;
  }

Vamos a considerar los métodos para la creación de la barra de estado. Para crear el fondo, vamos a usar el objeto primitivo del tipo CRectLabel. Para la creación de los elementos, hay que declarar el array dinámico de los objetos primitivos del tipo CEdit. Antes, para la creación de las líneas separadoras, ya hemos creado la clase CSeparateLine. Esta clase puede usarse como un elemento independiente de la interfaz. Por ejemplo, ahora vamos a usarla como la parte integrante de la barra de estado, y necesitaremos un array de estos elementos.

Antes de crear la barra de estado, hay que añadir obligatoriamente los elementos mediante el método CStatusBar::AddItem(). De lo contrario, la creación de la interfaz gráfica se interrumpe. Lamamos al método CStatusBar::ItemsTotal() para obtener el número de los elementos. 

//+------------------------------------------------------------------+
//|                                                    StatusBar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "SeparateLine.mqh"
//+------------------------------------------------------------------+
//| Clase para crear la barra de estado                              |
//+------------------------------------------------------------------+
class CStatusBar : public CElement
  {
private:
   //--- Objetos para crear el botón
   CRectLabel        m_area;
   CEdit             m_items[];
   CSeparateLine     m_sep_line[];
   //---
public:
   //--- Métodos para la creación de la barra de estado
   bool              CreateStatusBar(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateItems(void);
   bool              CreateSeparateLine(const int line_number,const int x,const int y);
   //---
public:
   //--- Número de elementos
   int               ItemsTotal(void)                           const { return(::ArraySize(m_items)); }

   //--- Añade un elemento con propiedades especificadas antes de la creación de la barra de estado
   void              AddItem(const int width);
  };

De todos estos métodos, nosotros vamos a considerar con más detalles sólo el método CStatusBar::CreateItems() para la creación de los elementos de la barra de estado. Los demás métodos no contienen nada nuevo de lo que hemos estudiado entes.

Los elementos se colocan dentro del área del fondo sin sobreponer su marco. Por eso, desde el principio, a las coordenadas se les añade un píxel. Luego, se comprueba el número de elementos. Si resulta que todavía no ha sido establecido ningún elemento, en el diario se muestra un mensaje sobre ello, y la creación de la interfaz gráfica se interrumpe. 

Hagamos que si el ancho del primer elemento no ha sido especificado, entonces va a calcularse automáticamente de la siguiente manera. Se supone que el ancho de la barra de estado es igual al ancho del formulario al que se adjunta (menos 2 píxeles para estar dentro del área). Entonces, para obtener el ancho del primer elemento, hay que sumar en el ciclo los tamaños (anchos) de los demás elementos y restar este valor del ancho del formulario. 

Luego, se ejecuta el ciclo en el que se crean los elementos de la barra de estado, seguido del ciclo para la creación de las líneas separadoras. Las coordenadas para las líneas separadoras se calculan en relación a las coordenadas de cada elemento. La línea separadora no se crea para el primer elemento. De esta manera, el número de las líneas separadoras siempre será a un objeto menos que el número de los elementos. 

//+------------------------------------------------------------------+
//| Crea la lista de elementos de la barra de estado                          |
//+------------------------------------------------------------------+
bool CStatusBar::CreateItems(void)
  {
   int l_w=0;
   int l_x=m_x+1;
   int l_y=m_y+1;
//--- Obtenemos el número de elementos
   int items_total=ItemsTotal();
//--- Si el grupo no tiene elementos, avisar sobre ello y salir
   if(items_total<1)
     {
      ::Print(__FUNCTION__," > ¡La llamada a este método debe realizarse "
              "cuando en el grupo hay por lo menos un elemento! Utilice el método CStatusBar::AddItem()");
      return(false);
     }
//--- Si el ancho del primer elemento no ha sido especificado, entonces...
   if(m_width[0]<1)
     {
      //--- ...lo calculamos respecto al ancho total de los demás elementos
      for(int i=1; i<items_total; i++)
         l_w+=m_width[i];
      //---
      m_width[0]=m_wnd.XSize()-l_w-(items_total+2);
     }
//--- Creamos el número especificado de elementos
   for(int i=0; i<items_total; i++)
     {
      //--- Formación del nombre del objeto
      string name=CElement::ProgramName()+"_statusbar_edit_"+string(i)+"__"+(string)CElement::Id();
      //--- Coordinada X
      l_x=(i>0)? l_x+m_width[i-1] : l_x;
      //--- Creación del objeto
      if(!m_items[i].Create(m_chart_id,name,m_subwin,l_x,l_y,m_width[i],m_y_size-2))
         return(false);
      //--- Establecemos las propiedades
      m_items[i].Description("");
      m_items[i].TextAlign(ALIGN_LEFT);
      m_items[i].Font(FONT);
      m_items[i].FontSize(FONT_SIZE);
      m_items[i].Color(m_label_color);
      m_items[i].BorderColor(m_area_color);
      m_items[i].BackColor(m_area_color);
      m_items[i].Corner(m_corner);
      m_items[i].Anchor(m_anchor);
      m_items[i].Selectable(false);
      m_items[i].Z_Order(m_zorder);
      m_items[i].ReadOnly(true);
      m_items[i].Tooltip("\n");
      //--- Márgenes desde el punto extremo del panel
      m_items[i].XGap(l_x-m_wnd.X());
      m_items[i].YGap(l_y-m_wnd.Y());
      //--- Coordenadas
      m_items[i].X(l_x);
      m_items[i].Y(l_y);
      //--- Tamaños
      m_items[i].XSize(m_width[i]);
      m_items[i].YSize(m_y_size-2);
      //--- Guardamos el puntero del objeto
      CElement::AddToArray(m_items[i]);
     }
//--- Creación de las líneas separadoras
   for(int i=1; i<items_total; i++)
     {
      //--- Coordinada X
      l_x=m_items[i].X();
      //--- Creación de la línea
      CreateSeparateLine(i,l_x,l_y+2);
     }
//---
   return(true);
  }

Claro que vamos a necesitar también el método público para editar el texto en cada elemento. Vamos a crear este método y llamarlo CStatusBar::ValueToItem(). Como parámetros, va a recibir el número del índice del elemento y la línea que tiene que reflejarse dentro. 

class CStatusBar : public CElement
  {
public:
   //--- Establecer el valor según el índice especificado
   void              ValueToItem(const int index,const string value);
  };
//+------------------------------------------------------------------+
//| Establece el valor según el índice especificado                     |
//+------------------------------------------------------------------+
void CStatusBar::ValueToItem(const int index,const string value)
  {
//--- Comprobar la superación del rango
   int array_size=::ArraySize(m_items);
   if(array_size<1 || index<0 || index>=array_size)
      return;
//--- Establecer el texto pasado
   m_items[index].Description(value);
  }

 


Prueba de la Barra de estado

Todo está listo para probar el elemento “Barra de estado”. Para la prueba vamos a usar el primer EA de la parte anterior (3) de la serie. Dejamos el menú principal y cinco botones tipo CIconButton, los demás controles se eliminan. El archivo StatusBar.mqh ya está incluido en la librería, y ahora en la clase personalizada CProgram se puede crear su instancia y el método para la creación de la barra de estado.

Vamos a crear la barra de estado compuesta de dos elementos. No vamos a especificar el ancho para el primer elemento, va a calcularse automáticamente. Después de la creación de la barra de estado, pondremos el texto “For Help press F1” para el primer elemento, como ejemplo.

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                     |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Barra de estado
   CStatusBar        m_status_bar;
   //---
private:
   //--- Barra de estado
#define STATUSBAR1_GAP_X         (1)
#define STATUSBAR1_GAP_Y         (175)
   bool              CreateStatusBar(void);
  };
//+------------------------------------------------------------------+
//| Crea el panel de trading                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creación del formulario 1 para los controles
//--- Creación de controles:
//    Menú principal
//--- Menús contextuales
//--- Creación de la barra de estado
   if(!CreateStatusBar())
      return(false);
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }
//+------------------------------------------------------------------+
//| Crea la barra de estado                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateStatusBar(void)
  {
#define STATUS_LABELS_TOTAL 2
//--- Pasa el objeto del panel
   m_status_bar.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+STATUSBAR1_GAP_X;
   int y=m_window1.Y()+STATUSBAR1_GAP_Y;
//--- Ancho
   int width[]={0,110};
//--- Establecemos las propiedades antes de la creación
   m_status_bar.YSize(24);
//--- Indicamos el número de las partes y establecemos las propiedades para ellas
   for(int i=0; i<STATUS_LABELS_TOTAL; i++)
      m_status_bar.AddItem(width[i]);
//--- Creamos el control
   if(!m_status_bar.CreateStatusBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Establecemos el texto en el primer elemento de la barra de estado
   m_status_bar.ValueToItem(0,"For Help, press F1");
//--- Añadimos el objeto al array común de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_status_bar);
   return(true);
  }

En el segundo elemento vamos a mostrar la hora local del ordenador. Para eso, en en el temporizador de la aplicación hay que insertar el código , tal como se muestra más abajo. Es decir, el elemento va a actualizarse cada 500 milisegundos.

//+------------------------------------------------------------------+
//| Temporizador                                                           |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();

//--- El segundo elemento de la barra de estado va a actualizarse cada 500 milisegundos
   static int count=0;
   if(count<500)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//---
   count=0;
   m_status_bar.ValueToItem(1,TimeToString(TimeLocal(),TIME_DATE|TIME_SECONDS));
   m_chart.Redraw();
  }

Si todo ha sido hecho correctamente, el resultado tiene que ser como en la captura de abajo:

Fig. 2. Prueba del elemento “Barra de estado”.

Fig. 2. Prueba del elemento “Barra de estado”.

 

Hemos terminado el desarrollo de la clase para la creación del elemento “Barra de estado”. Se puede ver su versión completa en los archivos adjuntos al artículo. 

 


Elemento “Descripción emergente (Tooltip)”

Ahora empezaremos el desarrollo de la clase para la creación de las descripciones emergentes. Antes, en el cuarto capítulo de la primera parte de la serie, cuando analizábamos las funciones para los botones del formulario, fue explicado bastante claro para qué este control necesitaba una clase separada. En pocas palabras, hacen falta las descripciones sin limitación del número de caracteres y con la posibilidad de resaltar algunas palabras. Vamos a implementar eso usando la clase para el dibujo de elementos. Antes ya hemos mostrado el ejemplo de cómo dibujar los elementos de la interfaz. En el segundo capítulo de la segunda parte de la serie, hemos creado la clase CSeparateLine para la creación del elemento “Línea separadora”. Ahora, siguiendo el mismo principio, escribiremos la clase para la creación de las descripciones emergentes.


Fig. 3. Ejemplo de una descripción emergente en Word.

 

Por favor, cree el archivo Tooltip.mqh e inclúyelo en el archivo WndContainer.mqh, en el que se incluyen todos los elementos de la interfaz. Luego, en este archivo cree una clase con los métodos estándar ya conocidos para todos los elementos de la interfaz. Además del puntero al formulario, en esta clase también vamos a necesitar el puntero al elemento al que va a vincularse la descripción emergente, así como el método a través del cual se puede guardar este puntero. 

//+------------------------------------------------------------------+
//|                                                      Tooltip.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Clase para crear la descripción emergente                         |
//+------------------------------------------------------------------+
class CTooltip : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el control
   CWindow          *m_wnd;
   //--- Puntero al elemento al que se adjunta la descripción emergente
   CElement         *m_element;
   //---
public:
    //--- (1) Guarda el puntero del formulario, (2) guarda el puntero del elemento
   void              WindowPointer(CWindow &object)   { m_wnd=::GetPointer(object);     }
   void              ElementPointer(CElement &object) { m_element=::GetPointer(object); }
   //---
public:
   //--- Manejador de eventos del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Desplazamiento del control
   virtual void      Moving(const int x,const int y);
   //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTooltip::CTooltip(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTooltip::~CTooltip(void)
  {
  }

Las propiedades que puede establecer el usuario antes de la creación de la descripción emergente:

  • Encabezado
  • Array de líneas

Las demás propiedades tendrán los valores predefinidos. Son las siguientes:

  • Colores del gradiente del fondo de la descripción
  • Color del marco del fondo
  • Color del encabezado
  • Color del texto
  • Canal Alpha para controlar la transparencia de la descripción

El array de cadenas puede añadirse línea por línea antes de la creación de la descripción emergente usando el método CTooltip::AddString()

class CTooltip : public CElement
  {
private:
  //--- Propiedades:
   //    Encabezado
   string            m_header;
   //--- Array de cadenas del texto de la descripción
   string            m_tooltip_lines[];
   //--- Valor del canal Alpha (transparencia de la descripción)
   uchar             m_alpha;
   //--- Colores del (2) texto, (3) encabezado y (3) marco del fondo
   color             m_text_color;
   color             m_header_color;
   color             m_border_color;
   //--- Array del gradiente del fondo
   color             m_array_color[];
   //---
public:
   //--- Encabezado de la ayuda emergente
   void              Header(const string text)        { m_header=text;                  }
   //--- Añade la línea para la descripción
   void              AddString(const string text);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTooltip::CTooltip(void) : m_header(""),
                           m_alpha(0),
                           m_text_color(clrDimGray),
                           m_header_color(C'50,50,50'),
                           m_border_color(C'118,118,118'),
                           m_gradient_top_color(clrWhite),
                           m_gradient_bottom_color(C'208,208,235')
  {
//--- Guardamos el nombre de la clase del control en la clase base  
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Añade la línea                                                 |
//+------------------------------------------------------------------+
void CTooltip::AddString(const string text)
  {
//--- Aumentamos el tamaño del array a un elemento
   int array_size=::ArraySize(m_tooltip_lines);
   ::ArrayResize(m_tooltip_lines,array_size+1);
//--- Guardamos los valores de los parámetros pasados
   m_tooltip_lines[array_size]=text;
  }

Igual que en la clase del elemento “Línea separadora”, para la creación de la descripción emergente va a utilizarse la clase CRectCanvas, y dos métodos (público y privado). 

class CTooltip : public CElement
  {
private:
   //--- Objetos para crear la descripción emergente
   CRectCanvas       m_canvas;
   //---
public:
   //--- Métodos para crear la descripción emergente
   bool              CreateTooltip (const long chart_id,const int subwin);
   //---
private:
   //--- Crea el lienzo para dibujar la descripción
   bool              CreateCanvas(void);
  };

Aparte de la comprobación de la presencia del puntero al formulario al que se adjunta el elemento, el método público CTooltip::CreateTooltip() también contiene la comprobación similar de la presencia del puntero al elemento para el que esta descripción está destinada. Si el puntero al elemento está presente, las coordenadas para la descripción emergente se calculan en relación a las coordenadas de este elemento. En este caso, será a un píxel por debajo del borde inferior del elemento.

//+------------------------------------------------------------------+
//| Crea el objeto Tooltip                                           |
//+------------------------------------------------------------------+
bool CTooltip::CreateTooltip(const long chart_id,const int subwin)
  {
//--- Salir si no hay puntero al formulario
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de crear la descripción emergente, hay que pasar a la clase "
              "el puntero al formuñario: CTooltip::WindowPointer(CWindow &object).");
      return(false);
     }
//--- Salir si no hay puntero al elemento
   if(::CheckPointer(m_element)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de crear la descripción emergente, hay que pasar a la clase "
              "el puntero al elemento: CTooltip::ElementPointer(CElement &object).");
      return(false);
     }
//--- Inicialización de variables
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =subwin;
   m_x        =m_element.X();
   m_y        =m_element.Y2()+1;
//--- Márgenes desde el punto extremo
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Crea la descripción emergente
   if(!CreateTooltip())
      return(false);
//---
   return(true);
  }

A diferencia del método similar en la clase CSeparateLine, en el método privado de la creación del lienzo para el dibujo CTooltip::CreateCanvas() se realizan las siguientes acciones en vez del dibujo tras la creación del objeto y establecimiento de propiedades:

  • Establecimiento del tamaño del array del gradiente para el fondo de la descripción. El tamaño del array es igual al número de píxeles en el parámetro del alto (tamaño por el eje Y) del objeto;
  • Inicialización del array del gradiente;
  • Limpieza del lienzo para el dibujo. Se establece la transparencia a 100%. El valor del canal Alpha es igual a cero.

Al final del método, el objeto se añade al array común de los objetos del del elemento. El código de este método se muestra con detalles a continuación. 

//+------------------------------------------------------------------+
//| Crea el lienzo para dibujar                                      |
//+------------------------------------------------------------------+
bool CTooltip::CreateCanvas(void)
  {
//--- Formación del nombre del objeto
   string name=CElement::ProgramName()+"_help_tooltip_"+(string)CElement::Id();
//--- Creamos el lienzo
   if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
//--- Adjuntar al gráfico
   if(!m_canvas.Attach(m_chart_id,name,m_subwin,1))
      return(false);
//--- Establecemos las propiedades
   m_canvas.Background(false);
//--- Márgenes desde el punto extremo
   m_canvas.XGap(m_x-m_wnd.X());
   m_canvas.YGap(m_y-m_wnd.Y());
//--- Establecimiento del tamaño del array del gradiente para el fondo de la descripción
   CElement::GradientColorsTotal(m_y_size);
   ::ArrayResize(m_array_color,m_y_size);
//--- Inicialización del array del gradiente
   CElement::InitColorArray(m_gradient_top_color,m_gradient_bottom_color,m_array_color);
//--- Limpieza del lienzo para el dibujo
   m_canvas.Erase(::ColorToARGB(clrNONE,0));
   m_canvas.Update();
   m_alpha=0;
//--- Guardamos el puntero del objeto
   CElement::AddToArray(m_canvas);
   return(true);
  }

A continuación, vamos a analizar los métodos para dibujar el gradiente vertical y el marco. Para el gradiente, ya tenemos un array inicializado con los valores necesarios en el momento de la creación del lienzo. Por eso, simplemente dibujamos una línea tras otra con el color especificado en el array en el ciclo con el número de iteraciones igual al alto del lienzo. Para el dibujo del marco, al principio del método se declaran y se incializan inmediatamente los arrays con las coordenadas para cada lado del lienzo. Una vez dibujado el marco, las esquinas se redondean a un píxel, y luego se añaden otros cuatro píxeles en cuatro esquinas para dentro del lienzo. 

Ambos métodos tienen sólo un parámetro: el canal Alpha. Es decir, cuando se llaman estos métodos, podemos indicar con qué grado de transparencia hay que dibujar el gradiente y el marco. 

//+------------------------------------------------------------------+
//| Gradiente vertical                                            |
//+------------------------------------------------------------------+
void CTooltip::VerticalGradient(const uchar alpha)
  {
//--- Coordinadas X
   int x1=0;
   int x2=m_x_size;
//--- Dibujamos el gradiente
   for(int y=0; y<m_y_size; y++)
      m_canvas.Line(x1,y,x2,y,::ColorToARGB(m_array_color[y],alpha));
  }
//+------------------------------------------------------------------+
//| Marco                                                            |
//+------------------------------------------------------------------+
void CTooltip::Border(const uchar alpha)
  {
//--- Color del marco
   color clr=m_border_color;
//--- Límites
   int x_size =m_canvas.X_Size()-1;
   int y_size =m_canvas.Y_Size()-1;
//--- Coordenadas: Arriba/Derecha/Abajo/Izquierda
   int x1[4]; x1[0]=0;      x1[1]=x_size; x1[2]=0;      x1[3]=0;
   int y1[4]; y1[0]=0;      y1[1]=0;      y1[2]=y_size; y1[3]=0;
   int x2[4]; x2[0]=x_size; x2[1]=x_size; x2[2]=x_size; x2[3]=0;
   int y2[4]; y2[0]=0;      y2[1]=y_size; y2[2]=y_size; y2[3]=y_size;
//--- Dibujamos el marco según las coordenadas especificadas
   for(int i=0; i<4; i++)
      m_canvas.Line(x1[i],y1[i],x2[i],y2[i],::ColorToARGB(clr,alpha));
//--- Redondeo de esquinas a un píxel
   clr=clrBlack;
   m_canvas.PixelSet(0,0,::ColorToARGB(clr,0));
   m_canvas.PixelSet(0,m_y_size-1,::ColorToARGB(clr,0));
   m_canvas.PixelSet(m_x_size-1,0,::ColorToARGB(clr,0));
   m_canvas.PixelSet(m_x_size-1,m_y_size-1,::ColorToARGB(clr,0));
//--- Adición de píxeles según las coordenadas especificadas
   clr=C'180,180,180';
   m_canvas.PixelSet(1,1,::ColorToARGB(clr,alpha));
   m_canvas.PixelSet(1,m_y_size-2,::ColorToARGB(clr,alpha));
   m_canvas.PixelSet(m_x_size-2,1,::ColorToARGB(clr,alpha));
   m_canvas.PixelSet(m_x_size-2,m_y_size-2,::ColorToARGB(clr,alpha));
  }

Hagamos que la descripción aparezca inmediatamente en cuanto el cursor se sitúe sobre el elemento, y se apague gradualmente cuando el cursor abandone el área del elemento. 

Para la visualización (aparición) de la descripción emergente, creamos el método CTooltip::ShowTooltip(). Al principio de este método, se realiza la comprobación del valor del campos m_alpha (canal Alpha). Si el valor del canal Alpha es igual o mayor de 225, eso significa que el programa ya ha entrado aquí y la descripción esta visible a 100%, por eso no tiene sentido seguir adelante. En caso contrario, en el siguiente paso establecemos las coordenadas y el margen para el encabezado de la descripción, dibujamos el gradiente y el marco. Si el usuario no ha especificado el texto del encabezado, éste no va a dibujarse. Si el texto del encabezado está presente, se establecen los parámetros de la fuente y se dibuja el encabezado. 

Después de eso, se determinan las coordenadas para el texto principal de la descripción tomando en cuenta la presencia del encabezado. Se establecen los parámetros para el texto principal. A diferencia del encabezado, cuyo texto va en negrita (FW_BLACK), el texto principal tiene la fuente menos notable (FW_THIN). Luego, el texto de la descripción se imprime en el lienzo en el ciclo desde el array inicializado por el usuario. Durante cada iteración, la coordenada Y se ajusta para cada línea al valor especificado. Al final del proceso, el lienzo se actualiza para visualizar los cambios, y se establece el indicio de la descripción completamente visible. El código detallado de este método se muestra a continuación. 

//+------------------------------------------------------------------+
//| Muestra la descripción emergente                                 |
//+------------------------------------------------------------------+
void CTooltip::ShowTooltip(void)
  {
//--- Salir si la descripción está visible a 100%
   if(m_alpha>=255)
      return;
//--- Coordenadas y margen para el encabezado
   int  x        =5;
   int  y        =5;
   int  y_offset =15;
//--- Dibujamos el gradiente
   VerticalGradient(255);
//--- Dibujamos el marco
   Border(255);
//--- Dibujamos el encabezado (si está especificado)
   if(m_header!="")
     {
      //--- Establecemos los parámetros de la fuente
      m_canvas.FontSet(FONT,-80,FW_BLACK);
      //--- Dibujamos el texto del encabezado
      m_canvas.TextOut(x,y,m_header,::ColorToARGB(m_header_color),TA_LEFT|TA_TOP);
     }
//--- Coordenadas para el texto principal de la descripción (tomando en cuenta la presencia del encabezado)
   x=(m_header!="")? 15 : 5;
   y=(m_header!="")? 25 : 5;
//--- Establecemos los parámetros de la fuente
   m_canvas.FontSet(FONT,-80,FW_THIN);
//--- Dibujamos el texto principal
   int lines_total=::ArraySize(m_tooltip_lines);
   for(int i=0; i<lines_total; i++)
     {
      m_canvas.TextOut(x,y,m_tooltip_lines[i],::ColorToARGB(m_text_color),TA_LEFT|TA_TOP);
      y=y+y_offset;
     }
//--- Actualizar el lienzo
   m_canvas.Update();
//--- Indicio de la descripción completamente visible
   m_alpha=255;
  }

Escribiremos el método CTooltip::FadeOutTooltip() para la desaparición suave de la descripción emergente. Al principio de este método, se realiza la comprobación inversa del valor del canal Alpha. Es decir, si ya hemos alcanzado la transparencia completa, no hay que seguir adelante. Si no es el caso, el lienzo se redibuja en el ciclo con el paso especificado a la disminución, hasta conseguir la transparencia absoluta. El código de abajo contiene la versión completa de este método.

//+------------------------------------------------------------------+
//| Desaparición suave de la descripción emergente                       |
//+------------------------------------------------------------------+
void CTooltip::FadeOutTooltip(void)
  {
//--- Salir si la descripción está invisible a 100%
   if(m_alpha<1)
      return;
//--- Margen para el encabezado
   int y_offset=15;
//--- Paso de transparencia
   uchar fadeout_step=7;
//--- Desaparición suave de la descripción emergente
   for(uchar a=m_alpha; a>=0; a-=fadeout_step)
     {
      //--- Si el siguiente paso se hace negativo, paramos el ciclo
      if(a-fadeout_step<0)
        {
         a=0;
         m_canvas.Erase(::ColorToARGB(clrNONE,0));
         m_canvas.Update();
         m_alpha=0;
         break;
        }
      //--- Coordenadas para el encabezado
      int x =5;
      int y =5;
      //--- Dibujamos el gradiente y el marco
      VerticalGradient(a);
      Border(a);
      //--- Dibujamos el encabezado (si está especificado)
      if(m_header!="")
        {
        //--- Establecemos los parámetros de la fuente
         m_canvas.FontSet(FONT,-80,FW_BLACK);
         //--- Dibujamos el texto del encabezado
         m_canvas.TextOut(x,y,m_header,::ColorToARGB(m_header_color,a),TA_LEFT|TA_TOP);
        }
      //--- Coordenadas para el texto principal de la descripción (tomando en cuenta la presencia del encabezado)
      x=(m_header!="")? 15 : 5;
      y=(m_header!="")? 25 : 5;
      //--- Establecemos los parámetros de la fuente
      m_canvas.FontSet(FONT,-80,FW_THIN);
      //--- Dibujamos el texto principal
      int lines_total=::ArraySize(m_tooltip_lines);
      for(int i=0; i<lines_total; i++)
        {
         m_canvas.TextOut(x,y,m_tooltip_lines[i],::ColorToARGB(m_text_color,a),TA_LEFT|TA_TOP);
         y=y+y_offset;
        }
      //--- Actualizar el lienzo
      m_canvas.Update();
     }
  }

Tenemos preparados todos los métodos para la creación y el dibujo de la descripción emergente. Hay que llamar a ellos en el manejador de eventos del elemento. Para averiguar si está pulsado el botón para la visualización de las descripciones en este momento en el formulario, en la clase CWindow hay que crear el método CWindow::TooltipBmpState():

class CWindow : public CElement
  {
public:
   //--- Comprobación del modo de visualización de descripciones emergentes
   bool              TooltipBmpState(void)                             const { return(m_button_tooltip.State());   }
  };

Ahora, si el modo de visualización de descripciones emergentes está activado, cuando el foco se encuentra sobre el elemento, al usuario se le muestra la descripción emergente. Si el cursor se encuentra fuera del área del elemento o el formulario está bloqueado, la descripción no se muestra.

//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                       |
//+------------------------------------------------------------------+
void CTooltip::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del desplazamiento del cursor
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el control está ocultado
      if(!CElement::IsVisible())
         return;
      //--- Salir si el botón de descripciones está desactivado en el formulario
      if(!m_wnd.TooltipBmpState())
         return;
      //--- Si el formulario está bloqueado
      if(m_wnd.IsLocked())
        {
         //--- Ocultar la descripción
         FadeOutTooltip();
         return;
        }
      //--- Si hay foco sobre el elemento
      if(m_element.MouseFocus())
         //--- Mostrar la descripción
         ShowTooltip();
      //--- Si no hay foco
      else
      //--- Ocultar la descripción
         FadeOutTooltip();
      //---
      return;
     }
  }

 


Prueba de las descripciones emergentes

Ahora podemos probar cómo funciona todo eso. El formulario ahora tiene cinco botones en el EA de pruebas. Hagamos que cada uno de ellos tenga su descripción emergente. Declaramos cinco instancias de las clases tipo CTooltip y cinco métodos. Como ejemplo, mostraremos la implementación sólo de uno de ellos.

class CProgram : public CWndEvents
  {
private:
   CTooltip          m_tooltip1;
   CTooltip          m_tooltip2;
   CTooltip          m_tooltip3;
   CTooltip          m_tooltip4;
   CTooltip          m_tooltip5;
   //---
private:
   bool              CreateTooltip1(void);
   bool              CreateTooltip2(void);
   bool              CreateTooltip3(void);
   bool              CreateTooltip4(void);
   bool              CreateTooltip5(void);
  };
//+------------------------------------------------------------------+
//| Crea la descripción emergente 5                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTooltip5(void)
  {
#define TOOLTIP5_LINES_TOTAL 3
//--- Guardamos el puntero a la ventana
   m_tooltip5.WindowPointer(m_window1);
//--- Guardamos el puntero al elemento
   m_tooltip5.ElementPointer(m_icon_button5);
//--- Array con el texto de la descripción
   string text[]=
     {
      "Control \"Botón con imagen\" (5).",
      "Es la segunda línea de la descripción.",
      "Es la tercera línea de la descripción."
     };
//--- Establecemos las propiedades antes de la creación
   m_tooltip5.Header("Icon Button 5");
   m_tooltip5.XSize(250);
   m_tooltip5.YSize(80);
//--- Añadimos el texto línea por línea
   for(int i=0; i<TOOLTIP5_LINES_TOTAL; i++)
      m_tooltip5.AddString(text[i]);
//--- Creamos el control
   if(!m_tooltip5.CreateTooltip(m_chart_id,m_subwin))
      return(false);
//--- Añadimos el objeto al array común de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_tooltip5);
   return(true);
  }

La creación de las descripciones emergentes debe realizarse en el último lugar en el método principal de la creación de la interfaz gráfica, para que se encuentren al final del array de los elementos en la base. De esta manera, siempre estarán por encima de los demás objetos en el gráfico. 

//+------------------------------------------------------------------+
//| Crea el panel de trading                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creación del formulario 1 para los controles
//--- Creación de controles:
//    Menú principal
//--- Menús contextuales
//--- Creación de la barra de estado
//--- Botones con imagen

//--- Descripciones emergentes
   if(!CreateTooltip1())
      return(false);
   if(!CreateTooltip2())
      return(false);
   if(!CreateTooltip3())
      return(false);
   if(!CreateTooltip4())
      return(false);
   if(!CreateTooltip5())
      return(false);
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Para mostrar el botón de visualización de descripciones emergentes en el formulario, hay que usar el método CWindow::UseTooltipsButton():

//+------------------------------------------------------------------+
//| Crea el formulario 1 para los controles                         |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow1(const string caption_text)
  {
//--- Añadimos el puntero de la ventana en el array de ventanas
   CWndContainer::AddWindow(m_window1);
//--- Coordenadas
   int x=1;
   int y=20;
//--- Propiedades
   m_window1.Movable(true);
   m_window1.XSize(251);
   m_window1.YSize(200);
   m_window1.UseTooltipsButton();
   m_window1.CaptionBgColor(clrCornflowerBlue);
   m_window1.CaptionBgColorHover(C'150,190,240');
//--- Creación del formulario
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

En la captura de pantalla de abajo se muestra el resultado final:


Fig. 4. Prueba de la Descripción emergente.

 

¡Tiene buena apariencia y funciona bien! Pero ahora, este elemento requiere un array personal en la base de punteros. Luego, durante el desarrollo del modo de ventanas múltiples mostraremos en qué ocasiones eso puede ser útil. 

 


Array privado para las descripciones emergentes

Pues, vamos a la clase CWndContainer y la completamos con nuevos miembros. Hay que añadir el array privado para las descripciones emergentes a la estructura WindowElements. Además, vamos a crear (1) el método CWndContainer::TooltipsTotal() para obtener el número de las descripciones y (2) el método CWndContainer::AddTooltipElements() para añadir el puntero de la descripción al array privado.

class CWndContainer
  {
protected:
   //--- Estructura de los arrays de controles
   struct WindowElements
     {
      //--- Descripciones emergentes
      CTooltip         *m_tooltips[];
     };
   //--- Array de los arrays de los controles para cada ventana
   WindowElements    m_wnd[];
   //---
public:
  Número de descripciones emergentes
   int               TooltipsTotal(const int window_index);
   //---
private:
  //--- Guarda los punteros a los elementos de las descripciones emergentes en la base
   bool              AddTooltipElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Devuelve el número de las descripciones según el índice especificado de la ventana          |
//+------------------------------------------------------------------+
int CWndContainer::TooltipsTotal(const int window_index)
  {
   if(window_index>=::ArraySize(m_wnd))
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return(::ArraySize(m_wnd[window_index].m_tooltips));
  }
//+------------------------------------------------------------------+
//| Guarda el puntero a la descripción en el array privado           |
//+------------------------------------------------------------------+
bool CWndContainer::AddTooltipElements(const int window_index,CElement &object)
  {
//--- Salimos si no es una descripción emergente
   if(object.ClassName()!="CTooltip")
      return(false);
//--- Obtenemos el puntero a la descripción
   CTooltip *t=::GetPointer(object);
//--- Añadimos el puntero al array privado
   AddToRefArray(t,m_wnd[window_index].m_tooltips);
   return(true);
  }

La llamada al método CWndContainer::AddTooltipElements() debe realizarse en el método público CWndContainer::AddToElementsArray() en la clase personalizada cuando el elemento se añade a la base. En el código de abajo se muestra su versión reducida.

//+------------------------------------------------------------------+
//| Añade el puntero al array de controles                           | 
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Si en la base no hay formularios para los controles
//--- Si se solicita el formulario que no existente
//--- Añadimos al array común de controles
//--- Añadir los objetos del control al array común de objetos
//--- Recordamos id del último control en todos los formularios
//--- Aumentamos el contador de los identificadores de controles

//--- Guarda los punteros a los objetos del menú contextual en la base
//--- Guarda los punteros a los objetos del menú principal en la base
//--- Guarda los punteros a los objetos del botón de división en la base

//--- Guarda los punteros a los objetos de las descripciones emergentes en la base
   if(AddTooltipElements(window_index,object))
      return;
  }

Hemos terminado el desarrollo de la clase para la creación de las descripciones emergentes. Puede descargar la versión completa al final del artículo. 

 


Conclusión

En este artículo (el primer capítulo de la cuarta parte), hemos considerado el desarrollo de los elementos informativos, tales como la “Barra de estado” y la “Descripción emergente”. En el segundo capítulo de la cuarta parte de la serie implementaremos la posibilidad de crear las interfaces gráficas de ventanas múltiples. Además, hablaremos del sistema de gestión de las prioridades para el clic izquierdo.

Más abajo puede descargar el material de la primera parte de la serie para poder probar cómo funciona todo eso. Si le surgen algunas preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.

Lista de artículos (capítulos) de la cuarta parte: