Descargar MetaTrader 5

Interfaces gráficas II: Configuración de los manejadores de eventos de la librería (Capítulo 3)

14 marzo 2016, 09:11
Anatoli Kazharski
0
770

Í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 biblioteca (Capítulo 1). Al final de cada artículo de la serie se muestra la lista 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 los artículos anteriores de la segunda serie sobre las interfaces gráficas hemos implementado las clases para la creación de todas las partes integrantes del menú principal. Pero antes de empezar a desarrollar la clase de cada control, hay que configurar con más precisión los manejadores de eventos en las clases base principales y en las clases de los controles creados. En este artículo trataremos las siguientes cuestiones:

  • Archivos personales para cada control significante.
  • Adición a la base de los punteros de controles de los que, en su lugar, se componen los controles complejos (compuestos).
  • Gestión del estado del gráfico dependiendo de la posición del cursor del ratón.
  • Identificadores de los eventos de la librería para el uso interno y externo.

Además, enseñaremos cómo recibir los mensajes en el manejador de la clase personalizada de la aplicación. 

 


Arrays personales de controles

Vamos a realizar un experimento. Pulsamos el botón izquierdo sobre un elemento del menú contextual en aquella área donde el cursor se encuentra fuera de la zona del formulario. Veremos que el deslizamiento del gráfico no está desactivado y se puede ejecutarlo encontrándose sobre el control. Es un error en la funcionalidad, y eso no puede ser así. Por eso haremos que el scrolling del gráfico y los modos del deslizamiento de los niveles comerciales estén desactivados independientemente del control sobre el que se encuentra el cursor del ratón. 

En primer lugar, añadimos el seguimiento del foco sobre el control al manejador del menú contextual (véase el código). Si el menú contextual se encuentra ocultado, no tiene sentido seguir adelante. Hay que seguir esta regla siempre para ahorrar el tiempo.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el control está ocultado
      if(!CElement::m_is_visible)
         return;
      //--- Obtenemos el foco
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
     }
  }

En esta fase del desarrollo de la librería, en la base de controles (para ser más exacto, en la clase CWndContainer) hay un array común con punteros de los controles m_elements[]. Forma parte de la estructura de los arrays de controles WindowElements. Es conveniente usar este array en los casos cuando es necesario aplicar una acción a todos los controles, o por lo menos a la mayoría de ellos. Si hace falta aplicar una acción a un determinado grupo de controles, esta técnica no resulta ser apropiada ya que requiere demasiados recursos. Por ejemplo, hay un grupo de controles cuya interacción provoca que sus tamaños salgan fuera del área del formulario al que están adjuntados. A este grupo le pertenecen diferentes listas desplegables, inclusive los menús contextuales. Cada tipo de estos controles tiene que almacenarse en unos arrays separados. En este caso, su manejo será más cómodo y eficaz. 

Añadimos el array para los menús contextuales a la estructura WindowElements, y creamos el método para obtener su tamaño:

//+------------------------------------------------------------------+
//| Clase para almacenar todos los objetos de la interfaz            |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Estructura de los arrays de controles
   struct WindowElements
     {
      //--- Array común de todos los objetos
      CChartObject     *m_objects[];
      //--- Array común de todos los controles
      CElement         *m_elements[];
      
      //--- Arrays personales de controles:
      //    Array de los menús contextuales
      CContextMenu     *m_context_menus[];
     };
   //--- Array de los arrays de los controles para cada ventana
   WindowElements    m_wnd[];
   //---
public:
   //--- Número de menús contextuales
   int               ContextMenusTotal(const int window_index);
   //---
  };
//+----------------------------------------------------------------------------------------+
//| Devuelve el número de menús contextuales según el índice especificado de la ventana    |
//+----------------------------------------------------------------------------------------+
int CWndContainer::ContextMenusTotal(const int window_index)
  {
   if(window_index>=::ArraySize(m_wnd))
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return(::ArraySize(m_wnd[window_index].m_context_menus));
  }

Cada vez después de la creación de un control en la clase personalizada de la aplicación (en nuestro caso es CProgram),   usamos el método CWndContainer::AddToElementsArray() para añadir el puntero a este control a la base. En este método para cada control complejo (compuesto) se utilizan los métodos que sirven para obtener y guardar los punteros a ellos en el array común. Para el menú contextual ya hemos creado un método parecido: CWndContainer::AddContextMenuElements(). En todos los métodos semejantes se puede hacer la distribución de los punteros en los arrays personales del control, si eso será necesario.

Luego vamos a necesitar un método de plantilla para añadir el puntero al control al array pasado por referencia porque esta acción va a ejecutarse más de una vez y con diferentes tipos de objetos.

class CWndContainer
  {
protected:
   //--- Método de plantilla para añadir los punteros al array pasado por referencia
   template<typename T1,typename T2>
   void              AddToRefArray(T1 &object,T2 &ref_array[]);
   //---
  };
//+------------------------------------------------------------------+
//| Guarda el puntero (T1) en el array (T2) pasado por referencia    |
//+------------------------------------------------------------------+
template<typename T1,typename T2>
void CWndContainer::AddToRefArray(T1 &object,T2 &array[])
  {
   int size=::ArraySize(array);
   ::ArrayResize(array,size+1);
   array[size]=object;
  }

Ahora al final del método CWndContainer::AddContextMenuElements(), se puede guardar el puntero al menú contextual en su array personal como se muestra a continuación (marcado con amarillo). Lo mismo hacemos para los demás controles.

//+------------------------------------------------------------------+
//| Guarda los punteros a los objetos del menú contextual en la base |
//+------------------------------------------------------------------+
bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object)
  {
//--- Salimos si no es menú contextual
   if(object.ClassName()!="CContextMenu")
      return(false);
//--- Obtenemos el puntero al menú contextual
   CContextMenu *cm=::GetPointer(object);
//--- Guardamos los punteros a sus objetos en la base
   int items_total=cm.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
       //--- Aumentar el array de controles
      int size=::ArraySize(m_wnd[window_index].m_elements);
      ::ArrayResize(m_wnd[window_index].m_elements,size+1);
      //--- Obtener el puntero al elemento del menú
      CMenuItem *mi=cm.ItemPointerByIndex(i);
      //--- Guardamos el puntero en el array
      m_wnd[window_index].m_elements[size]=mi;
       //--- Añadimos los punteros a todos los objetos del elemento del menú al array común
      AddToObjectsArray(window_index,mi);
     }
