English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas IV: Modo de ventanas múltiples y sistema de prioridades (Capítulo 2)

Interfaces gráficas IV: Modo de ventanas múltiples y sistema de prioridades (Capítulo 2)

MetaTrader 5Ejemplos | 28 abril 2016, 13:26
1 379 0
Anatoli Kazharski
Anatoli Kazharski

Í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 el capítulo anterior de la cuarta parte de la serie, hemos analizado los elementos “Barra de estado” y “Descripción emergente” que pertenecen a los componentes informativos de la interfaz gráfica. En este artículo vamos a ampliar la implementación de la librería hasta conseguir la posibilidad de crear las interfaces de ventanas múltiples para nuestras aplicaciones MQL. Aparte de eso, desarrollaremos el sistema de las prioridades para el clic izquierdo del ratón en los objetos gráficos, puesto que si no lo hacemos, podemos enfrentarnos a los problemas cuando los controles no responden inesperadamente a las acciones del usuario.


Modo de ventanas múltiples

Vamos a considerar el modo de ventanas múltiples de la interfaz gráfica de nuestra librería. Hasta ahora, en la enumeración ENUM_WINDOW_TYPE estaban previstos dos identificadores para la ventana principal (W_MAIN) y la ventana de diálogo (W_DIALOG). Se utilizaba sólo un modo- el modo de una ventana. A continuación, introduciremos las adiciones, después de las cuales para activar el modo de ventanas múltiples será suficiente sólo crear y añadir a la base el número necesario de los formularios para los controles.

En la clase principal del procesamiento de eventos CWndEvents, hay que crear un campo para guardar el índice de la ventana activa en este momento:

class CWndEvents : public CWndContainer
  {
protected:
   //--- Índice de la ventana activa
   int               m_active_window_index;
  };

Vamos a ver cómo va a identificarse el índice de la ventana activa. Por ejemplo, el usuario ha asignado la apertura de la ventana de diálogo (W_DIALOG) a un botón. Cuando se pulsa el botón, se genera el evento personalizado ON_CLICK_BUTTON. Se puede seguir este evento en el procesador de eventos CProgram::OnEvent() de la clase personalizada. Aquí mismo utilizamos el método CWindow::Show() del formulario a mostrar. En la implementación actual de la librería no es suficiente, por eso vamos a introducir las adiciones necesarias.

Desde el método CWindow::Show() hay que enviar el evento personalizado que va a avisar que ha sido abierta una ventana y hay que cambiar los valores de los parámetros del sistema de la interfaz gráfica. Para eso, hace falta un identificador separado. Vamos a llamarlo ON_OPEN_DIALOG_BOX y colocarlo en el archivo Defines.mqh donde se encuentran los demás identificadores de la librería: 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_OPEN_DIALOG_BOX        (11) // Evento de la apertura de la ventana de diálogo

Hay que añadir una línea al final del método CWindow::Show(), tal como se muestra en el código de abajo (la versión reducida del método). Para la identificación nítida del iniciador del mensaje, aparte del identificador del evento, hay que enviar el identificador del elemento y el nombre del programa.

//+------------------------------------------------------------------+
//| Muestra la ventana                                                  |
//+------------------------------------------------------------------+
void CWindow::Show(void)
  {
//--- Hacer que todos los objetos sean visibles
//--- Estado de visibilidad
//--- Puesta a cero del foco
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_OPEN_DIALOG_BOX,(long)CElement::Id(),0,m_program_name);
  }

Este evento va a procesarse en la clase CWndEvents. Antes de implementar el método en el que va a ejecutarse el procesamiento, hay que crear tres métodos más en la clase CWindow. Dos métodos van a utilizarse para guardar y obtener el índice del formulario del que se abre la ventana de diálogo, el tercer método servirá para gestionar el estado del formulario. 

Hay que guardar el índice de la ventana anterior activa ya que a la vez pueden estar abiertas varias ventanas. Por eso cuando se cierra una ventana de diálogo, es importante saber cuál de ellas es necesario devolver al estado activo. 

class CWindow : public CElement
  {
private:
   //--- Índice de la ventana anterior activa
   int               m_prev_active_window_index;
   //---
public:
   //--- (1) Guardar y (2) obtener el índice de la ventana anterior activa
   void              PrevActiveWindowIndex(const int index)                  { m_prev_active_window_index=index;   }
   int               PrevActiveWindowIndex(void)                       const { return(m_prev_active_window_index); }
  };

