English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas II: O Elemento Menu Principal (Capítulo 4)

Interfaces Gráficas II: O Elemento Menu Principal (Capítulo 4)

MetaTrader 5Exemplos | 15 agosto 2016, 09:28
1 008 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) considera em detalhes a finalidade desta biblioteca. A lista completa dos links para os artigos se encontra no final de cada capítulo da série. 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.    

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 falar sobre como bloquear os elementos que estão atualmente inativos.

 


Desenvolvimento da Classe para Criar o Menu Principal

As classes para a criação de todos os elementos para a construção do menu principal do programa foram desenvolvidas nos três capítulos anteriores. Nós temos as seguintes classes:

  • CMenuItem – elemento de menu.
  • CSeparateLine – linha de separação.
  • CContextMenu – menu de contexto.

Crie o arquivo MenuBar.mqh na pasta Controls localizada no diretório que contém os arquivos de todos os elementos. Neste arquivo, inclua o arquivo que contém a classe base, arquive-o com a classe de formulário e os arquivos de todos os elementos compostos que são componentes dela:

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

Os objetos básicos do menu principal são os elementos de menu e o fundo. Os menus de contexto serão anexados aos elementos de menu principal via ponteiros.

Fig. 1. Objetos básicos do menu principal.

Fig. 1. Objetos básicos do menu principal.


A seguir, apresentamos no código abaixo o formulário inicial da classe CMenuBar com as instâncias de classes necessárias, ponteiros e métodos virtuais padrões para cada elemento.

//+------------------------------------------------------------------+
//| Classe para criar o menu principal                               |
//+------------------------------------------------------------------+
class CMenuBar : public CElement
  {
private:
   //--- Ponteiro para o formulário ao qual o elemento está anexado
   CWindow          *m_wnd;
   //--- Objetos para a criação de um elemento de menu
   CRectLabel        m_area;
   CMenuItem         m_items[];
   //--- Array de ponteiros do menu de contexto
   CContextMenu     *m_contextmenus[];
   //---
public:
                     CMenuBar(void);
                    ~CMenuBar(void);
   //--- Armazena o ponteiro do formulário
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }

   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Move o elemento
   virtual void      Moving(const int x,const int y);
   //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //---
  };

Semelhante a qualquer outro elemento da interface, deve-se haver a possibilidade de configurar o aparência do menu principal. Os campos e métodos serão criados especificamente para essa finalidade permitindo a configuração de:

  • propriedades do fundo do menu principal;
  • propriedades gerais dos elementos de menu.

Além disso, serão necessários os métodos para definir e obter o estado atual do menu principal. Todos os campos têm de ser inicializado no construtor pelos valores padrão. A altura padrão do menu principal será de 22 pixels. A altura dos elementos de menu serão calculados automaticamente em relação à altura do fundo do menu principal. Este valor, no entanto, pode ser modificado antes de aplicar o elemento ao gráfico, com a ajuda do método CElement::YSize() da classe base. A largura do fundo do menu principal é igual à largura do formulário que ele está anexado. É por isso que nós vamos fazer o cálculo deste parâmetro automatizado ao anexar o elemento para o gráfico.

