English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas II: Configuração dos manipuladores de eventos da Biblioteca (Capítulo 3)

Interfaces Gráficas II: Configuração dos manipuladores de eventos da Biblioteca (Capítulo 3)

MetaTrader 5Exemplos | 12 agosto 2016, 12:50
873 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo

 

 

Introdução

O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) explica em detalhes a finalidade desta biblioteca. A lista completa dos links para os artigos da primeira parte se encontram no final de cada capítulo. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.   

Os artigos anteriores contêm a implementação das classes para criar os componentes do menu principal. O desenvolvimento da classe de cada controle requer um ajuste fino prévio dos manipuladores de eventos nas classes base principais e nas classes dos controles criados. As seguintes perguntas serão discutidas neste artigo:

  • Arrays privados para cada controle significativo.
  • Adição de ponteiros de elementos para a base. Estes elementos são componentes dos elementos complexos (compostos).
  • Gestão do estado do gráfico, dependendo da localização do cursor do mouse.
  • Identificadores dos eventos da biblioteca para uso interno e externo.

Mais adiante, será mostrado o processo de recebimento das mensagens no manipulador da classe personalizada do aplicativo. 

 


Arrays Privados de Elementos

Vamos realizar um pequeno experimento. Clique com o botão esquerdo em um dos itens do menu de contexto na área onde o cursor do mouse estiver fora da área do formulário. Nós veremos que a barra de rolagem do gráfico não foi desativada, podendo ser usada ao pairar o mouse sobre o controle. Este é um erro funcional que não deve estar presente. Nós vamos providenciar para que a barra de rolagem do gráfico e o deslocamento do modo dos níveis de negociação fiquem desabilitados nesse momento, não importando em qual controle o cursor do mouse estiver. 

Primeiramente, nós vamos adicionar o rastreio do foco sobre o elemento para o manipulador do menu de contexto, conforme mostrado no código abaixo. Se o menu de contexto estiver oculto, então não há necessidade de continuar. Siga esta abordagem para economizar tempo.

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai se o elemento está oculto
      if(!CElement::m_is_visible)
         return;
      //--- Obtém o foco
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
     }
  }

No estágio atual do desenvolvimento da biblioteca, a base dos elementos (para ser mais preciso, a classe CWndContainer) contém o array comum de ponteiros de elementos m_elements[]. Esta é uma parte da estrutura WindowElements dos arrays de elementos. Este array é adequado para os casos quando é necessário aplicar uma ação a todos os controles ou pelo menos a maioria deles. Se é necessário aplicar uma ação apenas a em um determinado grupo de elementos, esta abordagem se torna excessiva, já que ela requer muitos recursos. Por exemplo, vamos considerar um grupo de controles, com tamanhos que podem ultrapassar os limites do formulário que eles estão anexados. As listas suspensas e os menus de contexto pertencem a este grupo. Cada tipo destes elementos deve ser armazenado em seus correspondentes arrays. Isso permitirá uma maior eficiência e facilidade no gerenciamento. 

Adicione o array aos menus de contexto para a estrutura WindowElements e crie um método para obter o seu tamanho:

//+------------------------------------------------------------------+
//| Classe para armazenar todos os objetos da interface              |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Estrutura de arrays de elementos
   struct WindowElements
     {
      //--- Array comum de todos os objetos
      CChartObject     *m_objects[];
      //--- Array comum de todos os elementos
      CElement         *m_elements[];
      
      //---Arrays privados de Elementos
      //    Array do menu de contexto
      CContextMenu     *m_context_menus[];
     };
   //--- Array dos arrays de elemento para cada janela
   WindowElements    m_wnd[];
   //---
public:
   //--- Número de menus de contexto
   int               ContextMenusTotal(const int window_index);
   //---
  };
//+------------------------------------------------------------------+
//| Retorna o número de menus de contexto pelo índice da janela específica|
//+------------------------------------------------------------------+
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));
  }

Toda vez após a criação de um controle na classe personalizada da aplicação (em nosso caso a CProgram), nós usamos o método CWndContainer::AddToElementsArray() para adicionar um ponteiro a este controle para a base. Neste método, serão utilizados os métodos para obter e armazenar os ponteiros para cada elemento complexo (composto) no array comum. Anteriormente, criamos um método semelhante para o menu de contexto, o CWndContainer::AddContextMenuElements(). Todos os métodos semelhantes proporcionar a possibilidade de distribuir os ponteiros em arays privados do elemento, caso haja necessidade.

Em seguida, será necessário um método de modelo para adicionarmos um ponteiro ao elemento para o array passado por referência, pois esta ação será repetida mais de uma vez e aplicada a diferentes tipos de objetos.

class CWndContainer
  {
protected:
   //--- Método do modelo para adicionar ponteiros ao array passado por referência
   template<typename T1,typename T2>
   void              AddToRefArray(T1 &object,T2 &ref_array[]);
   //---
  };
//+------------------------------------------------------------------+
//| Armazena o ponteiro (T1) no array passado por referência (T2)    |
//+------------------------------------------------------------------+
template<typename T1,typename T2>
void CWndContainer::AddToRefArray(T1 &object,T2 &array[])
  {
   int size=::ArraySize(array);
   ::ArrayResize(array,size+1);
   array[size]=object;
  }

Agora, o ponteiro do menu de contexto pode ser armazenado em seu array privado no final do método CWndContainer::AddContextMenuElements() exibido abaixo (destacado em amarelo). Vamos fazer o mesmo para todos os outros controles.