//--- Añadimos el puntero al array personal
   AddToRefArray(cm,m_wnd[window_index].m_context_menus);
   return(true);
  }

 


Gestión del estado del gráfico

Luego, en la clase CWndEvents hay que añadir el método en el que va a comprobarse el foco del cursor del ratón sobre uno u otro control. Vamos a comprobar los formularios y las listas desplegables. Tanto los formularios como los menús contextuales ya tienen los arrays personales, por eso vamos a crear el método CWndEvents::SetChartState(). Abajo se muestra la declaración e implementación de este método:

class CWndEvents : public CWndContainer
  {
private:
  //--- Establecemos el estado del gráfico
   void              SetChartState(void);
  };
//+------------------------------------------------------------------+
//| Establece el estado del gráfico                                  |
//+------------------------------------------------------------------+
void CWndEvents::SetChartState(void)
  {
//--- Para determinar el estado cuando hay que desactivar la gestión
   bool condition=false;
//--- Comprobamos las ventanas
   int windows_total=CWndContainer::WindowsTotal();
   for(int i=0; i<windows_total; i++)
     {
      //--- Ir al siguiente si este formulario está ocultado
      if(!m_windows[i].IsVisible())
         continue;
      //--- Comprobar condiciones en el manejador interno del formulario
      m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam);
      //--- Si hay foco, lo anotamos
      if(m_windows[i].MouseFocus())
        {
         condition=true;
         break;
        }
     }
//--- Comprobamos el foco de los menús contextuales
   if(!condition)
     {
      int context_menus_total=CWndContainer::ContextMenusTotal(0);
      for(int i=0; i<context_menus_total; i++)
        {
         if(m_wnd[0].m_context_menus[i].MouseFocus())
           {
            condition=true;
            break;
           }
        }
     }
//---
   if(condition)
     {
      //--- Desactivamos el scroling y la gestión de niveles comerciales
      m_chart.MouseScroll(false);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false);
     }
   else
     {
      //--- Activamos la gestión
      m_chart.MouseScroll(true);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true);
     }
  }

En este método serán introducidas algunas adiciones en el futuro, pero ya está listo para ejercer la tarea actual. Hay que llamarlo en el método CWndEvents::ChartEventMouseMove(), como se muestra a continuación:

//+------------------------------------------------------------------+
//| Evento CHARTEVENT MOUSE MOVE                                     |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventMouseMove(void)
  {
//--- Salir si no es un evento de desplazamiento del cursor
   if(m_id!=CHARTEVENT_MOUSE_MOVE)
      return;
//--- Desplazamiento de la ventana
   MovingWindow();
//--- Establecer el estado del gráfico
   SetChartState();
//--- Redibujamos el gráfico
   m_chart.Redraw();
  }

Ahora compilamos todos los archivos y probamos el Asesor Experto (EA). Podemos ver que cuando hacemos clic izquierdo en el área del menú contextual que sale fuera de la zona del formulario, los modos del scrolling del gráfico y control de los niveles comerciales están deshabilitados. La prueba de colocación del control sobre el gráfico ha pasado con éxito, y en el futuro el menú contextual va a abrirse sólo por el orden del usuario. Por eso, elimine su visualización desde el método CProgram::CreateTradePanel() en la clase de la aplicación (véase el código de abajo).

   m_contextmenu.Show(); // <<< Hay que eliminar esta línea del código

 


Identificadores para el uso externo e interno

Ahora nos ocuparemos del procesamiento del clic izquierdo sobre un elemento del menú.

Nuestra siguiente tarea consiste en hacer que se abra el menú contextual cuando hacemos clic izquierdo en un elemento del menú (si incluye este menú contextual). Al volver a pulsar, este menú tiene que ocultarse. Este procesamiento va a tener lugar en la clase del elemento del menú CMenuItem, y en la clase del menú contextual CContextMenu. Es que el menú contextual tiene el acceso al elemento (nodo anterior) al que está vinculado, mientras que el elemento del menú que contiene el menú contextual no tiene el acceso directo a él. En la clase CMenuItem no se puede crear el puntero al menú contextual, porque si ahora incluimos el archivo ContextMenu.mqh en el archivo MenuItem.mqh, no podremos evitar los errores durante la compilación. Por eso en la clase CContextMenu vamos a ejecutar el procesamiento de la visualización del menú contextual. El manejador de la clase CMenuItem va a ser auxiliar. Va a generar un evento personalizado enviando al menú contextual los datos que detallan en qué elemento exactamente ha sido hecho el clic. Además, tenemos que hacer que el menú contextual se oculte cuando se pulsa fuera de su área, tal como se hace por ejemplo en los terminales MetaTrader o el editor del código MetaEditor. Bueno, en general, es un comportamiento estándar para los menús contextuales. 

Para implementar esta funcionalidad, vamos a necesitar los identificadores adicionales para los eventos personalizados. Algunos de ellos estarán destinados para el uso interno en las clases de la librería, otros servirán para el procesamiento externo en la clase de la aplicación personalizada (en nuestro caso es CProgram). 

Eventos para el uso interno:

  • ON_CLICK_MENU_ITEM  — clic en un elemento del menú.
  • ON_HIDE_CONTEXTMENUS — señal para ocultar todos los menús contextuales.
  • ON_HIDE_BACK_CONTEXTMENUS — señal para ocultar los menús contextuales a partir del elemento actual. Sobre este evento hablaremos un poco más tarde.

Para el uso externo vamos a crear un identificador que avisa al programa que se ha hecho un clic en el elemento del menú contextual: ON_CLICK_CONTEXTMENU_ITEM.

Colocamos los identificadores mencionados en el archivo Defines.mqh indicando el número único para cada uno de ellos:

#define ON_CLICK_MENU_ITEM        (4) // Clic en un elemento del menú
#define ON_CLICK_CONTEXTMENU_ITEM (5) // Clic en un elemento del menú contextual
#define ON_HIDE_CONTEXTMENUS      (6) // Ocultar todos los menús contextuales
#define ON_HIDE_BACK_CONTEXTMENUS (7) // Ocultar los menús contextuales a partir del elemento actual del menú

 


Mejorando la clase del menú contextual