class CMenuBar : public CElement
  {
private:
   //--- Propriedades do fundo
   int               m_area_zorder;
   color             m_area_color;
   color             m_area_color_hover;
   color             m_area_border_color;
   //--- Propriedades gerais dos elementos de menu
   int               m_item_y_size;
   color             m_item_color;
   color             m_item_color_hover;
   color             m_item_border_color;
   int               m_label_x_gap;
   int               m_label_y_gap;
   color             m_label_color;
   color             m_label_color_hover;
   //--- Estado do menu principal
   bool              m_menubar_state;
   //---
public:
   //--- Cor do (1) fundo e (2) a estrutura fundo do menu principal
   void              MenuBackColor(const color clr)       { m_area_color=clr;                    }
   void              MenuBorderColor(const color clr)     { m_area_border_color=clr;             }
   //--- (1) cor de fundo, (2) cor de fundo quando o cursor está sobre ele e (3) a cor da estrutura dos principais elementos de menu
   void              ItemBackColor(const color clr)       { m_item_color=clr;                    }
   void              ItemBackColorHover(const color clr)  { m_item_color_hover=clr;              }
   void              ItemBorderColor(const color clr)     { m_item_border_color=clr;             }
   //--- Margens do rótulo de texto a partir do ponto da borda do fundo do elemento de menu
   void              LabelXGap(const int x_gap)           { m_label_x_gap=x_gap;                 }
   void              LabelYGap(const int y_gap)           { m_label_y_gap=y_gap;                 }
   //--- (1) Padrão e (2) cor do texto em foco
   void              LabelColor(const color clr)          { m_label_color=clr;                   }
   void              LabelColorHover(const color clr)     { m_label_color_hover=clr;             }
   //--- Estado do menu principal
   void              State(const bool state);
   bool              State(void)                    const { return(m_menubar_state);             }
   //---
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CMenuBar::CMenuBar(void) : m_menubar_state(false),
                           m_area_zorder(0),
                           m_area_color(C'240,240,240'),
                           m_area_border_color(clrSilver),
                           m_item_color(C'240,240,240'),
                           m_item_color_hover(C'51,153,255'),
                           m_item_border_color(C'240,240,240'),
                           m_label_x_gap(15),
                           m_label_y_gap(3),
                           m_label_color(clrBlack),
                           m_label_color_hover(clrWhite)
  {
//--- Armazena o nome da classe do elemento na classe base
   CElement::ClassName(CLASS_NAME);
//--- Altura padrão do menu principal
   m_y_size=22;
  }
//+------------------------------------------------------------------+
//| Define o estado do menu principal                                |
//+------------------------------------------------------------------+
void CMenuBar::State(const bool state)
  {
   if(state)
      m_menubar_state=true;
   else
     {
      m_menubar_state=false;
      //--- Itera sobre todos os elementos do menu principal para
      //    definir o estado de menus de contexto desativados
      int items_total=ItemsTotal();
      for(int i=0; i<items_total; i++)
         m_items[i].ContextMenuState(false);
      //--- Desbloqueia o formulário
      m_wnd.IsLocked(false);
     }
  }

Para configurar as propriedades únicas para cada elemento de menu, é necessário de um array. As propriedades únicas são:

  • largura do elemento de menu;
  • texto exibido.

Estas propriedades serão estabelecidas antes de colocar o menu principal ao gráfico, ou seja, durante a sua formação no momento da adição de cada elemento. Para isso, o método CMenuBar::AddItem() será utilizado de forma semelhante aquele criado no início da classe CContextMenu. A única diferença entre eles é a passagem do conjunto de parâmetros. 

Vamos criar o método CMenuBar::AddContextMenuPointer() para anexar os menus de contexto a cada elemento do menu principal. O índice do elemento do menu principal e o objeto do menu de contexto devem ser passados para este método. O ponteiro para o objeto do menu de contexto será armazenado no array m_contextmenus[].

class CMenuBar : public CElement
  {
private:
   //--- Arrays de propriedades exclusivas dos elementos de menu:
   //    (1) Largura, (2) texto
   int               m_width[];
   string            m_label_text[];
   //---
public:
   //--- Adiciona o elemento de menu com as propriedades especificadas antes da criação do menu principal
   void              AddItem(const int width,const string text);
   //--- Coloca o menu de contexto passado para o elemento especificado do menu principal
   void              AddContextMenuPointer(const int index,CContextMenu &object);
   //---
  };
//+------------------------------------------------------------------+
//| Adiciona um elemento de menu                                     |
//+------------------------------------------------------------------+
void CMenuBar::AddItem(const int width,const string text)
  {
//--- Incrementa o tamanho dos arrays por um elemento  
   int array_size=::ArraySize(m_items);
   ::ArrayResize(m_items,array_size+1);
   ::ArrayResize(m_contextmenus,array_size+1);
   ::ArrayResize(m_width,array_size+1);
   ::ArrayResize(m_label_text,array_size+1);
//--- Armazena os valores dos parâmetros passados
   m_width[array_size]      =width;
   m_label_text[array_size] =text;
  }
//+------------------------------------------------------------------+
//| Adiciona o ponteiro do menu de contexto                          |
//+------------------------------------------------------------------+
void CMenuBar::AddContextMenuPointer(const int index,CContextMenu &object)
  {
//--- Verifica se o tamanho não foi excedido
   int size=::ArraySize(m_contextmenus);
   if(size<1 || index<0 || index>=size)
      return;
//--- Armazena o ponteiro
   m_contextmenus[index]=::GetPointer(object);
  }

Será necessário também os métodos para obter o ponteiro para o elemento do menu principal e o ponteiro para um dos menus de contexto que estão anexados aos elementos. Cada um destes métodos contará com a verificação do tamanho do array e o ajuste do índice caso o tamanho do array se exceda. Além disso, será realizado em larga escala a iteração através dos elementos do menu principal e dos menus de contexto. É por isso que os métodos para obter os tamanhos de seus arrays são obrigatórios.

class CMenuBar : public CElement
  {
public:
   //--- (1) Obter o ponteiro para o elemento do menu especificado, (2) obter o ponteiro para o menu de contexto especificado
   CMenuItem        *ItemPointerByIndex(const int index);
   CContextMenu     *ContextMenuPointerByIndex(const int index);

   //--- Quantidade dos (1) elementos de menu e (2) dos menus de contexto
   int               ItemsTotal(void)               const { return(::ArraySize(m_items));        }
   int               ContextMenusTotal(void)        const { return(::ArraySize(m_contextmenus)); }
   //---
  };
//+------------------------------------------------------------------+
//| Retorna o ponteiro do elemento de menu pelo índice               |
//+------------------------------------------------------------------+
CMenuItem *CMenuBar::ItemPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- Se o menu principal não tiver nenhum elemento, registra
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
      "quando o menu principal tiver, pelo menos, um elemento!");
     }
