Interfaces gráficas XI: Controles dibujados (build 14.2)

10 agosto 2017, 09:31
Anatoli Kazharski
0
199

Contenido

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 adjunta la versión completa de la librería en la fase actual del desarrollo. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.

En la versión renovada de la librería, todos sus controles van a dibujarse en los objetos gráficos separados tipo OBJ_BITMAP_LABEL. Además de eso, seguiré describiendo la optimización global del código de la librería. Puede leer el principio de esta descripción en el artículo anterior. Ahora veremos cómo se han cambiado las clases que representan el núcleo de la librería. La nueva versión de la librería se ha hecho aún más orientada a objetos. El código es más comprensible para el análisis. Gracias a eso, el usuario podrá desarrollar la librería personalmente según las tareas que tenga. 


Métodos para dibujar los controles

En la clase CElement, ha sido declarada la instancia de la clase para el dibujado. Sus métodos permiten crear un objeto para el dibujado y eliminarlo. Si es necesario, se puede obtener su puntero.

class CElement : public CElementBase
  {
protected:
   //--- Lienzo para dibujar el control
   CRectCanvas       m_canvas;
   //---
public:
   //--- Devuelve el puntero al lienzo del control
   CRectCanvas      *CanvasPointer(void) { return(::GetPointer(m_canvas)); }
  };

Ahora existe un método general para crear el objeto (lienzo) para el dibujado de la apariencia del control. Se encuentra en la clase base CElement y está disponible en todas las clases de los controles de la librería. Para crear un objeto gráfico de este tipo, se utiliza el método CElement::CreateCanvas(). Es necesario traspasar (1) el nombre, (2) coordenadas, (3) tamaños y (4) el formato de color como argumentos. Por defecto, se establece el formato COLOR_FORMAT_ARGB_NORMALIZE, que permite hacer que los controles sean trasparentes. Si se traspasan los tamaños incorrectos, van a corregirse al principio del método. Después de crear y fijar el objeto en el gráfico en el que se encuentra la aplicación MQL, se establecen las propiedades básicas que antes se repetían varias veces en todas las clases de los controles.

class CElement : public CElementBase
  {
public:
   //--- Creando el lienzo para el dibujado
   bool              CreateCanvas(const string name,const int x,const int y,
                                  const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE);
  };
//+------------------------------------------------------------------+
//| Creando el lienzo para el dibujado                               |
//+------------------------------------------------------------------+
bool CElement::CreateCanvas(const string name,const int x,const int y,
                            const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE)
  {
//--- Corrección de tamaños
   int xsize =(x_size<1)? 50 : x_size;
   int ysize =(y_size<1)? 20 : y_size;
//--- Resetear el último error
   ::ResetLastError();
//--- Creación del objeto
   if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,xsize,ysize,clr_format))
     {
      ::Print(__FUNCTION__," > Fallo al crear el lienzo para dibujar el control ("+m_class_name+"): ",::GetLastError());
      return(false);
     }
//--- Resetear el último error
   ::ResetLastError();
//--- Obtenemos el puntero a la clase base
   CChartObject *chart=::GetPointer(m_canvas);
//--- Adjuntar al gráfico
   if(!chart.Attach(m_chart_id,name,(int)m_subwin,(int)1))
     {
      ::Print(__FUNCTION__," > Fallo al adjuntar el lienzo al gráfico: ",::GetLastError());
      return(false);
     }
//--- Propiedades
   m_canvas.Tooltip("\n");
   m_canvas.Corner(m_corner);
   m_canvas.Selectable(false);
//--- Todos los controles, excepto el formulario, tienen la prioridad mayor que el control principal
   Z_Order((dynamic_cast<CWindow*>(&this)!=NULL)? 0 : m_main.Z_Order()+1);
//--- Coordenadas
   m_canvas.X(x);
   m_canvas.Y(y);
//--- Tamaños
   m_canvas.XSize(x_size);
   m_canvas.YSize(y_size);
//--- Márgenes desde el punto extremo
   m_canvas.XGap(CalculateXGap(x));
   m_canvas.YGap(CalculateYGap(y));
   return(true);
  }

Pasamos a los métodos básicos para el dibujado de los controles. Todos ellos se encuentran en la clase CElement y están declarados como virtuales (virtual). 

En primer lugar, es el dibujado del fondo. En la versión base, es el relleno de color para el que se usa el método CElement::DrawBackground(). Como una opción, se puede establecer la transparencia. Para eso sirve el método público CElement::Alpha(), para el que se traspasa el valor del canal Alpha de 0 a 255 como argumento. El valor cero significa la transparencia absoluta. En esta versión, la transparencia se aplica solamente para el relleno del fondo y para el borde. El texto y las imágenes van a quedarse no transparentes y completamente claros en caso de cualquier valor establecido del canal Alpha.

class CElement : public CElementBase
  {
protected:
   //--- Valor del canal Alpha (transparencia del control)
   uchar             m_alpha;
   //---
public:
   //--- Valor del canal Alpha (transparencia del control)
   void              Alpha(const uchar value)                        { m_alpha=value;                   }
   uchar             Alpha(void)                               const { return(m_alpha);                 }
   //---
protected:
   //--- Dibuja el fondo
   virtual void      DrawBackground(void);
  };
//+------------------------------------------------------------------+
//| Dibuja el fondo                                                  |
//+------------------------------------------------------------------+
void CElement::DrawBackground(void)
  {
   m_canvas.Erase(::ColorToARGB(m_back_color,m_alpha));
  }

A menudo es necesario dibujar el borde para algún control. El método CElement::DrawBorder() dibuja el borde en los lados del objeto para el dibujado. Para eso se puede usar también el método Rectangle() que dibuja un rectángulo sin relleno.

class CElement : public CElementBase
  {
protected:
   //--- Dibuja el borde
   virtual void      DrawBorder(void);
  };
//+------------------------------------------------------------------+
//| Dibuja el borde                                                  |
//+------------------------------------------------------------------+
void CElement::DrawBorder(void)
  {
//--- Coordenadas
   int x1=0,y1=0;
   int x2=m_canvas.X_Size()-1;
   int y2=m_canvas.Y_Size()-1;
//--- Dibujamos el rectángulo sin relleno
   m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(m_border_color,m_alpha));
  }

En el artículo anterior ya hemos contado que para cualquier control se puede establecer cualquier cantidad de los grupos de imágenes. Por eso el método para el dibujado del control debe saber visualizar todas las imágenes establecidas por el usuario. Aquí para eso se utiliza el método CElement::DrawImage(). El programa recorrerá consecutivamente todos los grupos y las imágenes dentro de ellos, visualizándolas píxel por píxel en el lienzo. Antes del ciclo de la visualización de la imagen, se determina cuál de ellas está seleccionada en el grupo en este momento. Vamos a ver el código de este método:

class CElement : public CElementBase
  {
protected:
   //--- Dibuja la imagen
   virtual void      DrawImage(void);
  };
//+------------------------------------------------------------------+
//| Dibuja la imagen                                                 |
//+------------------------------------------------------------------+
void CElement::DrawImage(void)
  {
//--- Número de grupos
   uint group_total=ImagesGroupTotal();
//--- Dibujamos las imágenes
   for(uint g=0; g<group_total; g++)
     {
      //--- Indice de la imagen seleccionada
      int i=SelectedImage(g);
      //--- Si no hay imágenes
      if(i==WRONG_VALUE)
         continue;
      //--- Coordenadas
      int x =m_images_group[g].m_x_gap;
      int y =m_images_group[g].m_y_gap;
      //--- Tamaños
      uint height =m_images_group[g].m_image[i].Height();
      uint width  =m_images_group[g].m_image[i].Width();
      //--- Dibujamos
      for(uint ly=0,p=0; ly<height; ly++)
        {
         for(uint lx=0; lx<width; lx++,p++)
           {
             //--- Si no hay color, ir al siguiente píxel
            if(m_images_group[g].m_image[i].Data(p)<1)
               continue;
             //--- Obtenemos el color de la capa inferior (fondo de la celda) y el color del píxel indicado de la imagen
            uint background  =::ColorToARGB(m_canvas.PixelGet(x+lx,y+ly));
            uint pixel_color =m_images_group[g].m_image[i].Data(p);
             //--- Mezclamos los colores
            uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
             //--- Dibujamos el píxel de la imagen solapada
            m_canvas.PixelSet(x+lx,y+ly,foreground);
           }
        }
     }
  }

Muchos controles tienen la descripción de texto. De eso, se encarga el método CElement::DrawText(). Algunos campos en este método permiten configurar la visualización del texto dependiendo del estado del control. Hay tres estados disponibles:

  • bloqueado
  • pulsado
  • se encuentra en el foco (debajo del cursor del ratón).

Aparte de eso, se toma en cuenta si está activado o no el modo de alineación del texto por el centro del control. Código del método:

class CElement : public CElementBase
  {
protected:
   //--- Dibuja el texto
   virtual void      DrawText(void);
  };