En la clase del menú contextual CContextMenu hay que añadir los siguientes campos y métodos: 

  • Para establecer y obtener el estado del menú contextual.
  • Para procesar el evento del clic en un elemento del menú.
  • Para obtener el identificador e índice desde el nombre del elemento del menú. Para este momento ya sabemos que precisamente para eso los nombres de los objetos que componen algún control contienen un índice e identificador. 

El código de abajo muestra las declaraciones e implementación de lo mencionado en la lista, con comentarios detallados:

class CContextMenu : public CElement
  {
private:
   //--- Estado del menú contextual
   bool              m_contextmenu_state;
public:   
   //--- (1) Obtener y (2) determinar el estado del menú contextual
   bool              ContextMenuState(void)                   const { return(m_context_menu_state);         }
   void              ContextMenuState(const bool flag)              { m_context_menu_state=flag;            }
   //---
private:
   //--- Procesamiento del clic en el elemento al que este menú contextual está vinculado
   bool              OnClickMenuItem(const string clicked_object);
   //--- Obtener el (1) identificador e (2) índice desde el nombre del elemento del menú
   int               IdFromObjectName(const string object_name);
   int               IndexFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Procesamiento del clic en un elemento del menú                   |
//+------------------------------------------------------------------+
bool CContextMenu::OnClickMenuItem(const string clicked_object)
  {
//--- Salimos si el menú contextual ya está abierto
   if(m_contextmenu_state)
      return(true);
//--- Salimos si el clic ha sido hecho fuera del elemento del menú
   if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0)
      return(false);
//--- Obtenemos el identificador e índice desde el nombre del objeto
   int id    =IdFromObjectName(clicked_object);
   int index =IndexFromObjectName(clicked_object);
//--- Salimos si el clic no ha sido hecho en el elemento al que este menú contextual está vinculado
   if(id!=m_prev_node.Id() || index!=m_prev_node.Index())
      return(false);
//--- Mostrar el menú contextual
   Show();
   return(true);
  }
//+------------------------------------------------------------------+
//| Extrae el identificador desde el nombre del objeto               |
//+------------------------------------------------------------------+
int CContextMenu::IdFromObjectName(const string object_name)
  {
//--- Obtenemos id desde el nombre del objeto
   int    length =::StringLen(object_name);
   int    pos    =::StringFind(object_name,"__",0);
   string id     =::StringSubstr(object_name,pos+2,length-1);
//---
   return((int)id);
  }
//+------------------------------------------------------------------+
//| Extrae el índice desde el nombre del objeto                      |
//+------------------------------------------------------------------+
int CContextMenu::IndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtenemos el código del separador
   u_sep=::StringGetCharacter("_",0);
//--- Dividimos la línea
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Comprobar la superación del rango del array
   if(array_size-2<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return((int)result[array_size-2]);
  }

Ahora al manejador de eventos del menú contextual CContextMenu::OnEvent() será suficiente añadir la llamada al método CContextMenu::OnClickMenuItem() cuando ocurre el evento CHARTEVENT_OBJECT_CLICK:

//--- Procesamiento del evento del clic izquierdo en el objeto
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickMenuItem(sparam))
         return;
     }

 


Mejorando la clase del elemento del menú

En total, una vez detectado el clic izquierdo en el elemento del menú, el programa pasará el parámetro string en el método CContextMenu::OnClickMenuItem(). El parámetro string contiene el nombre del objeto gráfico pulsado “etiqueta rectangular” que representa el fondo del elemento del menú. No olvidemos que la prioridad por el clic para todos los controles siempre será más alta para el fondo que para los demás objetos del control. Eso garantiza que el clic no será “interceptado” por algún otro objeto del control, lo que en su lugar puede provocar el comportamiento inesperado del programa. Por ejemplo, si el icono del elemento del menú tendrá la prioridad más alta que su fondo, el clic en el área del icono puede provocar el cambio de su imagen (recordemos que las imágenes del icono están determinadas para dos estados). Se debe al hecho de que este comportamiento ha sido establecido por defecto para todos los objetos tipo OBJ_BITMAP_LABEL

Al inicio del método CContextMenu::OnClickMenuItem() se comprueba el estado del menú contextual. Si ya está activado, no tiene sentido seguir adelante. Luego se comprueba el nombre del objeto pulsado. Si es un objeto de nuestro programa y hay indicio de que sea un elemento del menú, seguimos adelante. Luego extraemos el identificador e índice del elemento del menú desde el nombre del objeto. Para estas tareas ya hemos preparado los métodos especiales en los cuales se extraen los parámetros necesarios desde el nombre del objeto usando las funciones string del lenguaje MQL. El identificador del elemento del menú se extrae por el indicio del guión bajo doble. Para la extracción del índice, la cadena se divide en partes por el signo “guión bajo” (_) que representa separador de parámetros del objeto del control.

Luego creamos el método en la clase CMenuItem, pero su código va a diferenciarse del que ha sido desarrollado par el menú contextual. Abajo se muestra la declaración e implementación de este método. Ahí ya no es necesario extraer los parámetros desde el nombre del objeto. Basta con comparar el nombre del fondo con el nombre del objeto pasado. Luego se comprueba el estado actual del elemento: si está bloqueado, las siguientes acciones no se realizan. Después de eso, si el elemento contiene el menú contextual, se establece el estatus del elemento activado o desactivado. Si hasta este momento, el estado del menú contextual ha sido “activado”, al módulo principal del procesamiento de eventos se envía la señal para el cierre de todos los menús contextuales abiertos después. Eso funciona para los casos cuando al mismo tiempo están abiertos varios menús contextuales que se abren unos de otros. Más tarde mostraremos estos ejemplos. Aparte del identificador del evento ON_HIDE_BACK_CONTEXTMENUS, se envía el identificador del elemento del menú que permite determinar en qué menú contextual habrá que detener el ciclo.

class CMenuItem : public CElement
  {
   //--- Procesamiento del clic en un elemento del menú
   bool              OnClickMenuItem(const string clicked_object);
   //---
  };