//+------------------------------------------------------------------+
//| Armazena os ponteiros para os objetos do menu de contexto na base|
//+------------------------------------------------------------------+
bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object)
  {
//--- Retorna, se este não for um menu de contexto
   if(object.ClassName()!="CContextMenu")
      return(false);
//--- Obtém o ponteiro do menu de contexto
   CContextMenu *cm=::GetPointer(object);
//--- Armazena os ponteiros para os seus objetos na base
   int items_total=cm.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Incrementa o array de elementos
      int size=::ArraySize(m_wnd[window_index].m_elements);
      ::ArrayResize(m_wnd[window_index].m_elements,size+1);
      //--- Obtém o ponteiro do elemento de menu
      CMenuItem *mi=cm.ItemPointerByIndex(i);
      //--- Armazena o ponteiro no array
      m_wnd[window_index].m_elements[size]=mi;
      //--- Adiciona os ponteiros de todos os objetos do elemento de menu para o array comum
      AddToObjectsArray(window_index,mi);
     }
//--- Adiciona o ponteiro para o array privado
   AddToRefArray(cm,m_wnd[window_index].m_context_menus);
   return(true);
  }

 


Gestão do Estado do Gráfico

Em seguida, é necessário adicionar um método para verificar o foco do cursor do mouse sobre os controles na classe CWndEvents. Tal verificação será realizada para os formulários e as listas suspensas. Tanto os formulários quanto os menus de contexto já possuem arrays privados. Portanto, vamos criar o método CWndEvents::SetChartState(). Abaixo encontramos a declaração e implementação deste método:

class CWndEvents : public CWndContainer
  {
private:
   //--- Define o estado do gráfico
   void              SetChartState(void);
  };
//+------------------------------------------------------------------+
//| Define o estado do gráfico                                       |
//+------------------------------------------------------------------+
void CWndEvents::SetChartState(void)
  {
//--- Para identificar o evento quando a gestão for desativada
   bool condition=false;
//--- Verifica a janela
   int windows_total=CWndContainer::WindowsTotal();
   for(int i=0; i<windows_total; i++)
     {
      //--- Vai para o formulário seguinte, se este esta oculto

      if(!m_windows[i].IsVisible())
         continue;
      //--- Verifica as condições no manipulador interno do formulário
      m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam);
      //--- Se houver um foco, registra ele
      if(m_windows[i].MouseFocus())
        {
         condition=true;
         break;
        }
     }
//--- Verifica o foco dos menus de contexto
   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)
     {
      //--- Desativa a rolagem e gestão dos níveis de negociação
      m_chart.MouseScroll(false);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false);
     }
   else
     {
      //--- Ativa o gerenciamento
      m_chart.MouseScroll(true);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true);
     }
  }

Este método será melhorado posteriormente com algumas adições, mas ele já está adequado para a tarefa atual. Ele deve ser chamado no método CWndEvents::ChartEventMouseMove() como é mostrado abaixo.

//+------------------------------------------------------------------+
//| Evento CHARTEVENT MOUSE MOVE                                     |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventMouseMove(void)
  {
//--- Sai, se este não for um evento de deslocamento do cursor
   if(m_id!=CHARTEVENT_MOUSE_MOVE)
      return;
//--- Movendo a janela
   MovingWindow();
//--- Define o estado do gráfico
   SetChartState();
//--- Redesenha o gráfico
   m_chart.Redraw();
  }

Compile todos os arquivos e teste o EA. Nós podemos ver agora que quando há um clique esquerdo na área do menu de contexto, o que ultrapassa os limites do formulário, a barra de rolagem do gráfico e a gestão dos níveis de negociação estão desativadas. O teste de anexar um elemento ao gráfico foi bem-sucedido. A partir de agora, o menu de contexto será criado apenas pela solicitação do usuário. Remova a sua exibição do método CProgram::CreateTradePanel() na classe da aplicação (veja o código abaixo).

   m_contextmenu.Show(); // <<< Esta linha de código deve ser removida

 


Identificadores para o Uso Externo e Interno

Agora, nós vamos dar continuidade à manipulação do clique esquerdo sobre o elemento de menu.

Nossa próxima tarefa consiste em fazer com que o menu de contexto se abra, clicando no elemento de menu, desde que o menu de contexto esteja incluído. O segundo clique deve ocultá-lo. Tal manipulação irá estar presente tanto na classe CMenuItem do elemento de menu e na classe CContextMenu do menu de contexto. O problema é que o menu de contexto tem acesso ao elemento que ele está anexado (o nó anterior) e o elemento de menu que contém o menu de contexto não tem acesso direto a ele. O ponteiro do menu de contexto não pode ser criado na classe CMenuItem . Isto acontece pois se o arquivo ContextMenu.mqh for incluído no arquivo MenuItem.mqh, haverá erros de compilação. É por isso que nós vamos realizar a manipulação da exibição do menu de contexto na classe CContextMenu. O manipulador na classe CMenuItem será auxiliar. Ele irá gerar um evento personalizado através do envio de informações específicas para o menu de contexto sobre o elemento de menu que recebeu o clique. Além disso, nós precisamos fazer com que o menu de contexto se oculte quando o clique é realizado fora da área do menu de contexto, como é feito nos terminais MetaTrader e no editor de código MetaEditor. Este é um comportamento padrão para os menus de contexto. 

Para implementar esta funcionalidade, é necessário haver identificadores adicionais para os eventos personalizados. Alguns deles serão projetados para uso interno nas classes da biblioteca e alguns deles para a manipulação externa na classe da aplicação personalizada. No nosso caso, este é a CProgram

Eventos para uso interno:

  • ON_CLICK_MENU_ITEM  — clique em um elemento de menu.
  • ON_HIDE_CONTEXTMENUS — sinal para ocultar todos os menus de contexto.
  • ON_HIDE_BACK_CONTEXTMENUS — sinal para ocultar os menus de contexto abaixo do elemento de menu atual. Nós vamos discutir isso em detalhes mais tarde.

Para uso externo, crie o identificador ON_CLICK_CONTEXTMENU_ITEM que irá informar o programa que o clique aconteceu no elemento do menu de contexto.

Coloque os identificadores listados com números únicos atribuídos a cada um deles no arquivo Defines.mqh:

#define ON_CLICK_MENU_ITEM        (4) // Clique no elemento de menu
#define ON_CLICK_CONTEXTMENU_ITEM (5) // Clique no elemento de menu do menu de contexto
#define ON_HIDE_CONTEXTMENUS      (6) // Oculta todos os menus de contexto
#define ON_HIDE_BACK_CONTEXTMENUS (7) // Oculta os menus de contexto abaixo do elemento de menu atual

 


Melhorando a Classe do Menu de Contexto

Os seguintes campos e métodos devem ser adicionados na classe CContextMenu do menu de contexto: 

  • Para definir e obter o estado do menu de contexto.
  • Para a manipulação do clique sobre o evento elemento de menu.
  • Para obter o identificador e o índice do nome do elemento de menu. Nós já sabemos a razão pela qual o índice e o identificador fazem parte dos nomes dos objetos que são componentes de vários elementos. 

O código a seguir apresenta a declaração e a implementação de tudo o que foi listado acima, com os comentários detalhados:

class CContextMenu : public CElement
  {
private:
   //--- Estado do menu de contexto
   bool              m_contextmenu_state;
public:   
   //--- (1) Obter e (2) definir o estado do menu de contexto
   bool              ContextMenuState(void)                   const { return(m_context_menu_state);         }
   void              ContextMenuState(const bool flag)              { m_context_menu_state=flag;            }
   //---
private:
   //--- Manipulação do clique no elemento ao qual este menu de contexto está ligado
   bool              OnClickMenuItem(const string clicked_object);
   //--- Obtendo (1) o identificador e (2) o índice a partir do nome do elemento de menu
   int               IdFromObjectName(const string object_name);
   int               IndexFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Manipulação do clique no elemento de menu                        |
//+------------------------------------------------------------------+
bool CContextMenu::OnClickMenuItem(const string clicked_object)
  {
//--- Retorna, se o menu de contexto já está aberto 
   if(m_contextmenu_state)
      return(true);
//--- Retorna, se o clique não foi no elemento de menu
   if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0)
      return(false);
//--- Obtem o identificador e o índice do nome do objeto
   int id    =IdFromObjectName(clicked_object);
   int index =IndexFromObjectName(clicked_object);
//--- Retorna, se o clique não foi no elemento de menu ao qual este menu de contexto está ligado
   if(id!=m_prev_node.Id() || index!=m_prev_node.Index())
      return(false);
//--- Exibe o menu de contexto
   Show();
   return(true);
  }
//+------------------------------------------------------------------+
//| Extrai o identificado do nome do objeto                          |
//+------------------------------------------------------------------+
int CContextMenu::IdFromObjectName(const string object_name)
  {
//--- Obtém o ID do nome do objeto
   int    length =::StringLen(object_name);
   int    pos    =::StringFind(object_name,"__",0);
   string id     =::StringSubstr(object_name,pos+2,length-1);
//---
   return((int)id);
  }
//+------------------------------------------------------------------+
//| Extrai o índice do nome do objeto                                |
//+------------------------------------------------------------------+
int CContextMenu::IndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtém o código do separador
   u_sep=::StringGetCharacter("_",0);
//--- Divide a string
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Verifica se o tamanho do array não excedeu
   if(array_size-2<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return((int)result[array_size-2]);
  }

Agora, só precisamos adicionar a chamada do método CContextMenu::OnClickMenuItem() quando o evento CHARTEVENT_OBJECT_CLICK() acontecer no manipulador de eventos CContextMenu::OnEvent do menu de contexto:

//--- Manipulação de eventos do clique esquerdo do mouse em um objeto
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickMenuItem(sparam))
         return;
     }

 


Melhorando a Classe do Elemento de Menu

Quando o programa detecta o clique esquerdo do mouse sobre um elemento de menu, ele irá passar um parâmetro de string para o método CContextMenu::OnClickMenuItem(). O parâmetro de string contém o nome do objeto gráfico "rótulo retangular", que representa o fundo do elemento de menu. Você deve se lembrar que a prioridade para o clique no fundo será maior do que para o clique em outros objetos de elementos para quase todos os controles. Isto garante que o clique não será interceptado por qualquer outro objeto do elemento, o que pode desencadear um comportamento inesperado do programa. Por exemplo, se o rótulo do elemento de menu tiver uma prioridade maior do que o seu fundo, então, clicando na área do rótulo pode levar à mudança da imagem. Deixe-me lembrá-lo que as imagens dos ícones determinam dois estados. A razão para isso é que todos os objetos do tipo OBJ_BITMAP_LABEL irão se comportar dessa maneira por padrão. 

No início do método CContextMenu::OnClickMenuItem(), será realizado uma verificação do estado do menu de contexto. Se ele já estiver ativado, então não há necessidade de continuar. Em seguida, o nome do objeto que foi clicado é verificado. Se este é um objeto de nosso programa e não há indícios de que este seja um elemento de menu, então, nós continuamos. O identificador e o índice do elemento de menu são extraídos do nome do objeto. Para essas tarefas nós já havíamos designado os métodos na qual todos os parâmetros necessários são extraídos do nome do objeto usando as funções de string da linguagem MQL. O identificador do elemento de menu é extraído utilizando o caractere traço duplo como um delimitador. Para extrair um índice, a linha é dividida em duas partes pelo símbolo traço inferior (_), que é um separador dos parâmetros do objeto do elemento.

Crie o método OnClickMenuItem() na classe CMenuItem. Seu código será diferente daquele escrito para o menu de contexto. Segue abaixo a declaração e implementação deste método. Neste método, não há necessidade de extrair os parâmetros do nome do objeto. Basta comparar o nome de fundo com o nome do objeto passado. Em seguida, o estado atual do elemento de menu é verificado. Se ele está bloqueado, então, não há necessidade de outras ações. Depois disso, se o elemento contém um menu de contexto, é atribuído a ele o estado do elemento, ativado ou desativado. Se antes disso o estado do menu de contexto for ativado, então, o módulo principal do manipulador de eventos envia um sinal para fechar todos os menus de contexto que foram abertos depois. Isto é aplicável para aqueles casos em que há vários menus de contexto abertos ao mesmo tempo que se abrem um dos outros. Tais exemplos serão discutidos mais adiante neste artigo. Além do identificador de evento ON_HIDE_BACK_CONTEXTMENUS, o identificador do elemento de menu é passado como um outro parâmetro. Isto é usado para identificar em qual menu de contexto o loop poderá ser interrompido.