//--- Ajuste no caso do tamanho exceder
   int i=(index>=array_size)? array_size-1 : (index<0)? 0 : index;
//--- Retorna o ponteiro
   return(::GetPointer(m_items[i]));
  }
//+------------------------------------------------------------------+
//| Retorna o ponteiro do menu de contexto pelo índice               |
//+------------------------------------------------------------------+
CContextMenu *CMenuBar::ContextMenuPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_contextmenus);
//--- Se o menu principal não tiver nenhum elemento, registra
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
      "quando o menu principal tiver, pelo menos, um elemento!");
     }
//--- Ajuste no caso do tamanho exceder
   int i=(index>=array_size)? array_size-1 : (index<0)? 0 : index;
//--- Retorna o ponteiro
   return(::GetPointer(m_contextmenus[i]));
  }

O processo de construção do menu principal não tem nenhuma diferença fundamental da criação do menu de contexto na classe CContextMenu. Na verdade, a criação do menu principal é um pouco mais simples já que o menu principal não exige que haja um ponteiro para o nó anterior. Este não contém quaisquer linhas de separação. Nós não iremos estudar o código desses métodos para economizar espaço no artigo. Você pode visualizar eles no arquivo MenuBar.mqh anexado no final deste artigo.

Anteriormente, para assegurar que os elementos do menu contexto cheguem à base de todos os elementos na classe CWndContainer, o método especial AddContextMenuElements() foi escrito. Ele é chamado no método principal CWndContainer::AddToElementsArray() para adicionar os elementos à base. O mesmo método deve ser escrito para o elemento do menu principal, caso contrário, os elementos de menu não se deslocarão juntamente com o formulário e não alterará a sua cor quando o cursor do mouse estiver sobre eles. 

Abaixo está uma pequena lista de ações que devem ser executadas para os elementos compostos de algum elemento principal para chegar à base e o ponteiro do elemento principal para chegar ao array privado.

  • Inclua o arquivo com a classe de elemento para o arquivo WndContainer.mqh.
  • Adicione o elemento do array para a estrutura WindowElements.
  • Adicione o método para obter o número dos menus principais no array privado.
  • Declare e implemente o método privado para armazenar os ponteiros para os elementos que fazem parte de um outro elemento principal.
  • Coloque a chamada do novo método no método comum na qual ele deve ser usado na classe da aplicação para adicionar o ponteiro do elemento principal para a base.

Abaixo está uma versão resumida do código do arquivo WndContainer.mqh que apresenta apenas o que precisa ser adicionado nele:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "MenuBar.mqh"
//+------------------------------------------------------------------+
//| Classe para armazenar todos os objetos da interface              |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Estrutura de arrays de elementos
   struct WindowElements
     {
      //---Array do menu principal
      CMenuBar         *m_menu_bars[];
     };
   //---
public:
   //--- Número dos menus principais
   int               MenuBarsTotal(const int window_index);
   //---
private:
   //--- Armazena os ponteiros para os elementos do menu principal na base
   bool              AddMenuBarElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Retorna o número de menus principais pelo índice da janela especificada|
//+------------------------------------------------------------------+
int CWndContainer::MenuBarsTotal(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_menu_bars));
  }
//+------------------------------------------------------------------+
//| Adiciona um ponteiro ao array de elemento                        |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Se a base não contém formulários para os controles
/ --- Se a solicitação for para um formulário que não existe
//--- Adiciona ao array comum de elementos
//--- Adiciona os objetos do elemento para o array comum de objetos
//--- Armazena o ID do último elemento em todos os formulários
//--- Aumenta o contador de identificadores do elemento
//--- Armazena os ponteiros para os objetos do menu de contexto na base
//--- Armazena os ponteiros para os objetos do menu principal na base
   if(AddMenuBarElements(window_index,object))
      return;
  }