//+------------------------------------------------------------------+
//| Procesamiento del clic en un elemento del menú                   |
//+------------------------------------------------------------------+
bool CMenuItem::OnClickMenuItem(const string clicked_object)
  {
//--- Comprobación según el nombre del objeto
   if(m_area.Name()!=clicked_object)
      return(false);
//--- Salimos si el elemento no está activado
   if(!m_item_state)
      return(false);
//--- Si este elemento contiene un menú contextual
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU)
     {
      //--- Si el menú desplegable de este elemento no está activado
      if(!m_context_menu_state)
        {
         m_context_menu_state=true;
        }
      else
        {
         m_context_menu_state=false;
         //--- Enviamos la señal para el cierre de los menús contextuales más allá de este elemento
         ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,"");
        }
      return(true);
     }
//--- Si este elemento no contiene un menú contextual pero forma su parte
   else
     {
     }
//---
   return(true);
  }

 

 


Mejorando la clase principal del procesamiento de eventos de la interfaz gráfica

No es una versión final del método CMenuItem::OnClickMenuItem(), ya volveremos a él más tarde para introducir algunas adiciones. En este momento su tarea principal consiste sólo en enviar el mensaje para ocultar el menú contextual al módulo principal del procesamiento de eventos personalizados en la clase CWndEvents. Vamos a entrar en él y crear un método, el acceso al cual va a realizarse según el evento ON_HIDE_BACK_CONTEXTMENUS. Lo llamaremos CWndEvents::OnHideBackContextMenus(). Puede estudiar este método al detalle en el código de abajo:

class CWndEvents : public CWndContainer
  {
private:
   //--- Ocultar todos los menús contextuales a partir del elemento iniciador
   bool              OnHideBackContextMenus(void);
  };
//+------------------------------------------------------------------+
//| Evento ON_HIDE_BACK_CONTEXTMENUS                                 |
//+------------------------------------------------------------------+
bool CWndEvents::OnHideBackContextMenus(void)
  {
//--- Si hay señal para ocultar los menús contextuales desde el elemento iniciador
   if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_BACK_CONTEXTMENUS)
      return(false);
//--- Repasamos todos los menús desde el último abierto
   int context_menus_total=CWndContainer::ContextMenusTotal(0);
   for(int i=context_menus_total-1; i>=0; i--)
     {
      //--- Punteros del menú contextual y su nodo anterior
      CContextMenu *cm=m_wnd[0].m_context_menus[i];
      CMenuItem    *mi=cm.PrevNodePointer();
      //--- Si hemos llegado hasta el elemento iniciador de la señal, entonces...
      if(mi.Id()==m_lparam)
        {
         //--- ...en caso de que su menú contextual no esté enfocado, lo ocultamos
         if(!cm.MouseFocus())
            cm.Hide();
         //--- Finalizamos el ciclo
         break;
        }
      else
        {
         //--- Ocultar el menú contextual
         cm.Hide();
        }
     }
//---
   return(true);
  }

El método CWndEvents::OnHideBackContextMenus() tiene que llamarse dentro del método de procesamiento de los eventos personalizados, como se muestra a acontinuación:

//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                         |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Si llega la señal para minimizar el formulario
   if(OnWindowRollUp())
      return;
//--- Si llega la señal para maximizar el formulario
   if(OnWindowUnroll())
      return;
//--- Si llega la señal para ocultar los menús contextuales desde el elemento iniciador
   if(OnHideBackContextMenus())
      return;
  }

 


Prueba previa de manejadores de eventos

Una vez introducidos todos los cambios, hay que compilar todos los archivos e iniciar el programa en el gráfico para realizar la prueba. Ahora cuando hacemos clic en un elemento independiente del menú en el formulario, su menú contextual va a abrirse si estaba ocultado, y viceversa. Además, cuando el menú contextual está abierto, el color del fondo del elemento del menú en el formulario va a ser fijo: es decir, no va a cambiarse si quitamos el cursor de su área (véase captura de pantalla). 

Fig. 1. Prueba de mostrar y ocultar el menú contextual.

Fig. 1. Prueba de mostrar y ocultar el menú contextual.

 

Vamos a seguir ajustando la interacción del menú contextual con el usuario. En la mayoría de las aplicaciones, cuando tenemos abiertos uno o varios menús contextuales (uno del otro) y hacemos clic fuera de ellos, se cierran en el acto. Nosotros vamos a implementar el mismo comportamiento. 

Para poder probar esta funcionalidad en el futuro, vamos a ampliar la interfaz de nuestro EA con un menú contextual más. Insertamos este menú contextual en el tercer elemento del menú que ya tenemos. En el método de la creación del primer menú contextual CProgram::CreateContextMenu1() en el array items_type[] hay que asignar al tercer elemento el tipo MI_HAS_CONTEXT_MENU:

//--- Arrays de los tipos de los elementos del menú
   ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS]=
     {
      MI_SIMPLE,
      MI_SIMPLE,
      MI_HAS_CONTEXT_MENU,
      MI_CHECKBOX,
      MI_CHECKBOX
     };

Ahora creamos el método para el segundo menú contextual. Por favor, añada a la clase CProgram la segunda instancia de la clase CContextMenu y declare el método CreateContextMenu2():

class CProgram : public CWndEvents
  {
private:
   //--- Elemento del menú y menús contextuales
   CMenuItem         m_menu_item1;
   CContextMenu      m_mi1_contextmenu1;
   CContextMenu      m_mi1_contextmenu2;
   //---
private:
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (25)
   bool              CreateMenuItem1(const string item_text);
   bool              CreateMI1ContextMenu1(void);
   bool              CreateMI1ContextMenu2(void);
  };

El segundo menú contextual va a tener seis elementos. Que sean dos grupos de los elementos de radio (MI_RADIOBUTTON), tres en cada uno. Abajo se muestra el código de este método. ¿Cuál es la diferencia del primer menú contextual? Fíjense como se obtiene el puntero al tercer elemento del primer menú contextual al que es necesario vincular el segundo. Antes para eso ya hemos creado el método especial CContextMenu::ItemPointerByIndex(). Para los elementos de radio vamos a usar las imágenes predefinidas, por eso no necesitamos los arrays para ellas. En este caso, en el método CContextMenu::AddItem() hay que pasar los valores vacíos, en vez de las rutas hacia las imágenes. Necesitamos una línea separadora para dividir visualmente el primer grupo de los elementos de radio del segundo, por eso la colocamos después del tercer (2) elemento en la lista.

Antes ya hemos dicho y hemos mostrado en el esquema que cada grupo de elementos de radio debe tener su identificador único. Por defecto, el valor de este parámetro es igual a 0, por eso asignamos el identificados igual a 1 a cada elemento del segundo grupo (en el ciclo de tres a seis). Para establecer el identificador, en la clase CContextMenu ya existe el método CContextMenu::RadioItemIdByIndex().