En cuanto a la gestión del estado del formulario, los formularios desactivados tendrán el color del encabezado diferente que puede ser cambiado por el usuario. Además, el color de los elementos no va a cambiarse al situar el cursor sobre ellos porque el formulario estará bloqueado. Aparte de eso, en el momento de la desactivación va a generarse un evento de usuario cuyo objetivo será avisar sobre el hecho de que el formulario está bloqueado y hay que anular los focos y los colores de todos los elementos. Es que cuando el formulario está bloqueado, el foco sobre los elementos no se monitorea. Cuando se abre la ventana de diálogo, el color del control con el que ha sido abierta seguirá siendo el mismo como si el foco del ratón permanezca estando sobre él. 

Para este mensaje, en el archivo Defines.mqh se crea el identificador ON_RESET_WINDOW_COLORS:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_RESET_WINDOW_COLORS    (13) // Resetear los colores de todos los elementos del formulario

Aquí tenemos el método para gestionar el estado del formulario:

class CWindow : public CElement
  {
public:
  //--- Establecer el estado de la ventana
   void              State(const bool flag);
  };
//+------------------------------------------------------------------+
//| Establece el estado de la ventana                                    |
//+------------------------------------------------------------------+
void CWindow::State(const bool flag)
  {
//--- Si hay que bloquear la ventana
   if(!flag)
     {
      //--- Establecemos el estatus
      m_is_locked=true;
      //--- Establecemos el color del encabezado
      m_caption_bg.BackColor(m_caption_bg_color_off);
      //--- Señal para resetear el color. Los demás elementos también serán reseteados.
      ::EventChartCustom(m_chart_id,ON_RESET_WINDOW_COLORS,(long)CElement::Id(),0,"");
     }
//--- Si hay que desbloquear la ventana
   else
     {
      //--- Establecemos el estatus
      m_is_locked=false;
      //--- Establecemos el color del encabezado
      m_caption_bg.BackColor(m_caption_bg_color);
      //--- Resetear el foco
      CElement::MouseFocus(false);
     }
  }

Ahora volvemos al procesamiento del evento ON_OPEN_DIALOG_BOX. En la clase principal del procesamiento de eventos de la interfaz gráfica (CWndEvents) creamos el método CWndEvents::OnOpenDialogBox(). Vamos a llamar a este método en el método común de todos los eventos de usuario CWndEvents::ChartEventCustom().

El método CWndEvents::OnOpenDialogBox() se empieza con dos comprobaciones: para el identificador del evento y para el nombre del programa. Si han sido superadas con éxito, recorremos en el ciclo por todas las ventanas para averiguar qué ventana ha generado el evento. Podemos averiguarlo por el identificador del control que figura en este mensaje (lparam). Los formularios cuyos identificadores no coinciden serán bloqueados junto con todos los elementos adjuntos. Las prioridades de todos los objetos se ponen a cero a través del método ResetZorders() y no van a reaccionar al clic izquierdo del ratón. Cuando llegamos al formulario cuyos identificadores han coincidido, guardamos dentro el índice de la ventana actual activa como el índice de la “ventana anterior activa”. Activamos este formulario y restauramos la prioridad del clic izquierdo del ratón para todos sus objetos. Guardamos el índice de esta ventana como la “ventana activa en este momento”. Luego, hacemos que todos los elementos de este formulario sean visibles y recuperamos sus prioridades para el clic izquierdo del ratón, omitiendo el elemento del formulario (ya que está visible) y los elementos desplegables. 

Si la ventana de diálogo se abre en el momento cuando se ve la descripción emergente, hay que ocultarla. No va a desaparecer por sí misma, porque el formulario al que pertenece ya está bloqueado. Precisamente para estas ocasiones hemos creado antes el array privado para las descripciones emergentes. En la clase principal del procesamiento de eventos (CWndEvents), en cualquier momento se puede obtener el acceso a los métodos de cualquier elemento de la base. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Apertura de la ventana de diálogo
   bool              OnOpenDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                        |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Si hay la señal para minimizar el formulario
//--- Si hay la señal para maximizar el formulario
//--- Si hay señal para ocultar los menús contextuales desde el elemento iniciador
//--- Si hay señal para cerrar todos los menús contextuales

 //--- Si hay la señal para abrir la ventana de diálogo
   if(OnOpenDialogBox())
      return;
  }