//+------------------------------------------------------------------+
//| Dibuja el texto                                                  |
//+------------------------------------------------------------------+
void CElement::DrawText(void)
  {
//--- Coordenadas
   int x =m_label_x_gap;
   int y =m_label_y_gap;
//--- Determinamos el color para la etiqueta de texto
   color clr=clrBlack;
//--- Si el control está bloqueado
   if(m_is_locked)
      clr=m_label_color_locked;
   else
     {
       //--- Si el control se encuentra en el estado pulsado
      if(!m_is_pressed)
         clr=(m_mouse_focus)? m_label_color_hover : m_label_color;
      else
        {
         if(m_class_name=="CButton")
            clr=m_label_color_pressed;
         else
            clr=(m_mouse_focus)? m_label_color_hover : m_label_color_pressed;
        }
     }
//--- Propiedades de la fuente
   m_canvas.FontSet(m_font,-m_font_size*10,FW_NORMAL);
//--- Dibujar el texto tomando en cuenta el modo de alineación por el centro
   if(m_is_center_text)
     {
      x =m_x_size>>1;
      y =m_y_size>>1;
      m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_CENTER|TA_VCENTER);
     }
   else
      m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_LEFT);
  }

Todos los métodos arriba descritos van a invocarse en el método común público CElement::Draw(). No tiene un código base porque el conjunto llamado de los métodos para el dibujado será único en cada control.

class CElement : public CElementBase
  {
public:
  //--- Dibuja el control
   virtual void      Draw(void) {}
  };

Para el usuario de la librería se usa el método CElement::Update(). Se invoca cada vez después de la introducción de cambios de programa en el control de la interfaz gráfica. Hay dos opciones para la llamada: (1) con redibujado completo del control o (2) para la aplicación de los cambios introducidos antes (véase el código de abajo). Este método también está declarado como virtual, porque en algunas clases de los controles pueden haber sus versiones únicas que toman en consideración las particularidades en los métodos y las secuencias del dibujado.