Usando el método CContextMenu::SelectedRadioItem(), establecemos qué elementos de radio tienen que estar marcados en cada grupo desde el principio. En el código de abajo, en el primer grupo se marca el segundo (índice 1) elemento de radio, y en el segundo grupo se marca el tercero (índice 2).

//+------------------------------------------------------------------+
//| Crea el menú contextual 2                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateMI1ContextMenu2(void)
  {
//--- Seis elementos en el menú contextual
#define CONTEXTMENU_ITEMS2 6
//--- Guardamos el puntero a la ventana
   m_mi1_contextmenu2.WindowPointer(m_window);
 //--- Guardamos el puntero al nodo anterior
   m_mi1_contextmenu2.PrevNodePointer(m_mi1_contextmenu1.ItemPointerByIndex(2));
 //--- Arrays de los nombres de los elementos del menú
   string items_text[CONTEXTMENU_ITEMS2]=
     {
      "ContextMenu 2 Item 1",
      "ContextMenu 2 Item 2",
      "ContextMenu 2 Item 3",
      "ContextMenu 2 Item 4",
      "ContextMenu 2 Item 5",
      "ContextMenu 2 Item 6"
     };
//--- Establecemos las propiedades antes de la creación
   m_mi1_contextmenu2.XSize(160);
   m_mi1_contextmenu2.ItemYSize(24);
   m_mi1_contextmenu2.AreaBackColor(C'240,240,240');
   m_mi1_contextmenu2.AreaBorderColor(clrSilver);
   m_mi1_contextmenu2.ItemBackColorHover(C'240,240,240');
   m_mi1_contextmenu2.ItemBackColorHoverOff(clrLightGray);
   m_mi1_contextmenu2.ItemBorderColor(C'240,240,240');
   m_mi1_contextmenu2.LabelColor(clrBlack);
   m_mi1_contextmenu2.LabelColorHover(clrWhite);
   m_mi1_contextmenu2.SeparateLineDarkColor(C'160,160,160');
   m_mi1_contextmenu2.SeparateLineLightColor(clrWhite);
//--- Añadimos los elementos en el menú contextual
   for(int i=0; i<CONTEXTMENU_ITEMS2; i++)
      m_mi1_contextmenu2.AddItem(items_text[i],"","",MI_RADIOBUTTON);
//--- Línea separadora tras el tercer elemento
   m_mi1_contextmenu2.AddSeparateLine(2);
//--- Establecemos el identificador único (1) para el segundo grupo
   for(int i=3; i<6; i++)
      m_mi1_contextmenu2.RadioItemIdByIndex(i,1);
//--- Selección de los elementos de radio en ambos grupos
   m_mi1_contextmenu2.SelectedRadioItem(1,0);
   m_mi1_contextmenu2.SelectedRadioItem(2,1);
//--- Crear el menú contextual
   if(!m_mi1_contextmenu2.CreateContextMenu(m_chart_id,m_subwin))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_mi1_contextmenu2);
   return(true);
  }

 

La llamada al método CProgram::CreateContextMenu2() se encuentra dentro del método CProgram::CreateTradePanel(), como para todos los demás.

 

 

Prueba de varios menús contextuales y configuración precisa

Por favor, compile los archivos del EA y cárguelo en el gráfico para ver el resultado:

Fig. 2. Prueba de varios menús contextuales.

Fig. 2. Prueba de varios menús contextuales.

 

Cuando los dos menús contextuales están abiertos (como se muestra en la imagen de arriba), al hacer clic en el elemento que abre el primer menú, ambos menús se cierran. Precisamente este comportamiento ha sido establecido en el método CWndEvents::OnHideBackContextMenus() que hemos analizado anteriormente. Pero si ahora pulsamos en el gráfico o encabezado del formulario, los menús contextuales no se cierran. A continuación, vamos a buscar la solución de este problema.

La posición de cursor (foco) del ratón se determina en el manejador de eventos OnEvent() de la clase del menú contextual (CContextMenu). Por eso nosotros vamos a enviar la señal para el cierre de todos los menús contextuales abiertos al manejador principal de eventos (en la clase CWndEvents) desde allí. El problema se soluciona de la siguiente manera.

1. En el momento de la llegada del evento del desplazamiento del ratón (CHARTEVENT_MOUSE_MOVE), el parámetro string (sparam) contiene el estado del botón izquierdo del ratón.

2. Luego, después de identificar el foco del ratón, hay que comprobar el estado actual del menú contextual y botón izquierdo. Si el menú contextual está activado y el botón ha sido pulsado, vamos a la siguiente comprobación donde se averigua la posición actual del cursor respecto a este menú contextual y al nodo anterior.

3. Si el cursos se encuentra dentro del área de uno de ellos, no hace falta enviar la señal para el cierre de todos los menús contextuales. Si el cursor se encuentra fuera del área de estos controles, hay que comprobar si hay menús contextuales abiertos más tarde.

4. Para eso hay que repasar la lista de este menú contextual en el ciclo para averiguar si contiene un elemento que incluye su menú contextual y si éste está activado en caso de que este elemento haya sido encontrado. Si resulta que está activado, significa que tal vez el cursor se encuentre dentro de su área. Eso a su vez significa que en este caso tampoco hace falta enviar la señal para el cierre de todos los menús contextuales desde este control. Pero si resulta que el menú contextual actual es el último menú abierto, y en todos los que le preceden tampoco se han cumplido las condiciones para el envío de la señal, eso significa con absoluta seguridad que el cursor se encuentra fuera de las áreas de todos los menús contextuales activos.

5. Precisamente aquí se puede generar el evento personalizado ON_HIDE_CONTEXTMENUS.

Como podemos ver, el momento clave consiste en que todos los menús contextuales tienen que cerrarse sólo en el caso cuando el cursor del ratón (si el botón izquierdo está pulsado) se encuentra fuera del área del último menú contextual activado y fuera del área del elemento desde el que ha sido abierto.

El código de abajo permite comprender la lógica descrita. Para eso ha sido escrito el método especial CContextMenu::CheckHideContextMenus().