class CMenuItem : public CElement
  {
   //--- Manipulação do clique no elemento de menu
   bool              OnClickMenuItem(const string clicked_object);
   //---
  };
//+------------------------------------------------------------------+
//| Manipulação do clique no elemento de menu                        |
//+------------------------------------------------------------------+
bool CMenuItem::OnClickMenuItem(const string clicked_object)
  {
//--- Verificação pelo nome do objeto
   if(m_area.Name()!=clicked_object)
      return(false);
//--- Retorna, se o item não for ativado
   if(!m_item_state)
      return(false);
//--- Se este elemento contém um menu de contexto
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU)
     {
      //--- Se o menu suspenso deste elemento não foi ativado
      if(!m_context_menu_state)
        {
         m_context_menu_state=true;
        }
      else
        {
         m_context_menu_state=false;
         //--- Envia um sinal para fechar os menus de contexto, que estão abaixo deste elemento
         ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,"");
        }
      return(true);
     }
//--- Se este elemento não contém um menu de contexto, mas é uma parte de um menu de contexto em si
   else
     {
     }
//---
   return(true);
  }

 

 


Melhorando a Classe Principal de Manipulação de Eventos da Interface Gráfica

Esta não é a versão final do método CMenuItem::OnClickMenuItem(), e nós vamos voltar a ele mais tarde para realizarmos algumas melhorias. Atualmente, sua principal tarefa é enviar uma mensagem para ocultar o menu de contexto para o módulo principal de manipulação de eventos personalizados na classe CWndEvents. Nessa classe, vamos criar um método de acesso no qual será realizado pelo evento ON_HIDE_BACK_CONTEXTMENUS. Vamos nomeá-lo de CWndEvents::OnHideBackContextMenus(). O código deste método é apresentado a seguir:

class CWndEvents : public CWndContainer
  {
private:
   //--- Oculta todos os menus de contexto abaixo do elemento inicial
   bool              OnHideBackContextMenus(void);
  };
//+------------------------------------------------------------------+
//| Evento ON_HIDE_BACK_CONTEXTMENUS                                 |
//+------------------------------------------------------------------+
bool CWndEvents::OnHideBackContextMenus(void)
  {
//--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial
   if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_BACK_CONTEXTMENUS)
      return(false);
//--- Itera sobre todos os menus da última chamada
   int context_menus_total=CWndContainer::ContextMenusTotal(0);
   for(int i=context_menus_total-1; i>=0; i--)
     {
      //--- Ponteiros para o menu de contexto e seu nó anterior
      CContextMenu *cm=m_wnd[0].m_context_menus[i];
      CMenuItem    *mi=cm.PrevNodePointer();
      //--- Se fez isso para o elemento inicial do sinal, então ...
      if(mi.Id()==m_lparam)
        {
         //--- ...se seu menu de contexto não tem foco, oculte-o
         if(!cm.MouseFocus())
            cm.Hide();
         //--- Para o loop
         break;
        }
      else
        {
         //--- Oculta o menu de contexto
         cm.Hide();
        }
     }
//---
   return(true);
  }

O método CWndEvents::OnHideBackContextMenus() deve ser chamado no método do manipulador de eventos personalizados, como mostrado abaixo.

//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                         |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Se o sinal for para minimizar o formulário
   if(OnWindowRollUp())
      return;
//--- Se o sinal for para maximizar o formulário
   if(OnWindowUnroll())
      return;
//--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial
   if(OnHideBackContextMenus())
      return;
  }

 


Teste preliminar dos Manipuladores de Eventos

Depois que todas as mudanças foram realizadas, compile todos os arquivos e carregue o programa ao gráfico para realizar o teste. Agora, quando um elemento de menu independente sobre o formulário for clicado, o menu de contexto será exibido se este estava oculto antes e oculta se este estava aberto. Além disso, quando um menu de contexto está aberto, então a cor de fundo do elemento de menu será fixa, ou seja, ela não irá mudar novamente se o cursor do mouse estiver fora de sua área, como é exibido na imagem abaixo. 

Fig. 1. Teste de exibir e ocultar um menu de contexto.

Fig. 1. Teste de exibir e ocultar um menu de contexto.

 

Nós estamos continuando a ajustar a interação do usuário com o menu de contexto. Na maioria das aplicações, quando um ou vários menus de contexto estão abertos (um do outro), quando um clique do mouse ocorre fora de seus limites, eles se fecham ao mesmo tempo. Aqui, nós replicaremos o mesmo comportamento. 

Para ser capaz de testar esta funcionalidade em sua totalidade, vamos adicionar outro menu de contexto para a interface de nosso EA. Nós vamos anexar um menu de contexto ao terceiro elemento do atual menu de contexto. Para isso, atribua o terceiro elemento do tipo MI_HAS_CONTEXT_MENU no método CProgram::CreateContextMenu1() que cria o primeiro menu de contexto no array items_type[]:

//--- Array dos tipos de elementos
   ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS]=
     {
      MI_SIMPLE,
      MI_SIMPLE,
      MI_HAS_CONTEXT_MENU,
      MI_CHECKBOX,
      MI_CHECKBOX
     };

Agora, vamos criar um método para o segundo menu de contexto. Adicione a segunda instância da classe CContextMenu para a classe CProgram e declare o método CreateContextMenu2():

class CProgram : public CWndEvents
  {
private:
   //--- Elemento de menu e do contexto
   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);
  };