//+------------------------------------------------------------------+
//| Armazena os ponteiros para os objetos do menu principal na base  |
//+------------------------------------------------------------------+
bool CWndContainer::AddMenuBarElements(const int window_index,CElement &object)
  {
//--- Sai, se isso não for o menu principal
   if(object.ClassName()!="CMenuBar")
      return(false);
//--- Obtém o ponteiro do menu principal
   CMenuBar *mb=::GetPointer(object);
//--- Armazena os ponteiros para os seus objetos na base
   int items_total=mb.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=mb.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(mb,m_wnd[window_index].m_menu_bars);
   return(true);
  }

 

 

Teste da Colocação do Menu Principal

Nesta fase, nós podemos testar a configuração do menu principal. Nós vamos construir esse elemento desses três elementos. A largura e a exibição do texto para cada um dos elementos será configurado enquanto o resto das propriedades ficará como o padrão.

Adicione o código para criar o menu principal para a classe da aplicação CProgram, como mostrado no código abaixo. Ajuste as coordenadas dos outros elementos que foram anexados ao formulário anteriormente a isto. E finalmente, coloque a chamada do novo método CProgram::CreateMenuBar() no método principal para criar a interface gráfica.

class CProgram : public CWndEvents
  {
private:
   //--- Menu principal
   CMenuBar          m_menubar;
   //---
private:
   //---
#define MENUBAR_GAP_X    (1)
#define MENUBAR_GAP_Y    (20)
   bool              CreateMenuBar(void);
   //---
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (45)
   //---
#define SEP_LINE_GAP_X   (6)
#define SEP_LINE_GAP_Y   (75)
   //---
  };
//+------------------------------------------------------------------+
//| Cria o painel de negociação                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Criação de um formulário para os controles
//--- Criação dos controles:
//    Menu principal
   if(!CreateMenuBar())
      return(false);
//--- Elemento de menu
//--- Linha de separação
//--- Redesenho do gráfico
   return(true);
  }