class CContextMenu : public CElement
  {
private:
   //--- Comprobación de las condiciones para cerrar todos los menús contextuales
   void              CheckHideContextMenus(void);
   //---
  };
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el control está ocultado
      if(!CElement::m_is_visible)
         return;
      //--- Obtenemos el foco
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Si el menú contextual está activado y el botón izquierdo ha sido pulsado
      if(m_context_menu_state && sparam=="1")
        {
         //--- Comprobación de las condiciones para cerrar todos los menús contextuales
         CheckHideContextMenus();
         return;
        }
      //---
      return;
     }
  }
//+--------------------------------------------------------------------------+
//| Comprobación de las condiciones para cerrar todos los menús contextuales |
//+--------------------------------------------------------------------------+
void CContextMenu::CheckHideContextMenus(void)
  {
//--- Salir si el cursor se encuentra dentro del área del menú contextual o en el área del nodo anterior
   if(CElement::MouseFocus() || m_prev_node.MouseFocus())
      return;
//--- Si el cursor se encuentra fuera del área de estos controles, ...
//    ... hay que comprobar si hay menús contextuales abiertos que han sido activados después de éste
//--- Para eso recorremos en el ciclo la lista de este menú contextual ...
//    ... para detectar la presencia del elemento que contiene un menú contextual
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Si este elemento ha sido encontrado, hay que comprobar si su menú contextual está abierto.
      //    Si está abierto, no hace falta enviar la señal para el cierre de todos los menús contextuales desde este control por que ...
      //    ... tal vez el cursor se encuentre en el área del siguiente, y hay que comprobar ahí.
      if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU)
         if(m_items[i].ContextMenuState())
            return;
     }
//--- Enviar la señal para cerrar todos los menús contextuales
   ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
  }

Ahora hay que recibir el evento ON_HIDE_CONTEXTMENUS en el manejador principal de la librería, en la clase CWndEvents. Para eso vamos a escribir un método especial que tendrá el nombre OnHideContextMenus(). Es bastante sencillo ya que por ahora se encarga sólo de repasar en el ciclo el array personalizado de los menús contextuales y cerrarlos. 

En el listado del código de abajo se muestra la declaración e implementación del método CWndEvents::OnHideContextMenus():

class CWndEvents : public CWndContainer
  {
private:
  //--- Cerrar todos los menús contextuales
   bool              OnHideContextMenus(void);
  };
//+------------------------------------------------------------------+
//| Evento ON_HIDE_CONTEXTMENUS                                      |
//+------------------------------------------------------------------+
bool CWndEvents::OnHideContextMenus(void)
  {
//--- Si hay señal para cerrar todos los menús contextuales
   if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_CONTEXTMENUS)
      return(false);
//---
   int cm_total=CWndContainer::ContextMenusTotal(0);
   for(int i=0; i<cm_total; i++)
      m_wnd[0].m_context_menus[i].Hide();
//---
   return(true);
  }

Después de compilar los archivos de la librería y cargar el EA en el gráfico, podemos comprobar que si hacemos clic fuera de las áreas de los menús contextuales activos, ellos van a cerrarse.

Hablaremos ahora de otro fallo significante que es necesario solucionar. Fíjense en la captura de pantalla de abajo. Se muestra la situación cuando el cursor se encuentra en el área del primer menú contextual pero fuera del elemento que abre el segundo menú contextual. Normalmente, en estas situaciones se cierran todos los menús contextuales “descendientes” del que contiene el cursor en este momento. Vamos a escribir el código que asegura este comportamiento.

Fig. 3. En esta situación todos los menús a la derecha deben cerrarse.

Fig. 3. En esta situación todos los menús a la derecha deben cerrarse.

 

El siguiente método va a llamarse CContextMenu::CheckHideBackContextMenus(). Su lógica ya ha sido descrita en el párrafo anterior, así que mostramos su implementación directamente (véase el código de abajo). Si se cumplen todas las condiciones, se genera el evento ON_HIDE_BACK_CONTEXTMENUS

class CContextMenu : public CElement
  {
private:
   //--- Comprobación de las condiciones para cerrar todos los menús contextuales que han sido abiertos después de éste
   void              CheckHideBackContextMenus(void);
   //---
  };
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el control está ocultado
      if(!CElement::m_is_visible)
         return;
      //--- Obtenemos el foco
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Si el menú contextual está activado y el botón izquierdo ha sido pulsado
      if(m_context_menu_state && sparam=="1")
        {
         //--- Comprobación de las condiciones para cerrar todos los menús contextuales
         CheckHideContextMenus();
         return;
        }
      //--- Comprobación de las condiciones para cerrar todos los menús contextuales que han sido abiertos después de éste
      CheckHideBackContextMenus();
      return;
     }
  }
//+--------------------------------------------------------------------------+
//| Comprobación de las condiciones para cerrar todos los menús contextuales |
//| que han sido abiertos después de éste                                    |
//+--------------------------------------------------------------------------+
void CContextMenu::CheckHideBackContextMenus(void)
  {
//--- Recorrer todos los elementos del menú
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Si este elemento contiene un menú contextual y está activado
      if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU && m_items[i].ContextMenuState())
        {
         //--- Si el foco se encuentra en este menú contextual pero no en este elemento
         if(CElement::MouseFocus() && !m_items[i].MouseFocus())
            //--- Enviar la señal para cerrar todos los menús contextuales que han sido abiertos después de éste
            ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,"");
        }
     }
  }

Antes en la clase CWndEvents ya ha sido escrito el método OnHideBackContextMenus() para procesar el evento ON_HIDE_BACK_CONTEXTMENUS, por eso ahora ya se puede compilar los archivos del proyecto y probar el EA. Si todo ha sido hecho de forma correcta, los menús contextuales van a reaccionar al movimiento del ratón de acuerdo con las condiciones de la tarea planteada.

Hemos hecho lo más complicado pero el trabajo todavía no está terminado. Luego hay que configurar el manejador de eventos de tal manera que al hacer clic en los elementos del menú contextual a la clase personalizada de la aplicación (CProgram) se envíe un evento con los valores de parámetros que permiten averiguar en qué elemento del menú exactamente ha sido hecho el clic. De esta manera el desarrollador de la aplicación podrá asignar determinadas funciones a los elementos del menú. Aparte de eso, hay que configurar el cambio del estado de las casillas de verificación y elementos de radio del menú contextual.