//+------------------------------------------------------------------+
//| Evento ON_OPEN_DIALOG_BOX                                       |
//+------------------------------------------------------------------+
bool CWndEvents::OnOpenDialogBox(void)
  {
//--- Si hay la señal para abrir la ventana de diálogo
   if(m_id!=CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
      return(false);
//--- Salir si es la señal de otro programa
   if(m_sparam!=m_program_name)
       return(true);
//--- Recorremos el array de ventanas
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Si los identificadores coinciden
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Guarde en este formulario el índice de la ventana desde la cual ha sido llamado
         m_windows[w].PrevActiveWindowIndex(m_active_window_index);
         //--- Activamos el formulario
         m_windows[w].State(true);
         //--- Restauramos para los objetos del formulario las prioridad del clic izquierdo del ratón
         m_windows[w].SetZorders();
         //--- Guardamos el índice de la ventana activada
         m_active_window_index=w;
        //--- Hacer que todos los elementos de la ventana activada sean visibles
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
           {
            //--- Saltamos los formularios y los objetos deplegables
            if(m_wnd[w].m_elements[e].ClassName()=="CWindow" || 
               m_wnd[w].m_elements[e].IsDropdown())
               continue;
            //--- Hacer que el elemento sea visible
            m_wnd[w].m_elements[e].Show();
            //--- Restaurar para el elemento la prioridad del clic izquierdo del ratón
            m_wnd[w].m_elements[e].SetZorders();
           }
        Ocultar las descripciones emergentes
         int tooltips_total=CWndContainer::TooltipsTotal(m_windows[w].PrevActiveWindowIndex());
         for(int t=0; t<tooltips_total; t++)
            m_wnd[m_windows[w].PrevActiveWindowIndex()].m_tooltips[t].FadeOutTooltip();
        }
      //--- Otros formularios estarán bloqueados hasta que no se cierra la ventana activada
      else
        {
         //--- Bloqueamos el formulario
         m_windows[w].State(false);
         //--- Resetemos las prioridades de los elementos del formulario para el clic izquierdo
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

Ahora vamos a aclarar el evento con el identificador ON_RESET_WINDOW_COLORS que hemos creado antes en este artículo. Antes de escribir el método para el procesamiento de este evento, hay que añadir otro método virtual estándar a la clase base de todos los controles CElement que va a utilizarse para resetear los colores. Lo llamaremos CElement::ResetColors():

class CElement
  {
public:
   //--- Resetear color del elemento
   virtual void      ResetColors(void) {}
  };

En todas las clases derivadas hay que crear sus métodos ResetColors() con las particularidades propias a cada elemento. El código de abajo contiene el ejemplo para el control “Botón con imagen” (CIconButton). Puede ver el método ResetColors() para los demás controles en los archivos adjuntos al artículo.

class CIconButton : public CElement
  {
public:
   //--- Resetear color del elemento
   void              ResetColors(void);
  };
//+------------------------------------------------------------------+
//| Resetea el color                                                  |
//+------------------------------------------------------------------+
void CIconButton::ResetColors(void)
  {
//--- Salir si es el modo de dos estados y el botón está pulsado
   if(m_two_state && m_button_state)
      return;
//--- Resetear color
   m_button.BackColor(m_back_color);
//--- Poner a cero el foco
   m_button.MouseFocus(false);
   CElement::MouseFocus(false);
  }

Por tanto, teniendo el método virtual en la clase base de los controles y su propia versión en las derivadas, se puede resetear en el ciclo el color de todos los elementos desde el manejador de eventos de la clase principal de la librería (CWndEvents).

Para el procesamiento del evento ON_RESET_WINDOW_COLORS escribiremos el método CWndEvents::OnResetWindowColors(). Aquí todo es muy simple. Buscamos el formulario recién desactivado por el identificador del control que ha llegado en el mensaje. Si lo encontramos, guardamos su índice. Luego, si el índice ha sido guardado, reseteamos los colores de todos los elementos de este formulario. Más detalles en el código de abajo: 

class CWndEvents : public CWndContainer
  {
private:
   //--- Resetear los colores del formulario y sus elementos
   bool              OnResetWindowColors(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                        |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Si hay la señal para minimizar el formulario
//--- Si hay la señal para maximizar el formulario
//--- Si hay señal para ocultar los menús contextuales desde el elemento iniciador
//--- Si hay señal para cerrar todos los menús contextuales
//--- Si hay la señal para abrir la ventana de diálogo
//--- Si hay señal para resetear el color de los elementos en el formulario especificado
   if(OnResetWindowColors())
      return;
  }
//+------------------------------------------------------------------+
//| Evento ON_RESET_WINDOW_COLORS                                   |
//+------------------------------------------------------------------+
bool CWndEvents::OnResetWindowColors(void)
  {
//--- Si hay la señal para resetear el color de la ventana
   if(m_id!=CHARTEVENT_CUSTOM+ON_RESET_WINDOW_COLORS)
      return(false);
//--- Para identificar el índice del formulario del que ha llegado el mensaje
   int index=WRONG_VALUE;
//--- Recorremos el array de ventanas
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Si los identificadores coinciden
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Guardamos el índice
         index=w;
         //--- Reseteamos el color del formulario
         m_windows[w].ResetColors();
         break;
        }
     }
//--- Salimos si el índice no está definido
   if(index==WRONG_VALUE)
       return(true);
//--- Reseteamos los colores de todos los elementos del formulario
  int pConnection
   for(int e=0; e<elements_total; e++)
      m_wnd[index].m_elements[e].ResetColors();
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Pues, con el proceso de la apertura de las ventanas todo está aclarado. Ahora hay que implementar los métodos para el cierre y la recuperación de la ventana anterior activa. Para el procesamiento de este evento hay que crear el identificador ON_CLOSE_DIALOG_BOX en el archivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CLOSE_DIALOG_BOX       (12) // Evento del cierre de la ventana de diálogo

Ahora para el cierre del formulario, y para el cierre del programa, en la clase CWindow se utiliza el método CWindow::CloseWindow(). En este método, la sección para el cierre de las ventanas de diálogo (W_DIALOG) todavía no está implementada. Vamos a escribir el método adicional que va a generar el evento para el cierre de las ventanas de diálogo. Aparte del (1) identificador, el mensaje va a contener (2) el identificador del elemento, (3) el índice de la ventana anterior activa y (4) el texto del encabezado. Esté método va a llamarse CWindow::CloseDialogBox(). A continuación, vamos a usarlo también en los controles complejos donde el cierre de la ventana va a realizarse mediante otros elementos, distintos del botón de cierre.

class CWindow : public CElement
  {
public:
   //--- Cierre de la ventana de diálogo
   void              CloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Cierre de la ventana de diálogo                                        |
//+------------------------------------------------------------------+
void CWindow::CloseDialogBox(void)
  {
//--- Estado de visibilidad
   CElement::IsVisible(false);
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_CLOSE_DIALOG_BOX,CElement::Id(),m_prev_active_window_index,m_caption_text);
  }

En la clase CWindow la llamada al método CWindow::CloseDialogBox() debe realizarse en el método CWindow::CloseWindow(), tal como se muestra en la versión reducida del código de abajo. Los archivos adjuntos contienen su versión completa.

//+------------------------------------------------------------------+
//| Cierre del cuadro de diálogo o la ventana del programa                          |
//+------------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- Si es clic fuera del botón del cierre de la ventana
   if(pressed_object!=m_button_close.Name())
      return(false);
//--- Si es laa ventana principal
   if(m_window_type==W_MAIN)
     {
      //--- ...
     }
//--- Si es cuadro de diálogo
   else if(m_window_type==W_DIALOG)
     {
      //--- La cerramos
      CloseDialogBox();
     }
//---
   return(false);
  }

Después del enví del mensaje con el identificador ON_CLOSE_DIALOG_BOX, hay que seguir y procesarlo en el manejador de la clase CWndEvents. Para eso escribiremos el método CWndEvents::OnCloseDialogBox(). Ahí, en el ciclo recorremos todas las ventanas en la base buscando la ventana cuyo identificador del elemento va a coincidir con el identificador en el mensaje. Si la encontramos, hay que desactivarla. Luego la ocultamos junto con todos los elementos adjuntos y activamos el formulario por el índice pasado en el mensaje. Guardamos el índice de la ventana actual activa y recuperamos las prioridades de los elementos para el clic izquierdo.

class CWndEvents : public CWndContainer
  {
private:
   //--- Cierre de la ventana de diálogo
   bool              OnCloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                        |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Si hay la señal para minimizar el formulario
//--- Si hay la señal para maximizar el formulario
//--- Si hay señal para ocultar los menús contextuales desde el elemento iniciador
//--- Si hay señal para cerrar todos los menús contextuales
//--- Si hay la señal para abrir la ventana de diálogo
//--- Si hay la señal para cerrar la ventana de diálogo
   if(OnCloseDialogBox())
      return;
//--- Si hay señal para resetear el color de los elementos en el formulario especificado
  }
//+------------------------------------------------------------------+
//| Evento ON_CLOSE_DIALOG_BOX                                      |
//+------------------------------------------------------------------+
bool CWndEvents::OnCloseDialogBox(void)
  {
//--- Si hay la señal para cerrar la ventana de diálogo
   if(m_id!=CHARTEVENT_CUSTOM+ON_CLOSE_DIALOG_BOX)
      return(false);
//--- Recorremos el array de ventanas
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Si los identificadores coinciden
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Bloqueamos el formulario
         m_windows[w].State(false);
         //--- Ocultamos el formulario
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].Hide();
         //--- Activamos el formulario anterior
         m_windows[int(m_dparam)].State(true);
        //--- Redibujar el gráfico
         m_chart.Redraw();
         break;
        }
     }