O segundo menu de contexto irá conter seis elementos. Esses serão dois grupos de elementos do botão de rádio (MI_RADIOBUTTON), três itens em cada. Abaixo está o código desse método. Qual é a diferença entre este método e o método de criação do primeiro menu contexto? Por favor, note como obtemos o ponteiro para o terceiro elemento do primeiro menu de contexto para o qual o segundo menu de contexto precisa ser ligado. O método CContextMenu::ItemPointerByIndex() designado para isso foi criado anteriormente. Como nós estaremos utilizando os ícones padrões para os elementos do botão de radio, eles não precisarão de arrays. No método CContextMenu::AddItem() em vez de passar o caminho para os ícones, ele passa valores vazios. A linha de separação é necessária para a separação visual do primeiro grupo de elementos de radio com o segundo. Portanto, defina ele após o terceiro (2) elemento na lista.

Foi mencionado anteriormente e mostrado em um esquema que cada grupo de elementos de radio deve ter seu próprio identificador exclusivo. O valor padrão deste parâmetro é 0. Por essa razão, atribua o identificador igual a 1 para cada elemento de rádio do segundo grupo (no loop do terceiro ao sexto). A classe CContextMenu já contém o método CContextMenu::RadioItemIdByIndex() para definir o identificador.

Vamos especificar o que os elementos de radio cada grupo tiveram que ser destacados inicialmente usando o método CContextMenu::SelectedRadioItem(). No código abaixo, o segundo elemento de rádio (índice 1) está destacado no primeiro grupo e o terceiro elemento de rádio (índice 2) está destacado no segundo grupo.

//+------------------------------------------------------------------+
//| Cria o menu de contexto 2                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateMI1ContextMenu2(void)
  {
//--- Seis itens em um menu de contexto
#define CONTEXTMENU_ITEMS2 6
//--- Armazena o ponteiro da janela
   m_mi1_contextmenu2.WindowPointer(m_window);
//--- Armazena o ponteiro para o nó anterior
   m_mi1_contextmenu2.PrevNodePointer(m_mi1_contextmenu1.ItemPointerByIndex(2));
//--- Array de nomes dos elementos
   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"
     };
//--- Configura as propriedades antes da criação
   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);
//--- Adiciona os elementos ao menu de contexto
   for(int i=0; i<CONTEXTMENU_ITEMS2; i++)
      m_mi1_contextmenu2.AddItem(items_text[i],"","",MI_RADIOBUTTON);
//--- Linha de separação após o terceiro elemento
   m_mi1_contextmenu2.AddSeparateLine(2);
//--- Define um identificador único (1) para o segundo grupo
   for(int i=3; i<6; i++)
      m_mi1_contextmenu2.RadioItemIdByIndex(i,1);
//--- Elementos de radio selecionados em ambos os grupos
   m_mi1_contextmenu2.SelectedRadioItem(1,0);
   m_mi1_contextmenu2.SelectedRadioItem(2,1);
//--- Cria um menu de contexto
   if(!m_mi1_contextmenu2.CreateContextMenu(m_chart_id,m_subwin))
      return(false);
//--- Adiciona o ponteiro do elemento para a base
   CWndContainer::AddToElementsArray(0,m_mi1_contextmenu2);
   return(true);
  }

 

A chamada do método CProgram::CreateContextMenu2() situa-se no método CProgram::CreateTradePanel() como para o restante deles.

 

 

Teste de Vários Menus de Contexto e Ajuste fino

O resultado da compilação dos arquivos do EA e e seu carregamento ao gráfico será como mostrado abaixo.

Fig. 2. Teste de vários menus de contexto.

Fig. 2. Teste de vários menus de contexto.

 

Se ambos os menus de contexto estiverem abertos ao clicar no elemento, que traz o primeiro menu, ambos os menus serão fechados. Este comportamento é subjacente ao método CWndEvents::OnHideBackContextMenus(), no qual tem sido considerado acima. No entanto, se clicarmos no gráfico do cabeçalho do formulário, os menus de contexto não serão fechados. Nós iremos trabalhar nisso.

A localização do cursor do mouse (foco) é definido no manipulador de evento OnEvent() da classe do menu de contexto (CContextMenu). Portanto, será enviado para lá também um sinal para fechar todos os menus de contexto abertos no manipulador de evento principal (na classe CWndEvents). Esta tarefa tem a seguinte solução.

1. Quando o evento do movimento do mouse (CHARTEVENT_MOUSE_MOVE) ocorrer, o parâmetro de string sparam irá conter o estado do botão esquerdo do mouse.

2. Então, depois de o foco do mouse ser identificado, nós realizamos uma verificação do estado atual do menu de contexto e do botão esquerdo do mouse. Se o menu de contexto for ativado e o botão ter sido pressionado, nós vamos passaremos para a próxima verificação onde a localização atual do cursor é identificada em relação a este menu de contexto e o nó anterior.

3. Se o cursor estiver na área de uma delas, não é necessário enviar um sinal para fechar todos os menus de contexto. Se o cursor estiver fora da área desses elementos, nós temos de verificar se existem menus de contexto que foram abertos mais tarde.

4. Para isso, itere sobre a lista deste menu de contexto para identificar se este contém um elemento com o seu próprio menu de contexto em anexo. Se houver um elemento deste tipo, verifique se o seu menu de contexto foi ativado. Se ele descobriu que o menu de contexto foi ativado, o cursor poderá estar em sua área. Isso significa que não é necessário enviar um sinal para fechar todos os menus de contexto. Se acontecer do menu de contexto atual ser o último aberto e em todos os menus que o precedem as condições para o envio de um sinal não forem cumpridas, isso significa que definitivamente o cursor está fora das áreas de todos os menus de contexto ativados.

5. O evento personalizado ON_HIDE_CONTEXTMENUS pode ser gerado aqui.

Como nós podemos ver, o mais importante é que todos os menus de contexto têm de ser fechados somente quando o cursor do mouse (se o botão esquerdo do mouse é pressionado) estiver fora da área do último menu de contexto ativado e fora da área do elemento a partir de onde ele foi chamado.

A lógica descrita está no código abaixo. O método CContextMenu::CheckHideContextMenus() foi dedicado para isso.