class CElement : public CElementBase
  {
public:
   //--- Actualiza el control para mostrar los últimos cambios
   virtual void      Update(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Actualización del control                                        |
//+------------------------------------------------------------------+
void CElement::Update(const bool redraw=false)
  {
//--- Con redibujado del control
   if(redraw)
     {
      Draw();
      m_canvas.Update();
      return;
     }
//--- Aplicar
   m_canvas.Update();
  }

Nuevo diseño de la interfaz gráfica

Puesto que ahora todos los controles se dibujan, ha aparecido la posibilidad de implementar el nuevo diseño para la interfaz gráfica. Aquí no hace falta inventar algo de otro mundo, sino usar la solución ya hecha. He tomado como base la estética concisa del diseño del sistema operativo Windows 10

Las imágenes para los iconos en los controles como los botones del formulario para los controles, botones de opción, casillas de verificación (checkbox), cuadros combinados (combobox), elementos del menú, elementos de listas jerárquicas, etc. están hechas como en Windows 10

Antes ya hemos dicho que a cualquier control se le puede asignar la transparencia. En la captura de pantalla de abajo se muestra el ejemplo de una ventana semitransparente (CWindow). El valor del canal Alpha es igual a 200.  

Fig. 8. Demostración de la transparencia del formulario para los controles. 

Fig. 8. Demostración de la transparencia del formulario para los controles.

Para que el formulario sea transparente por toda el área, hace falta el método CWindow::TransparentOnlyCaption(). Por defecto, se establece el modo cuando el efecto de la transparencia se aplica sólo al encabezado

class CWindow : public CElement
  {
private:
   //--- Activa la transparencia sólo para el encabezado
   bool              m_transparent_only_caption;
   //---
public:
   //--- Activa la transparencia sólo para el encabezado de la ventana
   void              TransparentOnlyCaption(const bool state) { m_transparent_only_caption=state; }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_transparent_only_caption(true)
  {
...
  }

Abajo se muestra la apariencia de diferentes tipos de botones:

 Fig. 9. Demostración de la apariencia de algunos tipos de botones.

Fig. 9. Demostración de la apariencia de algunos tipos de botones.

En la siguiente captura de pantalla se muestra la apariencia actual de los checkbox, campos de edición, combobox con lista desplegable con una barra de desplazamiento y los sliders numéricos. Nótese que ahora se puede hacer las imágenes animadas. En el tercer punto de la barra de estado se imita el fallo de conexión con el servidor. Su apariencia es una copia exacta del control análogo en la barra de estado de MetaTrader 5.

Fig. 10. Demostración de la apariencia de los checkbox, combobox, sliders y otros controles.

Fig. 10 Demostración de la apariencia de los checkbox, combobox, sliders y otros controles.

La apariencia de los demás controles de la interfaz gráfica de la librería se muestra en la aplicación de prueba adjunta al artículo.

Descripciones emergentes

A la clase CElement, han sido añadidos los métodos adicionales para gestionar la visualización de las descripciones emergentes en los controles. Ahora, se puede establecer una descripción emergente estándar para cualquier control si el texto no supera 63 caracteres. Utilice los métodos CElement::Tooltip() para establecer y obtener el texto de la descripción emergente.

class CElement : public CElementBase
  {
protected:
   //--- Texto de la descripción emergente
   string            m_tooltip_text;
   //---
public:
  Descripción emergente
   void              Tooltip(const string text)                      { m_tooltip_text=text;             }
   string            Tooltip(void)                             const { return(m_tooltip_text);          }
  };

Para activar o desactivar el modo de visualización de la descripción emergente, hay que usar el método CElement::ShowTooltip(). 

class CElement : public CElementBase
  {
public:
   //--- Modo de visualización de la descripción emergente
   void              ShowTooltip(const bool state);
  };
//+------------------------------------------------------------------+
//| Establecer la visualización de la descripción emergente          |
//+------------------------------------------------------------------+
void CElement::ShowTooltip(const bool state)
  {
   if(state)
      m_canvas.Tooltip(m_tooltip_text);
   else
      m_canvas.Tooltip("\n");
  }

Cada clase de los controles contiene los métodos para obtener los punteros de los controles incluidos. Por ejemplo, si hace falta crear las descripciones emergentes para los botones del formulario, hay que escribir las siguientes líneas en el método de la creación del formulario de la clase personalizada:

...
//--- Establecemos las descripciones emergentes
   m_window.GetCloseButtonPointer().Tooltip("Close");
   m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
   m_window.GetTooltipButtonPointer().Tooltip("Tooltips");
...

A continuación, se muestra cómo funciona eso. Para los botones del formulario, se activan las descripciones estándar. Para los controles donde pueden haber más de 63 caracteres, se utiliza el control CTooltip.

 Fig. 11. Demostración de dos tipos de las descripciones emergentes (estándar y personalizadas).

Fig. 11. Demostración de dos tipos de las descripciones emergentes (estándar y personalizadas).


Nuevos identificadores de eventos

Han sido añadidos nuevos identificadores de eventos. Gracias a eso, el consumo de los recursos de la CPU se ha reducido considerablemente. ¿Cómo se ha podido conseguir eso?

Al crear una aplicación MQL con interfaz gráfica y muchos controles, es importante minimizar el consumo de los recursos de la CPU.

El control se resalta con color si situamos el cursor sobre él.  De esta manera, se muestra que este control está disponible para la interacción. Pero no todos los controles están disponibles y visibles a la vez.

  • Las listas desplegables, los calendarios y los menús contextuales se encuentran ocultados la mayor parte del tiempo. Se abren sólo de vez en cuando para que el usuario pueda seleccionar la función necesaria, una fecha o un modo.
  • Los grupos de los controles pueden ser asignados a diferentes pestañas, pero sólo una ellas puede estar abierta al mismo tiempo. 
  • Si el formulario está minimizado, todos los controles también están ocultados.
  • Si la ventana de diálogo está abierta, el procesamiento de eventos estará disponible sólo para los controles de este formulario.

Es lógico que no tiene sentido procesar constantemente la lista completa de los controles de la interfaz gráfica cuando sólo algunos de ellos están disponibles para el uso. Hay que formar el array del procesamiento de eventos solamente para la lista de los controles abiertos.

Hay controles cuyo resultado de interacción influirá sólo en ellos. Entonces, sólo este control debe quedar disponible para el procesamiento. Vamos a nombrar estos casos y controles:

  • Desplazamiento del deslizador de la barra de desplazamiento (CScroll). Para el procesamiento hay que dejar sólo la propia barra de desplazamiento y el control que está formando (lista, tabla, campo de edición multilínea, etc.). 
  • Desplazamiento del deslizador del slider (CSlider). Para eso, bastará con que esté disponible el control slider y el campo de edición numérico donde se muestra el cambio de valores.
  • Cambio del ancho de las columnas en la tabla (CTable). Para el procesamiento, hay que dejar disponible sólo la tabla.
  • Cambio del ancho de las listas en el control «Lista jerárquica» (CTreeView). En el momento de captar la línea entre las listas, hay que procesar sólo este control.
  • Desplazamiento del formulario (CWindow). Todos los controles, excepto el formulario en el momento de su desplazamiento, se eliminan del procesamiento.

En todos los casos mencionados, es necesario enviar los mensajes a los controles que hay que recibir y procesar en el núcleo de la librería. Dos identificadores de eventos para determinar la disponibilidad de los controles (ON_SET_AVAILABLE) y la formación del array de controles (ON_CHANGE_GUI) van a procesarse en el núcleo. Todos los identificadores de eventos se encuentran en el archivo Define.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_CHANGE_GUI               (28) // Interfaz gráfica ha cambiado
#define ON_SET_AVAILABLE            (39) // Determinar controles disponibles
...

Ocultamos y mostramos los controles, usando los métodos Show() y Hide(). Para determinar la disponibilidad, a la clase CElement ha sido añadido una nueva propiedad: Su valor se establece a través del método virtual público CElement::IsAvailable(). Aquí, igual como en otros métodos que determinan el estado de los controles, el valor traspasado también se establece para los controles incluidos. Respecto al estado traspasado, se establecen las prioridades para el clic izquierdo del ratón. Si el control debe estar no disponible, las prioridades serán reseteados.

class CElement : public CElementBase
  {
protected:
   bool              m_is_available;   // disponibilidad
   //---
public:
   //--- Indicio de la disponibilidad del control
   virtual void      IsAvailable(const bool state)                   { m_is_available=state;                 }
   bool              IsAvailable(void)                         const { return(m_is_available);               }
  };
//+------------------------------------------------------------------+
//| Disponibilidad del control                                       |
//+------------------------------------------------------------------+
void CElement::IsAvailable(const bool state)
  {
//--- Salir si ya está establecido
   if(state==CElementBase::IsAvailable())
      return;
//--- Establecer
   CElementBase::IsAvailable(state);
//--- Otros controles
   int elements_total=ElementsTotal();
   for(int i=0; i<elements_total; i++)
      m_elements[i].IsAvailable(state);
//--- Establecer las prioridades para el clic izquierdo del ratón
   if(state)
      SetZorders();
   else
      ResetZorders();
  }

Como ejemplo, vamos a mostrar aquí el código del método CComboBox::ChangeComboBoxListState() en el que se determina la visibilidad de la lista desplegable en el control «Combobox». 

Si el botón de combobox se pulsa y es necesario mostrar la lista, el evento con el identificador ON_SET_AVAILABLE se envía inmediatamente después de la visualización de la lista. Como parámetros adicionales, se envía (1) el identificador del control y (2) el indicio para determinar qué es lo que debe hacer exactamente el procesador de eventos: recuperar todos los controles visibles o hacer disponible sólo el identificador que figura en el evento. Para la recuperación se usa el indicio con el valor 1, y para el establecimiento de la disponibilidad del control especificado, se usa 0

Después del mensaje con el identificador del evento ON_SET_AVAILABLE, se envía el mensaje con el identificador del evento ON_CHANGE_GUI. Al procesar este evento, se formará el array con los controles disponibles en este momento.

//+------------------------------------------------------------------+
//| Cambia el estado actual del combobox por el contrario            |
//+------------------------------------------------------------------+
void CComboBox::ChangeComboBoxListState(void)
  {
//--- Si el botón está pulsado
   if(m_button.IsPressed())
     {
      //--- Mostrar la lista
      m_listview.Show();
      //--- Enviamos el mensaje para determinar la disponibilidad de controles
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- Enviamos el mensaje sobre el cambio en la interfaz gráfica
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
     }
   else
     {
      //--- Ocultar la lista
      m_listview.Hide();
      //--- Enviamos el mensaje para recuperar los controles
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
      //--- Enviamos el mensaje sobre el cambio en la interfaz gráfica
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
     }
  }

Pero por ejemplo, para el control «Pestañas» será suficiente enviar al procesamiento sólo uno de los eventos descritos, con el identificador ON_CHANGE_GUI. Aquí no hay que hacer que determinados controles estén disponibles. Al cambiar las pestañas, se establece el estado de la visibilidad para los controles que están asignados a un grupo de pestañas. Para controlar la visibilidad de los grupos de controles, en la clase CTabs se utiliza el método CTabs::ShowTabElements(), que ha sido modificado en la nueva versión de la librería. A veces, en una de las pestañas es necesario colocar otro grupo de pestañas. Por eso, si durante la visualización de los controles de la pestaña seleccionada, resulta que uno de ellos tiene el tipo CTabs, entonces el método CTabs::ShowTabElements() también se invoca inmediatamente en este control. Este enfoque permite colocar las pestañas a cualquier nivel de anidación.

//+------------------------------------------------------------------+
//| Muestra los controles sólo de la pestaña seleccionada            |
//+------------------------------------------------------------------+
void CTabs::ShowTabElements(void)
  {
//--- Salir si las pestañas están ocultadas
   if(!CElementBase::IsVisible())
      return;
//--- Comprobación del índice de la pestaña seleccionada
   CheckTabIndex();
//---
   uint tabs_total=TabsTotal();
   for(uint i=0; i<tabs_total; i++)
     {
      //--- Obtenemos el número de controles adjuntados a la pestaña
      int tab_elements_total=::ArraySize(m_tab[i].elements);
      //--- Si esta pestaña está seleccionada
      if(i==m_selected_tab)
        {
         //--- Mostrar los controles de la pestaña
         for(int j=0; j<tab_elements_total; j++)
           {
            //--- Mostrar los controles
            CElement *el=m_tab[i].elements[j];
            el.Reset();
             //--- Si este control «Pestañas», mostrar los controles de la pestaña abierta
            CTabs *tb=dynamic_cast<CTabs*>(el);
            if(tb!=NULL)
               tb.ShowTabElements();
           }
        }
      //--- Ocultar los controles de las pestañas no activas
      else
        {
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Hide();
        }
     }
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_CLICK_TAB,CElementBase::Id(),m_selected_tab,"");
  }

Después de mostrar los controles de la pestaña seleccionada, se envía el mensaje sobre el cambio de la interfaz gráfica y hay que formar el array de los controles disponibles para el procesamiento.

//+------------------------------------------------------------------+
//| Clic en la pestaña en el grupo                                   |
//+------------------------------------------------------------------+
bool CTabs::OnClickTab(const int id,const int index)
  {
//--- Salir si (1) los identificadores no coinciden o (2) el control está bloqueado
   if(id!=CElementBase::Id() || CElementBase::IsLocked())
      return(false);
//--- Salir si el índice no coincide
   if(index!=m_tabs.SelectedButtonIndex())
      return(true);
//--- Guardar el índice de la pestaña seleccionada
   SelectedTab(index);
//--- Redibujar el control
   Reset();
   Update(true);
//--- Mostrar los controles sólo de la pestaña seleccionada
   ShowTabElements();
//--- Enviamos el mensaje sobre el cambio en la interfaz gráfica
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0.0,"");
   return(true);
  }

Al archivo Defines.mqh, han sido añadidos dos identificadores nuevos para la generación de los eventos:

  • ON_MOUSE_FOCUS — el cursor del ratón ha entrado en el área del control;
  • ON_MOUSE_BLUR — el cursor del ratón ha salido del área del control.

...
#define ON_MOUSE_BLUR               (34) // El cursor del ratón ha salido del área del control
#define ON_MOUSE_FOCUS              (35) // El cursor del ratón ha entrado en el área del control
...

Estos eventos se generan solamente en el momento de la intersección de los límites de los controles. En la clase base de los controles (CElementBase) hay método CElementBase::CheckCrossingBorder() que comprueba el momento de la intersección de los límites de los controles por el cursor del ratón. Vamos a completarlo con la generación de los eventos descritos:

//+------------------------------------------------------------------+
//| Comprobación de la intersección de los límites del control       |
//+------------------------------------------------------------------+
bool CElementBase::CheckCrossingBorder(void)
  {
//--- Si es el momento de la intersección de los límites del control
   if((MouseFocus() && !IsMouseFocus()) || (!MouseFocus() && IsMouseFocus()))
     {
      IsMouseFocus(MouseFocus());
      //--- Mensaje sobre la intersección en el control
      if(MouseFocus())
         ::EventChartCustom(m_chart_id,ON_MOUSE_FOCUS,m_id,m_index,m_class_name);
      //--- Mensaje sobre la intersección desde el control
      else
         ::EventChartCustom(m_chart_id,ON_MOUSE_BLUR,m_id,m_index,m_class_name);
      //---
      return(true);
     }
//---
   return(false);
  }

En la versión actual de la librería, estos eventos se procesan sólo en el menú principal (CMenuBar). Veremos cómo funciona eso.

Una vez creado y guardado el menú principal, sus elementos (CMenuItem) se colocan en la lista del repositorio como unos controles separados. La clase CMenuItem es una clase derivada de CButton (control «Botón»). Por eso la llamada al manejador (handle) de eventos del elemento del menú se empieza con la llamada al manejador de la clase base CButton

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesar el evento la clase base
   CButton::OnEvent(id,lparam,dparam,sparam);
...
  }