//--- Establecer el índice de la ventana anterior
   m_active_window_index=int(m_dparam);
//--- Recuperación de las prioridades del clic izquierdo del ratón para la ventana activada
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

Todo está listo para probar el modo de ventanas múltiples. 

 


Prueba del modo de ventanas múltiples

En el EA que ha sido usado para probar los elementos informativos de la interfaz, vamos a crear dos instancias de la clase CWindow Como resultado, tendremos tres formularios en la interfaz gráfica del EA. El primer formulario es principal (W_MAIN), otros dos van a servir de las ventanas de diálogo (W_DIALOG). Adjuntaremos la primera ventana de diálogo a uno de los botones del formulario principal. Ahí crearemos tres botones y adjuntaremos la segunda ventana de diálogo a uno de ellos.  Al final, conseguiremos la situación cuando los tres formularios estarán abiertos al mismo tiempo, y sólo uno de ellos estará activo (disponible).

En el siguiente código se muestra qué es lo que hay que añadir a la clase de usuario de la aplicación (CProgram) en esta fase del desarrollo: 

class CProgram : public CWndEvents
  {
private:
   //--- Formulario 2
   CWindow           m_window2;
   //--- Botones con imágenes
   CIconButton       m_icon_button6;
   CIconButton       m_icon_button7;
   CIconButton       m_icon_button8;

   //--- Formulario 3
   CWindow           m_window3;
   //---
private:
   //--- Formulario 2
   bool              CreateWindow2(const string text);
   //--- Botones con imágenes
#define ICONBUTTON6_GAP_X        (7)
#define ICONBUTTON6_GAP_Y        (25)
   bool              CreateIconButton6(const string text);
#define ICONBUTTON7_GAP_X        (7)
#define ICONBUTTON7_GAP_Y        (50)
   bool              CreateIconButton7(const string text);
#define ICONBUTTON8_GAP_X        (7)
#define ICONBUTTON8_GAP_Y        (75)
   bool              CreateIconButton8(const string text);

   //--- Formulario 3
   bool              CreateWindow3(const string text);
  };

