Interfaces Gráficas II: O Elemento Menu Principal (Capítulo 4)
Conteúdo
- Introdução
- Desenvolvimento da Classe para Criar o Menu Principal
- Teste da Colocação do Menu Principal
- Bloqueio dos Controles Inativos
- Métodos de Comunicação com o Menu Principal
- Teste Final do Menu Principal
- Conclusão
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.
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.
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.
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.
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.
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.
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:
- Interfaces Gráficas II: O Elemento de Menu (Capítulo 1)
- Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)
- Interfaces Gráficas II: Configuração dos manipuladores de eventos da Biblioteca (Capítulo 3)
- Interfaces Gráficas II: O Elemento Menu Principal (Capítulo 4)
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2207
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso