Interfaces gráficas II: Control "Elemento del menú" (Capítulo 1)

Anatoli Kazharski | 25 febrero, 2016

Índice

 

Introducción

En los capítulos anteriores de la primera parte hemos demostrado al detalle el proceso del desarrollo de la estructura principal de la librería para la creación de las interfaces gráficas, además de crear el elemento principal de la interfaz: el formulario para los controles. 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 los artículos de cada parte se puede encontrar la lista de los capítulos con los enlaces, así como 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 la fase actual del desarrollo, algunas clases no tienen aún las versiones definitivas. Según se vayan añadiendo nuevos controles a la librería, también habrá que introducir algunas adiciones en la clase CWndContainer que sirve de base para el almacenamiento de los punteros a los controles y objetos de los que se componen. También habrá que introducir adiciones en la clase CWndEvents donde se procesan los eventos del gráfico y los eventos que se generan por los controles.

La clase del formulario para los controles CWindow tampoco es una versión definitiva para este momento. Es que aún no ha sido implementado el modo de ventanas múltiples del que hemos mencionado en los artículos anteriores. Nos ocuparemos de la implementación de este modo en la parte 4 de la serie. A parte de eso, también vamos a demostrar la creación del menú tipo contextual. Forma parte del menú principal, pero también puede ser parte de algunos otros controles que van a considerarse en otros artículos de esta serie.

Se recomienda ejecutar todas las acciones consecutivamente para consolidar mejor el material. Con el fin de ahorrar el espacio del artículo, vamos a omitir el código de algunos métodos del mismo tipo y que se repiten en diferentes clases. En estos casos, hay que dirigirse a los archivos adjuntos al artículo y coger el código de ahí, y sólo después continuar estudiando consecutivamente el material del artículo.

 

Menú principal del programa

Es complicado encontrar un programa que no tenga el menú principal. En los terminales MetaTrader, este elemento de la interfaz también está presente (véase la captura de pantalla más abajo). En la mayoría de los casos, el menú se ubica en la parte superior de la ventana del programa y se compone de varios elementos. Al hacer clic izquierdo en un elemento del menú, debajo de este elemento aparece una lista con las opciones del programa.


Fig. 1. Menú principal en el terminal MetaTrader 5

Esta lista desplegable se llama “Menú contextual” y puede incluir varios tipos de elementos. Vamos a examinar cada uno de ellos con más detalles:

  • Elemento botón. Esté es el elemento más sencillo en el menú contextual. En la mayoría de los casos, al hacer clic izquierdo en este elemento, se abre una ventana con las funcionalidades ampliadas para la configuración del programa, o la ventana que contiene alguna información. Pero pueden ser las funciones más simples que, tras el clic en el elemento botón, cambian algo en seguida en la apariencia de la interfaz del programa.
  • Elemento con dos estados tipo “Casilla de verificación” (check box en inglés). Usando este elemento se puede activar algún proceso o abrir (hacer visible) alguna parte de la interfaz del programa. En este caso, el elemento cambia su apariencia, es decir muestra al usuario de la aplicación en qué estado se encuentra.
  • Grupo de elementos entre los cuales sólo uno puede estar habilitado. Este tipo de controles se llama “botones de opción” o “botones de radio” (radio button en inglés). En nuestro artículo vamos a llamarlos “elementos de radio”.
  • Elemento de la llamada del menú contextual. O sea, en el menú contextual que ha sido llamado desde el menú principal del programa pueden haber elementos que contienen otro menú contextual. Después de pinchar en este elemento, el menú contextual va a aparecer a la derecha de él.

El menú principal está presente también en el editor del código MetaEditor:


Fig. 2. Menú principal en el editor del código MetaEditor

Ahora tenemos que determinar qué clases hay que crear para poder diseñar un control de la interfaz tan complicado. Es obvio que reunir todo en una sola clase es irracional ya que sería bastante complicado examinar y mantener esta clase en el futuro. Por eso tiene sentido implementar todo de tal manera que el complejo entero se reuniera al final de las partes simples. Vamos a aclarar qué partes serán.

El menú principal y el menú contextual se componen de varios elementos. En ambos tipos de menús, puede servir la misma clase de estos elementos. Además, en el menú contextual a menudo se puede ver una línea separadora que sirve para separar sus elementos según categorías. Por eso se perfilan, como mínimo, cuatro clases del código para crear estos elementos de la interfaz:

  1. Elemento del menú. Para crear este control, será desarrollada la clase CMenuItem.
  2. Para la línea separadora, vamos a crear la clase CSeparateLine.
  3. Menú contextual. Clase CContextMenu. Este elemento de la interfaz va a componerse de los objetos de la clase CMenuItem.
  4. Menú principal. Clase CMenuBar. Igual que en el menú contextual, sus partes integrantes van a ser lo elementos del menú (CMenuItem).

Pues, hemos planteado las tareas principales, y como se ve de la descripción de arriba, la parte más importante de las que se compone el menú principal y contextual es el elemento del menú porque el usuario va a interactuar precisamente con este control. Por esta razón, continuaremos el artículo con la creación de la clase CMenuItem.

 

Desarrollo de la clase para crear un elemento del menú

En el menú Controls, donde ahora se ubican todos los demás archivos de la librería, hay que crear el archivo MenuItem.mqh con la clase derivada CMenuItem, en el que se puede declarar en seguida los métodos virtuales estándar para todos los controles. De la clase base sirve CElement, que ha sido considerada detalladamente en el artículo anterior. Vamos a dejar estos métodos sin implementación por ahora, ya que me gustaría remarcar una particularidad bastante interesante del compilador.