Colocamos la llamada a estos métodos en el método principal de la creación de la interfaz gráfica de la aplicación. En el código de abajo se muestra la versión reducida de este método. 

//+------------------------------------------------------------------+
//| 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

//--- Creación del formulario 2 para los controles
   if(!CreateWindow2("Icon Button 1"))
      return(false);
//--- Botones con imagen
   if(!CreateIconButton6("Icon Button 6..."))
      return(false);
   if(!CreateIconButton7("Icon Button 7"))
      return(false);
   if(!CreateIconButton8("Icon Button 8"))
      return(false);

//--- Creación del formulario 3 para los controles
   if(!CreateWindow3("Icon Button 6"))
      return(false);

//--- Descripciones emergentes
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Nosotros vamos a considerar el método sólo para la primera ventana de diálogo (el segundo formulario). Usted ya sabe que hay que usar el método CWndContainer::AddWindow() para añadir el formulario a la base. Luego, fíjese (véase el código de abajo) cómo se definen las coordenadas para el formulario. Por defecto, cuando el programa se carga en el gráfico tienen los valores cero, por eso van a establecerse las que considere convenientes. En este caso, como ejemplo, son las siguientes: x=1, y=20. Después de eso, se puede mover el formulario, y luego cambiar el período de tiempo o el símbolo del gráfico. Con está técnica, como se muestra en el código de abajo, el formulario se queda donde ha estado la última vez. Si necesita que el formulario se coloque en la posición inicial como durante el primer inicio del programa en el gráfico, simplemente quite estas condiciones. En nuestro ejemplo, los tres formularios de la interfaz gráfica van a tener estas condiciones.

Hagamos que se pueda mover los formularios por el gráfico. El tipo de la ventana tiene que ser establecido obligatoriamente como de diálogo (W_DIALOG). De lo contrario, la interfaz no va a comportarse de forma correcta. Se puede redefinir el icono de la ventana usando el método CWindow::IconFile(). Las ventanas de diálogo pueden tener el mismo icono que tiene el elemento a través del cual se abre esta ventana.