El seguimiento de la intersección del botón ya existe en el manejador base de eventos, no es necesario repetirlo en la clase derivada CMenuItem.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CButton::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)
     {
      //--- Redibujar el control si la intersección de los bordes ha tenido lugar
      if(CheckCrossingBorder())
         Update(true);
      //---
      return;
     }
...
  }

Si el cursor ha cruzado los bordes hacia dentro del área del botón, se genera el evento con el identificador ON_MOUSE_FOCUS. Ahora en el manejador de la clase CMenuBar, precisamente según este evento se cambian los menús contextuales, cuando se activa el control «Menú principal». 

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del cambio del foco en los botones del menú
   if(id==CHARTEVENT_CUSTOM+ON_MOUSE_FOCUS)
     {
       //--- Salir si (1) el menú principal no está activado o (2) los identificadores no coinciden
      if(!m_menubar_state || lparam!=CElementBase::Id())
         return;
      //--- Cambiar el menú contextual según el elemento activado del menú principal
      SwitchContextMenuByFocus();
      return;
     }
...
  }

Optimizamos el núcleo de la librería

Vamos a analizar los cambios, correcciones y adiciones en las clases CWndContainer y CWndEvents, que pueden llamarse el núcleo de la librería. Es que precisamente aquí está organizado el acceso a todos los controles y se procesan los flujos de eventos generados por los controles de la interfaz gráfica.

A la clase CWndContainer ha sido añadido el método de plantilla CWndContainer::ResizeArray() para trabajar con los arrays. El array de cualquier tipo traspasado en este método será aumentado a un control, y el método devolverá el índice del último control.

//+------------------------------------------------------------------+
//| Clase para almacenar todos los objetos de la interfaz            |
//+------------------------------------------------------------------+
class CWndContainer
  {
private:
   //--- Aumenta el array a un control y devuelve el último índice
   template<typename T>
   int               ResizeArray(T &array[]);
  };
//+------------------------------------------------------------------+
//| Aumenta el array a un control y devuelve el último índice        |
//+------------------------------------------------------------------+
template<typename T>
int CWndContainer::ResizeArray(T &array[])
  {
   int size=::ArraySize(array);
   ::ArrayResize(array,size+1,RESERVE_SIZE_ARRAY);
   return(size);
  }

Recordaré que los arrays personales están declarados para muchos controles en la clase CWndContainer (repositorio de los punteros a todos los controles de la interfaz), en la estructura WindowElements. Para obtener el número de controles de un determinado tipo de esta lista, ha sido implementado el método universal CWndContainer::ElementsTotal(). Si le pasamos el índice de la ventana y el tipo del control, obtenemos su cantidad en la interfaz gráfica de la aplicación MQL. Para especificar el tipo del control, la nueva enumeración ENUM_ELEMENT_TYPE ha sido añadida al archivo Enums.mqh:

//+------------------------------------------------------------------+
//| Enumeración de tipos de controles                                |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE
  {
   E_CONTEXT_MENU    =0,
   E_COMBO_BOX       =1,
   E_SPLIT_BUTTON    =2,
   E_MENU_BAR        =3,
   E_MENU_ITEM       =4,
   E_DROP_LIST       =5,
   E_SCROLL          =6,
   E_TABLE           =7,
   E_TABS            =8,
   E_SLIDER          =9,
   E_CALENDAR        =10,
   E_DROP_CALENDAR   =11,
   E_SUB_CHART       =12,
   E_PICTURES_SLIDER =13,
   E_TIME_EDIT       =14,
   E_TEXT_BOX        =15,
   E_TREE_VIEW       =16,
   E_FILE_NAVIGATOR  =17,
   E_TOOLTIP         =18
  };

El código del método CWndContainer::ElementsTotal() se muestra a continuación:

//+------------------------------------------------------------------------------+
//| Número de controles según el índice de la ventana del tipo especificado      |
//+------------------------------------------------------------------------------+
int CWndContainer::ElementsTotal(const int window_index,const ENUM_ELEMENT_TYPE type)
  {
//--- Comprobar la superación del rango
   int index=CheckOutOfRange(window_index);
   if(index==WRONG_VALUE)
      return(WRONG_VALUE);
//---
   int elements_total=0;
//---
   switch(type)
     {
      case E_CONTEXT_MENU    : elements_total=::ArraySize(m_wnd[index].m_context_menus);   break;
      case E_COMBO_BOX       : elements_total=::ArraySize(m_wnd[index].m_combo_boxes);     break;
      case E_SPLIT_BUTTON    : elements_total=::ArraySize(m_wnd[index].m_split_buttons);   break;
      case E_MENU_BAR        : elements_total=::ArraySize(m_wnd[index].m_menu_bars);       break;
      case E_MENU_ITEM       : elements_total=::ArraySize(m_wnd[index].m_menu_items);      break;
      case E_DROP_LIST       : elements_total=::ArraySize(m_wnd[index].m_drop_lists);      break;
      case E_SCROLL          : elements_total=::ArraySize(m_wnd[index].m_scrolls);         break;
      case E_TABLE           : elements_total=::ArraySize(m_wnd[index].m_tables);          break;
      case E_TABS            : elements_total=::ArraySize(m_wnd[index].m_tabs);            break;
      case E_SLIDER          : elements_total=::ArraySize(m_wnd[index].m_sliders);         break;
      case E_CALENDAR        : elements_total=::ArraySize(m_wnd[index].m_calendars);       break;
      case E_DROP_CALENDAR   : elements_total=::ArraySize(m_wnd[index].m_drop_calendars);  break;
      case E_SUB_CHART       : elements_total=::ArraySize(m_wnd[index].m_sub_charts);      break;
      case E_PICTURES_SLIDER : elements_total=::ArraySize(m_wnd[index].m_pictures_slider); break;
      case E_TIME_EDIT       : elements_total=::ArraySize(m_wnd[index].m_time_edits);      break;
      case E_TEXT_BOX        : elements_total=::ArraySize(m_wnd[index].m_text_boxes);      break;
      case E_TREE_VIEW       : elements_total=::ArraySize(m_wnd[index].m_treeview_lists);  break;
      case E_FILE_NAVIGATOR  : elements_total=::ArraySize(m_wnd[index].m_file_navigators); break;
      case E_TOOLTIP         : elements_total=::ArraySize(m_wnd[index].m_tooltips);        break;
     }
//--- Devolver el número de controles del tipo especificado
   return(elements_total);
  }

Para disminuir la carga sobre la CPU, ha sido necesario completar la estructura WindowElements con algunos arrays donde van a guardarse los punteros de los controles de las siguientes categorías.

  • Array de los controles principales
  • Array de los controles con temporizador
  • Array de los controles visibles y disponibles para el procesamiento
  • Array de los controles que tienen activado el modo del cambio automático de los tamaños por el eje X
  • Array de los controles que tienen activado el modo del cambio automático de los tamaños por el eje Y
class CWndContainer
  {
protected:
...
   //--- Estructura de los arrays de controles
   struct WindowElements
     {
      ...
      //--- Array de los controles principales
      CElement         *m_main_elements[];
      //--- Controles con temporizador
      CElement         *m_timer_elements[];
      //--- Controles visibles y disponibles en este momento
      CElement         *m_available_elements[];
      //--- Controles con cambio automático de los tamaños por el eje X
      CElement         *m_auto_x_resize_elements[];
      //--- Controles con cambio automático de los tamaños por el eje Y
      CElement         *m_auto_y_resize_elements[];
      ...
     };
   //--- Array de los arrays de los controles para cada ventana
   WindowElements    m_wnd[];
...
  };

Obtenemos los tamaños de estos arrays usando los métodos correspondientes:

class CWndContainer
  {
public:
   //--- Número de controles principales
   int               MainElementsTotal(const int window_index);
   //--- Número de controles con temporizadores
   int               TimerElementsTotal(const int window_index);
  //--- Controles con cambio automático de los tamaños por el eje X
   int               AutoXResizeElementsTotal(const int window_index);
  //--- Controles con cambio automático de los tamaños por el eje Y
   int               AutoYResizeElementsTotal(const int window_index);
  //--- Número de controles disponibles en este momento
   int               AvailableElementsTotal(const int window_index);
  };

En el array destinado para los controles principales, los punteros se añaden al método CWndContainer::AddToElementsArray(). Abajo se muestra la versión reducida de este método:

//+------------------------------------------------------------------+
//| Añade el puntero al array de controles                           | 
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElementBase &object)
  {
...
//--- Añadimos al array de los controles principales
   last_index=ResizeArray(m_wnd[window_index].m_main_elements);
   m_wnd[window_index].m_main_elements[last_index]=::GetPointer(object);
...
  }

Los arrays de otras categorías se forman en la clase CWndEvents (véase a continuación). Para añadir los punteros, se utilizan los métodos separados.