class CContextMenu : public CElement
  {
private:
   //--- Verificação da condição para fechar todos os menus de contexto
   void              CheckHideContextMenus(void);
   //---
  };
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai se o elemento está oculto
      if(!CElement::m_is_visible)
         return;
      //--- Obtém o foco
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Se o menu de contexto está habilitado e o botão esquerdo do mouse for pressionado
      if(m_context_menu_state && sparam=="1")
        {
         //--- Verificação da condição para fechar todos os menus de contexto
         CheckHideContextMenus();
         return;
        }
      //---
      return;
     }
  }
//+------------------------------------------------------------------+
//| Verificação da condição para fechar todos os menus de contexto   |
//+------------------------------------------------------------------+
void CContextMenu::CheckHideContextMenus(void)
  {
//--- Sair, se o cursor estiver na área do menu de contexto ou na área do nó anterior
   if(CElement::MouseFocus() || m_prev_node.MouseFocus())
      return;
//--- Se o cursor estiver fora da área destes elementos,então, ...
//    ... uma verificação é necessária se existem menus de contexto abertos que foram ativados depois disso
//--- Para isso itere sobre a lista deste menu de contexto...
//    ... para identificação, se há um elemento de menu que contém um menu de contexto
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Se houver um item deste tipo, verifique se o seu menu de contexto está aberto.
      //    Se este está aberto, não envie um sinal para fechar todos os menus de contexto a partir deste elemento já que ...
      //    ... é possível que o cursor esteja na área do mesmo, sendo que isso deve ser verificado.
      if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU)
         if(m_items[i].ContextMenuState())
            return;
     }
// --- Enviar um sinal para esconder todos os menus de contexto
   ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
  }

Agora o evento ON_HIDE_CONTEXTMENUS tem que ser recebido no manipulador principal da biblioteca em desenvolvimento na classe CWndEvents. Vamos escrever um método designado a isto e nomeá-lo de OnHideContextMenus(). Isto é bastante simples, já que atualmente ele tem apenas que iterar sobre o array privado de menus de contexto e ocultá-los. 

A declaração e implementação do método CWndEvents::OnHideContextMenus() está no código abaixo:

class CWndEvents : public CWndContainer
  {
private:
   //--- Esconder todos os menus de contexto
   bool              OnHideContextMenus(void);
  };
//+------------------------------------------------------------------+
//| Evento ON_HIDE_CONTEXTMENUS                                      |
//+------------------------------------------------------------------+
bool CWndEvents::OnHideContextMenus(void)
  {
//--- Se o sinal for para esconder todos os menus de contexto
   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);
  }

Depois de compilar os arquivos da biblioteca e carregar o EA ao gráfico para testes, você vai ver que os menus de contexto ativados serão ocultados se o clique do mouse ocorrer fora de suas áreas.

Temos de eliminar outra falha perceptível do projeto. Dê uma olhada na imagem abaixo. Ela mostra uma situação, quando o cursor do mouse estiver na área do primeiro menu de contexto, mas fora da área do elemento de menu a partir do qual o segundo menu de contexto é chamado. Normalmente, em tais casos, todos os menus de contexto abertos após aquele em que o cursor está localizado atualmente estão fechados. Vamos escrever um código para ele.

Fig. 3. Em tal situação, todos os menus de contexto do lado direito devem ser ocultos.

Fig. 3. Em tal situação, todos os menus de contexto do lado direito devem ser ocultos.

 

Vamos nomear o próximo método CContextMenu::CheckHideBackContextMenus(). Sua lógica foi descrita no parágrafo anterior e nós podemos prosseguir direto para sua implementação (veja o código abaixo). Se todas as condições forem atendidas, o evento ON_HIDE_BACK_CONTEXTMENUS evento será gerado. 

class CContextMenu : public CElement
  {
private:
   //--- Verificação da condição para fechar todos os menus de contexto que foram abertos após este
   void              CheckHideBackContextMenus(void);
   //---
  };
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai se o elemento está oculto
      if(!CElement::m_is_visible)
         return;
      //--- Obtém o foco
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Se o menu de contexto está habilitado e o botão esquerdo do mouse for pressionado
      if(m_context_menu_state && sparam=="1")
        {
         //--- Verificação da condição para fechar todos os menus de contexto
         CheckHideContextMenus();
         return;
        }
      //--- Verificação da condição para fechar todos os menus de contexto que foram abertos após este
      CheckHideBackContextMenus();
      return;
     }
  }
//+------------------------------------------------------------------+
//| Verificação da condição para fechar todos os menus de contexto   |
//| que foram abertos após este                                      |
//+------------------------------------------------------------------+
void CContextMenu::CheckHideBackContextMenus(void)
  {
//--- Itera sobre todos os elementos do menu
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Se o elemento contém um menu de contexto e ele está habilitado
      if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU && m_items[i].ContextMenuState())
        {
         //--- Se o foco estiver no menu de contexto, mas não neste elemento
         if(CElement::MouseFocus() && !m_items[i].MouseFocus())
            //--- Envia um sinal para esconder todos os menus de contexto que foram abertos depois dele
            ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,"");
        }
     }
  }

Inicialmente, o método OnHideBackContextMenus() foi escrito na classe CWndEvents para a manipulação do evento ON_HIDE_BACK_CONTEXTMENUS, portanto, os arquivos do projeto podem ser compilados e, assim, o EA ser testado. Se tudo foi feito corretamente, os menus de contexto reagirão ao movimento do cursor do mouse de acordo com os requisitos do programa.

A parte mais difícil terminou, mas o trabalho ainda não está terminado. Os manipuladores de eventos devem ser configurados de modo que, quando qualquer um dos elementos do menu de contexto ser clicado, uma mensagem com os valores dos parâmetros é enviada para a classe personalizada da aplicação (CProgram). Estes parâmetros permitirão identificar qual elemento de menu foi clicado exatamente. Desta forma, o desenvolvedor do aplicativo pode atribuir certas funções para os elementos de menu. Além disso, a mudança dos estados dos elementos caixas de seleção e de radio do menu de contexto ainda serão criados.