//+------------------------------------------------------------------+
//|                                                     MenuItem.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//+------------------------------------------------------------------+
//| Clase de creación del elemento del menú                                       |
//+------------------------------------------------------------------+
class CMenuItem : public CElement
  {
   //---
public:
                     CMenuItem(void);
                    ~CMenuItem(void);  
   //---
public:
   //--- Manejador de eventos del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Temporizador
   virtual void      OnEventTimer(void);
   //--- Desplazamiento del control
   virtual void      Moving(const int x,const int y);
   //--- Mostrar, ocultar, resetear, eliminar
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- Establecer, poner a cero las prioridades para el clic izquierdo del ratón
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);  
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMenuItem::CMenuItem(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CMenuItem::~CMenuItem(void)
  {
  }  
//+------------------------------------------------------------------+

¿Qué objetos gráficos forman parte del control “Elemento del menú”? En el menú principal, normalmente son los títulos que cambian el color del fondo y/o el color del texto cuando se sitúa el cursor sobre ellos. En el menú contextual se puede ver los elementos con iconos, y si un elemento contiene su propio menú contextual, en la parte derecha del área del elemento se muestra la flecha a la derecha que indica al usuario en la presencia del menú incorporado. De todo eso se desprende que un elemento del menú puede tener varios tipos diferentes dependiendo de su pertenencia y la tarea que debe cumplir. Vamos a listar sus partes integrantes en conjunto máximo:

  1. Fondo.
  2. Icono.
  3. Título.
  4. Indicio de la la existencia del menú contextual.


Fig. 3. Partes integrantes del control “Elemento del menú”.

Añadimos a la clase CMenuItem las instancias de las clases de objetos gráficos (que están disponibles en el archivo Element.mqh) y declaraciones de los métodos para la creación de estos objetos gráficos. Cuando un elemento se coloca en el gráfico, hay que pasar el número del índice del elemento del menú al método. Este número va a utilizarse en la formación del nombre de los objetos gráficos de los que se compone el elemento del menú, así como será necesario para la identificación en la lista de elementos en el momento de pulsación sobre uno de ellos.

class CMenuItem : public CElement
  {
private:
   //--- Objetos para crear el elemento del menú
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CBmpLabel         m_arrow;
   //---
public:
   //--- Métodos para crear el elemento del menú
   bool              CreateMenuItem(const long chart_id,const int window,const int index_number,const string label_text,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateArrow(void);
   //---
  };

Puesto que un elemento del menú puede tener varios tipos diferentes, es necesaria la opción (antes de su creación) para indicar a qué tipo corresponde. Vamos a necesitar la enumeración de los tipos del elemento del menú (ENUM_TYPE_MENU_ITEM).

Hay que añadirla al archivo Enums.mqh donde se almacenan todas las enumeraciones de la librería. En la enumeración ENUM_TYPE_MENU_ITEM tienen que haber las opciones de las que hemos hablado antes:

  • MI_SIMPLE — elemento simple.
  • MI_HAS_CONTEXT_MENU — elemento que contiene menú contextual.
  • MI_CHECKBOX — casilla de verificación.
  • MI_RADIOBUTTON — elemento que pertenece al grupo de botones de radio.
//+------------------------------------------------------------------+
//| Enumeración de los tipos del elemento del menú                                   |
//+------------------------------------------------------------------+
enum ENUM_TYPE_MENU_ITEM
  {
   MI_SIMPLE           =0,
   MI_HAS_CONTEXT_MENU =1,
   MI_CHECKBOX         =2,
   MI_RADIOBUTTON      =3
  };

En la clase CMenuItem hay que añadir el campo correspondiente y los métodos para establecer y obtener el tipo del elemento del menú:

class CMenuItem : public CElement
  {
private:
   //--- Propiedades del elemento del menú
   ENUM_TYPE_MENU_ITEM m_type_menu_item;
   //---
public:
   //--- Establecer y obtener el tipo
   void              TypeMenuItem(const ENUM_TYPE_MENU_ITEM type)   { m_type_menu_item=type;                 }
   ENUM_TYPE_MENU_ITEM TypeMenuItem(void)                     const { return(m_type_menu_item);              }
   //---
  };

Por defecto, se establece el tipo MI_SIMPLE, es decir el elemento simple:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMenuItem::CMenuItem(void) : m_type_menu_item(MI_SIMPLE)
  {
  }

Para cada objeto gráfico del elemento del menú hay que crear los métodos que permiten configurar su apariencia igual que hemos hecho en el artículo anterior para el formulario. Pero aquí van a haber algunas propiedades únicas, por eso especificaremos todo lo que vamos a necesitar para este control:

  1. Cambio del color del fondo.
  2. Color del fondo, cuando el elemento del menú no está disponible (bloqueado por no poder usar la función del elemento en este momento).
  3. Cambio del color del marco del fondo.
  4. La prioridad del clic izquierdo para todos los objetos tiene que ser común y su valor es igual a cero, salvo el fondo porque precisamente ahí vamos a seguir la pulsación.
  5. Redefinición de imágenes para el icono e indicio de la presencia del menú contextual.
  6. Métodos para la gestión de las propiedades de la etiqueta de texto, tales como:
    • sangrías desde la coordenada cero del fondo del elemento del menú;
    • color del texto;
    • color del texto al apuntar con el cursor;
    • color del texto en caso del elemento bloqueado;
  7. Variables para el seguimiento del estado del elemento del menú:
    • estado general (disponible/bloqueado);
    • estado de la casilla de verificación;
    • estado del elemento de radio;
    • estado del menú contextual si está incluido en este elemento.
  8. Identificador para el grupo de elementos de radio. Es necesario porque en un menú contextual puede haber varios grupos de elementos de radio. El identificador permite averiguar a qué grupo pertenece exactamente un elemento de radio.
  9. Número del índice del elemento del menú.

Añadimos todo lo mencionado arriba a la clase CMenuItem:

class CMenuItem : public CElement
  {
private:
   //--- Propiedades del fondo
   int               m_area_zorder;
   color             m_area_border_color;
   color             m_area_color;
   color             m_area_color_off;
   color             m_area_color_hover;
  //--- Propiedades del icono
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- Propiedades de la etiqueta de texto
   string            m_label_text;
   int               m_label_x_gap;
   int               m_label_y_gap;
   color             m_label_color;
   color             m_label_color_off;
   color             m_label_color_hover;
   //--- Propiedades del indicio del menú contextual
   string            m_right_arrow_file_on;
   string            m_right_arrow_file_off;
   //--- Prioridad común para hacer clic
   int               m_zorder;
   //--- Disponible/bloqueado
   bool              m_item_state;
   //--- Estado de la casilla de verificación
   bool              m_checkbox_state;
   //--- Estado del botón de radio y su edentificador
   bool              m_radiobutton_state;
   int               m_radiobutton_id;
   //--- Estado del menú contextual
   bool              m_context_menu_state;
   //---
public:
   //--- Métodos del fondo
   void              AreaBackColor(const color clr)                 { m_area_color=clr;                      }
   void              AreaBackColorOff(const color clr)              { m_area_color_off=clr;                  }
   void              AreaBorderColor(const color clr)               { m_area_border_color=clr;               }
   //--- Métodos del icono
   void              IconFileOn(const string file_path)             { m_icon_file_on=file_path;              }
   void              IconFileOff(const string file_path)            { m_icon_file_off=file_path;             }
   //--- Métodos de la etiqueta de texto
   string            LabelText(void)                          const { return(m_label.Description());         }
   void              LabelXGap(const int x_gap)                     { m_label_x_gap=x_gap;                   }
   void              LabelYGap(const int y_gap)                     { m_label_y_gap=y_gap;                   }
   void              LabelColor(const color clr)                    { m_label_color=clr;                     }
   void              LabelColorOff(const color clr)                 { m_label_color_off=clr;                 }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;               }
   //--- Métodos del indicio de la la existencia del menú contextual
   void              RightArrowFileOn(const string file_path)       { m_right_arrow_file_on=file_path;       }
   void              RightArrowFileOff(const string file_path)      { m_right_arrow_file_off=file_path;      }
   //--- Estado general del (1) elemento del menú y (2) casilla de verificación
   void              ItemState(const bool state);
   bool              ItemState(void)                          const { return(m_item_state);                  }
   void              CheckBoxState(const bool flag)                 { m_checkbox_state=flag;                 }
   bool              CheckBoxState(void)                      const { return(m_checkbox_state);              }
   //--- Identificador del elemento de radio
   void              RadioButtonID(const int id)                    { m_radiobutton_id=id;                   }
   int               RadioButtonID(void)                      const { return(m_radiobutton_id);              }
   //--- Estado del elemento de radio
   void              RadioButtonState(const bool flag)              { m_radiobutton_state=flag;              }
   bool              RadioButtonState(void)                   const { return(m_radiobutton_state);           }
   //--- Estado del menú contextual si está incluido en este elemento
   bool              ContextMenuState(void)                   const { return(m_context_menu_state);          }
   void              ContextMenuState(const bool flag)              { m_context_menu_state=flag;             }
   //---
  };

En el artículo anterior hemos mencionado que la librería está organizada de tal manera que su estructura proteje a los usuarios de las situaciones cuando no se sabe qué hacer en adelante. Es que pasado un tiempo uno puede olvidar la secuencia de acciones. Antes de crear un control, primero hay que pasarle el puntero al formulario al que debe adjuntarse. Si no lo hacemos, el programa simplemente no terminará la colocación de todos los controles en el formulario, mostrando en el registro un mensaje sobre ello. Es necesario generar el mensaje de tal manera que esté claro qué mensaje ha provocado el error en la secuencia de acciones del usuario.

Hay que incluir el archivo en el que se encuentra la clase del formulario (CWindow) y declarar el puntero con el tipo del formulario. Para guardar el puntero al formulario, vamos a necesitar el método correspondiente en la clase de cada control. Lo llamaremos WindowPointer(). Como parámetro único, él recibe por referencia el objeto del tipo CWindow. Su tarea consiste en guardar el puntero del objeto pasado. Para obtener el puntero del objeto, se utiliza la función GetPointer(). Tendremos que hacer lo mismo en la clase de cada control que vamos a crear.

//+------------------------------------------------------------------+
//|                                                     MenuItem.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Clase de creación del elemento del menú                                       |
//+------------------------------------------------------------------+
class CMenuItem : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el elemento
   CWindow          *m_wnd;
   //---
public:
   //--- Guarda el puntero del formulario pasado
   void              WindowPointer(CWindow &object)                 { m_wnd=::GetPointer(object);            }
   //---
  };