//+------------------------------------------------------------------+
//| Cria o menu principal                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateMenuBar(void)
  {
//--- Três elementos no menu de principal
#define MENUBAR_TOTAL 3
//--- Armazena o ponteiro da janela
   m_menubar.WindowPointer(m_window);
//--- Coordenadas
   int x=m_window.X()+MENUBAR_GAP_X;
   int y=m_window.Y()+MENUBAR_GAP_Y;
//--- Arrays de propriedades exclusivas de cada elemento:
   int    width[MENUBAR_TOTAL] ={50,55,53};
   string text[MENUBAR_TOTAL]  ={"File","View","Help"};
//--- Adiciona os elementos ao menu principal
   for(int i=0; i<MENUBAR_TOTAL; i++)
      m_menubar.AddItem(width[i],text[i]);
//--- Cria um controle
   if(!m_menubar.CreateMenuBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Adiciona o objeto para o array comum dos grupos de objetos
   CWndContainer::AddToElementsArray(0,m_menubar);
   return(true);
  }

Compile os arquivos e carregue o EA ao gráfico. O resultado deve ser como na imagem abaixo. O menu principal estará se deslocando juntamente com o formulário e seus elementos alterarão de cores quando o cursor do mouse estiver sobre eles.

Fig. 2. Teste do elemento do menu principal.

Fig. 2. Teste do elemento do menu principal.

 

 


Bloqueio dos Controles Inativos

Antes de criar os menus de contexto e anexá-los aos elementos do menu principal, nossa biblioteca necessita de uma funcionalidade que bloqueie o formulário e outros elementos quando um dos elementos está ativado. Por que isso tem que ser feito? Os elementos ativados aqui são os que se tornam visíveis (chamado) através de outros elementos e se ocultam quando eles não forem mais necessários. Por exemplo, as listas suspensas, os menus de contexto, calendários e "curtir" pertencem a este grupo. Tente ativar qualquer menu de contexto ou uma lista suspensa no terminal de negociação MetaTrader. Você verá que quando um elemento deste tipo é ativado, outros controles em todo o terminal se tornam indisponíveis. Is se manifesta, por exemplo, quando eles não alteram sua cor quando o cursor do mouse estiver sobre eles. A razão para tal bloqueio consiste em evitar uma situação quando o cursor reage a um elemento que está temporariamente ofuscado por uma lista suspensa.

Para implementar este mesmo comportamento, basta bloquear o formulário ao qual o controle ativado pertence. Qualquer outro elemento neste formulário tem acesso a ele através do ponteiro e, portanto, é possível verificar qual o seu estado em qualquer momento. O princípio aqui é muito simples - se o formulário está bloqueado, então, não é necessário chamar o método para modificar a cor do elemento.

Adicione um campo especial e os métodos para configurar e obter o estado do formulário (bloqueado/desbloqueado) para a classe CWindow para a criação de um formulário como mostrado no código abaixo. O campo m_is_locked no construtor deve ser inicializado pelo valor false. Isto significa que o formulário deve ser desbloqueado por padrão. Nós podemos adicionar a condição que define a cor do formulário e seus elementos devem mudar de cor somente quando este não estiver bloqueado.

class CWindow : public CElement
  {
private:
   //--- Estado de uma janela bloqueada
   bool              m_is_locked;
   //---
public:
   //--- Estado de uma janela bloqueada
   bool              IsLocked(void)                                    const { return(m_is_locked);                }
   void              IsLocked(const bool flag)                               { m_is_locked=flag;                   }
   //---
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_is_locked(false)
  {
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWindow::OnEventTimer(void)
  {
//--- Se a janela não está bloqueada
   if(!m_is_locked)
     {
      //--- Alterando a cor dos objetos do formulário
      ChangeObjectsColor();
     }
  }

Quanto aos outros controles, neste momento nós temos apenas um elemento que é clicável que nós podemos testar está funcionalidade - o elemento de menu. A mudança da cor do elemento de menu, quando o cursor do mouse estiver sobre ele, dependerá do estado do formulário ao qual ele está ligado. Portanto, essa verificação deve ser colocada também na sua classe CMenuItem como mostrado no código abaixo.

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CMenuItem::OnEventTimer(void)
  {
//--- Se a janela está disponível
   if(!m_wnd.IsLocked())
     {
      //--- Se o estado de um menu de contexto está desativado
      if(!m_context_menu_state)
         //--- Alterando a cor dos objetos do formulário
         ChangeObjectsColor();
     }
  }

O formulário deve ser bloqueado no momento em que o menu de contexto se tornar visível, que está no método CContextMenu::Show().

//+------------------------------------------------------------------+
//| Exibe o menu de contexto                                         |
//+------------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- Retorna, se o elemento já for visível
//--- Mostra os objetos do menu de contexto
//--- Mostra os elementos de menu
//--- Atribui o estado de um elemento visível
//--- Estado do menu de contexto
//--- Registra o estado no nó anterior
//--- Bloqueia o formulário
   m_wnd.IsLocked(true);
  }

Pode parecer que chamar o método CWindow::IsLocked() no método CContextMenu::Hide() já é suficiente para o desbloqueio. Esta opção não é adequada já que vários menus de contexto podem ser abertos ao mesmo tempo. Nem todos deles são fechados simultaneamente. Vamos recordar quais casos os menus de contexto abertos são fechados ao todo. Algumas condições devem ser satisfeitas para isso. Por exemplo, no método CContextMenu::CheckHideContextMenus(), quando após a verificação de todas as condições, envia-se um sinal para fechar todos os menus de contexto. O segundo caso é no método CContextMenu::ReceiveMessageFromMenuItem(), quando o evento ON_CLICK_MENU_ITEM está sendo manipulado. 

Vamos adicionar o desbloqueio do formulário nestes métodos. A seguir estão listadas as versões reduzidas dos métodos. Os comentários irá lhe ajudar a identificar onde o código destacado em amarelo deve ser adicionado.

//+------------------------------------------------------------------+
//| 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
//--- 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
//--- Desbloqueia o formulário
   m_wnd.IsLocked(false);
//--- Enviar um sinal para esconder todos os menus de contexto
  }
//+------------------------------------------------------------------+
//| 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
//--- Oculta o menu de contexto
//--- Desbloqueia o formulário
   m_wnd.IsLocked(false);
  }

Se, nesta fase, todos os arquivos são compilados e o EA testado anteriormente for carregado no gráfico, nós veremos logo após o menu de contexto ser aberto que tudo está funcionando de forma bem diferente do que se espera. Todos os elementos de menu, mesmo os que estão no menu de contexto ativado, serão impedidos de mudar sua cor. Isso não deveria estar acontecendo. Em tais casos, um menu de contexto deve ter seu próprio método para alterar a cor de seus elementos. Vamos criar tal método na classe CContextMenu e localizar a sua chamada no timer como mostrado no código abaixo.

class CContextMenu : public CElement
  {
public:
   //--- Altera a cor dos elementos de menu quando o cursor estiver sobre eles
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CContextMenu::OnEventTimer(void)
  {
//--- Altera a cor dos elementos de menu quando o cursor estiver sobre eles
   ChangeObjectsColor();
  }
//+------------------------------------------------------------------+
//| Alterar a cor do objeto quando o cursor estiver pairando sobre ele|
//+------------------------------------------------------------------+
void CContextMenu::ChangeObjectsColor(void)
  {
//--- Retorna, se o menu de contexto estiver desativado
   if(!m_context_menu_state)
      return;
//--- Itera sobre todos os elementos do menu
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Muda a cor do elemento de menu
      m_items[i].ChangeObjectsColor();
     }
  }

Agora, tudo deve estar funcionando como projetado.