class CWndContainer
  {
protected:
   //--- Añade el puntero al array de controles con temporizadores
   void              AddTimerElement(const int window_index,CElement &object);
   //--- Añade el puntero al array de controles con cambio automático de los tamaños por el eje X
   void              AddAutoXResizeElement(const int window_index,CElement &object);
   //--- Añade el puntero al array de controles con cambio automático de los tamaños por el eje Y
   void              AddAutoYResizeElement(const int window_index,CElement &object);
   //--- Añade el puntero al array de controles controles disponibles en este momento
   void              AddAvailableElement(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Añade el puntero al array de controles con temporizadores        |
//+------------------------------------------------------------------+
void CWndContainer::AddTimerElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_timer_elements);
   m_wnd[window_index].m_timer_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------------------------------------+
//| Añade el puntero al array de controles con cambio automático de los tamaños por el eje X       |
//+------------------------------------------------------------------------------------------------+
void CWndContainer::AddAutoXResizeElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_auto_x_resize_elements);
   m_wnd[window_index].m_auto_x_resize_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
 //--- Añade el puntero al array de controles con cambio automático de los tamaños por el eje Y
//+------------------------------------------------------------------+
void CWndContainer::AddAutoYResizeElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_auto_y_resize_elements);
   m_wnd[window_index].m_auto_y_resize_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
//| Añade el puntero al array de controles disponibles               |
//+------------------------------------------------------------------+
void CWndContainer::AddAvailableElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_available_elements);
   m_wnd[window_index].m_available_elements[last_index]=::GetPointer(object);
  }

Los nuevos métodos para el uso interno también se utilizan en la clase CWndEvents. Pues, necesitaremos el método CWndEvents::Hide() para ocultar todos los controles de la interfaz gráfica. Aquí se usa el ciclo doble: primero, se ocultan los formularios, y luego, en el segundo ciclo interno, todos los controles adjuntados al formulario. Nótese, en este método, el segundo ciclo recorre el array de los controles compuesto de los punteros a los controles principales. Los métodos de los controles Hide() y Show() ahora están organizados de tal manera que influyen en toda la serie de estos métodos y los controles incluidos a toda la profundidad de la inclusión.

//+------------------------------------------------------------------+
//| Clase para procesar los eventos                                  |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
   // Ocultación de todos los controles
   void              Hide();
  };
//+------------------------------------------------------------------+
//| Ocultación de los controles                                      |
//+------------------------------------------------------------------+
void CWndEvents::Hide(void)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      m_windows[w].Hide();
      int main_total=MainElementsTotal(w);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[w].m_main_elements[e];
         el.Hide();
        }
     }
  }

Además, en esta clase, ha aparecido el método CWndEvents::Show() para mostrar los controles para el formulario especificado. Primero, se muestra la ventana especificada en el argumento. Luego, si la ventana no está minimizada, todos los controles adjuntados a este formulario se hacen visibles. En este ciclo se omiten sólo los controles que (1) son desplegables o (2) si ellos tienen el control «Pestañas» como el control principal. Luego, los controles en las pestañas se muestran ya fuera del ciclo a través del método CWndEvents::ShowTabElements().

class CWndEvents : public CWndContainer
  {
protected:
   //--- Visualización de los controles de la ventana especificada
   void              Show(const uint window_index);
  };
//+------------------------------------------------------------------+
//| Visualización de los controles de la ventana especificada        |
//+------------------------------------------------------------------+
void CWndEvents::Show(const uint window_index)
  {
//--- Mostrar los controles de la ventana especificada
   m_windows[window_index].Show();
//--- Si la ventana no está minimizada
   if(!m_windows[window_index].IsMinimized())
     {
      int main_total=MainElementsTotal(window_index);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         //--- Mostrar el control (1) si no es desplegable y (2) su control principal no son pestañas
         if(!el.IsDropdown() && dynamic_cast<CTabs*>(el.MainPointer())==NULL)
            el.Show();
        }
      //--- Mostrar los controles sólo de las pestañas seleccionadas
      ShowTabElements(window_index);
     }
  }

Necesitaremos el método CWndEvents::Update() para redibujar todos los controles de la interfaz gráfica de la aplicación MQL. El método puede trabajar en dos modos: (1) redibujar completamente todos los controles o (2) aplicar las modificaciones hechas anteriormente. Para redibujar completamente y actualizar la interfaz gráfica, hay que traspasar el valor true.

class CWndEvents : public CWndContainer
  {
protected:
  //--- Redibujar los controles
   void              Update(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Redibujar de los controles                                       |
//+------------------------------------------------------------------+
void CWndEvents::Update(const bool redraw=false)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      //--- Redibujar los controles
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         CElement *el=m_wnd[w].m_elements[e];
         el.Update(redraw);
        }
     }
  }

Hablaremos sobre estos métodos un poco más tarde, entretanto vamos a hablar de una serie de los métodos destinados para formar el array de las categorías consideradas.

En la versión anterior, el color de los controles se cambiaba suavemente usando el temporizador cuando el cursor se situaba sobre ellos. Para reducir el volumen y el consumo de los recursos, he renunciado esta demasía. Por eso en la versión actual de la librería, el temporizador no se utiliza en todos los controles. Se utiliza sólo en el avance/retroceso rápido de (1) los deslizadores del scrolling, (2) de los valores en los campos de edición numéricos y (3) de las fechas en el calendario. Por eso, en el método CWndEvents::FormTimerElementsArray() añadimos sólo los controles correspondientes al array correspondiente (véase el código de abajo). 

Puesto que los punteros a los controles se almacenan en los arrays del tipo base de los controles (CElement), entonces aquí y en muchos otros métodos de las clases se utiliza la conversión dinámica de tipos (dynamic_cast) para determinar el tipo derivado de los controles. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Forma el array de los controles con temporizador
   void              FormTimerElementsArray(void);
  };
//+------------------------------------------------------------------+
//| Forma el array de los controles con temporizador                 |
//+------------------------------------------------------------------+
void CWndEvents::FormTimerElementsArray(void)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         CElement *el=m_wnd[w].m_elements[e];
         //---
         if(dynamic_cast<CCalendar    *>(el)!=NULL ||
            dynamic_cast<CColorPicker *>(el)!=NULL ||
            dynamic_cast<CListView    *>(el)!=NULL ||
            dynamic_cast<CTable       *>(el)!=NULL ||
            dynamic_cast<CTextBox     *>(el)!=NULL ||
            dynamic_cast<CTextEdit    *>(el)!=NULL ||
            dynamic_cast<CTreeView    *>(el)!=NULL)
           {
            CWndContainer::AddTimerElement(w,el);
           }
        }
     }
  }

Ahora el temporizador es menos cargado: no tiene que comprobar la lista entera de los controles como antes, sino sólo aquellos que contienen esta función:

//+----------------------------------------------------------------------------------------------+
//| Comprobación de los eventos de todos los controles por el temporizador                       |
//+----------------------------------------------------------------------------------------------+
void CWndEvents::CheckElementsEventsTimer(void)
  {
   int awi=m_active_window_index;
   int timer_elements_total=CWndContainer::TimerElementsTotal(awi);
   for(int e=0; e<timer_elements_total; e++)
     {
      CElement *el=m_wnd[awi].m_timer_elements[e];
      if(el.IsVisible())
         el.OnEventTimer();
     }
  }

El procesamiento del apuntamiento del cursor del ratón no concierne a todos los controles de la interfaz gráfica. Del array de los controles disponibles para este tipo del procesamiento han sido excluidos los siguientes controles:

  • CButtonsGroup — grupo de botones.
  • CFileNavigator — explorador de archivos;
  • CLineGraph — gráfico lineal;
  • CPicture — imagen;
  • CPicturesSlider — slider de imágenes;
  • CProgressBar — indicador de progreso;
  • CSeparateLine — línea separadora;
  • CStatusBar — barra de estado;
  • CTabs — pestañas;
  • CTextLabel — etiqueta de texto.

Todos estos controles no se resaltan cuando el cursor se sitúa sobre ellos. No obstante, algunos de ellos tienen controles incluidos que se resaltan con color. Pero puesto que al formar el array de los controles disponibles, se utiliza en el ciclo el array común, los controles incluidos van a participar en la selección. Todos los controles que están visibles, disponibles y no bloqueados serán seleccionados en el array. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Forma el array de controles disponibles
   void              FormAvailableElementsArray(void);
  };
//+------------------------------------------------------------------+
//| Forma el array de controles disponibles                          |
//+------------------------------------------------------------------+
void CWndEvents::FormAvailableElementsArray(void)
  {
//--- Índice de la ventana
   int awi=m_active_window_index;
//--- Número total de controles
   int elements_total=CWndContainer::ElementsTotal(awi);
//--- Vaciamos el array
   ::ArrayFree(m_wnd[awi].m_available_elements);
//---
   for(int e=0; e<elements_total; e++)
     {
      CElement *el=m_wnd[awi].m_elements[e];
      //--- Añadimos sólo los controles visibles y disponibles para el procesamiento
      if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked())
         continue;
      //--- Excluir los controles que no requieren el procesamiento para el apuntamiento del cursor del ratón
      if(dynamic_cast<CButtonsGroup   *>(el)==NULL &&
         dynamic_cast<CFileNavigator  *>(el)==NULL &&
         dynamic_cast<CLineGraph      *>(el)==NULL &&
         dynamic_cast<CPicture        *>(el)==NULL &&
         dynamic_cast<CPicturesSlider *>(el)==NULL &&
         dynamic_cast<CProgressBar    *>(el)==NULL &&
         dynamic_cast<CSeparateLine   *>(el)==NULL &&
         dynamic_cast<CStatusBar      *>(el)==NULL &&
         dynamic_cast<CTabs           *>(el)==NULL &&
         dynamic_cast<CTextLabel      *>(el)==NULL)
        {
         AddAvailableElement(awi,el);
        }
     }
  }