En la clase CMenuItem en el método OnClickMenuItem() queda un bloque sin rellenar para la condición cuando el elemento no contiene el menú contextual pero forma parte de él. De aquí va a enviarse el evento personalizado ON_CLICK_MENU_ITEM, y como parámetros adicionales el mensaje va a contener:

  1. Índice de la lista común. 
  2. Identificador del control.
  3. Cadena que va a componerse de:

  • nombre del programa;
  • indicio del checbox o elemento de radio;
  • en caso del elemento de radio, entonces su identificador.

Como podemos ver, cuando faltan las posibilidades de la función EventChartCustom(), siempre se puede crear una cadena con el número necesario de parámetros para una identificación exacta. Igual como lo hemos hecho con los nombre de objetos gráficos, vamos a separar los parámetros con el guión bajo “_”.

Y por último, en el mismo bloque va a cambiarse el estado del checbox o elemento de radio. Abajo se muestra la versión reducida CMenuItem::OnClickMenuItem() sólo con el código que hay que insertar en el bloque else.

//+------------------------------------------------------------------+
//| Clic al encabezado del control                                   |
//+------------------------------------------------------------------+
bool CMenuItem::OnClickMenuItem(const string clicked_object)
  {
//--- Comprobación según el nombre del objeto
//--- Salimos si el elemento no está activado
//--- Si este elemento contiene un menú contextual
      //... 
//--- Si este elemento no contiene un menú contextual pero forma su parte
   else
     {
      //--- Prefijo del mensaje con el nombre del programa
      string message=CElement::ProgramName();
      //--- Si es una casilla de verificación, cambiamos su estado
      if(m_type_menu_item==MI_CHECKBOX)
        {
         m_checkbox_state=(m_checkbox_state)? false : true;
         m_icon.Timeframes((m_checkbox_state)? OBJ_NO_PERIODS : OBJ_ALL_PERIODS);
         //--- Añadimos en el mensaje que es un checbox
         message+="_checkbox";
        }
      //--- Si es un elemento de radio, cambiamos su estado
      else if(m_type_menu_item==MI_RADIOBUTTON)
        {
         m_radiobutton_state=(m_radiobutton_state)? false : true;
         m_icon.Timeframes((m_radiobutton_state)? OBJ_NO_PERIODS : OBJ_ALL_PERIODS);
         //--- Añadimos en el mensaje que es un elemento de radio
         message+="_radioitem_"+(string)m_radiobutton_id;
        }
      //--- Enviamos el mensaje sobre ello
      ::EventChartCustom(m_chart_id,ON_CLICK_MENU_ITEM,m_index,CElement::Id(),message);
     }
//---
   return(true);
  }

El evento personalizado con el identificador ON_CLICK_MENU_ITEM está destinado para el manejador de la clase del menú contextual (CContextMenu). Vamos a necesitar los métodos adicionales para extraer el identificador desde el parámetro string del evento si se trata del clic en el elemento de radio, así como para obtener el índice respecto al grupo al que este elemento de radio pertenece. Abajo se puede ver el código de estos métodos.

Puesto que la extracción del identificador desde el parámetro string del mensaje depende de la estructura de la cadena pasada, entonces en el método CContextMenu::RadioIdFromMessage() habrán las comprobaciones adicionales en cuanto a la corrección de la cadena formada y superación de los límites del array.

En el método CContextMenu::RadioIndexByItemIndex(), que sirve para devolver el índice del elemento de radio según el índice común, al principio obtenemos el identificador del elemento de radio según el índice común usando para ello el método CContextMenu::RadioItemIdByIndex() escrito anteriormente. Después de eso, sólo hay que calcular en el ciclo los elementos de radio con este identificador. Al alcanzar el elemento de radio con el índice común cuyo valor es igual al índice pasado, hay que recordar el valor del contador y parar el ciclo. Es decir, el último valor del contador será aquel índice que hay que devolver.

class CContextMenu : public CElement
  {
private:
   //--- Obtener el (1) identificador e (2) índice desde el mensaje del elemento de radio
   int               RadioIdFromMessage(const string message);
   int               RadioIndexByItemIndex(const int index);
   //---
  };
//+---------------------------------------------------------------------+
//| Extrae el identificador desde el mensaje para el elemento de radio  |
//+---------------------------------------------------------------------+
int CContextMenu::RadioIdFromMessage(const string message)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtenemos el código del separador
   u_sep=::StringGetCharacter("_",0);
//--- Dividimos la línea
   ::StringSplit(message,u_sep,result);
   array_size=::ArraySize(result);
//--- Si la estructura del mensaje se diferencia de la esperada
   if(array_size!=3)
     {
      ::Print(__FUNCTION__," > ¡Estructura incorrecta en el mensaje para el elemento de radio! message: ",message);
      return(WRONG_VALUE);
     }
//--- Prevención de superar el tamaño del array
   if(array_size<3)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Devolver id del elemento de radio
   return((int)result[2]);
  }
//+------------------------------------------------------------------+
//| Devuelve el índice del elemento de radio según el índice común   |
//+------------------------------------------------------------------+
int CContextMenu::RadioIndexByItemIndex(const int index)
  {
   int radio_index =0;
//--- Obtenemos ID del elemento de radio según el índice común
   int radio_id =RadioItemIdByIndex(index);
//--- Contador de elementos en el grupo necesario
   int count_radio_id=0;
//--- Recorremos en el ciclo la lista
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Ir al siguiente si no es un elemento de radio
      if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON)
         continue;
      //--- Si los identificadores coinciden
      if(m_items[i].RadioButtonID()==radio_id)
        {
         //--- Si los índices han coincidido, 
         //    recordamos el valor actual del contador y terminamos el ciclo
         if(m_items[i].Index()==index)
           {
            radio_index=count_radio_id;
            break;
           }
         //--- Aumentar el contador
         count_radio_id++;
        }
     }
//--- Devolver el índice
   return(radio_index);
  }

A continuación, creamos el método CContextMenu::ReceiveMessageFromMenuItem() para procesar el evento personalizado ON_CLICK_MENU_ITEM del elemento del menú. En este método hay que pasar los parámetros del evento: identificador, índice y mensaje de cadena. En el inicio del método se comprueban las condiciones si este mensaje ha llegado de nuestro programa y si los identificadores coinciden. Si es así, entonces si este mensaje ha llegado del elemento de radio, inmediatamente se ejecuta el cambio en el grupo que se determina por el identificador, y el elemento necesario por el índice. Se puede obtener el identificador y el índice usando los métodos creados en el código anterior. 