Ahora vamos a considerar el método de creación del elemento del menú CMenuItem::CreateMenuItem(). Al principio del método, vamos a comprobar que el puntero al formulario al que debe adjuntarse el control es correcto. Para eso se usa la función CheckPointer(). Si el puntero no es válido, el programa mostrará en el registro un mensaje con la ayuda y saldrá del método, devolviendo false. Si el puntero es válido, se inicializan las variables del control.

En el artículo anterior hemos dicho que cada control debe tener su propio identificador y hemos considerado detalladamente de qué manera va a formarse para cada control de la interfaz. Para no repetirse, voy a recordar brevemente cómo se hace.

Cuando se crea el formulario principal, en el momento de adición de su puntero en la base de controles, cuyo papel desempeña la clase CWndContainer, el identificador se determina por el contador de controles de la interfaz en el método CWndContainer::AddWindow(). El valor del contador se guarda en las clases de los formularios cada vez que el control se añade a la base. Y como el puntero al formulario es obligatorio en cada control, el número del identificador del último control está disponible para cada nuevo control creado a través del puntero al formulario. Por eso, antes de la creación de un control, su identificador va a formarse como se muestra en el código de abajo (marcado con amarillo): es decir, a uno más.

Luego, para el control se calculan y se guardan las sangrías desde el punto extremo del formulario. Hay acceso a las coordenadas del formulario a través del puntero a él, y hay posibilidad de calcular la distancia de ellas. De la misma manera van a calcularse las sangrías para cada objeto de un control. Después de eso, se crean todos los objetos del control, y al final se comprueba en qué estado se encuentra la ventana en este momento. Si resulta que está minimizada, hay que ocultar el control.