O bloco para a condição quando um elemento de menu não contém um menu de contexto, mas uma parte dela ainda está vazia no método OnClickMenuItem() da classe CMenuItem. O evento personalizado ON_CLICK_MENU_ITEM será enviado a partir daqui. A mensagem irá conter os seguintes parâmetros adicionais:

  1. O índice da lista comum. 
  2. O identificador de elemento.
  3. Uma linha que vai ser formada a partir do:

  • o nome do programa;
  • a indicação da caixa de seleção ou elemento de rádio;
  • no caso deste ser um elemento de radio, a linha também irá conter o identificador do elemento de radio.

Como você pode ver, quando a função EventChartCustom() não for suficiente, uma string com o número necessário dos parâmetros para a identificação exata sempre poderá ser formada. Similar aos nomes dos objetos gráficos, os parâmetros serão divididos pelo sublinhado "_".

O estado do elemento caixa de seleção e radio também será alterado no mesmo bloco. Abaixo está uma versão abreviada do método CMenuItem::OnClickMenuItem(). Ele mostra apenas o código que deve ser adicionada ao bloco se não.

//+------------------------------------------------------------------+
//| Clique no cabeçalho do elemento                                  |
//+------------------------------------------------------------------+
bool CMenuItem::OnClickMenuItem(const string clicked_object)
  {
//--- Verificação pelo nome do objeto
//--- Retorna, se o item não for ativado
//--- Se este elemento contém um menu de contexto
      //... 
//--- Se este elemento não contém um menu de contexto, mas é uma parte de um menu de contexto em si
   else
     {
      //--- Prefixo da mensagem com o nome do programa
      string message=CElement::ProgramName();
      //--- Se este é uma caixa de seleção, altera o seu 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);
         //--- Adiciona à mensagem que esta é uma caixa de seleção
         message+="_checkbox";
        }
      //--- Se este é um botão de radio, muda o seu 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);
         //--- Adiciona à mensagem que este é um elemento de radio
         message+="_radioitem_"+(string)m_radiobutton_id;
        }
      //--- Envia uma mensagem sobre ele
      ::EventChartCustom(m_chart_id,ON_CLICK_MENU_ITEM,m_index,CElement::Id(),message);
     }
//---
   return(true);
  }

Um evento personalizado com o identificador ON_CLICK_MENU_ITEM foi designado para o manipulador da classe menu de contexto (CContextMenu). Nós vamos precisar dos métodos adicionais para extrair o identificador do parâmetro da string do evento se o clique foi no elemento de radio e também para obter o índice em relação ao grupo que este elemento de radio pertence. Você pode ver o código dos métodos abaixo.

À medida que a extração do identificador a partir do parâmetro de string depender da estrutura da cadeia transmitida, o método CContextMenu::RadioIdFromMessage() irá conter as verificações adicionais para correção da string formada e caso ela exceda o tamanho do array.

Obtém o identificador do elemento de radio pelo índice geral no início do método CContextMenu::RadioIndexByItemIndex(), que é dedicado retornar o índice do elemento de radio através do índice geral. Use o método CContextMenu::RadioItemIdByIndex() escrito anteriormente. Depois disso, conte os elementos de radio com este identificador no loop. Tendo feito isso para o item de rádio com o índice geral cujo o valor é igual ao índice passado, armazena o valor do contador e para o loop. Isso significa que o último valor do contador será o índice que precisa ser retornado.

class CContextMenu : public CElement
  {
private:
   //--- Obtendo (1) o identificador e (2) o índice a partir da mensagem do elemento de radio.
   int               RadioIdFromMessage(const string message);
   int               RadioIndexByItemIndex(const int index);
   //---
  };
//+------------------------------------------------------------------+
//| Extrai o identificador da mensagem para o elemento de radio      |
//+------------------------------------------------------------------+
int CContextMenu::RadioIdFromMessage(const string message)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtém o código do separador
   u_sep=::StringGetCharacter("_",0);
//--- Divide a string
   ::StringSplit(message,u_sep,result);
   array_size=::ArraySize(result);
//--- Se a estrutura da mensagem difere do esperado
   if(array_size!=3)
     {
      ::Print(__FUNCTION__," > Estrutura errada na mensagem para o elemento de radio! message: ",message);
      return(WRONG_VALUE);
     }
//--- Prevenção para exceder o tamanho do array
   if(array_size<3)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Retorna o ID do item de rádio
   return((int)result[2]);
  }
//+------------------------------------------------------------------+
//| Retorna o índice do elemento de radio através do índice geral    |
//+------------------------------------------------------------------+
int CContextMenu::RadioIndexByItemIndex(const int index)
  {
   int radio_index =0;
//--- Obtém o ID do elemento de rádio pelo índice Geral
   int radio_id =RadioItemIdByIndex(index);
//--- Contador de elementos do grupo desejado
   int count_radio_id=0;
//--- Itera sobre a lista
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Se isto não é um elemento de radio, passar para a próxima
      if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON)
         continue;
      //--- Se os identificadores forem iguais
      if(m_items[i].RadioButtonID()==radio_id)
        {
         //--- Se os índices corresponder 
         //    Armazena o valor atual do contador e completar o loop
         if(m_items[i].Index()==index)
           {
            radio_index=count_radio_id;
            break;
           }
         //--- Aumenta o contador
         count_radio_id++;
        }
     }
//--- Retorna o índice
   return(radio_index);
  }

Agora, vamos criar o método CContextMenu::ReceiveMessageFromMenuItem() para o tratamento do evento personalizado ON_CLICK_MENU_ITEM a partir do elemento de menu. Os seguintes parâmetros de evento devem ser passados para este método: o identificador, o índice e a mensagem de string. As condições caso esta mensagem seja recebida de nosso programa e se a condições dos identificadores são verificadas no início deste método. Se a verificação for positiva e se esta mensagem foi enviada a partir de um produto de rádio, a troca é realizada no grupo que foi definido pelo identificador e o produto requerido pelo índice. O identificador e o índice podem ser obtidos com a ajuda dos métodos criados acima. 