Fig. 3. Teste do bloqueio do formulário e todos os controles, exceto o que está ativo no momento.

Fig. 3. Teste do bloqueio do formulário e todos os controles, exceto o que está ativo no momento.

 

 


Métodos de Comunicação com o Menu Principal

Nós vamos continuar a desenvolver a classe CMenuBar para criar o menu principal. A parte restante envolve o gerenciamento deste elemento. Vamos analisar em detalhes como que o menu principal funciona com os exemplos de outros programas. Após o programa ser carregado, o menu principal é desativado por padrão. Neste estado, quando o cursor do mouse estiver sobre os elementos do menu principal, eles serão apenas realçados. Quando um dos elementos é clicado, o menu principal é ativado e o menu de contexto aparece a partir do elemento que ele foi clicado. Quando o menu principal é ativado desta forma, se o cursor do mouse está se movendo ao longo dos elementos de menu, os menus de contexto mudarão de forma automática dependendo de qual elemento o cursor do mouse estiver pairado.

Antes de implementar tal comportamento em nossa biblioteca, vamos criar três menus de contexto e anexá-los aos elementos do menu principal. Para economizar espaço, nós vamos analisar uma versão reduzida de um deles. Você pode visualizar os arquivos completos do AE no final deste artigo. 

O código abaixo contém apenas as linhas mais significativas do código. Por favor, note como o ponteiro para o nó anterior é passado para o menu de contexto e o ponteiro do menu de contexto é armazenada no menu principal. O cálculo das coordenadas a partir da parte inferior do elemento deve ser estabelecido quando as propriedades dos menus de contexto para o menu principal são configurados. Em todos os outros aspectos, a criação do menu de contexto não é diferente da que foi considerada anteriormente.

//+------------------------------------------------------------------+
//| Cria um menu de contexto                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateMBContextMenu1(void)
  {
//--- Três elementos do menu de contexto
//--- Armazena o ponteiro da janela
   m_mb_contextmenu1.WindowPointer(m_window);
//--- Armazena o ponteiro para o nó anterior
   m_mb_contextmenu1.PrevNodePointer(m_menubar.ItemPointerByIndex(0));
// --- Fixa o menu de contexto para o elemento de menu desejado.
   m_menubar.AddContextMenuPointer(0,m_mb_contextmenu1);
//--- Array de nomes dos elementos
//--- Array dos rótulos para o modo disponível
//--- Array do rótulo para o modo bloqueado
//--- Array dos tipos de elementos
//--- Configura as propriedades antes da criação
   m_mb_contextmenu1.FixSide(FIX_BOTTOM);
//--- Adiciona os elementos ao menu de contexto
//--- Linha de separação após o segundo item
//--- Desativar o segundo elemento.
//--- Cria um menu de contexto
   if(!m_mb_contextmenu1.CreateContextMenu(m_chart_id,m_subwin))
      return(false);
//--- Adiciona o objeto para o array comum dos grupos de objetos
   CWndContainer::AddToElementsArray(0,m_mb_contextmenu1);
   return(true);
  }

Agora, vamos configurar os manipuladores de eventos na classe do menu principal СMenuBar. Nós vamos começar com o tratamento do clique nos elementos de menu. Antes disso, da mesma maneira que as classes CMenuItem e CContextMenu, será necessário o método OnClickMenuItem() e aqueles que possuem o papel de extrair o identificador do índice e do elemento de menu a partir do nome do objeto que recebeu o clique. 

Os métodos de identificação são os mesmos que se encontram na classe CContextMenu. Contudo, a manipulação do clique no elemento de menu possui suas peculiaridades na classe СMenuBar. A verificação do identificador é seguido por uma verificação se o ponteiro do menu de contexto está correto pelo índice obtido a partir do nome do objeto. Se não houver nenhum ponteiro, é enviado um sinal para fechar todos os menus de contexto que estão abertos. Se existe um ponteiro, então, um sinal para fechar todos os menus de contexto é enviado apenas no caso do clique ser para fechar o menu de contexto atual. 

class CMenuBar : public CElement
  {
private:
   //--- Manipulação do clique no elemento de menu
   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);
   //---
  };
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipulação de eventos do clique esquerdo do mouse sobre o elemento do menu principal
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickMenuItem(sparam))
         return;
     }
  }