Independientemente del tipo del elemento del que ha llegado el mensaje, en caso del éxito al comprobar el nombre del programa y comparar los identificadores, se envía el mensaje personalizado ON_CLICK_CONTEXTMENU_ITEM. Está destinado para el manejador en la clase de la aplicación personalizada CProgram. Junto con este mensaje se envían los siguientes parámetros: (1) identificador, (2) índice común en la lista del menú contextual y (3) el texto del elemento.

Al final del método, independientemente de la primera comprobación, (1) el menú contextual se oculta, (2) el formulario se desbloquea y (3) se envía la señal para el cierre de todos los menús contextuales.

class CContextMenu : public CElement
  {
private:
   //--- Recepción del mensaje del elemento del menú para el procesamiento
   void              ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item);
   //---
  };
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento ON_CLICK_MENU_ITEM
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM)
     {
      int    item_id      =int(dparam);
      int    item_index   =int(lparam);
      string item_message =sparam;
      //--- Recepción del mensaje del elemento del menú para el procesamiento
      ReceiveMessageFromMenuItem(item_id,item_index,item_message);
      return;
     }
  }
//+-------------------------------------------------------------------+
//| Recepción del mensaje del elemento del menú para el procesamiento |
//+-------------------------------------------------------------------+
void CContextMenu::ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item)
  {
//--- Si hay indicio que el mensaje ha llegado de este programa y id del control coincide
   if(::StringFind(message_item,CElement::ProgramName(),0)>-1 && id_item==CElement::Id())
     {
      //--- Si el clic ha sido hecho en el elemento de radio
      if(::StringFind(message_item,"radioitem",0)>-1)
        {
         //--- Obtenemos id del elemento de radio desde el mensaje recibido
         int radio_id=RadioIdFromMessage(message_item);
         //--- Obtenemos el índice del elemento de radio según el índice común
         int radio_index=RadioIndexByItemIndex(index_item);
         //--- Conmutar el elemento de radio
         SelectedRadioItem(radio_index,radio_id);
        }
      //--- Enviamos el mensaje sobre ello
      ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,index_item,id_item,DescriptionByIndex(index_item));
     }
//--- Ocultar el menú contextual
   Hide();
 //--- Desbloquear el formulario
   m_wnd.IsLocked(false);
//--- Enviar la señal para cerrar todos los menús contextuales
   ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
  }

 

 


Prueba de la llegada de los mensajes a la clase de usuario de la aplicación

Ahora podemos probar recibir este mensaje en el manejador de la clase CProgram. Para eso inserte el siguiente código:

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_CONTEXTMENU_ITEM)
     {
      ::Print(__FUNCTION__," > index: ",lparam,"; id: ",int(dparam),"; description: ",sparam);
     }
  }

Por favor, compile los archivos e inicie el EA en el gráfico. Al hacer clic en los elementos del menú, en el registro de los EAs van a mostrarse los mensajes con los parámetros de estos elementos:

2015.10.23 20:16:27.389 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5
2015.10.23 20:16:10.895 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 0; id: 3; description: ContextMenu 2 Item 1
2015.10.23 19:27:58.520 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 5; id: 3; description: ContextMenu 2 Item 6
2015.10.23 19:27:26.739 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 2; id: 3; description: ContextMenu 2 Item 3
2015.10.23 19:27:23.351 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 3; id: 3; description: ContextMenu 2 Item 4
2015.10.23 19:27:19.822 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5
2015.10.23 19:27:15.550 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 1; id: 2; description: ContextMenu 1 Item 2

Hemos terminado el desarrollo de la parte principal de la clase CContextMenu para la creación del menú contextual. Habrá que introducir algunas adiciones, pero de eso nos ocuparemos un poco más tarde, cuando surge algún problema durante la prueba. Mejor dicho, vamos a seguir el orden natural de nuestro proyecto. Así será más fácil aprender el material.

 


Conclusión

En este artículo hemos mejorado las clases de controles creadas anteriormente (véase los artículos anteriores). Ahora tenemos todo listo para el desarrollo del control “Menú principal”. Nos ocuparemos de esta tarea en el siguiente artículo.

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:

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

Archivos adjuntos |
Método de las áreas Método de las áreas

El sistema comercial "Método de las áreas" funciona basándose en una interpretación poco habitual de los índices del oscilador RSI. En este artículo se muestra un indicador que visualiza el método de las áreas, y un asesor que comercia con este sistema. El artículo se complementa con los resultados de la simulación del asesor en símbolos, marcos temporales y valores de las áreas diferentes.

Interfaces gráficas II: Controles "Línea separadora" y "Menú contextual" (Capítulo 2) Interfaces gráficas II: Controles "Línea separadora" y "Menú contextual" (Capítulo 2)

En este artículo nos ocuparemos de la creación del control llamado “Línea separadora”. Se podrá utilizarlo no sólo como un elemento independiente de la interfaz, sino también como parte de otros controles. Después de eso, tendremos todo lo necesario para desarrollar la clase del menú contextual, que también será considerado al detalle en el presente artículo. Además, vamos a introducir adiciones necesarias en la clase que sirve de base para almacenar los punteros a todos los controles de la interfaz gráfica de la aplicación.

Recetas MQL5 - Programando los canales móviles Recetas MQL5 - Programando los canales móviles

En este artículo se muestra un método de programación del sistema de canales equidistantes. Se analizan ciertos matices en la construcción de este tipo de canales. Asimismo, se realiza una tipificación de los canales, proponiendo un método de canales móviles de tipo universal. Para implementar el código, se ha utilizado el instrumental de la POO.

Interfaces gráficas II: Control "Menú principal" (Capítulo 4) Interfaces gráficas II: Control "Menú principal" (Capítulo 4)

Es el artículo final de la segunda parte de la serie sobre las interfaces gráficas. Aquí vamos a considerar la creación del control “Menú principal”. Se demostrará el proceso de su desarrollo y la configuración de los manejadores de las clases de la librería para una correcta reacción a las acciones del usuario. Además, hablaremos de los modos de conexión de los menús contextuales a los elementos del menú principal. Aparte de eso, trataremos la cuestión del bloqueo de los controles inactivos en el momento actual.