Nos queda considerar los métodos CWndEvents::FormAutoXResizeElementsArray() y CWndEvents::FormAutoYResizeElementsArray() que forman los arrays con punteros a los controles que tienen activados los modos del cambio automático de los tamaños. Estos controles se orientan a los tamaños de los controles principales a los que están adjuntados. No todos los controles tienen determinado el código de los métodos para el cambio automático de los tamaños. Vamos a nombrar los controles que lo tienen:

Los controles que tienen determinado el código en el método virtual CElement::ChangeWidthByRightWindowSide() para el cambio automático del ancho:

  • CButton — botón.
  • CFileNavigator — explorador de archivos.
  • CLineGraph — gráfico lineal.
  • CListView — lista.
  • CMenuBar — menú principal.
  • CProgressBar — indicador de progreso.
  • CStandardChart — gráfico estándar.
  • CStatusBar — barra de estado.
  • CTable — tabla.
  • CTabs — pestañas.
  • CTextBox — campo de edición del texto.
  • CTextEdit — campo de edición.
  • CTreeView — lista jerárquica.

Los controles que tienen determinado el código en el método virtual CElement:: ChangeHeightByBottomWindowSide() para el cambio automático del alto:

  • CLineGraph — gráfico lineal.
  • CListView — lista.
  • CStandardChart — gráfico estándar.
  • CTable — tabla.
  • CTabs — pestañas.
  • CTextBox — campo de edición del texto.

Cuando se crean los los arrays para estas categorías, se comprueba si los modos del cambio automático de los tamaños están activados en estos controles, y si están activados, ellos se añaden al array. No vamos a mostrar el código de estos métodos, por que los métodos parecidos han sido considerados antes. 

Ahora vamos a aclarar en qué momento se forman los arrays para las categorías mencionadas. En el método principal de la creación de la interfaz gráfica que el usuario de la librería forma personalmente, después de la creación de todos los controles especificados, ahora hay que llamar sólo a un método CWndEvents::CompletedGUI() para visualizarlos. Él señaliza al programa que la creación de la interfaz gráfica de la aplicación MQL ha terminado. 

Vamos a considerar el método CWndEvents::CompletedGUI() más detalladamente. Ahí se invocan todos los métodos descritos antes en este apartado. Primero, todos los controles de la interfaz se ocultan. Ninguno de ellos está dibujado todavía, por eso primero hay que ocultarlos antes del proceso del dibujado para evitar su aparición consecutiva uno por uno. Luego se realiza el dibujado, y a cada control se le aplican los últimos cambios. Después de eso, hay que mostrar sólo los controles de la ventana principal. Luego, se forman los arrays de los punteros a los controles por las categorías, y luego el gráfico se actualiza al final del método. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Terminando la creación de GUI
   void              CompletedGUI(void);
  };
//+------------------------------------------------------------------+
//| Terminando la creación de GUI                                    |
//+------------------------------------------------------------------+
void CWndEvents::CompletedGUI(void)
  {
//--- Salir si todavía no hay ninguna ventana
   int windows_total=CWndContainer::WindowsTotal();
   if(windows_total<1)
      return;
//--- Mostrar el comentario que informa al usuario
   ::Comment("Update. Please wait...");
//--- Ocultar controles
   Hide();
//--- Dibujarlos controles
   Update(true);
//--- Mostrar los controles de la ventana activada
   Show(m_active_window_index);
//--- Formamos el array de los controles con temporizador
   FormTimerElementsArray();
//--- Formamos el array de los controles visibles y disponibles al mismo tiempo
   FormAvailableElementsArray();
//--- Formamos el array de los controles con cambio automático
   FormAutoXResizeElementsArray();
   FormAutoYResizeElementsArray();
//--- Redibujar el gráfico
   m_chart.Redraw();
//--- Vaciar el comentario
   ::Comment("");
  }

El método para comprobar y procesar los eventos de los controles CWndEvents::CheckElementsEvents() ha cambiado significativamente. Vamos a hablar de eso con más detalles. 

Ahora en este método hay dos bloques para procesar los eventos. Uno de ellos sirve exclusivamente para procesar el desplazamiento del cursor del ratón (CHARTEVENT_MOUSE_MOVE). En vez de eso, para recorrer en el ciclo la lista de todos los controles de la ventana activada, como era antes, ahora el ciclo recorre sólo los controles disponibles para el procesamiento. Precisamente para eso se formaba el array con los punteros a los controles disponibles. La interfaz gráfica de una aplicación MQL grande puede contener varias centenares o incluso miles de diferentes controles, y en este momento pueden estar visibles y disponibles sólo algunos de toda esta lista. Este enfoque permite liberar considerablemente los recursos de la CPU. 

Otro alteración consiste en que ahora las comprobaciones (1) dela subventana en la que se encuentra el formulario y (2) del foco sobre el control se realizan en el ciclo externo, y no en los manejadores de cada clase de los controles. De esta manera, ahora las comprobaciones referentes a cada control se encuentran en un determinado lugar. Será muy cómodo en el futuro si es necesario introducir cambios en el algoritmo del procesamiento de eventos.

Todos los demás tipos de eventos se procesan en un bloque separado. En la versión actual, el recorrido se realiza por toda la lista de los controles de la interfaz gráfica. Todas las comprobaciones que antes se encontraban en las clases de los controles ahora también se realizarán en el ciclo externo. 

Al final del método, el evento se envía a la clase personalizada de la aplicación MQL.

//+------------------------------------------------------------------+
//| Comprobación de los eventos de controles                         |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
//--- Procesamiento del evento del desplazamiento del cursor
   if(m_id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el formulario se encuentra en otra subventana del gráfico
      if(!m_windows[m_active_window_index].CheckSubwindowNumber())
         return;
      //--- Comprobamos sólo los controles disponibles
      int available_elements_total=CWndContainer::AvailableElementsTotal(m_active_window_index);
      for(int e=0; e<available_elements_total; e++)
        {
         CElement *el=m_wnd[m_active_window_index].m_available_elements[e];
        //--- Comprobación del foco sobre los controles
         el.CheckMouseFocus();
         //--- Procesamiento del evento
         el.OnEvent(m_id,m_lparam,m_dparam,m_sparam);
        }
     }
//--- Todos los eventos salvo el desplazamiento del cursor del ratón
   else
     {
      int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
      for(int e=0; e<elements_total; e++)
        {
         //--- Comprobamos sólo los controles disponibles
         CElement *el=m_wnd[m_active_window_index].m_elements[e];
         if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked())
            continue;
         //--- Procesamiento del evento en el manejador del control
         el.OnEvent(m_id,m_lparam,m_dparam,m_sparam);
        }
     }
//--- Envío del evento en el archivo de la aplicación
   OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

El método CWndEvents::FormAvailableElementsArray() para la formación del array de los controles visibles y disponibles a la vez para el procesamiento se invoca en los siguientes casos:

  • Apertura de la ventana de diálogo. Después de la apertura de la ventana de diálogo, se genera el evento ON_OPEN_DIALOG_BOX que se procesa en el método CWndEvents::OnOpenDialogBox(). Después de la formación de este evento, hay que formar el array de los controles disponibles de la ventana abierta.

  • Cambio en la interfaz gráfica. Cualquier cambio en la interfaz gráfica, en caso de la interacción con él se genera el evento ON_CHANGE_GUI. Lo procesa el método privado (private) CWndEvents::OnChangeGUI(). Aquí, cuando llega el evento ON_CHANGE_GUI, primero se forma el array de controles disponibles. Luego, todas las descripciones emergentes se desplazan en la capa superior. Al final del método, el se redibuja para mostrar los últimos cambios realizados.

class CWndEvents : public CWndContainer
  {
private:
   //--- Cambio en la interfaz gráfica
   bool              OnChangeGUI(void);
  };