//+------------------------------------------------------------------+
//| Crea el control “Elemento del menú”                                     |
//+------------------------------------------------------------------+
bool CMenuItem::CreateMenuItem(const long chart_id,const int subwin,const int index_number,const string label_text,const int x,const int y)
  {
//--- Salir si no hay puntero al formulario
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de crear el elemento del menú, hay que pasar a la clase "
            "el puntero a la ventana: CMenuItem::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Inicialización de variables
   m_id           =m_wnd.LastId()+1;
   m_index        =index_number;
   m_chart_id     =chart_id;
   m_subwin       =subwin;
   m_label_text   =label_text;
   m_x            =x;
   m_y            =y;
//--- Sangrías desde el punto extremo
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Creación del elemento del menú
   if(!CreateArea())
      return(false);
   if(!CreateIcon())
      return(false);
   if(!CreateLabel())
      return(false);
   if(!CreateArrow())
      return(false);
//--- Si la ventana está minimizada, ocultar el control tras su creación
   if(m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

En el método de ocultación del control CMenuItem::Hide() hay que ocultar todos los objetos, poner a cero algunas variables y resetear los colores:

//+------------------------------------------------------------------+
//| Oculta el elemento del menú                                              |
//+------------------------------------------------------------------+
void CMenuItem::Hide(void)
  {
//--- Salir si el control está ocultado
   if(!CElement::m_is_visible)
      return;
//--- Ocultar todos los objetos
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_NO_PERIODS);
//--- Puesta a cero de las variables
   m_context_menu_state=false;
   CElement::m_is_visible=false;
   CElement::MouseFocus(false);
//--- Resetear colores
   m_area.BackColor(m_area_color);
   m_arrow.State(false);
  }

La formación del nombre para los objetos gráficos del control del elemento del menú va a ser un poco más compleja que en la clase del formulario CWindow. Si el elemento del menú se crea como un control independiente que no forma parte del menú principal o contextual (mostraremos estos ejemplos también), no hay ningún problema porque va a tener su identificador único. Pero si el control se crea en un grupo donde además de éste hay otros objetos, un solo identificador ya no es suficiente, puesto que los nombres de todos los objetos gráficos del mismo tipo van a ser los mismos, y al final en el gráficos se visualizará sólo un control.

Cuando se crea un elemento del menú, en el método CMenuItem::CreateMenuItem() se pasa el número del índice para este elemento y luego se guarda en el campo de la clase m_index_number que a continuación va a utilizarse en la formación del nombre de cada objeto gráfico del control. Vamos a listar las partes integrantes para el nombre de los objetos del elemento del menú:

  • Nombre del programa.
  • Pertenencia al control.
  • Pertenencia a una parte del control.
  • Número del índice del control.
  • Identificador del control.

Los métodos para crear el fondo, etiqueta de texto y el indicio de la presencia del menú contextual no tienen ningunas diferencias importantes de los que han sido presentados en la clase CWindow, por eso puede estudiar su código por su propia cuenta en el artículo adjunto MenuItem.mqh. Sólo para poner un ejemplo, aquí se puede mostrar el método para la creación del icono CMenuItem::CreateIcon() en el que se comprueba el tipo del elemento del menú, y en función del tipo establecido se determina qué imagen va a utilizarse.

Los elementos simples (MI_SIMPLE) y los elementos que contienen un menú contextual (MI_HAS_CONTEXT_MENU) pueden no tener los iconos en absoluto. Si no están definidos por el usuario de la librería, el programa sale inmediatamente del método devolviendo true porque eso no se considera como un error (simplemente el icono no es necesario). Si para los elementos del menú con el tipo “casilla de verificación” o “elemento de radio” no han sido determinadas sus propias imágenes, entonces se determinan las que han sido predefinidas. Las imágenes que se utilizan en el código de la librería se adjuntan al artículo.

Código del método CMenuItem::CreateIcon():

//+------------------------------------------------------------------+
//| Crea el icono del elemento                                             |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\CheckBoxOn_min_gray.bmp"
#resource "\\Images\\Controls\\CheckBoxOn_min_white.bmp"
//---
bool CMenuItem::CreateIcon(void)
  {
//--- Si es un elemento simple o elemento que contiene un menú contextual
   if(m_type_menu_item==MI_SIMPLE || m_type_menu_item==MI_HAS_CONTEXT_MENU)
     {
      //--- Si el icono no es necesario (imagen no determinada), salimos
      if(m_icon_file_on=="" || m_icon_file_off=="")
         return(true);
     }
//--- Si es una casilla de verificación
   else if(m_type_menu_item==MI_CHECKBOX)
     {
      //--- Si la imagen no está definida, establecer por defecto
      if(m_icon_file_on=="")
         m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp";
      if(m_icon_file_off=="")
         m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp";
     }
//--- Si es un elemento de radio    
   else if(m_type_menu_item==MI_RADIOBUTTON)
     {
      //--- Si la imagen no está definida, establecer por defecto
      if(m_icon_file_on=="")
         m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp";
      if(m_icon_file_off=="")
         m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp";
     }
//--- Formación del nombre del objeto
   string name=CElement::ProgramName()+"_menuitem_icon_"+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Coordenadas del objeto
   int x =m_x+7;
   int y =m_y+4;
//--- Establecemos el icono
   if(!m_icon.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Establecemos las propiedades
   m_icon.BmpFileOn("::"+m_icon_file_on);
   m_icon.BmpFileOff("::"+m_icon_file_off);
   m_icon.State(m_item_state);
   m_icon.Corner(m_corner);
   m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_icon.Selectable(false);
   m_icon.Z_Order(m_zorder);
   m_icon.Tooltip("\n");
//--- Sangrías desde el punto extremo
   m_icon.XGap(x-m_wnd.X());
   m_icon.YGap(y-m_wnd.Y());
//--- Guardamos el puntero del objeto
   CElement::AddToArray(m_icon);
   return(true);
  }

El método para ocultar un control CMenuItem::Hide() ya ha sido mostrado más arriba, ahora implementaremos el método CMenuItem::Show() que va a hacer que el control sea visible. Para los elementos que son casillas de verificación o de radio, en este método hay que tomar en cuenta su estado (habilitado/deshabilitado):

//+------------------------------------------------------------------+
//| Hace que el elemento del menú sea visible                                        |
//+------------------------------------------------------------------+
void CMenuItem::Show(void)
  {
//--- Salir si el control ya es visible
   if(CElement::m_is_visible)
      return;
//--- Hacer que todos los objetos sean visibles
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_ALL_PERIODS);
//--- Si es una casilla de verificación, tomar en cuenta su estado
   if(m_type_menu_item==MI_CHECKBOX)
      m_icon.Timeframes((m_checkbox_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
//--- Si es un elemento de radio, tomar en cuenta su estado
   else if(m_type_menu_item==MI_RADIOBUTTON)
      m_icon.Timeframes((m_radiobutton_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
//--- Puesta a cero de las variables
   CElement::m_is_visible=true;
   CElement::MouseFocus(false);
  }

Una vez implementados todos los métodos para la creación de un control de la interfaz, se puede probar cómo se coloca en el gráfico. Incluimos la clase CMenuItem en el archivo WndContainer.mqh, para que esté disponible para el uso:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Window.mqh"
#include "MenuItem.mqh"

Para la prueba podemos usar el EA que hemos probado en el artículo anterior. En la clase CProgram creamos una instancia de la clase CMenuItem y el método para la creación del elemento del menú CProgram::CreateMenuItem1(). Las sangrías desde el punto extremos del formulario para cada control creado van a encontrarse a mano, en las macros. Cuando hay muchos controles, así será más cómodo y rápido corregir su posición respecto unos a otros, en vez de ir de una implementación del método a la otra.

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                     |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Ventana
   CWindow           m_window;
   //--- Elemento del menú
   CMenuItem         m_menu_item1;
public:
                     CProgram();
                    ~CProgram();
   //--- Inicialización/deinicialización
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Temporizador
   void              OnTimerEvent(void);
   //---
protected:
   //--- Manejador de eventos del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //---
public:
   //--- Crea el panel de trading
   bool              CreateTradePanel(void);
   //---
private:
 //--- Creación del formulario
   bool              CreateWindow(const string text);
//--- Creación del elemento del menú
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (25)
   bool              CreateMenuItem1(const string item_text);
  };

Para la prueba vamos a crear un elemento del menú con el icono que debe contener un menú contextual para ver cómo es un control en conjunto completo. Las imágenes para el icono las haremos en dos versiones: en color y descolorida. La versión descolorida va a usarse cuando el elemento está bloqueado (no disponible).

Abajo se muestra el código del método CProgram::CreateMenuItem1(). Los recursos (imágenes) incluidos están disponibles para la descarga al final del artículo. Al principio del método, el puntero al formulario al que debe adjuntarse el control se guarda en la clase del control. Luego se calculan las coordenadas, se establecen las propiedades con las que debe crearse el control, y luego se coloca el control “elemento del menú” en el gráfico.

//+------------------------------------------------------------------+
//| Crea “Elemento del menú”                                               |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\bar_chart.bmp"
#resource "\\Images\\Controls\\bar_chart_colorless.bmp"
//---
bool CProgram::CreateMenuItem1(string item_text)
  {
//--- Guardamos el puntero a la ventana
   m_menu_item1.WindowPointer(m_window);
//--- Coordenadas  
   int x=m_window.X()+MENU_ITEM1_GAP_X;
   int y=m_window.Y()+MENU_ITEM1_GAP_Y;
//--- Establecemos las propiedades antes de la creación
   m_menu_item1.XSize(193);
   m_menu_item1.YSize(24);
   m_menu_item1.TypeMenuItem(MI_HAS_CONTEXT_MENU);
   m_menu_item1.IconFileOn("Images\\Controls\\bar_chart.bmp");
   m_menu_item1.IconFileOff("Images\\Controls\\bar_chart_colorless.bmp");
   m_menu_item1.LabelColor(clrWhite);
   m_menu_item1.LabelColorHover(clrWhite);
//--- Creación del elemento del menú
   if(!m_menu_item1.CreateMenuItem(m_chart_id,m_subwin,0,item_text,x,y))
      return(false);
//---
   return(true);
  }

Ahora en el método CProgram::CreateTradePanel() se puede añadir la llamada al método para crear un elemento del menú:

//+------------------------------------------------------------------+
//| Crea el panel de trading                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creación del formulario para los controles
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
//--- Creación de controles:
//    Elemento del menú
   if(!CreateMenuItem1("Menu item"))
      return(false);
 //--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Un lector atento notará y preguntará a sí mismo: ¿Cómo el puntero del control llegará encontrándose en la base? Es cierto, por ahora eso no está implementado, o sea, el proceso de guardar un puntero del control en la base no ha sido realizado todavía. Antes he mencionado que en el artículo se mostrará una particularidad interesante del compilador. Ahora ya está todo listo para demostrarlo. En este momento en la clase CMenuItem no están implementados los métodos que sirven para gestionar el control y procesar sus eventos. Repito, por ahora el puntero no se encuentra en la base. La compilación del archivo con la clase CMenuItem no ha revelado ningún error. Pero intente compilar el archivo del EA y recibirá el mensaje sobre un error donde van a figurar los métodos para los que se requiere su implementación (véase la captura de pantala de abajo).


Fig. 4. Mensajes sobre la falta de implementación de los métodos

Durante la compilación del archivo con la clase CMenuItem no ha surgido ningún error porque en ninguna parte de la clase en la fase actual de su desarrollo no se realiza la llamada de los métodos sin implementación. Ahora en la base de controles hay sólo un puntero al formulario. A base de lógica parece que la llamada a los métodos que vemos en la captura de pantalla de arriba se realiza sólo para el formulario (CWindow) en la clase CWndEvents en los ciclos de los métodos CheckElementsEvents(), MovingWindow(), CheckElementsEventsTimer() y Destroy(). Pero los punteros de controles se guardan en el array tipo CElement, o sea la llamada se realiza a través de la clase base CElement, donde estos métodos han sido declarados como virtuales.

Puesto que a la base (al archivo WndContainer.mqh) ahora están conectados dos archivos con los controles (Window.mqh y MenuItem.mqh), cuyas clases son derivadas de la clase CElement, el compilador determina todas las clases derivadas y exige la implementación de los métodos llamados incluso si no hay llamada directa para uno de ellos. Por eso a continuación, implementamos los métodos necesarios en la clase CMenuItem, y en la clase CWndContainer creamos el método que permite añadir los punteros de controles a la base.

En esta fase ya se puede añadir el método CMenuItem::ChangeObjectsColor() para el cambio del color de objetos del control al apuntar con el cursor. En este método, para el control “elemento del menú” se toma en cuenta el tipo del elemento y su estado (disponible/bloqueado). Al principio hay que comprobar si este elemento contiene un menú contextual, y si es así, comprobar si está activado o no. Es que, cuando el menú contextual está activado, la gestión se pasa a este menú contextual activado y hay que fijar el color del elemento del que ha sido llamado.

Además, vamos a necesitar el método para resetear el color del foco en el elemento del menú, por eso crearemos el método CMenuItem::ResetColors().

class CMenuItem : public CElement
  {
public:
   //--- Cambio del color de los objetos del control
   void              ChangeObjectsColor(void);
   //--- Resetear color
   void              ResetColors(void);
   //---
  };
//+------------------------------------------------------------------+
//| Cambio del color del objeto al situar el cursor sobre él                    |
//+------------------------------------------------------------------+
void CMenuItem::ChangeObjectsColor(void)
  {
//--- Salir si este elemento tiene menú contextual y está habilitado
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU && m_context_menu_state)
      return;
//--- Bloque del código para los elementos simples o elementos que contienen un menú contextual
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU || m_type_menu_item==MI_SIMPLE)
     {
      //--- Si hay foco
      if(CElement::MouseFocus())
        {
         m_icon.State(m_item_state);
         m_area.BackColor((m_item_state)? m_area_color_hover : m_area_color_off);
         m_label.Color((m_item_state)? m_label_color_hover : m_label_color_off);
         if(m_item_state)
            m_arrow.State(true);
        }
      //--- Si no hay foco
      else
        {
         m_arrow.State(false);
         m_area.BackColor(m_area_color);
         m_label.Color((m_item_state)? m_label_color : m_label_color_off);
        }
     }
//--- Bloque del código para las casillas de verificación y elementos de radio
   else if(m_type_menu_item==MI_CHECKBOX || m_type_menu_item==MI_RADIOBUTTON)
     {
      m_icon.State(CElement::MouseFocus());
      m_area.BackColor((CElement::MouseFocus())? m_area_color_hover : m_area_color);
      m_label.Color((CElement::MouseFocus())? m_label_color_hover : m_label_color);
     }
  }
//+------------------------------------------------------------------+
//| Resetear colores del elemento                                               |
//+------------------------------------------------------------------+
void CMenuItem::ResetColors(void)
  {
   CElement::MouseFocus(false);
   m_area.BackColor(m_area_color);
   m_label.Color(m_label_color);
  }

Los método para mover y eliminar los objetos están implementados de la misma manera como ha sido hecho en la clase CWindow, por eso no tiene sentido repetirlos aquí. Su código está disponible en los archivos adjuntos y Usted ya puede utilizarlo. Por eso, por favor, añada el código mínimo necesario (véase el listado del código a continuación) en los métodos CMenuItem::OnEvent() y CMenuItem::OnEventTimer(), compile los archivos de la librería y EA. Ahora los errores sobre la necesidad de implementación de los métodos no aparecen.

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Si el control no está ocultado
      if(!CElement::m_is_visible)
         return;
      //--- Definimos el foco
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      return;
     }  
  }