//+------------------------------------------------------------------+
//| Crea el formulario 2 para los controles                         |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow2(const string caption_text)
  {
//--- Añadimos el puntero de la ventana al array de ventanas
   CWndContainer::AddWindow(m_window2);
//--- Coordenadas
   int x=(m_window2.X()>0) ? m_window2.X() : 1;
   int y=(m_window2.Y()>0) ? m_window2.Y() : 20;
//--- Propiedades
   return(true);
   m_window2.WindowType(W_DIALOG);
   m_window2.XSize(160);
   m_window2.YSize(160);
   m_window2.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp");
   m_window2.CaptionBgColor(clrCornflowerBlue);
   m_window2.CaptionBgColorHover(C'150,190,240');
//--- Creación del formulario
   if(!m_window2.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Voy a recordar algunos momentos respecto a la manera de adjuntar los controles a una determinada ventana de diálogo. Como ejemplo, veremos uno de los métodos de los botones destinados para este formulario. Aquí hay que destacar sólo dos momentos. 

Debe recordar que:

  • Al control hay que pasarle el puntero del formulario al que debe adjuntarse
  • Cuando guarda el puntero al control en la base, indique el índice del formulario al que se adjunta el control. En este caso, es 1. 
//+------------------------------------------------------------------+
//| Crea el botón con imagen 6                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateIconButton6(const string button_text)
  {
//--- Guardamos el puntero a la ventana
   m_icon_button6.WindowPointer(m_window2);
//--- Coordenadas
   int x=m_window2.X()+ICONBUTTON6_GAP_X;
   int y=m_window2.Y()+ICONBUTTON6_GAP_Y;
//--- Establecemos las propiedades antes de la creación
   m_icon_button6.TwoState(false);
   m_icon_button6.ButtonXSize(146);
   m_icon_button6.ButtonYSize(22);
   m_icon_button6.LabelColor(clrBlack);
   m_icon_button6.LabelColorPressed(clrBlack);
   m_icon_button6.BorderColorOff(clrWhite);
   m_icon_button6.BackColor(clrLightGray);
   m_icon_button6.BackColorHover(C'193,218,255');
   m_icon_button6.BackColorPressed(C'153,178,215');
   m_icon_button6.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp");
   m_icon_button6.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp");
//--- Creamos el control
   if(!m_icon_button6.CreateIconButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(1,m_icon_button6);
   return(true);
  }

El desarrollador de la aplicación debe encargarse del control de la visualización de una u otra ventana. En el manejador de eventos de la clase personalizada (CProgram), hay que monitorear el clic en un control y mostrar la ventana necesaria. La llamada a la primera ventana de diálogo se vinculará al botón en la ventana principal del EA (el segundo formulario), la llamada a la segunda ventana se vinculará al botón en la primera ventana de diálogo (tercer formulario).

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento del clic en el botón
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Si el texto coincide
      if(sparam==m_icon_button1.Text())
        {
         //--- Mostrar la ventana 2
         m_window2.Show();
        }
      //--- Si el texto coincide
      if(sparam==m_icon_button6.Text())
        {
         //--- Mostrar la ventana 3
         m_window3.Show();
        }
     }
  }

En la captura de abajo se muestra el resultado que se debe obtener. Fíjese en los puntos suspensivos en los nombres de los botones «Icon Button 1...» y «Icon Button 6...». Normalmente, de esta manera al usuario se la da a entender que al hacer clic en este control, se abre una ventana de diálogo.

Fig. 1. Prueba del modo de ventanas múltiples.

Fig. 1. Prueba del modo de ventanas múltiples.

 

Si ahora, en el momento cuando están abiertos varios formularios, Usted cambia el símbolo o período del gráfico, se enfrentará con un problema. Las ventanas de diálogo desaparecerán, como debe ser, pero la ventana principal no obtendrá el control. Por esta razón, el formulario no va a reaccionar a las acciones del usuario. Este problema se soluciona muy fácil. Recordamos que en el método de la deinicialización de la clase personalizada CProgram::OnDeinitEvent() se invoca el método CWndEvents::Destroy(). Ahí se elimina la interfaz gráfica de la aplicación. Hay que pasar el control a la ventana principal precisamente en el momento de la eliminación de la interfaz gráfica. Por eso hay que introducir pequeñas adiciones en el método CWndEvents::Destroy():

  • Establecer el índice de la ventana principal como “activo”.
  • Activar la ventana principal y desactivar las demás.

En el listado del código de abajo se muestra la versión actual del método CWndEvents::Destroy(): 

//+------------------------------------------------------------------+
//| Eliminación de todos los objetos                                           |
//+------------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
//--- Establecemos el índice de la ventana principal
   m_active_window_index=0;
//--- Obtenemos el número de ventanas
   int window_total=CWndContainer::WindowsTotal();
//--- Recorremos el array de ventanas
   for(int w=0; w<window_total; w++)
     {
      //--- Activamos la ventana principal
      if(m_windows[w].WindowType()==W_MAIN)
         m_windows[w].State(true);
       //--- Bloqueamos las ventanas de diálogo
      else
         m_windows[w].State(false);
     }
//--- Liberamos los arrays de controles
   for(int w=0; w<window_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         //--- Si el puntero no es válido, ir al siguiente
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Eliminar los objetos del control
         m_wnd[w].m_elements[e].Delete();
        }
       //--- Liberar los arrays de controles
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
      ::ArrayFree(m_wnd[w].m_context_menus);
     }
