Interfaces Gráficas II: Configuração dos manipuladores de eventos da Biblioteca (Capítulo 3)
Conteúdo
- Introdução
- Arrays Privados de Elementos
- Gestão do Estado do Gráfico
- Identificadores para o Uso Externo e Interno
- Melhorando a Classe do Menu de Contexto
- Melhorando a Classe do Elemento de Menu
- Melhorando a Classe Principal de Manipulação de Eventos da Interface Gráfica
- Teste preliminar dos Manipuladores de Eventos
- Teste de Vários Menus de Contexto e Ajuste fino
- Teste do Recebimento das mensagens na Classe Personalizada da Aplicação
- Conclusão
Introdução
O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) explica em detalhes a finalidade desta biblioteca. A lista completa dos links para os artigos da primeira parte se encontram no final de cada capítulo. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.
Os artigos anteriores contêm a implementação das classes para criar os componentes do menu principal. O desenvolvimento da classe de cada controle requer um ajuste fino prévio dos manipuladores de eventos nas classes base principais e nas classes dos controles criados. As seguintes perguntas serão discutidas neste artigo:
- Arrays privados para cada controle significativo.
- Adição de ponteiros de elementos para a base. Estes elementos são componentes dos elementos complexos (compostos).
- Gestão do estado do gráfico, dependendo da localização do cursor do mouse.
- Identificadores dos eventos da biblioteca para uso interno e externo.
Mais adiante, será mostrado o processo de recebimento das mensagens no manipulador da classe personalizada do aplicativo.
Arrays Privados de Elementos
Vamos realizar um pequeno experimento. Clique com o botão esquerdo em um dos itens do menu de contexto na área onde o cursor do mouse estiver fora da área do formulário. Nós veremos que a barra de rolagem do gráfico não foi desativada, podendo ser usada ao pairar o mouse sobre o controle. Este é um erro funcional que não deve estar presente. Nós vamos providenciar para que a barra de rolagem do gráfico e o deslocamento do modo dos níveis de negociação fiquem desabilitados nesse momento, não importando em qual controle o cursor do mouse estiver.
Primeiramente, nós vamos adicionar o rastreio do foco sobre o elemento para o manipulador do menu de contexto, conforme mostrado no código abaixo. Se o menu de contexto estiver oculto, então não há necessidade de continuar. Siga esta abordagem para economizar tempo.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto if(!CElement::m_is_visible) return; //--- Obtém o foco int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); } }
No estágio atual do desenvolvimento da biblioteca, a base dos elementos (para ser mais preciso, a classe CWndContainer) contém o array comum de ponteiros de elementos m_elements[]. Esta é uma parte da estrutura WindowElements dos arrays de elementos. Este array é adequado para os casos quando é necessário aplicar uma ação a todos os controles ou pelo menos a maioria deles. Se é necessário aplicar uma ação apenas a em um determinado grupo de elementos, esta abordagem se torna excessiva, já que ela requer muitos recursos. Por exemplo, vamos considerar um grupo de controles, com tamanhos que podem ultrapassar os limites do formulário que eles estão anexados. As listas suspensas e os menus de contexto pertencem a este grupo. Cada tipo destes elementos deve ser armazenado em seus correspondentes arrays. Isso permitirá uma maior eficiência e facilidade no gerenciamento.
Adicione o array aos menus de contexto para a estrutura WindowElements e crie um método para obter o seu tamanho:
//+------------------------------------------------------------------+ //| Classe para armazenar todos os objetos da interface | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- Estrutura de arrays de elementos struct WindowElements { //--- Array comum de todos os objetos CChartObject *m_objects[]; //--- Array comum de todos os elementos CElement *m_elements[]; //---Arrays privados de Elementos // Array do menu de contexto CContextMenu *m_context_menus[]; }; //--- Array dos arrays de elemento para cada janela WindowElements m_wnd[]; //--- public: //--- Número de menus de contexto int ContextMenusTotal(const int window_index); //--- }; //+------------------------------------------------------------------+ //| Retorna o número de menus de contexto pelo índice da janela específica| //+------------------------------------------------------------------+ int CWndContainer::ContextMenusTotal(const int window_index) { if(window_index>=::ArraySize(m_wnd)) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return(::ArraySize(m_wnd[window_index].m_context_menus)); }
Toda vez após a criação de um controle na classe personalizada da aplicação (em nosso caso a CProgram), nós usamos o método CWndContainer::AddToElementsArray() para adicionar um ponteiro a este controle para a base. Neste método, serão utilizados os métodos para obter e armazenar os ponteiros para cada elemento complexo (composto) no array comum. Anteriormente, criamos um método semelhante para o menu de contexto, o CWndContainer::AddContextMenuElements(). Todos os métodos semelhantes proporcionar a possibilidade de distribuir os ponteiros em arays privados do elemento, caso haja necessidade.
Em seguida, será necessário um método de modelo para adicionarmos um ponteiro ao elemento para o array passado por referência, pois esta ação será repetida mais de uma vez e aplicada a diferentes tipos de objetos.
class CWndContainer { protected: //--- Método do modelo para adicionar ponteiros ao array passado por referência template<typename T1,typename T2> void AddToRefArray(T1 &object,T2 &ref_array[]); //--- }; //+------------------------------------------------------------------+ //| Armazena o ponteiro (T1) no array passado por referência (T2) | //+------------------------------------------------------------------+ template<typename T1,typename T2> void CWndContainer::AddToRefArray(T1 &object,T2 &array[]) { int size=::ArraySize(array); ::ArrayResize(array,size+1); array[size]=object; }
Agora, o ponteiro do menu de contexto pode ser armazenado em seu array privado no final do método CWndContainer::AddContextMenuElements() exibido abaixo (destacado em amarelo). Vamos fazer o mesmo para todos os outros controles.
//+------------------------------------------------------------------+ //| Armazena os ponteiros para os objetos do menu de contexto na base| //+------------------------------------------------------------------+ bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object) { //--- Retorna, se este não for um menu de contexto if(object.ClassName()!="CContextMenu") return(false); //--- Obtém o ponteiro do menu de contexto CContextMenu *cm=::GetPointer(object); //--- Armazena os ponteiros para os seus objetos na base int items_total=cm.ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Incrementa o array de elementos int size=::ArraySize(m_wnd[window_index].m_elements); ::ArrayResize(m_wnd[window_index].m_elements,size+1); //--- Obtém o ponteiro do elemento de menu CMenuItem *mi=cm.ItemPointerByIndex(i); //--- Armazena o ponteiro no array m_wnd[window_index].m_elements[size]=mi; //--- Adiciona os ponteiros de todos os objetos do elemento de menu para o array comum AddToObjectsArray(window_index,mi); } //--- Adiciona o ponteiro para o array privado AddToRefArray(cm,m_wnd[window_index].m_context_menus); return(true); }
Gestão do Estado do Gráfico
Em seguida, é necessário adicionar um método para verificar o foco do cursor do mouse sobre os controles na classe CWndEvents. Tal verificação será realizada para os formulários e as listas suspensas. Tanto os formulários quanto os menus de contexto já possuem arrays privados. Portanto, vamos criar o método CWndEvents::SetChartState(). Abaixo encontramos a declaração e implementação deste método:
class CWndEvents : public CWndContainer { private: //--- Define o estado do gráfico void SetChartState(void); }; //+------------------------------------------------------------------+ //| Define o estado do gráfico | //+------------------------------------------------------------------+ void CWndEvents::SetChartState(void) { //--- Para identificar o evento quando a gestão for desativada bool condition=false; //--- Verifica a janela int windows_total=CWndContainer::WindowsTotal(); for(int i=0; i<windows_total; i++) { //--- Vai para o formulário seguinte, se este esta oculto if(!m_windows[i].IsVisible()) continue; //--- Verifica as condições no manipulador interno do formulário m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam); //--- Se houver um foco, registra ele if(m_windows[i].MouseFocus()) { condition=true; break; } } //--- Verifica o foco dos menus de contexto if(!condition) { int context_menus_total=CWndContainer::ContextMenusTotal(0); for(int i=0; i<context_menus_total; i++) { if(m_wnd[0].m_context_menus[i].MouseFocus()) { condition=true; break; } } } //--- if(condition) { //--- Desativa a rolagem e gestão dos níveis de negociação m_chart.MouseScroll(false); m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false); } else { //--- Ativa o gerenciamento m_chart.MouseScroll(true); m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true); } }
Este método será melhorado posteriormente com algumas adições, mas ele já está adequado para a tarefa atual. Ele deve ser chamado no método CWndEvents::ChartEventMouseMove() como é mostrado abaixo.
//+------------------------------------------------------------------+ //| Evento CHARTEVENT MOUSE MOVE | //+------------------------------------------------------------------+ void CWndEvents::ChartEventMouseMove(void) { //--- Sai, se este não for um evento de deslocamento do cursor if(m_id!=CHARTEVENT_MOUSE_MOVE) return; //--- Movendo a janela MovingWindow(); //--- Define o estado do gráfico SetChartState(); //--- Redesenha o gráfico m_chart.Redraw(); }
Compile todos os arquivos e teste o EA. Nós podemos ver agora que quando há um clique esquerdo na área do menu de contexto, o que ultrapassa os limites do formulário, a barra de rolagem do gráfico e a gestão dos níveis de negociação estão desativadas. O teste de anexar um elemento ao gráfico foi bem-sucedido. A partir de agora, o menu de contexto será criado apenas pela solicitação do usuário. Remova a sua exibição do método CProgram::CreateTradePanel() na classe da aplicação (veja o código abaixo).
m_contextmenu.Show(); // <<< Esta linha de código deve ser removida
Identificadores para o Uso Externo e Interno
Agora, nós vamos dar continuidade à manipulação do clique esquerdo sobre o elemento de menu.
Nossa próxima tarefa consiste em fazer com que o menu de contexto se abra, clicando no elemento de menu, desde que o menu de contexto esteja incluído. O segundo clique deve ocultá-lo. Tal manipulação irá estar presente tanto na classe CMenuItem do elemento de menu e na classe CContextMenu do menu de contexto. O problema é que o menu de contexto tem acesso ao elemento que ele está anexado (o nó anterior) e o elemento de menu que contém o menu de contexto não tem acesso direto a ele. O ponteiro do menu de contexto não pode ser criado na classe CMenuItem . Isto acontece pois se o arquivo ContextMenu.mqh for incluído no arquivo MenuItem.mqh, haverá erros de compilação. É por isso que nós vamos realizar a manipulação da exibição do menu de contexto na classe CContextMenu. O manipulador na classe CMenuItem será auxiliar. Ele irá gerar um evento personalizado através do envio de informações específicas para o menu de contexto sobre o elemento de menu que recebeu o clique. Além disso, nós precisamos fazer com que o menu de contexto se oculte quando o clique é realizado fora da área do menu de contexto, como é feito nos terminais MetaTrader e no editor de código MetaEditor. Este é um comportamento padrão para os menus de contexto.
Para implementar esta funcionalidade, é necessário haver identificadores adicionais para os eventos personalizados. Alguns deles serão projetados para uso interno nas classes da biblioteca e alguns deles para a manipulação externa na classe da aplicação personalizada. No nosso caso, este é a CProgram.
Eventos para uso interno:
- ON_CLICK_MENU_ITEM — clique em um elemento de menu.
- ON_HIDE_CONTEXTMENUS — sinal para ocultar todos os menus de contexto.
- ON_HIDE_BACK_CONTEXTMENUS — sinal para ocultar os menus de contexto abaixo do elemento de menu atual. Nós vamos discutir isso em detalhes mais tarde.
Para uso externo, crie o identificador ON_CLICK_CONTEXTMENU_ITEM que irá informar o programa que o clique aconteceu no elemento do menu de contexto.
Coloque os identificadores listados com números únicos atribuídos a cada um deles no arquivo Defines.mqh:
#define ON_CLICK_MENU_ITEM (4) // Clique no elemento de menu #define ON_CLICK_CONTEXTMENU_ITEM (5) // Clique no elemento de menu do menu de contexto #define ON_HIDE_CONTEXTMENUS (6) // Oculta todos os menus de contexto #define ON_HIDE_BACK_CONTEXTMENUS (7) // Oculta os menus de contexto abaixo do elemento de menu atual
Melhorando a Classe do Menu de Contexto
Os seguintes campos e métodos devem ser adicionados na classe CContextMenu do menu de contexto:
- Para definir e obter o estado do menu de contexto.
- Para a manipulação do clique sobre o evento elemento de menu.
- Para obter o identificador e o índice do nome do elemento de menu. Nós já sabemos a razão pela qual o índice e o identificador fazem parte dos nomes dos objetos que são componentes de vários elementos.
O código a seguir apresenta a declaração e a implementação de tudo o que foi listado acima, com os comentários detalhados:
class CContextMenu : public CElement { private: //--- Estado do menu de contexto bool m_contextmenu_state; public: //--- (1) Obter e (2) definir o estado do menu de contexto bool ContextMenuState(void) const { return(m_context_menu_state); } void ContextMenuState(const bool flag) { m_context_menu_state=flag; } //--- private: //--- Manipulação do clique no elemento ao qual este menu de contexto está ligado bool OnClickMenuItem(const string clicked_object); //--- Obtendo (1) o identificador e (2) o índice a partir do nome do elemento de menu int IdFromObjectName(const string object_name); int IndexFromObjectName(const string object_name); }; //+------------------------------------------------------------------+ //| Manipulação do clique no elemento de menu | //+------------------------------------------------------------------+ bool CContextMenu::OnClickMenuItem(const string clicked_object) { //--- Retorna, se o menu de contexto já está aberto if(m_contextmenu_state) return(true); //--- Retorna, se o clique não foi no elemento de menu if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0) return(false); //--- Obtem o identificador e o índice do nome do objeto int id =IdFromObjectName(clicked_object); int index =IndexFromObjectName(clicked_object); //--- Retorna, se o clique não foi no elemento de menu ao qual este menu de contexto está ligado if(id!=m_prev_node.Id() || index!=m_prev_node.Index()) return(false); //--- Exibe o menu de contexto Show(); return(true); } //+------------------------------------------------------------------+ //| Extrai o identificado do nome do objeto | //+------------------------------------------------------------------+ int CContextMenu::IdFromObjectName(const string object_name) { //--- Obtém o ID do nome do objeto int length =::StringLen(object_name); int pos =::StringFind(object_name,"__",0); string id =::StringSubstr(object_name,pos+2,length-1); //--- return((int)id); } //+------------------------------------------------------------------+ //| Extrai o índice do nome do objeto | //+------------------------------------------------------------------+ int CContextMenu::IndexFromObjectName(const string object_name) { ushort u_sep=0; string result[]; int array_size=0; //--- Obtém o código do separador u_sep=::StringGetCharacter("_",0); //--- Divide a string ::StringSplit(object_name,u_sep,result); array_size=::ArraySize(result)-1; //--- Verifica se o tamanho do array não excedeu if(array_size-2<0) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return((int)result[array_size-2]); }
Agora, só precisamos adicionar a chamada do método CContextMenu::OnClickMenuItem() quando o evento CHARTEVENT_OBJECT_CLICK() acontecer no manipulador de eventos CContextMenu::OnEvent do menu de contexto:
//--- Manipulação de eventos do clique esquerdo do mouse em um objeto if(id==CHARTEVENT_OBJECT_CLICK) { if(OnClickMenuItem(sparam)) return; }
Melhorando a Classe do Elemento de Menu
Quando o programa detecta o clique esquerdo do mouse sobre um elemento de menu, ele irá passar um parâmetro de string para o método CContextMenu::OnClickMenuItem(). O parâmetro de string contém o nome do objeto gráfico "rótulo retangular", que representa o fundo do elemento de menu. Você deve se lembrar que a prioridade para o clique no fundo será maior do que para o clique em outros objetos de elementos para quase todos os controles. Isto garante que o clique não será interceptado por qualquer outro objeto do elemento, o que pode desencadear um comportamento inesperado do programa. Por exemplo, se o rótulo do elemento de menu tiver uma prioridade maior do que o seu fundo, então, clicando na área do rótulo pode levar à mudança da imagem. Deixe-me lembrá-lo que as imagens dos ícones determinam dois estados. A razão para isso é que todos os objetos do tipo OBJ_BITMAP_LABEL irão se comportar dessa maneira por padrão.
No início do método CContextMenu::OnClickMenuItem(), será realizado uma verificação do estado do menu de contexto. Se ele já estiver ativado, então não há necessidade de continuar. Em seguida, o nome do objeto que foi clicado é verificado. Se este é um objeto de nosso programa e não há indícios de que este seja um elemento de menu, então, nós continuamos. O identificador e o índice do elemento de menu são extraídos do nome do objeto. Para essas tarefas nós já havíamos designado os métodos na qual todos os parâmetros necessários são extraídos do nome do objeto usando as funções de string da linguagem MQL. O identificador do elemento de menu é extraído utilizando o caractere traço duplo como um delimitador. Para extrair um índice, a linha é dividida em duas partes pelo símbolo traço inferior (_), que é um separador dos parâmetros do objeto do elemento.
Crie o método OnClickMenuItem() na classe CMenuItem. Seu código será diferente daquele escrito para o menu de contexto. Segue abaixo a declaração e implementação deste método. Neste método, não há necessidade de extrair os parâmetros do nome do objeto. Basta comparar o nome de fundo com o nome do objeto passado. Em seguida, o estado atual do elemento de menu é verificado. Se ele está bloqueado, então, não há necessidade de outras ações. Depois disso, se o elemento contém um menu de contexto, é atribuído a ele o estado do elemento, ativado ou desativado. Se antes disso o estado do menu de contexto for ativado, então, o módulo principal do manipulador de eventos envia um sinal para fechar todos os menus de contexto que foram abertos depois. Isto é aplicável para aqueles casos em que há vários menus de contexto abertos ao mesmo tempo que se abrem um dos outros. Tais exemplos serão discutidos mais adiante neste artigo. Além do identificador de evento ON_HIDE_BACK_CONTEXTMENUS, o identificador do elemento de menu é passado como um outro parâmetro. Isto é usado para identificar em qual menu de contexto o loop poderá ser interrompido.
class CMenuItem : public CElement { //--- Manipulação do clique no elemento de menu bool OnClickMenuItem(const string clicked_object); //--- }; //+------------------------------------------------------------------+ //| Manipulação do clique no elemento de menu | //+------------------------------------------------------------------+ bool CMenuItem::OnClickMenuItem(const string clicked_object) { //--- Verificação pelo nome do objeto if(m_area.Name()!=clicked_object) return(false); //--- Retorna, se o item não for ativado if(!m_item_state) return(false); //--- Se este elemento contém um menu de contexto if(m_type_menu_item==MI_HAS_CONTEXT_MENU) { //--- Se o menu suspenso deste elemento não foi ativado if(!m_context_menu_state) { m_context_menu_state=true; } else { m_context_menu_state=false; //--- Envia um sinal para fechar os menus de contexto, que estão abaixo deste elemento ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,""); } return(true); } //--- Se este elemento não contém um menu de contexto, mas é uma parte de um menu de contexto em si else { } //--- return(true); }
Melhorando a Classe Principal de Manipulação de Eventos da Interface Gráfica
Esta não é a versão final do método CMenuItem::OnClickMenuItem(), e nós vamos voltar a ele mais tarde para realizarmos algumas melhorias. Atualmente, sua principal tarefa é enviar uma mensagem para ocultar o menu de contexto para o módulo principal de manipulação de eventos personalizados na classe CWndEvents. Nessa classe, vamos criar um método de acesso no qual será realizado pelo evento ON_HIDE_BACK_CONTEXTMENUS. Vamos nomeá-lo de CWndEvents::OnHideBackContextMenus(). O código deste método é apresentado a seguir:
class CWndEvents : public CWndContainer { private: //--- Oculta todos os menus de contexto abaixo do elemento inicial bool OnHideBackContextMenus(void); }; //+------------------------------------------------------------------+ //| Evento ON_HIDE_BACK_CONTEXTMENUS | //+------------------------------------------------------------------+ bool CWndEvents::OnHideBackContextMenus(void) { //--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_BACK_CONTEXTMENUS) return(false); //--- Itera sobre todos os menus da última chamada int context_menus_total=CWndContainer::ContextMenusTotal(0); for(int i=context_menus_total-1; i>=0; i--) { //--- Ponteiros para o menu de contexto e seu nó anterior CContextMenu *cm=m_wnd[0].m_context_menus[i]; CMenuItem *mi=cm.PrevNodePointer(); //--- Se fez isso para o elemento inicial do sinal, então ... if(mi.Id()==m_lparam) { //--- ...se seu menu de contexto não tem foco, oculte-o if(!cm.MouseFocus()) cm.Hide(); //--- Para o loop break; } else { //--- Oculta o menu de contexto cm.Hide(); } } //--- return(true); }
O método CWndEvents::OnHideBackContextMenus() deve ser chamado no método do manipulador de eventos personalizados, como mostrado abaixo.
//+------------------------------------------------------------------+ //| Evento CHARTEVENT_CUSTOM | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- Se o sinal for para minimizar o formulário if(OnWindowRollUp()) return; //--- Se o sinal for para maximizar o formulário if(OnWindowUnroll()) return; //--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial if(OnHideBackContextMenus()) return; }
Teste preliminar dos Manipuladores de Eventos
Depois que todas as mudanças foram realizadas, compile todos os arquivos e carregue o programa ao gráfico para realizar o teste. Agora, quando um elemento de menu independente sobre o formulário for clicado, o menu de contexto será exibido se este estava oculto antes e oculta se este estava aberto. Além disso, quando um menu de contexto está aberto, então a cor de fundo do elemento de menu será fixa, ou seja, ela não irá mudar novamente se o cursor do mouse estiver fora de sua área, como é exibido na imagem abaixo.
Fig. 1. Teste de exibir e ocultar um menu de contexto.
Nós estamos continuando a ajustar a interação do usuário com o menu de contexto. Na maioria das aplicações, quando um ou vários menus de contexto estão abertos (um do outro), quando um clique do mouse ocorre fora de seus limites, eles se fecham ao mesmo tempo. Aqui, nós replicaremos o mesmo comportamento.
Para ser capaz de testar esta funcionalidade em sua totalidade, vamos adicionar outro menu de contexto para a interface de nosso EA. Nós vamos anexar um menu de contexto ao terceiro elemento do atual menu de contexto. Para isso, atribua o terceiro elemento do tipo MI_HAS_CONTEXT_MENU no método CProgram::CreateContextMenu1() que cria o primeiro menu de contexto no array items_type[]:
//--- Array dos tipos de elementos ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS]= { MI_SIMPLE, MI_SIMPLE, MI_HAS_CONTEXT_MENU, MI_CHECKBOX, MI_CHECKBOX };
Agora, vamos criar um método para o segundo menu de contexto. Adicione a segunda instância da classe CContextMenu para a classe CProgram e declare o método CreateContextMenu2():
class CProgram : public CWndEvents { private: //--- Elemento de menu e do contexto CMenuItem m_menu_item1; CContextMenu m_mi1_contextmenu1; CContextMenu m_mi1_contextmenu2; //--- private: #define MENU_ITEM1_GAP_X (6) #define MENU_ITEM1_GAP_Y (25) bool CreateMenuItem1(const string item_text); bool CreateMI1ContextMenu1(void); bool CreateMI1ContextMenu2(void); };
O segundo menu de contexto irá conter seis elementos. Esses serão dois grupos de elementos do botão de rádio (MI_RADIOBUTTON), três itens em cada. Abaixo está o código desse método. Qual é a diferença entre este método e o método de criação do primeiro menu contexto? Por favor, note como obtemos o ponteiro para o terceiro elemento do primeiro menu de contexto para o qual o segundo menu de contexto precisa ser ligado. O método CContextMenu::ItemPointerByIndex() designado para isso foi criado anteriormente. Como nós estaremos utilizando os ícones padrões para os elementos do botão de radio, eles não precisarão de arrays. No método CContextMenu::AddItem() em vez de passar o caminho para os ícones, ele passa valores vazios. A linha de separação é necessária para a separação visual do primeiro grupo de elementos de radio com o segundo. Portanto, defina ele após o terceiro (2) elemento na lista.
Foi mencionado anteriormente e mostrado em um esquema que cada grupo de elementos de radio deve ter seu próprio identificador exclusivo. O valor padrão deste parâmetro é 0. Por essa razão, atribua o identificador igual a 1 para cada elemento de rádio do segundo grupo (no loop do terceiro ao sexto). A classe CContextMenu já contém o método CContextMenu::RadioItemIdByIndex() para definir o identificador.
Vamos especificar o que os elementos de radio cada grupo tiveram que ser destacados inicialmente usando o método CContextMenu::SelectedRadioItem(). No código abaixo, o segundo elemento de rádio (índice 1) está destacado no primeiro grupo e o terceiro elemento de rádio (índice 2) está destacado no segundo grupo.
//+------------------------------------------------------------------+ //| Cria o menu de contexto 2 | //+------------------------------------------------------------------+ bool CProgram::CreateMI1ContextMenu2(void) { //--- Seis itens em um menu de contexto #define CONTEXTMENU_ITEMS2 6 //--- Armazena o ponteiro da janela m_mi1_contextmenu2.WindowPointer(m_window); //--- Armazena o ponteiro para o nó anterior m_mi1_contextmenu2.PrevNodePointer(m_mi1_contextmenu1.ItemPointerByIndex(2)); //--- Array de nomes dos elementos string items_text[CONTEXTMENU_ITEMS2]= { "ContextMenu 2 Item 1", "ContextMenu 2 Item 2", "ContextMenu 2 Item 3", "ContextMenu 2 Item 4", "ContextMenu 2 Item 5", "ContextMenu 2 Item 6" }; //--- Configura as propriedades antes da criação m_mi1_contextmenu2.XSize(160); m_mi1_contextmenu2.ItemYSize(24); m_mi1_contextmenu2.AreaBackColor(C'240,240,240'); m_mi1_contextmenu2.AreaBorderColor(clrSilver); m_mi1_contextmenu2.ItemBackColorHover(C'240,240,240'); m_mi1_contextmenu2.ItemBackColorHoverOff(clrLightGray); m_mi1_contextmenu2.ItemBorderColor(C'240,240,240'); m_mi1_contextmenu2.LabelColor(clrBlack); m_mi1_contextmenu2.LabelColorHover(clrWhite); m_mi1_contextmenu2.SeparateLineDarkColor(C'160,160,160'); m_mi1_contextmenu2.SeparateLineLightColor(clrWhite); //--- Adiciona os elementos ao menu de contexto for(int i=0; i<CONTEXTMENU_ITEMS2; i++) m_mi1_contextmenu2.AddItem(items_text[i],"","",MI_RADIOBUTTON); //--- Linha de separação após o terceiro elemento m_mi1_contextmenu2.AddSeparateLine(2); //--- Define um identificador único (1) para o segundo grupo for(int i=3; i<6; i++) m_mi1_contextmenu2.RadioItemIdByIndex(i,1); //--- Elementos de radio selecionados em ambos os grupos m_mi1_contextmenu2.SelectedRadioItem(1,0); m_mi1_contextmenu2.SelectedRadioItem(2,1); //--- Cria um menu de contexto if(!m_mi1_contextmenu2.CreateContextMenu(m_chart_id,m_subwin)) return(false); //--- Adiciona o ponteiro do elemento para a base CWndContainer::AddToElementsArray(0,m_mi1_contextmenu2); return(true); }
A chamada do método CProgram::CreateContextMenu2() situa-se no método CProgram::CreateTradePanel() como para o restante deles.
Teste de Vários Menus de Contexto e Ajuste fino
O resultado da compilação dos arquivos do EA e e seu carregamento ao gráfico será como mostrado abaixo.
Fig. 2. Teste de vários menus de contexto.
Se ambos os menus de contexto estiverem abertos ao clicar no elemento, que traz o primeiro menu, ambos os menus serão fechados. Este comportamento é subjacente ao método CWndEvents::OnHideBackContextMenus(), no qual tem sido considerado acima. No entanto, se clicarmos no gráfico do cabeçalho do formulário, os menus de contexto não serão fechados. Nós iremos trabalhar nisso.
A localização do cursor do mouse (foco) é definido no manipulador de evento OnEvent() da classe do menu de contexto (CContextMenu). Portanto, será enviado para lá também um sinal para fechar todos os menus de contexto abertos no manipulador de evento principal (na classe CWndEvents). Esta tarefa tem a seguinte solução.
1. Quando o evento do movimento do mouse (CHARTEVENT_MOUSE_MOVE) ocorrer, o parâmetro de string sparam irá conter o estado do botão esquerdo do mouse.
2. Então, depois de o foco do mouse ser identificado, nós realizamos uma verificação do estado atual do menu de contexto e do botão esquerdo do mouse. Se o menu de contexto for ativado e o botão ter sido pressionado, nós vamos passaremos para a próxima verificação onde a localização atual do cursor é identificada em relação a este menu de contexto e o nó anterior.
3. Se o cursor estiver na área de uma delas, não é necessário enviar um sinal para fechar todos os menus de contexto. Se o cursor estiver fora da área desses elementos, nós temos de verificar se existem menus de contexto que foram abertos mais tarde.
4. Para isso, itere sobre a lista deste menu de contexto para identificar se este contém um elemento com o seu próprio menu de contexto em anexo. Se houver um elemento deste tipo, verifique se o seu menu de contexto foi ativado. Se ele descobriu que o menu de contexto foi ativado, o cursor poderá estar em sua área. Isso significa que não é necessário enviar um sinal para fechar todos os menus de contexto. Se acontecer do menu de contexto atual ser o último aberto e em todos os menus que o precedem as condições para o envio de um sinal não forem cumpridas, isso significa que definitivamente o cursor está fora das áreas de todos os menus de contexto ativados.
5. O evento personalizado ON_HIDE_CONTEXTMENUS pode ser gerado aqui.
Como nós podemos ver, o mais importante é que todos os menus de contexto têm de ser fechados somente quando o cursor do mouse (se o botão esquerdo do mouse é pressionado) estiver fora da área do último menu de contexto ativado e fora da área do elemento a partir de onde ele foi chamado.
A lógica descrita está no código abaixo. O método CContextMenu::CheckHideContextMenus() foi dedicado para isso.
class CContextMenu : public CElement { private: //--- Verificação da condição para fechar todos os menus de contexto void CheckHideContextMenus(void); //--- }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto if(!CElement::m_is_visible) return; //--- Obtém o foco int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Se o menu de contexto está habilitado e o botão esquerdo do mouse for pressionado if(m_context_menu_state && sparam=="1") { //--- Verificação da condição para fechar todos os menus de contexto CheckHideContextMenus(); return; } //--- return; } } //+------------------------------------------------------------------+ //| Verificação da condição para fechar todos os menus de contexto | //+------------------------------------------------------------------+ void CContextMenu::CheckHideContextMenus(void) { //--- Sair, se o cursor estiver na área do menu de contexto ou na área do nó anterior if(CElement::MouseFocus() || m_prev_node.MouseFocus()) return; //--- Se o cursor estiver fora da área destes elementos,então, ... // ... uma verificação é necessária se existem menus de contexto abertos que foram ativados depois disso //--- Para isso itere sobre a lista deste menu de contexto... // ... para identificação, se há um elemento de menu que contém um menu de contexto int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Se houver um item deste tipo, verifique se o seu menu de contexto está aberto. // Se este está aberto, não envie um sinal para fechar todos os menus de contexto a partir deste elemento já que ... // ... é possível que o cursor esteja na área do mesmo, sendo que isso deve ser verificado. if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU) if(m_items[i].ContextMenuState()) return; } // --- Enviar um sinal para esconder todos os menus de contexto ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,""); }
Agora o evento ON_HIDE_CONTEXTMENUS tem que ser recebido no manipulador principal da biblioteca em desenvolvimento na classe CWndEvents. Vamos escrever um método designado a isto e nomeá-lo de OnHideContextMenus(). Isto é bastante simples, já que atualmente ele tem apenas que iterar sobre o array privado de menus de contexto e ocultá-los.
A declaração e implementação do método CWndEvents::OnHideContextMenus() está no código abaixo:
class CWndEvents : public CWndContainer { private: //--- Esconder todos os menus de contexto bool OnHideContextMenus(void); }; //+------------------------------------------------------------------+ //| Evento ON_HIDE_CONTEXTMENUS | //+------------------------------------------------------------------+ bool CWndEvents::OnHideContextMenus(void) { //--- Se o sinal for para esconder todos os menus de contexto if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_CONTEXTMENUS) return(false); //--- int cm_total=CWndContainer::ContextMenusTotal(0); for(int i=0; i<cm_total; i++) m_wnd[0].m_context_menus[i].Hide(); //--- return(true); }
Depois de compilar os arquivos da biblioteca e carregar o EA ao gráfico para testes, você vai ver que os menus de contexto ativados serão ocultados se o clique do mouse ocorrer fora de suas áreas.
Temos de eliminar outra falha perceptível do projeto. Dê uma olhada na imagem abaixo. Ela mostra uma situação, quando o cursor do mouse estiver na área do primeiro menu de contexto, mas fora da área do elemento de menu a partir do qual o segundo menu de contexto é chamado. Normalmente, em tais casos, todos os menus de contexto abertos após aquele em que o cursor está localizado atualmente estão fechados. Vamos escrever um código para ele.
Fig. 3. Em tal situação, todos os menus de contexto do lado direito devem ser ocultos.
Vamos nomear o próximo método CContextMenu::CheckHideBackContextMenus(). Sua lógica foi descrita no parágrafo anterior e nós podemos prosseguir direto para sua implementação (veja o código abaixo). Se todas as condições forem atendidas, o evento ON_HIDE_BACK_CONTEXTMENUS evento será gerado.
class CContextMenu : public CElement { private: //--- Verificação da condição para fechar todos os menus de contexto que foram abertos após este void CheckHideBackContextMenus(void); //--- }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto if(!CElement::m_is_visible) return; //--- Obtém o foco int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Se o menu de contexto está habilitado e o botão esquerdo do mouse for pressionado if(m_context_menu_state && sparam=="1") { //--- Verificação da condição para fechar todos os menus de contexto CheckHideContextMenus(); return; } //--- Verificação da condição para fechar todos os menus de contexto que foram abertos após este CheckHideBackContextMenus(); return; } } //+------------------------------------------------------------------+ //| Verificação da condição para fechar todos os menus de contexto | //| que foram abertos após este | //+------------------------------------------------------------------+ void CContextMenu::CheckHideBackContextMenus(void) { //--- Itera sobre todos os elementos do menu int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Se o elemento contém um menu de contexto e ele está habilitado if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU && m_items[i].ContextMenuState()) { //--- Se o foco estiver no menu de contexto, mas não neste elemento if(CElement::MouseFocus() && !m_items[i].MouseFocus()) //--- Envia um sinal para esconder todos os menus de contexto que foram abertos depois dele ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,""); } } }
Inicialmente, o método OnHideBackContextMenus() foi escrito na classe CWndEvents para a manipulação do evento ON_HIDE_BACK_CONTEXTMENUS, portanto, os arquivos do projeto podem ser compilados e, assim, o EA ser testado. Se tudo foi feito corretamente, os menus de contexto reagirão ao movimento do cursor do mouse de acordo com os requisitos do programa.
A parte mais difícil terminou, mas o trabalho ainda não está terminado. Os manipuladores de eventos devem ser configurados de modo que, quando qualquer um dos elementos do menu de contexto ser clicado, uma mensagem com os valores dos parâmetros é enviada para a classe personalizada da aplicação (CProgram). Estes parâmetros permitirão identificar qual elemento de menu foi clicado exatamente. Desta forma, o desenvolvedor do aplicativo pode atribuir certas funções para os elementos de menu. Além disso, a mudança dos estados dos elementos caixas de seleção e de radio do menu de contexto ainda serão criados.
O bloco para a condição quando um elemento de menu não contém um menu de contexto, mas uma parte dela ainda está vazia no método OnClickMenuItem() da classe CMenuItem. O evento personalizado ON_CLICK_MENU_ITEM será enviado a partir daqui. A mensagem irá conter os seguintes parâmetros adicionais:
- O índice da lista comum.
- O identificador de elemento.
- Uma linha que vai ser formada a partir do:
- o nome do programa;
- a indicação da caixa de seleção ou elemento de rádio;
- no caso deste ser um elemento de radio, a linha também irá conter o identificador do elemento de radio.
Como você pode ver, quando a função EventChartCustom() não for suficiente, uma string com o número necessário dos parâmetros para a identificação exata sempre poderá ser formada. Similar aos nomes dos objetos gráficos, os parâmetros serão divididos pelo sublinhado "_".
O estado do elemento caixa de seleção e radio também será alterado no mesmo bloco. Abaixo está uma versão abreviada do método CMenuItem::OnClickMenuItem(). Ele mostra apenas o código que deve ser adicionada ao bloco se não.
//+------------------------------------------------------------------+ //| Clique no cabeçalho do elemento | //+------------------------------------------------------------------+ bool CMenuItem::OnClickMenuItem(const string clicked_object) { //--- Verificação pelo nome do objeto //--- Retorna, se o item não for ativado //--- Se este elemento contém um menu de contexto //... //--- Se este elemento não contém um menu de contexto, mas é uma parte de um menu de contexto em si else { //--- Prefixo da mensagem com o nome do programa string message=CElement::ProgramName(); //--- Se este é uma caixa de seleção, altera o seu estado if(m_type_menu_item==MI_CHECKBOX) { m_checkbox_state=(m_checkbox_state)? false : true; m_icon.Timeframes((m_checkbox_state)? OBJ_NO_PERIODS : OBJ_ALL_PERIODS); //--- Adiciona à mensagem que esta é uma caixa de seleção message+="_checkbox"; } //--- Se este é um botão de radio, muda o seu estado else if(m_type_menu_item==MI_RADIOBUTTON) { m_radiobutton_state=(m_radiobutton_state)? false : true; m_icon.Timeframes((m_radiobutton_state)? OBJ_NO_PERIODS : OBJ_ALL_PERIODS); //--- Adiciona à mensagem que este é um elemento de radio message+="_radioitem_"+(string)m_radiobutton_id; } //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_MENU_ITEM,m_index,CElement::Id(),message); } //--- return(true); }
Um evento personalizado com o identificador ON_CLICK_MENU_ITEM foi designado para o manipulador da classe menu de contexto (CContextMenu). Nós vamos precisar dos métodos adicionais para extrair o identificador do parâmetro da string do evento se o clique foi no elemento de radio e também para obter o índice em relação ao grupo que este elemento de radio pertence. Você pode ver o código dos métodos abaixo.
À medida que a extração do identificador a partir do parâmetro de string depender da estrutura da cadeia transmitida, o método CContextMenu::RadioIdFromMessage() irá conter as verificações adicionais para correção da string formada e caso ela exceda o tamanho do array.
Obtém o identificador do elemento de radio pelo índice geral no início do método CContextMenu::RadioIndexByItemIndex(), que é dedicado retornar o índice do elemento de radio através do índice geral. Use o método CContextMenu::RadioItemIdByIndex() escrito anteriormente. Depois disso, conte os elementos de radio com este identificador no loop. Tendo feito isso para o item de rádio com o índice geral cujo o valor é igual ao índice passado, armazena o valor do contador e para o loop. Isso significa que o último valor do contador será o índice que precisa ser retornado.
class CContextMenu : public CElement { private: //--- Obtendo (1) o identificador e (2) o índice a partir da mensagem do elemento de radio. int RadioIdFromMessage(const string message); int RadioIndexByItemIndex(const int index); //--- }; //+------------------------------------------------------------------+ //| Extrai o identificador da mensagem para o elemento de radio | //+------------------------------------------------------------------+ int CContextMenu::RadioIdFromMessage(const string message) { ushort u_sep=0; string result[]; int array_size=0; //--- Obtém o código do separador u_sep=::StringGetCharacter("_",0); //--- Divide a string ::StringSplit(message,u_sep,result); array_size=::ArraySize(result); //--- Se a estrutura da mensagem difere do esperado if(array_size!=3) { ::Print(__FUNCTION__," > Estrutura errada na mensagem para o elemento de radio! message: ",message); return(WRONG_VALUE); } //--- Prevenção para exceder o tamanho do array if(array_size<3) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- Retorna o ID do item de rádio return((int)result[2]); } //+------------------------------------------------------------------+ //| Retorna o índice do elemento de radio através do índice geral | //+------------------------------------------------------------------+ int CContextMenu::RadioIndexByItemIndex(const int index) { int radio_index =0; //--- Obtém o ID do elemento de rádio pelo índice Geral int radio_id =RadioItemIdByIndex(index); //--- Contador de elementos do grupo desejado int count_radio_id=0; //--- Itera sobre a lista int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Se isto não é um elemento de radio, passar para a próxima if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON) continue; //--- Se os identificadores forem iguais if(m_items[i].RadioButtonID()==radio_id) { //--- Se os índices corresponder // Armazena o valor atual do contador e completar o loop if(m_items[i].Index()==index) { radio_index=count_radio_id; break; } //--- Aumenta o contador count_radio_id++; } } //--- Retorna o índice return(radio_index); }
Agora, vamos criar o método CContextMenu::ReceiveMessageFromMenuItem() para o tratamento do evento personalizado ON_CLICK_MENU_ITEM a partir do elemento de menu. Os seguintes parâmetros de evento devem ser passados para este método: o identificador, o índice e a mensagem de string. As condições caso esta mensagem seja recebida de nosso programa e se a condições dos identificadores são verificadas no início deste método. Se a verificação for positiva e se esta mensagem foi enviada a partir de um produto de rádio, a troca é realizada no grupo que foi definido pelo identificador e o produto requerido pelo índice. O identificador e o índice podem ser obtidos com a ajuda dos métodos criados acima.
Independentemente do tipo de elemento de menu a partir do qual uma mensagem é proveniente de, no caso da verificação do nome do programa e comparação dos identificadores bem-sucedidos, o ON_CLICK_CONTEXTMENU_ITEM personalizado mensagem é enviada. Ela é dirigida para o manipulador na classe CProgram do aplicativo personalizado. Em conjunto com a mensagem, os seguintes parâmetros são a: (1) Identificador, (2) índice geral na lista do menu de contexto (3) exibindo o texto do elemento.
No final do método, independentemente da primeira verificação (1) o menu de contexto é oculto, (2) o formulário é desbloqueado (3) e um sinal de fechamento para todos os menus de contexto é enviado.
class CContextMenu : public CElement { private: //--- Recebendo uma mensagem do elemento de menu para manipulação void ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item); //--- }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento ON_CLICK_MENU_ITEM if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM) { int item_id =int(dparam); int item_index =int(lparam); string item_message =sparam; //--- Recebendo uma mensagem do elemento de menu para manipulação ReceiveMessageFromMenuItem(item_id,item_index,item_message); return; } } //+------------------------------------------------------------------+ //| Recebendo uma mensagem do elemento de menu para manipulação | //+------------------------------------------------------------------+ void CContextMenu::ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item) { //--- Se houver uma indicação de que a mensagem foi recebida a partir deste programa e o ID do elemento se correspondem if(::StringFind(message_item,CElement::ProgramName(),0)>-1 && id_item==CElement::Id()) { //--- Se o clique foi no elemento de radio if(::StringFind(message_item,"radioitem",0)>-1) { //--- Obtém o ID do elemento de rádio a partir da mensagem passada int radio_id=RadioIdFromMessage(message_item); //--- Obtém o índice do elemento de radio pelo índice geral int radio_index=RadioIndexByItemIndex(index_item); //--- Muda o elemento do botão de radio SelectedRadioItem(radio_index,radio_id); } //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,index_item,id_item,DescriptionByIndex(index_item)); } //--- Oculta o menu de contexto Hide(); //--- Desbloqueia o formulário m_wnd.IsLocked(false); // --- Enviar um sinal para esconder todos os menus de contexto ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,""); }
Teste do Recebimento das mensagens na Classe Personalizada da Aplicação
Agora, nós podemos testar e receber tal mensagem no manipulador da classe CProgram. Para isso, adicione o código a ele como mostrado abaixo:
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_CUSTOM+ON_CLICK_CONTEXTMENU_ITEM) { ::Print(__FUNCTION__," > index: ",lparam,"; id: ",int(dparam),"; description: ",sparam); } }
Agora, compile os arquivos e carregar o EA para o gráfico. Quando os elementos de menu são clicados, as mensagens com os parâmetros desses itens serão impressos no registro do EA:
2015.10.23 20:16:27.389 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5 2015.10.23 20:16:10.895 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 0; id: 3; description: ContextMenu 2 Item 1 2015.10.23 19:27:58.520 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 5; id: 3; description: ContextMenu 2 Item 6 2015.10.23 19:27:26.739 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 2; id: 3; description: ContextMenu 2 Item 3 2015.10.23 19:27:23.351 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 3; id: 3; description: ContextMenu 2 Item 4 2015.10.23 19:27:19.822 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5 2015.10.23 19:27:15.550 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 1; id: 2; description: ContextMenu 1 Item 2
We have completed the development of the main part of the CContextMenu class for creating a context menu. Isso irá exigir algumas adições mais tarde, mas vamos voltar a este quando os problemas se manifestam em testes. Em suma, nós vamos seguir a sequência natural de narração como desta forma, é mais fácil para estudar o material.
Conclusão
Neste artigo, nós melhoramos as classes de elementos criados nos artigos anteriores. Agora, nós temos tudo pronto para desenvolver o elemento do menu principal. Nós vamos trabalhar nisso no próximo artigo.
Você pode buscar e baixar os arquivos da biblioteca no atual estágio de desenvolvimento, as imagens e os arquivos dos programas discutidos no artigo (o EA, indicadores e o script) podem ser baixados para testes nos terminais MetaTrader 4 e MetaTrader 5. Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo.
Lista de artigos (capítulos) da segunda parte:
- 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/2204
- 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