Independentemente do tipo de elemento de menu a partir do qual uma mensagem é proveniente de, no caso da verificação do nome do programa e comparação dos identificadores bem-sucedidos, o ON_CLICK_CONTEXTMENU_ITEM personalizado mensagem é enviada. Ela é dirigida para o manipulador na classe CProgram do aplicativo personalizado. Em conjunto com a mensagem, os seguintes parâmetros são a: (1) Identificador, (2) índice geral na lista do menu de contexto (3) exibindo o texto do elemento.

No final do método, independentemente da primeira verificação (1) o menu de contexto é oculto, (2) o formulário é desbloqueado (3) e um sinal de fechamento para todos os menus de contexto é enviado.

class CContextMenu : public CElement
  {
private:
   //--- Recebendo uma mensagem do elemento de menu para manipulação
   void              ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item);
   //---
  };
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipulação do 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;
      //--- Recebendo uma mensagem do elemento de menu para manipulação
      ReceiveMessageFromMenuItem(item_id,item_index,item_message);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Recebendo uma mensagem do elemento de menu para manipulação      |
//+------------------------------------------------------------------+
void CContextMenu::ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item)
  {
//--- Se houver uma indicação de que a mensagem foi recebida a partir deste programa e o ID do elemento se correspondem
   if(::StringFind(message_item,CElement::ProgramName(),0)>-1 && id_item==CElement::Id())
     {
      //--- Se o clique foi no elemento de radio
      if(::StringFind(message_item,"radioitem",0)>-1)
        {
         //--- Obtém o ID do elemento de rádio a partir da mensagem passada
         int radio_id=RadioIdFromMessage(message_item);
         //--- Obtém o índice do elemento de radio pelo índice geral
         int radio_index=RadioIndexByItemIndex(index_item);
         //--- Muda o elemento do botão de radio
         SelectedRadioItem(radio_index,radio_id);
        }
      //--- Envia uma mensagem sobre ele
      ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,index_item,id_item,DescriptionByIndex(index_item));
     }
//--- Oculta o menu de contexto
   Hide();
//--- Desbloqueia o formulário
   m_wnd.IsLocked(false);
// --- Enviar um sinal para esconder todos os menus de contexto
   ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
  }

 

 


Teste do Recebimento das mensagens na Classe Personalizada da Aplicação

Agora, nós podemos testar e receber tal mensagem no manipulador da classe CProgram. Para isso, adicione o código a ele como mostrado abaixo:

//+------------------------------------------------------------------+
//| Manipulador 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);
     }
  }

Agora, compile os arquivos e carregar o EA para o gráfico. Quando os elementos de menu são clicados, as mensagens com os parâmetros desses itens serão impressos no registro do EA:

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

We have completed the development of the main part of the CContextMenu class for creating a context menu. Isso irá exigir algumas adições mais tarde, mas vamos voltar a este quando os problemas se manifestam em testes. Em suma, nós vamos seguir a sequência natural de narração como desta forma, é mais fácil para estudar o material.

 


Conclusão

Neste artigo, nós melhoramos as classes de elementos criados nos artigos anteriores. Agora, nós temos tudo pronto para desenvolver o elemento do menu principal. Nós vamos trabalhar nisso no próximo artigo.

Você pode buscar e baixar os arquivos da biblioteca no atual estágio de desenvolvimento, as imagens e os arquivos dos programas discutidos no artigo (o EA, indicadores e o script) podem ser baixados para testes nos terminais MetaTrader 4 e MetaTrader 5. Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo. 

Lista de artigos (capítulos) da segunda parte:

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2204

Arquivos anexados |
Interfaces Gráficas II: O Elemento Menu Principal (Capítulo 4) Interfaces Gráficas II: O Elemento Menu Principal (Capítulo 4)
Este é o capítulo final da segunda parte da série sobre interfaces gráficas. Aqui, nós vamos estudar a criação do menu principal. Demonstraremos neste artigo o desenvolvimento deste controle e a configuração dos manipuladores das classes da biblioteca para que ele tenha uma reação correta para as ações do usuário. Nós também vamos discutir como anexar os menus de contexto para os elementos do menu principal. Além disso, nós vamos mencionar a questão do bloqueio dos elementos inativos atualmente.
Como criar um indicador de gráfico não padronizado no Mercado MetaTrader Como criar um indicador de gráfico não padronizado no Mercado MetaTrader
Através de gráficos off-line, programação em MQL4 e uma disposição razoável, você pode obter uma variedade de tipos de gráficos: "Point & Figure", "Renko", "Kagi", "Range bars", gráficos equivolumes, etc. Neste artigo, vamos mostrar como isso pode ser alcançado sem o uso de DLL e como indicadores "dois-para-um" podem ser publicados e comprados no mercado.
Por onde começar ao criar um robô de negociação para operar na Bolsa de Valores de Moscou MOEX Por onde começar ao criar um robô de negociação para operar na Bolsa de Valores de Moscou MOEX
Na bolsa de valores de Moscou, muitos traders gostariam de automatizar seus algoritmos de negociação, mas não sabem por onde começar. A linguagem MQL5 não só oferece uma enorme gama de funções de negociação, mas também classes prontas que facilitam os primeiros passos no mundo do trading algorítmico.
Que testes deve passar o robô de negociação antes da publicação no Mercado Que testes deve passar o robô de negociação antes da publicação no Mercado
Todos os produtos do Mercado, antes de serem publicados, passam uma revisão preliminar obrigatória para garantir um único padrão de qualidade. Neste artigo, vamos falar sobre os erros mais comuns que os desenvolvedores cometem ao trabalhar com os seus indicadores técnicos e robôs de negociação. Além disso, mostraremos como testar por si mesmo o seu produto antes de enviá-lo para o Mercado.