//--- Liberar los arrays de formularios
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

Hemos implementado la primera versión del modo de ventanas múltiples. Como podemos ver, no ha sido tan complicado como podía parecer a la primera vista.  

 


Mejorando el sistema de prioridades para el clic izquierdo del ratón

Hasta este momento, el control de las prioridades del clic izquierdo en los controles de la interfaz se realizaba por los eventos con los identificadores ON_OPEN_DIALOG_BOX y ON_CLOSE_DIALOG_BOX. La razón de eso era que durante el desarrollo del siguiente control desplegable, el usuario asignaba personalmente el valor de la prioridad para cada objeto de este control. Se tomaban en cuenta las prioridades de otros elementos que pueden encontrarse debajo de ellos. Pero cuando se crean los controles complejos, eso se convierte en un sistema demasiado “embarazoso” en el que se puede confundirse fácilmente. Para hacer las cosas más claras y sencillas, vamos a crear dos identificadores más para estos eventos:

  • ON_ZERO_PRIORITIES – anulación de prioridades.
  • ON_SET_PRIORITIES – recuperación de prioridades.

Vamos a añadirlos al archivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_ZERO_PRIORITIES        (14) // Poner a cero las prioridades para el clic izquierdo del ratón
#define ON_SET_PRIORITIES         (15) // Recuperar las prioridades para el clic izquierdo del ratón

La generación de eventos con estos identificadores debe ubicarse en las clases de los elementos que son (pueden ser) desplegables. En esta fase del desarrollo de la librería, en el presente conjunto de la interfaz, este control es “Menú contextual”. Por eso ahora en la clase CContextMenu en los métodos Show() y Hide() hay que añadir el código, tal como se muestra en el código de abajo (versiones reducidas de los métodos):

//+------------------------------------------------------------------+
//| Muestra el menú contextual                                      |
//+------------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- Salir si el control ya está visible
//--- Mostrar los objetos del menú contextual
//--- Mostrar los elementos del menú
//--- Conceder el estatus del control visible
//--- Estado del menú contextual
//--- Anotar el estado en el nodo anterior
//--- Bloquear el formulario

//--- Enviar la señal para anular las prioridades para el clic izquierdo del ratón
   ::EventChartCustom(m_chart_id,ON_ZERO_PRIORITIES,CElement::Id(),0.0,"");
  }
//+------------------------------------------------------------------+
//| Oculta el menú contextual                                        |
//+------------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- Salir si el control está ocultado
//--- Ocultar los objetos del menú contextual
 //--- Ocultar los elementos del menú
//--- Poner a cero el foco
//--- Estado del menú contextual
//--- Anotar el estado en el nodo anterior

//--- Enviar la señal para recuperar las prioridades para el clic izquierdo del ratón
   ::EventChartCustom(m_chart_id,ON_SET_PRIORITIES,0,0.0,"");
  }

Vamos a recibir estos mensajes en la clase principal del procesamiento de todos los mensajes (CWndEvents). Para estos fines, escribiremos un método separado de procesamiento para cada identificador. Estos métodos van a invocarse en el método principal del procesamiento de eventos de usuario CWndEvents::ChartEventCustom(). 