//+------------------------------------------------------------------+
//| Evento de los cambios en la interfaz gráfica                     |
//+------------------------------------------------------------------+
bool CWndEvents::OnChangeGUI(void)
  {
//--- Si hay señal sobre el cambio en la interfaz gráfica
   if(m_id!=CHARTEVENT_CUSTOM+ON_CHANGE_GUI)
      return(false);
//--- Formamos el array de los controles visibles y disponibles al mismo tiempo
   FormAvailableElementsArray();
//--- Desplazar las descripciones emergentes se desplazan en la capa superior
   ResetTooltips();
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Luego veremos cómo se procesa el evento ON_SET_AVAILABLE para determinar los controles que estarán disponibles para el procesamiento.

Para el procesamiento del evento ON_SET_AVAILABLE ha sido implementado el método CWndEvents::OnSetAvailable(). Pero antes de pasar a la descripción de su código, hay que considerar una serie de métodos auxiliares. Hay 10 controles de la interfaz gráfica que generan el evento con este identificador. Todos ellos disponen de los medios para determinar su estado activado. Son los siguientes:

  • Menú principal — CMenuBar::State().
  • Elemento del menú — CMenuItem::GetContextMenuPointer().IsVisible().
  • Botón de división — CSplitButton::GetContextMenuPointer().IsVisible().
  • Combobox — CComboBox::GetListViewPointer().IsVisible().
  • Calendario desplegable — DropCalendar::GetCalendarPointer().IsVisible().
  • Barra de desplazamiento — CScroll::State().
  • Tabla — CTable::ColumnResizeControl().
  • Slider numérico — CSlider::State().
  • Lista jerárquica — CTreeView::GetMousePointer().State().
  • Gráfico estándar — CStandartChart::GetMousePointer().IsVisible().

Cada uno de estos controles tiene sus arrays personales en la clase CWndContainer. En la clase CWndEvents, están implementados los métodos para determinar cuál de ellos está activado en este momento. Todos estos métodos devuelven el índice del control activado en su array personal.

class CWndEvents : public CWndContainer
  {
private:
   //--- Devuelve el índice del menú principal activado
   int               ActivatedMenuBarIndex(void);
   //--- Devuelve el índice del elemento activado del menú
   int               ActivatedMenuItemIndex(void);
   //--- Devuelve el índice del botón de división activado
   int               ActivatedSplitButtonIndex(void);
   //--- Devuelve el índice del combobox activado
   int               ActivatedComboBoxIndex(void);
   //--- Devuelve el índice del calendario desplegable activado
   int               ActivatedDropCalendarIndex(void);
   //--- Devuelve el índice de la barra de desplazamiento activada
   int               ActivatedScrollIndex(void);
   //--- Devuelve el índice de la tabla activada
   int               ActivatedTableIndex(void);
   //--- Devuelve el índice del slider activado
   int               ActivatedSliderIndex(void);
   //--- Devuelve el índice de la lista jerárquica activada
   int               ActivatedTreeViewIndex(void);
   //--- Devuelve el índice del gráfico estándar activado
   int               ActivatedSubChartIndex(void);
  };

Puesto que prácticamente todos estos métodos se diferencian sólo en las condiciones para determinar el estado de los controles, mostraremos aquí el código sólo de uno ellos como ejemplo. Abajo se muestra el código del método CWndEvents::ActivatedTreeViewIndex() que devuelve el índice de la lista jerárquica activada. Si este tipo del control tiene habilitado el modo de elementos-pestañas, la comprobación se rechaza.

//+------------------------------------------------------------------+
//| Devuelve el índice de la lista jerárquica activada               |
//+------------------------------------------------------------------+
int CWndEvents::ActivatedTreeViewIndex(void)
  {
   int index=WRONG_VALUE;
//---
   int total=ElementsTotal(m_active_window_index,E_TREE_VIEW);
   for(int i=0; i<total; i++)
     {
      CTreeView *el=m_wnd[m_active_window_index].m_treeview_lists[i];
      //--- Ir al siguiente si el modo de elementos-pestañas está activado
      if(el.TabItemsMode())
         continue;
       //--- Si el proceso del cambio del ancho de las listas
      if(el.GetMousePointer().State())
        {
         index=i;
         break;
        }
     }
   return(index);
  }

Para establecer el estado de la disponibilidad de los controles, se utiliza el método CWndEvents::SetAvailable(). Como argumentos, hay que traspasar (1) el índice del formulario con el que trabajamos y (2) el estado en el que es necesario establecer los controles.

Si es necesario hacer que todos los controles estén no disponibles, simplemente los recorremos en ciclo y establecemos el valor false

Si es necesario hacer que los controles estén disponibles, entonces para las listas jerárquicas se invoca el método recargado homónimo CTreeView::IsAvailable(), en el que hay dos modos para establecer el estado: (1) sólo para el elemento principal y (2) para todos los elementos a toda la profundidad. Por eso aquí se utiliza la conversión dinámica de los tipos para obtener el puntero al control de la clase derivada del control

class CWndEvents : public CWndContainer
  {
protected:
   //--- Establecer el estado de la disponibilidad de controles
   void              SetAvailable(const uint window_index,const bool state);
  };
//+------------------------------------------------------------------+
//| Establecer el estado de la disponibilidad de controles           |
//+------------------------------------------------------------------+
void CWndEvents::SetAvailable(const uint window_index,const bool state)
  {
//--- Obtenemos el número de controles principales
   int main_total=MainElementsTotal(window_index);
//--- Si es necesario hacer que los controles no estén disponibles
   if(!state)
     {
      m_windows[window_index].IsAvailable(state);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         el.IsAvailable(state);
        }
     }
   else
     {
      m_windows[window_index].IsAvailable(state);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         //--- Si es la lista jerárquica
         if(dynamic_cast<CTreeView*>(el)!=NULL)
           {
            CTreeView *tv=dynamic_cast<CTreeView*>(el);
            tv.IsAvailable(true);
            continue;
           }
         //--- Si es el explorador de archivos
         if(dynamic_cast<CFileNavigator*>(el)!=NULL)
           {
            CFileNavigator *fn =dynamic_cast<CFileNavigator*>(el);
            CTreeView      *tv =fn.GetTreeViewPointer();
            fn.IsAvailable(state);
            tv.IsAvailable(state);
            continue;
           }
         //--- Hacer que el control esté disponible
         el.IsAvailable(state);
        }
     }
  }

Para los elementos del menú que tienen adjuntados los menús contextuales, necesitaremos el método que ayudará a recorrer los menús contextuales abiertos a toda la profundidad, obteniendo el acceso a ellos. En este caso, habrá que hacer que los menús contextuales estén disponibles para el procesamiento. Vamos a implementar eso a través del modo recursivo. 

Abajo se muestra el código del método CWndEvents::CheckContextMenu(). Primero, le traspasamos el objeto del elemento del menú e intentamos obtenemos el puntero al menú contextual. Si el puntero es correcto, comprobamos si este menú contextual está abierto. Si es así, establecemos el indicio del control disponible. Luego, establecemos en el ciclo el estado de la disponibilidad para todos los elementos de este menú. Además, comprobamos cada elemento en cuanto a la presencia del menú contextual dentro de él usando el método CWndEvents::CheckContextMenu().

class CWndEvents : public CWndContainer
  {
private:
   //--- Comprueba y hace que el menú contextual esté disponible
   void              CheckContextMenu(CMenuItem &object);
  };
//+---------------------------------------------------------------------------+
 //--- Comprueba recursivamente y hace que el menú contextual esté disponible
//+---------------------------------------------------------------------------+
void CWndEvents::CheckContextMenu(CMenuItem &object)
  {
//--- Obtenemos el puntero del menú contextual
   CContextMenu *cm=object.GetContextMenuPointer();
//--- Salir si en el elemento no hay menú contextual
   if(::CheckPointer(cm)==POINTER_INVALID)
      return;
//--- Salir si hay menú contextual pero está ocultado
   if(!cm.IsVisible())
      return;
//--- Establecer los indicios del control disponible
   cm.IsAvailable(true);
//---
   int items_total=cm.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Establecer los indicios del control disponible
      CMenuItem *mi=cm.GetItemPointer(i);
      mi.IsAvailable(true);
      //--- Comprobamos si este elemento contiene el menú contextual
      CheckContextMenu(mi);
     }
  }

Ahora vamos a considerar el método CWndEvents::OnSetAvailable() en el que se procesa el evento para determinar los controles disponibles. 

Si ha llegado el evento personalizado con el identificador ON_SET_AVAILABLE, primero hay que determinar si hay controles activados en este momento. Los índices de los controles activados se guardan en las variables locales para un acceso rápido a sus arrays personales.

Si ha llegado la señal para determinar los controles disponibles, primero desactivamos el acceso a todos los controles en la lista. Si es la señal para la recuperación, después de comprobar la ausencia de los controles activados, se recupera el acceso a todos los controles en la lista y el programa sale del método.

Si el programa ha llegado al siguiente bloque del código en este método, eso significa que se trata de (1) una señal para determinar los controles disponibles, o (2) de una señal para la recuperación, pero al mismo tiempo hay un calendario desplegable activado. La segunda situación puede surgir cuando ha sido abierto un calendario desplegable con el combobox activado, en el que ha sido cerrada la lista desplegable.

Si una de las condiciones descritas ha sido cumplida, intentamos obtener el puntero al control activado. Si el puntero no se obtiene, el programa saldrá del método.