//+------------------------------------------------------------------+
//| Clique no elemento do menu principal                             |
//+------------------------------------------------------------------+
bool CMenuBar::OnClickMenuItem(const string clicked_object)
  {
//--- 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 tipo definido não corresponder
   if(id!=CElement::Id())
      return(false);
//--- Se houver um ponteiro para o menu de contexto
   if(CheckPointer(m_contextmenus[index])!=POINTER_INVALID)
     {
      //--- O estado do menu principal depende da visibilidade do menu de contexto
      m_menubar_state=(m_contextmenus[index].ContextMenuState())? false : true;
      //--- Define o estado do formulário
      m_wnd.IsLocked(m_menubar_state);
      //--- Se o menu principal está desativado
      if(!m_menubar_state)
         //--- Enviar um sinal para esconder todos os menus de contexto
         ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
     }
//--- Se não há nenhum ponteiro do menu de contexto
   else
     {
      //--- Enviar um sinal para esconder todos os menus de contexto
      ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
     }
//---
   return(true);
  }

Como podemos lembrar, a manipulação do evento ON_HIDE_CONTEXTMENUS é realizada na classe CWndEvents. Nós precisamos adicionar ao método CWndEvents::OnHideContextMenus() mais um ciclo que irá forçar o desligamento de todos os menus principais que estão presentes na base de dados.

//+------------------------------------------------------------------+
//| 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);
//--- Oculta todos os menus de contexto
   int cm_total=CWndContainer::ContextMenusTotal(0);
   for(int i=0; i<cm_total; i++)
      m_wnd[0].m_context_menus[i].Hide();
//--- Desativa os menus principais
   int menu_bars_total=CWndContainer::MenuBarsTotal(0);
   for(int i=0; i<menu_bars_total; i++)
      m_wnd[0].m_menu_bars[i].State(false);
//---
   return(true);
  }

Se compilarmos todos os arquivos e carregar o EA ao gráfico, os menus de contexto serão abertos quando os elementos do menu principal forem clicados, fechando após o segundo clique.

Fig. 4. Teste da chamada dos menus de contexto a partir do menu principal.

Fig. 4. Teste da chamada dos menus de contexto a partir do menu principal.

 

Em seguida, nós vamos implementar os métodos que nos permitam alterar os menus de contexto movendo o cursor do mouse quando o menu principal for ativado, da mesma maneira que ele foi implementado nas aplicações das janelas. Para isso, nós precisaremos de um método para destacar os elementos do menu principal, quando eles forem ativados e um método auxiliar para definir o elemento ativo (o elemento em foco) do menu principal ativado. 

class CMenuBar : public CElement
  {
public:
   //--- Alterar a cor do objeto quando o cursor estiver pairando sobre ele
   void              ChangeObjectsColor(void);
   //---
private:
   //--- Retorna o elemento ativo do menu principal
   int               ActiveItemIndex(void);
   //---
  };
//+------------------------------------------------------------------+
//| Alterar a cor do objeto quando o cursor estiver pairando sobre ele|
//+------------------------------------------------------------------+
void CMenuBar::ChangeObjectsColor(void)
  {
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].ChangeObjectsColor();
  }
//+------------------------------------------------------------------+
//| Retorna o índice do elemento do menu ativado                     | 
//+------------------------------------------------------------------+
int CMenuBar::ActiveItemIndex(void)
  {
   int active_item_index=WRONG_VALUE;
//---
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Se o elemento está em foco
      if(m_items[i].MouseFocus())
        {
         //--- Armazena o índice e para o loop
         active_item_index=i;
         break;
        }
     }
//---
   return(active_item_index);
  }

Além disso, nós vamos criar um método que irá facilitar a mudança dos menus de contexto ao passar o cursor sobre ele, quando o menu principal estiver ativado. Vamos nomear este método de SwitchContextMenuByFocus(). O índice do elemento ativado do menu principal é para ser passado para este método. Este índice permitirá definir qual menu de contexto tem que se tornar visível. Todos os outros menus de contexto estão ocultos. Ao mesmo tempo, uma verificação é realizada se houver alguns menus de contexto abertos que são chamados a partir dos elementos do menu principal. Se for verificado que existem tais menus, então, o evento personalizado ON_HIDE_BACK_CONTEXTMENUS é gerado. Nós já estudamos este evento em detalhes neste artigo.

Após o menu de contexto ser oculto, a cor do elemento de menu tem de ser reposta para evitar ter dois elementos do menu principal sendo destacados, ao mesmo tempo.

class CMenuBar : public CElement
  {
private:
   //--- Muda os menus de contexto do menu principal ao passar o cursor sobre ele
   void              SwitchContextMenuByFocus(const int active_item_index);
   //---
  };