class CWndEvents : public CWndContainer
  {
private:
   //--- Resetear las prioridades para el clic izquierdo del ratón
   bool              OnZeroPriorities(void);
   //--- Recuperar las prioridades para el clic izquierdo del ratón
   bool              OnSetPriorities(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                        |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Si hay la señal para minimizar el formulario
//--- Si hay la señal para maximizar el formulario
//--- Si hay señal para ocultar los menús contextuales desde el elemento iniciador
//--- Si hay señal para cerrar todos los menús contextuales
//--- Si hay la señal para abrir la ventana de diálogo
//--- Si hay la señal para cerrar la ventana de diálogo
//--- Si hay señal para resetear el color de los elementos en el formulario especificado

//--- Si hay señal para resetear las prioridades para el clic izquierdo del ratón
   if(OnZeroPriorities())
      return;
//--- Si hay señal para recuperar las prioridades para el clic izquierdo del ratón
   if(OnSetPriorities())
      return;
  }

En el método CWndEvents::OnZeroPriorities() repasamos en el ciclo todos los controles de la ventana activa y anulamos las prioridades para todos a excepción del control cuyo identificador se encuentra en el mensaje (parámetro lparam), y salvo los elementos del menú y menús contextuales. La razón de la excepción de los elementos del menú y menús contextuales consiste en el hecho de que pueden estar abiertos varios menús contextuales al mismo tiempo (uno de otro).

//+------------------------------------------------------------------+
//| Evento ON_ZERO_PRIORITIES                                       |
//+------------------------------------------------------------------+
bool CWndEvents::OnZeroPriorities(void)
  {
//--- Si hay señal para anular las prioridades para el clic izquierdo del ratón
   if(m_id!=CHARTEVENT_CUSTOM+ON_ZERO_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
     {
      //--- Anular las prioridades de todos los elementos excepto el elemento cuyo id ha sido pasado en el evento y ...
      if(m_lparam!=m_wnd[m_active_window_index].m_elements[e].Id())
        {
         //--- ... excepto los menús contextuales
         if(m_wnd[m_active_window_index].m_elements[e].ClassName()=="CMenuItem" ||
            m_wnd[m_active_window_index].m_elements[e].ClassName()=="CContextMenu")
            continue;
         //---
         m_wnd[m_active_window_index].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

Si ha llegado el mensaje con el identificador del evento ON_SET_PRIORITIES, simplemente hay que recuperar las prioridades del clic izquierdo para todos los elementos de la ventana activa. 

//+------------------------------------------------------------------+
//| Evento ON_SET_PRIORITIES                                        |
//+------------------------------------------------------------------+
bool CWndEvents::OnSetPriorities(void)
  {
//--- Si hay señal para recuperar las prioridades para el clic izquierdo del ratón
   if(m_id!=CHARTEVENT_CUSTOM+ON_SET_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

 


Conclusión

En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema tiene el siguiente aspecto

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

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

 

Pues, aquí terminamos la cuarta parte de la serie sobre las interfaces gráficas. En el primer capítulo de esta parte, hemos implementado los elementos informativos, tales como la “Barra de estado” y la “Descripción emergente”. En el segundo capítulo, hemos analizado los temas sobre el modo de ventanas múltiples y el sistema de prioridades para el clic izquierdo del ratón.

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:

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

Archivos adjuntos |
Calculadora de señales Calculadora de señales
La calculadora de señales funciona directamente desde el terminal MetaTrader 5, y esta es su gran ventaja, ya que el terminal lleva a cabo la preselección y la clasificación de las señales. De este modo, el usuario ve en el terminal MetaTrader 5 sólo las señales con la máxima compatibilidad con su cuenta comercial.
Interfaces gráficas IV: Elementos informativos de la interfaz (Capítulo 1) Interfaces gráficas IV: Elementos informativos de la interfaz (Capítulo 1)
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. Ahora tenemos todo preparado para considerar la cuestión del modo de ventanas múltiples, pero nos ocuparemos de ello en el segundo capítulo de este artículo. Primero, 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”.
Vista del Análisis Técnico en el contexto de Sistemas de Control Automáticos (SCA), o "Vista inversa". Vista del Análisis Técnico en el contexto de Sistemas de Control Automáticos (SCA), o "Vista inversa".
El artículo demuestra una vista alternativa del análisis técnico, que se basa en los principios de la teoría del control automático moderno y el análisis técnico en sí. Es un artículo introductorio representando la teoría con algunas aplicaciones prácticas.
Interfaces gráficas III: Grupos de botones simples y multifuncionales (Capítulo 2) Interfaces gráficas III: Grupos de botones simples y multifuncionales (Capítulo 2)
El primer capítulo de la tercera pare de la serie estaba dedicada a los botones simples y multifuncionales. En el segundo capítulo hablaremos de los grupos de botones interconectados que permiten crear los controles, cuando el usuario puede elegir una opción de un determinado conjunto (grupo).