Si el puntero ha sido obtenido, el control se hace disponible. Algunos controles tienen ciertas detalles en cuanto a la manera de hacerlo. Vamos a ver todos estos casos:

  • Menú principal (CMenuBar). Si está activado, habrá que hacer disponibles también todos los menús contextuales referentes a él. Para eso, en el código de arriba ha sido considerado el método recursivo CWndEvents::CheckContextMenu().

  • Elemento del menú (CMenuItem). Los elementos del menú pueden ser controles independientes a los cuales se puede adjuntar los menús contextuales. Por eso, si el control de este tipo está activado, aquí también se hace disponible el propio control (elemento del menú), así como todos los menús contextuales que están abiertos en él.

  • Barra de desplazamiento (CScroll). Si la barra de desplazamiento está activada (el deslizador está en el proceso de desplazamiento), hay que hacer disponibles todos los controles empezando desde el principal. Por ejemplo, si la barra de desplazamiento está adjuntada a una lista, esta lista se hará disponible, así como todos los controles adjuntos a ella a toda la profundidad.

  • Lista jerárquica (CTreeView). Este control puede activarse cuando se cambia el ancho de sus listas. Hay que excluir el procesamiento de los elementos de las listas al situar el cursor encima, y hacer que la lista jerárquica esté disponible para el procesamiento.

El listado de abajo muestra con detalles el código del método CWndEvents::OnSetAvailable():

class CWndEvents : public CWndContainer
  {
private:
   //--- Determinación de los controles disponibles
   bool              OnSetAvailable(void);
  };
//+------------------------------------------------------------------+
//| Evento para determinar los controles disponibles                 |
//+------------------------------------------------------------------+
bool CWndEvents::OnSetAvailable(void)
  {
//--- Si hay señal sobre el cambio en la disponibilidad de controles
   if(m_id!=CHARTEVENT_CUSTOM+ON_SET_AVAILABLE)
      return(false);
//--- Señal para establecer/recuperar
   bool is_restore=(bool)m_dparam;
//--- Determinamos los controles activos
   int mb_index =ActivatedMenuBarIndex();
   int mi_index =ActivatedMenuItemIndex();
   int sb_index =ActivatedSplitButtonIndex();
   int cb_index =ActivatedComboBoxIndex();
   int dc_index =ActivatedDropCalendarIndex();
   int sc_index =ActivatedScrollIndex();
   int tl_index =ActivatedTableIndex();
   int sd_index =ActivatedSliderIndex();
   int tv_index =ActivatedTreeViewIndex();
   int ch_index =ActivatedSubChartIndex();
//--- Si es la señal para determinar los controles disponibles, primero desactivamos el acceso
   if(!is_restore)
      SetAvailable(m_active_window_index,false);
//--- Recuperamos sólo si no hay controles activados
   else
     {
      if(mb_index==WRONG_VALUE && mi_index==WRONG_VALUE && sb_index==WRONG_VALUE &&
         dc_index==WRONG_VALUE && cb_index==WRONG_VALUE && sc_index==WRONG_VALUE &&
         tl_index==WRONG_VALUE && sd_index==WRONG_VALUE && tv_index==WRONG_VALUE && ch_index==WRONG_VALUE)
        {
         SetAvailable(m_active_window_index,true);
         return(true);
        }
     }
//--- Si es (1) la señal para desactivar el acceso o (2) para recuperar el calendario desplegable
   if(!is_restore || (is_restore && dc_index!=WRONG_VALUE))
     {
      CElement *el=NULL;
      //--- Menú principal
      if(mb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_menu_bars[mb_index];      }
      //--- Elemento del menú
      else if(mi_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_menu_items[mi_index];     }
      //--- Botón de división
      else if(sb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_split_buttons[sb_index];  }
      //--- Calendarios desplegable sin lista desplegable
      else if(dc_index!=WRONG_VALUE && cb_index==WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_drop_calendars[dc_index]; }
      //--- Lista desplegable
      else if(cb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_combo_boxes[cb_index];    }
      //--- Barra de desplazamiento
      else if(sc_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_scrolls[sc_index];        }
      //--- Tabla
      else if(tl_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_tables[tl_index];         }
      //--- Slider
      else if(sd_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_sliders[sd_index];        }
      //--- Lista jerárquica
      else if(tv_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_treeview_lists[tv_index]; }
      //--- Gráfico estándar
      else if(ch_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_sub_charts[ch_index];     }
      //--- Salir si el puntero al control no ha sido obtenido
      if(::CheckPointer(el)==POINTER_INVALID)
         return(true);
      //--- Bloque para el menú principal
      if(mb_index!=WRONG_VALUE)
        {
         //--- Hacer disponible el menú principal y sus menús contextuales visibles
         el.IsAvailable(true);
         //--- 
         CMenuBar *mb=dynamic_cast<CMenuBar*>(el);
         int items_total=mb.ItemsTotal();
         for(int i=0; i<items_total; i++)
           {
            CMenuItem *mi=mb.GetItemPointer(i);
            mi.IsAvailable(true);
             //--- Comprueba y hace que el menú contextual esté disponible
            CheckContextMenu(mi);
           }
        }
      //--- Bloque para el elemento del menú
      if(mi_index!=WRONG_VALUE)
        {
         CMenuItem *mi=dynamic_cast<CMenuItem*>(el);
         mi.IsAvailable(true);
         //--- Comprueba y hace que el menú contextual esté disponible
         CheckContextMenu(mi);
        }
      //--- Bloque para las barras de desplazamiento
      else if(sc_index!=WRONG_VALUE)
        {
         //--- Hacer disponible a partir del nodo principal
         el.MainPointer().IsAvailable(true);
        }
      //--- Bloque para la lista jerárquica
      else if(tv_index!=WRONG_VALUE)
        {
         //--- Bloquear todos los controles excepto el principal
         CTreeView *tv=dynamic_cast<CTreeView*>(el);
         tv.IsAvailable(true,true);
         int total=tv.ElementsTotal();
         for(int i=0; i<total; i++)
            tv.Element(i).IsAvailable(false);
        }
      else
        {
         //--- Hacer que el control esté disponible
         el.IsAvailable(true);
        }
     }
//---
   return(true);
  }


Aplicación para la prueba de controles

Para las pruebas ha sido implementada una aplicación MQL cuya interfaz gráfica incluye todos los controles de la librería. Véase a continuación: 

Fig. 12. Interfaz gráfica de la aplicación MQL.

Fig. 12. Interfaz gráfica de la aplicación MQL.

Puede descargar la aplicación al final del artículo para estudiarla más detalladamente.


Conclusión

Esta versión de la librería se diferencia muchísimo de la que ha sido presentada en el artículo Interfaces gráficas: Selección del texto en el campo de edición multilínea (build 13). Ha sido realizado un gran trabajo que ha influido prácticamente en todos los archivos de la librería. Ahora, todos los controles de la librería se dibujan en los objetos separados. La legibilidad del código se ha mejorado, su volumen se ha reducido aproximadamente a un 30% y las posibilidades se han aumentado. Han sido corregidos muchos errores y problemas de los que han avisado los usuarios en sus comentarios para el artículo.

Si ya ha empezado a crear sus aplicaciones MQL usando la versión anterior de la librería, se recomienda primero descargar la nueva versión en una copia del terminal MetaTrader 5 instalada separadamente con el fin de familiarizarse con ella y probar todo minuciosamente.

En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema general tiene el siguiente aspecto. No es una versión definitiva, la librería va a seguir desarrollando y mejorándose en el futuro.

 Fig. 13. Estructura de la librería en la fase actual del desarrollo.

Fig. 13 Estructura de la librería en la fase actual del desarrollo.

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 de esta serie, o bien hacer su pregunta en los comentarios para el artículo.

Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/3366

Archivos adjuntos |
Métodos de ordenamiento y su visualización a través de MQL5 Métodos de ordenamiento y su visualización a través de MQL5

Para trabajar con la gráfica, en MQL5 ha sido creada una librería especial— Graphic.mqh. El ejemplo de su aplicación práctica se describe en este artículo y se explica la esencia de los ordenamientos. Existe por lo menos un artículo separado para cada tipo de ordenamiento, y para algunos de ellos ya has sido publicadas las investigaciones completas, por eso aquí se describe sólo la idea general.

Interfaces gráficas XI: Refactorización del código de la librería (build 14.1) Interfaces gráficas XI: Refactorización del código de la librería (build 14.1)

A medida que la librería va creciendo, es necesario optimizar de nuevo su código para reducir su tamaño. La versión de la librería descrita en este artículo se ha hecho aún más orientada a objetos. Eso ha mejorado la facilidad de comprensión del código. La descripción detallada de los últimos cambios permitirá al lector desarrollar la librería por sí mismo, según las necesidades que tenga.

Indicadores personalizados e infografía en CCanvas Indicadores personalizados e infografía en CCanvas

En este artículo se analizarán nuevos tipos de indicadores con una estructura de implementación más compleja. Se describirá la construcción de los indicadores de los tipos pseudo-3D y la creación de infografías que cambian de manera dinámica.

Optimización Walk-Forward en MetaTrader 5 con sus propias manos Optimización Walk-Forward en MetaTrader 5 con sus propias manos

En este artículo se consideran las técnicas que permiten emular con precisión la optimización walk-forward a través del Probador incorporado y librerías auxiliares implementadas en MQL.