//+------------------------------------------------------------------+
//| Temporizador                                                           |
//+------------------------------------------------------------------+
void CMenuItem::OnEventTimer(void)
  {
//--- Cambio del color de los objetos del formulario
   ChangeObjectsColor();
  }

 

Prueba de colocación de un elemento del menú

Después de cargar el EA en el gráfico, verá el formulario con el control “elemento del menú”, como en la captura de pantalla de abajo. Por defecto, el color del fondo del control coincide con el color del fondo del formulario, por eso aquí no se ve. Pero Usted ya puede utilizar la funcionalidad de la clase para cambiar muchas propiedades de los objetos tanto del formulario, como del control “elemento del menú”, si le surge esta necesidad.

Fig. 5. Prueba de colocación del control “elemento del menú” en el gráfico.

Fig. 5. Prueba de colocación del control “elemento del menú” en el gráfico

El control se coloca en el gráfico, pero si intentamos mover el formulario, este control se queda inmóvil, y al situar el cursor sobre él, su apariencia no cambia. Por eso ahora vamos a crear el método para añadir los punteros de los controles a la base. Pues, precisamente así se puede controlar las propiedades de todos los controles dirigiéndose a cada uno de ellos en un ciclo.

Vamos a llamar este método CWndContainer::AddToElementsArray(). Va a tener dos parámetros: (1) el número del índice del formulario al que se adjunta el control y (2) el objeto del control cuyo puntero hay que guardar en la base. Al principio va a comprobarse si en la base hay un formulario (formularios). Si no hay ninguno, en el registro se mostrará un mensaje diciendo que antes del intento de adjuntar un control al formulario, primero hay que añadirlo a la base. Luego, se realiza la prueba de la superación del tamaño del array.