//+------------------------------------------------------------------+
//|Muda os menus de contexto do menu principal ao passar o cursor sobre ele|
//+------------------------------------------------------------------+
void CMenuBar::SwitchContextMenuByFocus(const int active_item_index)
  {
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Move para o seguinte elemento de menu se este não tiver um menu de contexto
      if(::CheckPointer(m_contextmenus[i])==POINTER_INVALID)
         continue;
      //--- Se você fazer ele para o elemento do menu especificado, faça om que o seu menu de contexto  seja visível
      if(i==active_item_index)
         m_contextmenus[i].Show();
      //--- Esconde o resto dos menus de contexto
      else
        {
         CContextMenu *cm=m_contextmenus[i];
         //--- Esconde os menus de contexto, que estão abertas a partir de outros menus de contexto.
         //    Itera sobre os elementos dos menus de contexto atuais para descobrir se houve alguma.
         int cm_items_total=cm.ItemsTotal();
         for(int c=0; c<cm_items_total; c++)
           {
            CMenuItem *mi=cm.ItemPointerByIndex(c);
            //--- Move para o elemento de menu seguinte se o ponteiro para este está incorreto
            if(::CheckPointer(mi)==POINTER_INVALID)
               continue;
            //--- Move para o elemento de menu seguinte, se este não tiver um menu de contexto
            if(mi.TypeMenuItem()!=MI_HAS_CONTEXT_MENU)
               continue;
            //--- Se houver um menu de contexto este estiver ativado
            if(mi.ContextMenuState())
              {
               //--- Envia um sinal para fechar todos os menus de contexto, que estão abertas a partir deste
               ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,"");
               break;
              }
           }
         //--- Oculta o menu de contexto do menu principal
         m_contextmenus[i].Hide();
         //--- Reseta a cor do elemento de menu
         m_items[i].ResetColors();
        }
     }
  }

Agora, nós só temos que adicionar os métodos recém-criados para o manipulador de eventos CHARTEVENT_MOUSE_MOVE da classe CMenuBar para verificar o evento do movimento do cursor do mouse:

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai, se o menu principal não foi ativado
      if(!m_menubar_state)
         return;
      //--- Obtém o índice do elemento ativado do menu principal
      int active_item_index=ActiveItemIndex();
      if(active_item_index==WRONG_VALUE)
         return;
      //--- Mude a cor se o foco foi alterado
      ChangeObjectsColor();
      //--- Desliga o menu de contexto pelo elemento ativado do menu principal
      SwitchContextMenuByFocus(active_item_index);
      return;
     }
  }

 


Teste Final do Menu Principal

Agora, nós podemos testar tudo o que foi feito neste artigo. Adicione vários elemento de menu independentes para o formulário. Adicione mais um menu de contexto interno para o menu principal e anexe isso para o segundo elemento do terceiro menu de contexto, como mostrado na imagem abaixo.

Compile os arquivos e carregue o EA ao gráfico. Para ter o mesmo resultado como na imagem abaixo, você pode carregar os arquivos anexados no final deste artigo.

Fig. 5. Teste geral do menu principal.

Fig. 5. Teste geral do menu principal.


Os arquivos anexados no final deste artigo também contêm um indicador para testes com uma interface gráfica semelhante ao do EA na imagem acima. Há também versões para testes na plataforma de negociação MetaTrader 4.

 

 


Conclusão

Este é o último artigo da segunda parte da série. Ele é vasto, porém, conseguimos analisar quase todas as partes componentes da biblioteca para o desenvolvimento de interfaces gráficas. A estrutura atual da biblioteca pode ser apresentada da mesma maneira que o esquema abaixo. A descrição detalhada pode ser encontrada no último capítulo da primeira parte.

Fig. 6. Estrutura da biblioteca, no atual estágio de desenvolvimento.

Fig. 6. Estrutura da biblioteca, no atual estágio de desenvolvimento.


Se você chegou a este ponto, a boa notícia é que a parte mais difícil do trabalho já foi feita. Na primeira e na segunda parte desta série nós discutimos os tópicos mais complexos relativos ao desenvolvimento das interfaces gráficas. Os artigos seguintes serão dedicados especialmente para a criação dos controles. O material será significativamente mais simples e não haverá tanta variedade de classes.

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

Arquivos anexados |
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.
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)
Os artigos anteriores contêm a implementação das classes para criar os componentes do menu principal. Agora, está na hora de olharmos com mais atenção os manipuladores de eventos nas classes base principais e dos controles criados. Nós também prestaremos uma atenção especial na gestão do estado do gráfico, dependendo da localização do cursor do mouse.
Interfaces Gráficas III: Botões Simples e Multifuncionais (Capítulo 1) Interfaces Gráficas III: Botões Simples e Multifuncionais (Capítulo 1)
Vamos começar a estudos sobre o controle chamado botão. Nós vamos mostrar exemplos de várias classes para a criação de um botão simples, botões com funcionalidades estendidas (botão com ícones/imagens e botão de divisão - "split button") e aqueles que são interconectados (grupos de botões e botão de radio). Além disso, nós vamos apresentar alguns incrementos para as classes existentes afim de ampliar a capacidade dos controles.
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.