Si no ha surgido ningún problema, (1) el puntero se añade al array del formulario al que se adjunta, (2) los objetos del control se añaden al array común de objetos, (3) en todos los formularios se guarda el último identificador de este control y (4) el contador de controles se aumenta a un 1. No es la versión definitiva de este método, ya volveremos a él más tarde. A continuación, se muestra el código de la versión actual:

//+------------------------------------------------------------------+
//| Añade el puntero al array de controles                           | 
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Si en la base no hay formularios para los controles
   if(ArraySize(m_windows)<1)
     {
      Print(__FUNCTION__," > Antes de crear un control, hay que crear el formulario "
            " y añadirlo a la base usando el método CWndContainer::AddWindow(CWindow &object).");
      return;
     }
//--- Si se solicita el formulario no existente
   if(window_index>=ArraySize(m_windows))
     {
      Print(PREVENTING_OUT_OF_RANGE," window_index: ",window_index,"; ArraySize(m_windows): ",ArraySize(m_windows));
      return;
     }
//--- Añadimos al array común de controles
   int size=ArraySize(m_wnd[window_index].m_elements);
   ArrayResize(m_wnd[window_index].m_elements,size+1);
   m_wnd[window_index].m_elements[size]=GetPointer(object);
 //--- Añadir los objetos del control al array común de objetos
   AddToObjectsArray(window_index,object);
//--- Recordamos id del último control en todos los formularios
   int windows_total=ArraySize(m_windows);
   for(int w=0; w<windows_total; w++)
      m_windows[w].LastId(m_counter_element_id);
//--- Aumentamos el contador de identificadores de controles
   m_counter_element_id++;
  }

La versión actual del método CWndContainer::AddToElementsArray() ya es suficiente para probar el EA con el que hemos empezado a trabajar más arriba. Al final del método CProgram::CreateMenuItem(), tras la creación del control, hay que añadir la línea del código como se muestra en la versión reducida del método de abajo.

//+------------------------------------------------------------------+
//| Crea “Elemento del menú”                                               |
//+------------------------------------------------------------------+
bool CProgram::CreateMenuItem(string item_text)
  {
//--- Guardamos el puntero a la ventana
//--- Coordenadas  
//--- Establecemos las propiedades antes de la creación
//--- Creación del elemento del menú
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_menu_item);
   return(true);
  }

Si ahora compilamos los archivos que han sufrido modificaciones y cargamos el EA en el gráfico, al mover el formulario, el control “elemento del menú” va a moverse junto con él y sus objetos van a cambiar su apariencia cuando el cursor se sitúa encima:

Fig. 6. Prueba del control “elemento del menú” como parte de la interfaz gráfica

Fig. 6. Prueba del control “elemento del menú” como parte de la interfaz gráfica

 

Mejorando las clases principales de la librería

Pero si intentamos ahora minimizar le formulario, el control “elemento del menú” no se oculta, como se espera. ¿De qué manera se puede implementar la ocultación de los controles adjuntados al formulario? Ahora la gestión de los eventos del gráfico está organizada en la clase CWndEvents que tiene el acceso a la base de todos los controles de su clase base CWndContainer. ¿Pero cómo entender que ha sido pulsado el botón de minimizar o maximizar el formulario? Para estas situaciones el lenguaje MQL ofrece la función EventChartCustom() que permite generar los eventos personalizados.

Ahora al pulsar el botón para minimizar el formulario, el programa seguirá el evento CHARTEVENT_OBJECT_CLICK en el manejador OnEvent() de la clase CWindow, y después de comparar el valor del parámetro string (sparam) de este evento con el nombre del objeto gráfico, llamará al método CWindow::RollUp(). Precisamente en este momento se puede enviar su mensaje a la cola del flujo de eventos, recibirlo en el manejador de la clase CWndEvents y, como ahí hay acceso a todos los controles, llamar en el ciclo los métodos CElement::Hide() de cada uno de ellos. Lo mismo hay que hacer para el evento de maximizar el formulario, haciendo que todos sus controles sean visibles usando sus métodos CElement::Show().

Cada evento personalizado necesita su identificador único. Añadimos los identificadores para minimizar y maximizar el formulario al archivo Defines.mqh:

//--- Eventos
#define ON_WINDOW_UNROLL          (1) // Maximizar el formulario
#define ON_WINDOW_ROLLUP          (2) // Minimizar el formulario

Al final de los métodos CWindow::RollUp() y CWindow::Unroll() hay que llamar a la función EventChartCustom(), pasándole lo siguiente:

  1. Identificador del gráfico.
  2. Identificador del evento personalizado.
  3. Como el tercer parámetro (lparam) enviamos el identificador del control.
  4. El cuarto parámetro (dparam) será el número de la subventana del gráfico en la que se encuentra el programa.

El tercer y el cuarto parámetro son necesarios para una comprobación adicional si este evento llega de algún otro control u otro programa.

Abajo se muestran las versiones reducidas de los métodos CWindow::RollUp() y CWindow::Unroll() que contienen sólo lo que hay que añadir a ellos (se dejan los comentarios):

//+------------------------------------------------------------------+
//| Minimiza la ventana                                                 |
//+------------------------------------------------------------------+
void CWindow::RollUp(void)
  {
//--- Reemplazar el botón
//--- Establecer y recordar los tamaños
//--- Deshabilitar el botón
//--- Estado del formulario “minimizado”
//--- Si es un indicador en la subventana con el alto fijo y el modo de reducción de la subventana,
//    establecemos el tamaño de la subventana del indicador
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_WINDOW_ROLLUP,CElement::Id(),m_subwin,"");
  }
//+------------------------------------------------------------------+
//| Maximiza la ventana                                               |
//+------------------------------------------------------------------+
void CWindow::Unroll(void)
  {
//--- Reemplazar el botón
//--- Establecer y recordar los tamaños
//--- Deshabilitar el botón
//--- Estado del formulario “maximizado”
//--- Si es un indicador en la subventana con el alto fijo y el modo de reducción de la subventana,
//    establecemos el tamaño de la subventana del indicador
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_WINDOW_UNROLL,CElement::Id(),m_subwin,"");
  }

Ahora, en la clase CWndEvents hay que crear los métodos que van a procesar estos eventos personalizados. Vamos a llamarlos igual que a las macros para los identificadores. Abajo se muestra el código de la declaración e implementación de los métodos en la clase CWndEvents:

class CWndEvents : public CWndContainer
  {
private:
   //--- Minimizar/maximizar el formulario
   bool              OnWindowRollUp(void);
   bool              OnWindowUnroll(void);
   //---
  };
//+------------------------------------------------------------------+
//| Evento ON_WINDOW_ROLLUP                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowRollUp(void)
  {
//--- Si hay la señal para minimizar el formulario
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_ROLLUP)
      return(false);
//--- Si el identificador de la ventana y el número de la subventana coinciden
   if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin)
     {
      int elements_total=CWndContainer::ElementsTotal(0);
      for(int e=0; e<elements_total; e++)
        {
         //--- Ocultar todos los controles excepto el formulario
         if(m_wnd[0].m_elements[e].ClassName()!="CWindow")
            m_wnd[0].m_elements[e].Hide();
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Evento ON_WINDOW_UNROLL                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowUnroll(void)
  {
//--- Si hay la señal para maximizar el formulario
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL)
      return(false);
//--- Si el identificador de la ventana y el número de la subventana coinciden
   if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin)
     {
      int elements_total=CWndContainer::ElementsTotal(0);
      for(int e=0; e<elements_total; e++)
        {
         //--- Mostrar todos los controles excepto el formulario y
         //    los que son desplegables
         if(m_wnd[0].m_elements[e].ClassName()!="CWindow")
            if(!m_wnd[0].m_elements[e].IsDropdown())
               m_wnd[0].m_elements[e].Show();
        }
     }
//---
   return(true);
  }

Para este momento, en la clase del formulario no ha sido implementado el método CWindow::Show(). Puesto que este método va a llamarse en el ciclo en todos los controles, su implementación es obligatoria incluso si la condición (como se puede ver en el código de arriba) no deja pasar al programa a la clase con el nombre CWindow.

Código del método CWindow::Show():

//+------------------------------------------------------------------+
//| Muestra la ventana                                                  |
//+------------------------------------------------------------------+
void CWindow::Show(void)
  {
//--- Hacer que todos los objetos sean visibles
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_ALL_PERIODS);
//--- Estado de visibilidad
   CElement::m_is_visible=true;
//--- Puesta a cero del foco
   CElement::MouseFocus(false);
   m_button_close.MouseFocus(false);
   m_button_close.State(false);
  }

Vamos a llamar a estos métodos en el método CWndEvents::ChartEventCustom():

//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                        |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Si hay la señal para minimizar el formulario
   if(OnWindowRollUp())
      return;
//--- Si hay la señal para maximizar el formulario
   if(OnWindowUnroll())
      return;
  }

En el futuro, todos los métodos que van a procesar los eventos personalizados, van a ubicarse en el método CWndEvents::ChartEventCustom().

En su lugar, hay que ubicar la llamada a CWndEvents::ChartEventCustom() en el método CWndEvents::ChartEvent(), antes de los métodos donde se procesan los eventos del gráfico:

//+------------------------------------------------------------------+
//| Manejo de eventos del programa                                      |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Si el array está vacío, salimos
   if(CWndContainer::WindowsTotal()<1)
      return;
 //--- Inicialización de los campos de los parámetros de eventos
   InitChartEventsParams(id,lparam,dparam,sparam);
//--- Eventos personalizados
   ChartEventCustom();
//| Comprobación de los eventos de los controles de la interfaz
   CheckElementsEvents();
//--- Evento de desplazamiento del ratón
   ChartEventMouseMove();
//--- Evento del cambio de propiedades del gráfico
   ChartEventChartChange();
  }

Después de compilar los archivos modificados y el archivo principal, cargue el programa en el gráfico. Ahora al minimizar el formulario, el control “elemento del menú” va a ocultarse, y al maximizar, será visible.

Se puede considerar el desarrollo de la parte principal de la clase CMenuItem por terminado. Sólo queda configurar el manejador de eventos de este control. Volveremos a ello después de implementar la clase para la creación del menú contextual para poder testear todos los cambios introducidos de manera consecuente y eficaz. Pero antes de eso nos ocuparemos del desarrollo de otro control que forma parte del menú contextual: se trata de la línea separadora.

 

Conclusión

En este capítulo hemos considerado al detalle el proceso de creación del control “Elemento del menú”, así como hemos introducido las adiciones necesarias en la clase del formulario para los controles (CWindow) y la clase principal del procesamiento de eventos (CWndEvents). En el siguiente artículo nos ocuparemos de la creación de la línea separadora y menú contextual.

Más abajo se puede descargar los archivos comprimidos con los ficheros de la librería en esta fase del desarrollo, imágenes y ficheros de los programas examinados en el artículo (EA, indicadores y script) para realizar las pruebas en los terminales MetaTrader 4 y MetaTrader 5. 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 segunda parte: