Interfaces Gráficas II: O Elemento de Menu (Capítulo 1)
Conteúdo
- Introdução
- O Menu Principal do Programa
- Desenvolvimento da Classe para a Criação do Elemento de Menu
- Teste da colocação de um Elemento de Menu
- Melhorando as Classes Principais da Biblioteca
- Conclusão
Introdução
Nos capítulos da primeira parte, nós discutimos em detalhes os processos de desenvolvimento da estrutura principal da biblioteca para a criação das interfaces gráficas. Lá, nós também criamos o elemento principal da interface - o formulário para os controles. 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 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.
Algumas classes, no estágio de desenvolvimento atual, ainda estão incompletas. Conforme a biblioteca for incrementada com os novos controles, algumas adições terão que ser introduzidas na classe CWndContainer, que é a base para armazenar os ponteiros para os elementos e objetos que fazem parte dele. Será necessário introduzirmos também algumas adições na classe CWndEvents. Isto é por causa que os eventos do gráfico e os eventos gerados pelos controles serão tratados aqui.
A versão atual da classe de formulário CWindow para os controles também não é definitiva. O modo multi-janela mencionado nos artigos anteriores ainda não foi implementado. Nós vamos entender este modo na quarta parte desta série. Juntando a isso, será demonstrado a criação de um menu de contexto. Embora esta seja uma parte do menu principal, ela pode ser parte de alguns outros controles que serão considerados em outros artigos desta série.
Recomenda-se a realização consistente de todas as ações para um melhor entendimento do material. Por questões de economia de espaço, o código dos métodos pertencendo ao mesmo tipo e repetidos em diferentes classes será omitido. Se você se deparar com tais métodos, você deve se dirigir aos arquivos anexados neste artigo para procurar o código exigido e, então, continuar estudando o material deste artigo.
O Menu Principal do Programa
É difícil encontrar um programa que não possui um menu principal. Os terminais MetaTrader também possuem este elemento de interface (veja a imagem abaixo). Geralmente, o menu está localizado na parte superior esquerda da janela do programa, consistindo de vários elementos. Um clique com o botão esquerdo do mouse em um elemento de menu traz uma lista suspensa com as opções do programa.
Fig. 1. Menu principal no terminal MetaTrader 5
Esta lista suspensa é chamada de menu de contexto, podendo conter vários tipos de elementos. Vamos estudar cada um deles em detalhes:
- O elemento botão. Este é o elemento mais simples do menu de contexto. Normalmente, um clique esquerdo neste elemento, faz abrir uma janela com uma funcionalidade estendida para a configuração de um programa ou uma janela contendo algumas informações. Lá, também pode haver funções muito simples. Eles mudam algo na aparência da interface do programa após o elemento botão ser clicado.
- Um elemento com dois estados do tipo caixa de seleção. Este elemento pode ser usado para ativar algum processo ou abrir (tornar visível) alguma parte da interface do programa. Quando isso acontece, este elemento muda sua aparência e mostra ao usuário do aplicativo qual estado ele se encontra.
- Um grupo de elementos. Neste grupo apenas um elemento pode ser habilitado. Este tipo de controle é chamado de botão do tipo radio ou botão switch. Neste artigo, nós vamos chamá-lo de elemento de rádio.
- Um elemento para a chamada do menu de contexto. O menu de contexto que foi chamado do arquivo principal do programa pode ter elementos contendo outro menu de contexto. Após clicar neste elemento, o menu de contexto será exibido à direita da mesma.
O editor de código MetaEditor também contém o menu principal:
Fig. 2. Menu principal no editor de código MetaEditor
Agora, nós precisamos identificar quais classes são necessárias para compor tal elemento de interface complexo. É claro que reunir tudo em uma única classe é impraticável já que seria muito difícil estudar e realizar manutenções em tal classe. Portanto, faz sentido realizar tudo de maneira que todo o complexo seja montado a partir de várias partes simples. Vamos decidir quais serão essas partes.
O menu principal e menu de contexto consistem de vários itens. A mesma classe pode ser usada para estes elementos em ambos os tipos de menu. Um menu de contexto, muitas vezes contém uma linha de separação, que serve para distinguir os elementos do menu em categorias. Portanto, nós podemos ver que é necessário, pelo menos, quatro classes de código para criar tais elementos de interface como:
- O elemento de menu. Para criar este elemento, nós vamos desenvolver a classe CMenuItem.
- A classe CSeparateLine será criada para a linha de separação.
- O menu de contexto. A classe CContextMenu. Este elemento de interface irá ser montado a partir dos objetos da classe CMenuItem.
- O menu principal. A classe CMenuBar. Da mesma forma que no menu de contexto, as partes constituintes serão os elementos do menu (CMenuItem).
Nós definimos as principais tarefas. A partir do que foi dito acima, ficou claro que a parte mais importante para a criação do menu principal e o de contexto é o elemento de menu. Portanto, nós vamos continuar com a criação da classe CMenuItem.
Desenvolvimento da Classe para a Criação do Elemento de Menu
Na pasta Controls, onde todos os outros arquivos da biblioteca estão localizados, crie o arquivo MenuItem.mqh com a classe derivada CMenuItem. Você pode declarar os métodos virtuais padrões para todos os controles de maneira imediata. A classe base para isso é a classe CElement, que foi discutida em detalhes no artigo anterior. Por enquanto, nós vamos deixar esses métodos sem sua implementação já que mais tarde eu gostaria de destacar uma peculiaridade interessante do compilador.
//+------------------------------------------------------------------+ //| MenuItem.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //+------------------------------------------------------------------+ //| Classe para criar o elemento de menu | //+------------------------------------------------------------------+ class CMenuItem : public CElement { //--- public: CMenuItem(void); ~CMenuItem(void); //--- public: //--- Manipulador de eventos do gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Timer virtual void OnEventTimer(void); //--- Elemento móvel virtual void Moving(const int x,const int y); //--- Exibir, ocultar, resetar, remover virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- Definir, resetar as prioridades para o clique esquerdo do mouse virtual void SetZorders(void); virtual void ResetZorders(void); }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CMenuItem::CMenuItem(void) { } //+------------------------------------------------------------------+ //| Destrutor | //+------------------------------------------------------------------+ CMenuItem::~CMenuItem(void) { } //+------------------------------------------------------------------+
Quais objetos gráficos serão utilizados para compor o elemento de menu? No menu principal, estes são geralmente títulos que mudam suas cores de fundo e/ou as cores da fonte quando o cursor está pairando sobre eles. Geralmente, o menu de contexto tem um ícone. Se um elemento contém o seu próprio menu de contexto, então, na parte direita da área dele há uma seta apontando para a direita, indicando para o usuário que há um menu anexado. Isto significa que um elemento de menu pode ser de vários tipos diferentes, dependendo a quem ele pertence e que tarefa que se destina a cumprir. Vamos enumerar todas as suas possíveis partes constituintes:
- Fundo.
- Rótulo.
- Legenda.
- Indicação da presença do menu de contexto.
Fig. 3. Partes integrantes do controle elementos de menu.
Adicione instâncias de classe de objetos gráficos, que estão disponíveis no arquivo Element.mqh e declarações de métodos para a criação de objetos gráficos para a classe CMenuItem. No momento de anexar o elemento de menu para o gráfico, o número de índice do elemento de menu tem de ser passado para o método. Este número irá ser utilizada para formar os nomes dos objetos gráficos, que serão usados para a composição do elemento de menu. Isto também será utilizado para a identificação na lista de elementos ou no momento do clique sobre um deles.
class CMenuItem : public CElement { private: //--- Objetos para a criação de um elemento de menu CRectLabel m_area; CBmpLabel m_icon; CLabel m_label; CBmpLabel m_arrow; //--- public: //--- Métodos para a criação do elemento de menu bool CreateMenuItem(const long chart_id,const int window,const int index_number,const string label_text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateIcon(void); bool CreateLabel(void); bool CreateArrow(void); //--- };
Como um elemento de menu pode ser de vários tipos diferentes, é necessário haver a opção para especificar o tipo a que ele pertence antes de sua criação. Nós vamos precisar de uma enumeração dos tipos de elementos de menu (ENUM_TYPE_MENU_ITEM).
Adicione isso ao arquivo Enums.mqh onde todas as enumerações da biblioteca são armazenadas. A enumeração ENUM_TYPE_MENU_ITEM deve ter as opções que foram discutidas anteriormente:
- MI_SIMPLE — elemento simples.
- MI_HAS_CONTEXT_MENU — elemento que contém um menu de contexto.
- MI_CHECKBOX — o elemento caixa de seleção.
- MI_RADIOBUTTON — elemento que pertence ao grupo dos botões de radio.
//+------------------------------------------------------------------+ //| Enumeração dos tipos de elementos de menu | //+------------------------------------------------------------------+ enum ENUM_TYPE_MENU_ITEM { MI_SIMPLE =0, MI_HAS_CONTEXT_MENU =1, MI_CHECKBOX =2, MI_RADIOBUTTON =3 };
Adicione o campo e os métodos correspondentes para configurar e obter o tipo de elemento de menu para a classe CMenuItem :
class CMenuItem : public CElement { private: //--- Propriedades do elemento de menu ENUM_TYPE_MENU_ITEM m_type_menu_item; //--- public: //--- Configurar e obter o tipo void TypeMenuItem(const ENUM_TYPE_MENU_ITEM type) { m_type_menu_item=type; } ENUM_TYPE_MENU_ITEM TypeMenuItem(void) const { return(m_type_menu_item); } //--- };
O tipo MI_SIMPLE , que é um elemento de menu simples, será definido por padrão:
//+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CMenuItem::CMenuItem(void) : m_type_menu_item(MI_SIMPLE) { }
Os métodos para definir a aparência de cada objeto gráfico de um elemento de menu devem ser criados da mesma maneira que foi feito no artigo anterior para o formulário. Estes métodos possuem algumas propriedades que são únicas. Vamos listar tudo o que será necessário para esse controle:
- Alterar a cor do fundo.
- A cor de fundo quando um elemento de menu não está disponível, ou seja, está bloqueado devido à impossibilidade de utilizar a função do elemento neste momento.
- Alterar a cor de fundo do quadro.
- A prioridade para o clique esquerdo do mouse deve ser generalizada e igual a zero para todos os objetos, com exceção do plano de fundo já que é sobre ele que será monitorado o clique.
- Redefinição das imagens para os ícones e a indicação da presença do menu de contexto.
- Métodos para gerenciar as propriedades do rótulo de texto, tais como:
- margens partindo da coordenada zero do fundo do elemento de menu;
- cor da fonte;
- cor do texto quando o cursor está pairando sobre ele;
- cor do texto quando o elemento está bloqueado.
- Variáveis para acompanhar o estado de um elemento de menu:
- estado geral (disponível/bloqueado);
- estado da caixa de seleção;
- estado do botão de radio;
- estado do menu de contexto, se ele for anexado a este elemento.
- Identificador para um grupo de elementos de botão de radio. Isto é necessário já que um menu de contexto pode conter vários grupos de elementos de botão de radio. O identificador permitirá entender a qual grupo o botão de radio pertence exatamente.
- Número de índice de um elemento de menu.
Adicione tudo o que foi listado acima para a classe CMenuItem:
class CMenuItem : public CElement { private: //--- Propriedades do fundo int m_area_zorder; color m_area_border_color; color m_area_color; color m_area_color_off; color m_area_color_hover; //--- Propriedades do rótulo string m_icon_file_on; string m_icon_file_off; //--- Propriedades do rótulo de texto string m_label_text; int m_label_x_gap; int m_label_y_gap; color m_label_color; color m_label_color_off; color m_label_color_hover; //--- Propriedades do indício do menu de contexto string m_right_arrow_file_on; string m_right_arrow_file_off; //--- Prioridade Geral para o clique int m_zorder; //--- Disponível/bloqueado bool m_item_state; //--- Estado da caixa de seleção bool m_checkbox_state; //--- Estado do botão de rádio e seu identificador bool m_radiobutton_state; int m_radiobutton_id; //--- Estado do menu de contexto bool m_context_menu_state; //--- public: //--- Métodos do fundo void AreaBackColor(const color clr) { m_area_color=clr; } void AreaBackColorOff(const color clr) { m_area_color_off=clr; } void AreaBorderColor(const color clr) { m_area_border_color=clr; } //--- Métodos do rótulo void IconFileOn(const string file_path) { m_icon_file_on=file_path; } void IconFileOff(const string file_path) { m_icon_file_off=file_path; } //--- Métodos do rótulo de texto string LabelText(void) const { return(m_label.Description()); } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } void LabelColor(const color clr) { m_label_color=clr; } void LabelColorOff(const color clr) { m_label_color_off=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } //--- Métodos para indicar a presença do menu de contexto void RightArrowFileOn(const string file_path) { m_right_arrow_file_on=file_path; } void RightArrowFileOff(const string file_path) { m_right_arrow_file_off=file_path; } //--- (1) Estado comum do elemento e (2) o elemento de caixa de seleção void ItemState(const bool state); bool ItemState(void) const { return(m_item_state); } void CheckBoxState(const bool flag) { m_checkbox_state=flag; } bool CheckBoxState(void) const { return(m_checkbox_state); } //--- Identificador do elemento radio void RadioButtonID(const int id) { m_radiobutton_id=id; } int RadioButtonID(void) const { return(m_radiobutton_id); } //--- Estado do elemento radio void RadioButtonState(const bool flag) { m_radiobutton_state=flag; } bool RadioButtonState(void) const { return(m_radiobutton_state); } //--- Estado do menu de contexto anexado a este elemento bool ContextMenuState(void) const { return(m_context_menu_state); } void ContextMenuState(const bool flag) { m_context_menu_state=flag; } //--- };
No artigo anterior, foi explicado que a biblioteca será estruturada de uma forma para que seus usuários não se encontram em uma situação onde eles não sabem o que fazer. A sequência de ações pode ser esquecida ao longo do tempo. Antes de criar um controle, tem de ser passado o ponteiro ao formulário em que ele será anexado. Se isso não for feito, o programa não irá terminar de anexar todos os controles ao formulário e imprimir uma mensagem relevante sobre ele nos registros. Esta mensagem deve ser formada de uma forma clara sobre o qual controle o usuário cometeu um erro no curso de suas ações.
Faça a inclusão do arquivo que contém a classe do formulário (CWindow) e declare um ponteiro com o tipo do formulário. Para armazenar o ponteiro ao formulário na classe de cada controle, nós vamos precisar de um método correspondente. Vamos nomeá-lo de WindowPointer(). Como parâmetro único, ele recebe como referência o objeto do tipo CWindow. Sua tarefa é de armazenar o ponteiro para o objeto passado. A função GetPointer() pode ser usada para obter um ponteiro do objeto. O mesmo terá de ser feito na classe de cada controle que nós vamos criar.
//+------------------------------------------------------------------+ //| MenuItem.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Classe para criar o elemento de menu | //+------------------------------------------------------------------+ class CMenuItem : public CElement { private: //--- Ponteiro para o formulário ao qual o controle será anexado CWindow *m_wnd; //--- public: //--- Armazena o ponteiro do formulário passado void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- };
Agora, vamos considerar o método CMenuItem::CreateMenuItem() para a criação de um elemento de menu. No início do método, nós vamos verificar a validade do ponteiro do formulário que o elemento deve ser anexado para utilizar a função CheckPointer(). Se o ponteiro não é válido, então o programa irá imprimir uma mensagem para o registro e deixará o método, retornando false. Se o ponteiro estiver presente, então as variáveis dos elementos são inicializadas.
No artigo anterior, nós estabelecemos que cada elemento de interface deve ter o seu próprio identificador e analisado em detalhes como é que ele será formado. Para evitar repetições, eu vou lembrá-lo brevemente como isso acontece.
No momento da criação da formulário principal, quando um ponteiro para ele é adicionado ao elemento base (a classe CWndContainer) o identificador é definido pelo contador dos elementos de interface do método CWndContainer::AddWindow(). O valor do contador é armazenado nas classes do formulário sob a adição de cada elemento para a base. Como o ponteiro ao formulário é obrigatório em cada elemento, o número do identificador do último controle está disponível para cada novo elemento de interface criado através do ponteiro ao formulário. Antes de criar um elemento, o identificador será formado, como é mostrado no código abaixo (destacado em amarelo.
Em seguida, as margens do ponto extremo do formulário são calculadas e armazenadas para o elemento. O acesso às coordenadas do formulário está disponível a partir do ponteiro de formulário, existindo a possibilidade de calcular a distância a partir deles. As mesmas margens maneira serão calculadas para cada objeto de um elemento. Depois disso, todos os objetos do elemento são criados e uma verificação de estado de janela é levada a cabo no final. Se a janela é minimizada, então o elemento tem de ser escondido.
//+------------------------------------------------------------------+ //| Cria o elemento de menu | //+------------------------------------------------------------------+ bool CMenuItem::CreateMenuItem(const long chart_id,const int subwin,const int index_number,const string label_text,const int x,const int y) { //--- Sai, se não houver um ponteiro para o formulário if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Antes de criar o elemento de menu, a classe deve ser passada. " "pelo ponteiro da janela: CMenuItem::WindowPointer(CWindow &object)"); return(false); } //--- Inicialização das variáveis m_id =m_wnd.LastId()+1; m_index =index_number; m_chart_id =chart_id; m_subwin =subwin; m_label_text =label_text; m_x =x; m_y =y; //--- Margens do ponto extremo CElement::XGap(m_x-m_wnd.X()); CElement::YGap(m_y-m_wnd.Y()); //--- Criando o elemento de menu if(!CreateArea()) return(false); if(!CreateIcon()) return(false); if(!CreateLabel()) return(false); if(!CreateArrow()) return(false); //--- Se a janela está minimizada, oculte o elemento após sua criação if(m_wnd.IsMinimized()) Hide(); //--- return(true); }
No método CMenuItem::Hide() de esconder o elemento, todos os objetos tem de ser ocultos, algumas variáveis zeradas e as cores redefinidas:
//+------------------------------------------------------------------+ //| Oculta o elemento de menu | //+------------------------------------------------------------------+ void CMenuItem::Hide(void) { //--- Sai se o elemento está oculto if(!CElement::m_is_visible) return; //--- Oculta todos os objetos for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_NO_PERIODS); //--- Zerando as variáveis m_context_menu_state=false; CElement::m_is_visible=false; CElement::MouseFocus(false); //--- Reseta a cor m_area.BackColor(m_area_color); m_arrow.State(false); }
A elaboração do nome para os objetos gráficos do elemento de menu será um pouco mais complexa que na classe de formulário CWindow. Se um elemento de menu é criado como um elemento separado, como um elemento de menu independente, que não faz parte do menu principal ou de contexto (exemplos a seguir), então não haverá qualquer problema já que este terá um identificador único. Se este elemento for criado em um grupo, onde existem outros objetos semelhantes, então, apenas um identificador não é suficiente. Isto deve-se pelo fato dos nomes de todos os objetos gráficos do mesmo tipo serem o mesmo e, como resultado apenas um elemento será visível no gráfico.
Quando um elemento de menu é criado, o número do índice deste elemento será passado para o método CMenuItem::CreateMenuItem(). Em seguida, ele será armazenado no campo da classe m_index_number, que mais tarde será utilizado para formar o nome de cada objeto gráfico do elemento. Abaixo estão as partes integrantes do nome do objeto do elemento de menu:
- Nome do programa.
- Pertencente ao elemento.
- Pertencente a uma parte do elemento.
- Número de índice do elemento.
- Identificador do elemento (ID).
Os métodos de criação de um fundo, um rótulo de texto e da indicação da presença do menu de contexto não possuem nenhuma diferença importante em relação as apresentadas na classe CWindow e, portanto, você pode estudar o seu código no arquivo MenuItem.mqh anexado neste artigo. Aqui, nós vamos demonstrar o método CMenuItem::CreateIcon() para a criação de um ícone como exemplo. Neste método, o tipo de elemento de menu é verificado e, dependendo do resultado, um ícone relevante é definido.
Elementos de menu simples (MI_SIMPLE) e os que contêm um menu de contexto (MI_HAS_CONTEXT_MENU) não pode ter um ícone de modo algum. Se eles não são definidos pelo usuário, então o programa sai do método retornando true, já que este não é um erro e um ícone não é necessário. Se os elementos de menu do tipo caixa de seleção ou botão de rádio não foram definidos, utiliza-se seus tipos padrões. Os ícones utilizados no código da biblioteca estão anexados neste artigo.
O código do método CMenuItem::CreateIcon():
//+------------------------------------------------------------------+ //| Cria o elemento rótulo | //+------------------------------------------------------------------+ #resource "\\Images\\Controls\\CheckBoxOn_min_gray.bmp" #resource "\\Images\\Controls\\CheckBoxOn_min_white.bmp" //--- bool CMenuItem::CreateIcon(void) { //--- Se este é um elemento simples ou um que contém um menu de contexto if(m_type_menu_item==MI_SIMPLE || m_type_menu_item==MI_HAS_CONTEXT_MENU) { //--- Se o rótulo não é exigido (ícone não está definido), retorna "true" if(m_icon_file_on=="" || m_icon_file_off=="") return(true); } //--- Se este é uma caixa de seleção else if(m_type_menu_item==MI_CHECKBOX) { //--- Se o ícone não foi definido, define o seu padrão if(m_icon_file_on=="") m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp"; if(m_icon_file_off=="") m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp"; } //--- Se este é um elemento de botão de rádio else if(m_type_menu_item==MI_RADIOBUTTON) { //--- Se o ícone não foi definido, define o seu padrão if(m_icon_file_on=="") m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp"; if(m_icon_file_off=="") m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp"; } //--- Elaborando o nome do objeto string name=CElement::ProgramName()+"_menuitem_icon_"+(string)CElement::Index()+"__"+(string)CElement::Id(); //--- Coordenadas do objeto int x =m_x+7; int y =m_y+4; //--- Define o rótulo if(!m_icon.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- Define as propriedades m_icon.BmpFileOn("::"+m_icon_file_on); m_icon.BmpFileOff("::"+m_icon_file_off); m_icon.State(m_item_state); m_icon.Corner(m_corner); m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor); m_icon.Selectable(false); m_icon.Z_Order(m_zorder); m_icon.Tooltip("\n"); //--- Margens do ponto extremo m_icon.XGap(x-m_wnd.X()); m_icon.YGap(y-m_wnd.Y()); //--- Armazena o ponteiro de objeto CElement::AddToArray(m_icon); return(true); }
O método CMenuItem::Hide() para ocultar o elemento foi mostrado anteriormente. Agora, nós vamos implementar o método CMenuItem::Show() que fará com que o elemento seja visível. No caso de o elemento de menu ser do tipo caixa de seleção ou botão de rádio, este método tem de considerar o seu estado atual (ativado/desativado):
//+------------------------------------------------------------------+ //| Faz com que o elemento de menu seja visível | //+------------------------------------------------------------------+ void CMenuItem::Show(void) { //--- Retorna, se o elemento já for visível if(CElement::m_is_visible) return; //--- Faz com que todos os objetos sejam visíveis for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_ALL_PERIODS); //--- Se este é uma caixa de seleção, verifica o seu estado if(m_type_menu_item==MI_CHECKBOX) m_icon.Timeframes((m_checkbox_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); //--- Se este é um botão de rádio, verifica o seu estado else if(m_type_menu_item==MI_RADIOBUTTON) m_icon.Timeframes((m_radiobutton_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); //--- Zerando as variáveis CElement::m_is_visible=true; CElement::MouseFocus(false); }
Quando todos os métodos para a criação do elemento de interface forem implementados, será possível realizar os testes de anexar a interface ao gráfico. Faça a inclusão da classe CMenuItem no arquivo WndContainer.mqh para que ela esteja disponível para uso:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Window.mqh" #include "MenuItem.mqh"
Nós podemos usar o EA que testamos no artigo anterior. Na classe CProgram , crie uma instância da classe CMenuItem e do método CProgram::CreateMenuItem1() para a criação do elemento de menu. As margens do ponto da borda do formulário para cada elemento criado serão mantidas acessível em macros. Quando há um grande número de elementos, esta é uma maneira mais conveniente e rápida para ajustar a sua posição em relação aos outros do que mudar de uma implementação de método para outro.
//+------------------------------------------------------------------+ //| Classe para a criação de um aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Janela CWindow m_window; //--- Elemento de menu CMenuItem m_menu_item1; public: CProgram(); ~CProgram(); //--- Inicialização/desinicialização void OnInitEvent(void); void OnDeinitEvent(const int reason); //--- Timer void OnTimerEvent(void); //--- protected: //--- Manipulador de eventos do gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- public: //--- Cria o painel de negociação bool CreateTradePanel(void); //--- private: //--- Criação de um formulário bool CreateWindow(const string text); //--- Criando o elemento de menu #define MENU_ITEM1_GAP_X (6) #define MENU_ITEM1_GAP_Y (25) bool CreateMenuItem1(const string item_text); };
Para visualizar como se parece um elemento com um conjunto completo de recursos, vamos criar um elemento de menu com um ícone, que contém um menu de contexto para realizar um teste. Nós teremos dois ícones - coloridos e incolores. O ícone incolor será usado quando o elemento estiver bloqueado (indisponível).
Segue abaixo o código do método CProgram::CreateMenuItem1(). Os resources (ícones) incluídos estão disponíveis no final deste artigo. No início do método, o ponteiro do formulário na qual o elemento será anexado, é armazenado no elemento da classe. Em seguida, as coordenadas são calculadas, as propriedades necessárias do elemento são definidas e o elemento de menu é anexado ao gráfico.
//+------------------------------------------------------------------+ //| Cria o elemento de menu | //+------------------------------------------------------------------+ #resource "\\Images\\Controls\\bar_chart.bmp" #resource "\\Images\\Controls\\bar_chart_colorless.bmp" //--- bool CProgram::CreateMenuItem1(string item_text) { //--- Armazena o ponteiro da janela m_menu_item1.WindowPointer(m_window); //--- Coordenadas int x=m_window.X()+MENU_ITEM1_GAP_X; int y=m_window.Y()+MENU_ITEM1_GAP_Y; //--- Configura as propriedades antes da criação m_menu_item1.XSize(193); m_menu_item1.YSize(24); m_menu_item1.TypeMenuItem(MI_HAS_CONTEXT_MENU); m_menu_item1.IconFileOn("Images\\Controls\\bar_chart.bmp"); m_menu_item1.IconFileOff("Images\\Controls\\bar_chart_colorless.bmp"); m_menu_item1.LabelColor(clrWhite); m_menu_item1.LabelColorHover(clrWhite); //--- Criando o elemento de menu if(!m_menu_item1.CreateMenuItem(m_chart_id,m_subwin,0,item_text,x,y)) return(false); //--- return(true); }
Agora, é possível adicionar a chamada do método para a criação do elemento de menu no método CProgram::CreateTradePanel():
//+------------------------------------------------------------------+ //| Cria o painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação de um formulário para os controles if(!CreateWindow("EXPERT PANEL")) return(false); //--- Criação dos controles: // Elemento de menu if(!CreateMenuItem1("Menu item")) return(false); //--- Redesenho do gráfico m_chart.Redraw(); return(true); }
Um leitor atento terá uma pergunta: "Como o ponteiro do elemento chegará na base?". Esta é uma boa pergunta já que isso ainda não foi implementado e o ponteiro do elemento ainda não foi armazenado na base. Anteriormente, eu mencionei que iria demonstrar uma peculiaridade interessante do compilador. Agora, tudo está pronto para isso. Atualmente, os métodos para gerenciar o elemento e manipular os eventos não estão implementados na classe CMenuItem e não há ponteiro na base. A compilação do arquivo com a classe CMenuItem não indica erro algum. Tente compilar o arquivo do EA e você receberá uma mensagem de erro especificando os métodos para os quais a sua implementação é necessária (veja a imagem abaixo).
Fig. 4. Mensagem sobre a falta de implementação do método
Quando o arquivo foi compilado com a classe CMenuItem, nenhum erro foi encontrado pois no nível atual de desenvolvimento desta classe, não foi chamado nenhum método sem sua implementação. O elemento base,no momento, tem apenas o ponteiro do formulário. Parece que a chamada dos métodos que você vê na imagem acima foi realizada apenas para o formulário (CWindow) na classe CWndEvents nos loops dos métoos CheckElementsEvents(), MovingWindow(), CheckElementsEventsTimer() e Destroy(). Os ponteiros de elemento são armazenados no array do tipo CElement, o que significa que a chamada é realizada através da classe base CElement, onde estes métodos são declarados como virtual.
Uma vez que no arquivo WndContainer.mqh, na base estão incluídos dois arquivos com os elementos Window.mqh e MenuItem.mqh e suas classes são derivadas a partir da classe CElement, o compilador identifica todas as classes derivadas dele e exige a implementação dos métodos chamados, mesmo se um deles não tiver uma chamada direta. Vamos criar os métodos necessários na classe CMenuItem. Nós também vamos criar um método que nos permitirá adicionar ponteiros de controle para a base na classe CWndContainer.
Nesta fase, é possível adicionar o método CMenuItem::ChangeObjectsColor() para alterar a cor dos objetos do elemento quando o cursor do mouse estiver pairando sobre ele. Neste método, o tipo e o estado do elemento de menu (disponível/bloqueado) precisa ser considerado. No início deste método, deve haver uma verificação se este elemento contém um menu de contexto e se o mesmo está habilitado no momento. A razão é que quando um menu de contexto é habilitado, a gestão é passada para ele. Além disso, a cor do elemento que foi chamado deve ser gravada.
Mais tarde, nós também precisaremos de um método para "resetar" a cor do foco no elemento de menu, por isso vamos criar o método CMenuItem::ResetColors().
class CMenuItem : public CElement { public: //--- Alteração da cor dos objetos de controle void ChangeObjectsColor(void); //--- Resetar a cor void ResetColors(void); //--- }; //+------------------------------------------------------------------+ //| Alterar a cor do objeto quando o cursor estiver pairando sobre ele| //+------------------------------------------------------------------+ void CMenuItem::ChangeObjectsColor(void) { //--- Retorna, se esse elemento tem um menu de contexto e ele está habilitado if(m_type_menu_item==MI_HAS_CONTEXT_MENU && m_context_menu_state) return; //--- Bloco de código para elementos simples e os que contêm um menu de contexto if(m_type_menu_item==MI_HAS_CONTEXT_MENU || m_type_menu_item==MI_SIMPLE) { //--- Se houver um foco if(CElement::MouseFocus()) { m_icon.State(m_item_state); m_area.BackColor((m_item_state)? m_area_color_hover : m_area_color_off); m_label.Color((m_item_state)? m_label_color_hover : m_label_color_off); if(m_item_state) m_arrow.State(true); } //--- Se não houver um foco else { m_arrow.State(false); m_area.BackColor(m_area_color); m_label.Color((m_item_state)? m_label_color : m_label_color_off); } } //--- Código do bloco para os elementos de caixa de seleção e botão de radio else if(m_type_menu_item==MI_CHECKBOX || m_type_menu_item==MI_RADIOBUTTON) { m_icon.State(CElement::MouseFocus()); m_area.BackColor((CElement::MouseFocus())? m_area_color_hover : m_area_color); m_label.Color((CElement::MouseFocus())? m_label_color_hover : m_label_color); } } //+------------------------------------------------------------------+ //| Reseta a cor do elemento | //+------------------------------------------------------------------+ void CMenuItem::ResetColors(void) { CElement::MouseFocus(false); m_area.BackColor(m_area_color); m_label.Color(m_label_color); }
Os métodos para mover e remover os objetos são implementados de uma forma semelhante ao que foi feito na classe CWindow e, portanto, não há nenhum ponto em repeti-los aqui. Você pode encontrar o seu código nos arquivos anexados a este artigo. Por isso, adicione o mínimo de código necessário (veja o código abaixo) para os métodos CMenuItem::OnEvent() e CMenuItem::OnEventTimer() e compile os arquivos da biblioteca e o EA. Agora, não haverá quaisquer erros que relatam a necessidade da implementação do método.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- Se o controle não está oculto if(!CElement::m_is_visible) return; //--- Identificador do foco int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); return; } } //+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CMenuItem::OnEventTimer(void) { //--- Alterando a cor dos objetos do formulário ChangeObjectsColor(); }
Teste da colocação de um Elemento de Menu
Quando você carrega o EA para o gráfico, você verá um formulário com o elemento de menu, como na imagem abaixo. Você não pode visualizá-lo aqui já que a cor padrão de fundo do elemento coincide com a cor de fundo do formulário. Você já pode usar a funcionalidade da classe para mudar muitas propriedades dos objetos - é necessário tanto o formulário como o elemento de menu.
Fig. 5. Teste de anexar o elemento de menu ao gráfico
O elemento é anexado ao gráfico, mas se você tentar mover o formulário, o elemento irá ficar no mesmo local e se você passar o cursor sobre ele, sua aparência não irá mudar. Vamos criar um método para adicionar ponteiros de elemento para a base. As propriedades de todos os elementos podem ser gerenciadas através da chamada de cada uma delas em um loop.
Vamos chamar este método de CWndContainer::AddToElementsArray(). Ele terá dois parâmetros: (1) Número do índice do formulário, a qual o elemento supostamente será anexado (2) elemento do objeto, um ponteiro que deverá ser guardado na base. No início deste método, haverá uma verificação se a base contém um ou mais formulários. Se não houver nenhum, então, será impresso uma mensagem no registro dizendo que antes de tentar anexar o elemento ao formulário, ele tem que ser adicionado primeiramente à base. Em seguida, haverá uma verificação se o tamanho do array não excedeu.
Se não há problemas, então (1) o ponteiro é adicionado ao array de formulário a qual ele está anexado, (2) os objetos do elemento são adicionados ao array comum de objetos, (3) o último identificador deste elemento é armazenado em todos os formulários e (4) o contador de elementos é incrementado de um. Esta não é a versão final do método, nós vamos voltar a isso mais tarde. O código da versão atual é apresentado a seguir:
//+------------------------------------------------------------------+ //| 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 if(ArraySize(m_windows)<1) { Print(__FUNCTION__," > Antes de criar um controle, crie um formulário " "e adicione-o a base usando o método CWndContainer::AddWindow(CWindow &object)."); return; } //--- Se houver uma solicitação para uma formulário não-existente if(window_index>=ArraySize(m_windows)) { Print(PREVENTING_OUT_OF_RANGE," window_index: ",window_index,"; ArraySize(m_windows): ",ArraySize(m_windows)); return; } //--- Adiciona ao array comum de elementos int size=ArraySize(m_wnd[window_index].m_elements); ArrayResize(m_wnd[window_index].m_elements,size+1); m_wnd[window_index].m_elements[size]=GetPointer(object); //--- Adiciona os objetos do elemento para o array comum de objetos AddToObjectsArray(window_index,object); //--- Armazena o ID do último elemento em todos os formulários int windows_total=ArraySize(m_windows); for(int w=0; w<windows_total; w++) m_windows[w].LastId(m_counter_element_id); //--- Aumenta o contador de identificadores do elemento m_counter_element_id++; }
A versão atual do método CWndContainer::AddToElementsArray() já é suficiente para testar o EA, que começamos a trabalhar anteriormente. No final do método CProgram::CreateMenuItem(), após a criação de um elemento, adicione uma linha de código, como mostrado na versão curta do método abaixo.
//+------------------------------------------------------------------+ //| Cria o elemento de menu | //+------------------------------------------------------------------+ bool CProgram::CreateMenuItem(string item_text) { //--- Armazena o ponteiro da janela //--- Coordenadas //--- Configura as propriedades antes da criação //--- Criando o elemento de menu //--- Adiciona o ponteiro do elemento para a base CWndContainer::AddToElementsArray(0,m_menu_item); return(true); }
Se os arquivos onde as mudanças foram introduzidas são compilados e o EA é carregado para o gráfico, então, quando o formulário é deslocado, o elemento de menu estará se movendo junto com ele e todos os seus objetos irão mudar sua aparência quando o cursor do mouse estiver sobre ele:
Fig. 6. Teste do elemento de menu como uma parte da interface gráfica
Melhorando as Classes Principais da Biblioteca
Se o formulário for minimizado agora, o elemento de menu não será oculto, como esperado. Como podemos implementar o ocultamento dos elementos anexados ao formulário? Atualmente, a gestão dos eventos do gráfico está organizada na classe CWndEvents, que tem acesso à base de todos os elementos de sua própria classe base, a CWndContainer. Existe alguma indicação de que um botão de minimizar ou maximizar foi pressionado? Para tais casos, a MQL possui a função EventChartCustom() que pode gerar eventos personalizados.
Agora, quando o botão para minimizar o formulário for clicado, o programa irá acompanhar o evento CHARTEVENT_OBJECT_CLICK no manipulador OnEvent() da classe CWindow e, tendo verificado o valor do parâmetro de string (sparam) deste evento junto do nome do objeto gráfico, o método CWindow::RollUp() será chamado. Este é o momento em que uma mensagem poderá ser enviada para a fila do fluxo de evento e ser recebida no manipulador da classe CWndEvents. Como a classe CWndEvents tem acesso a todos os elementos, o método CElement::Hide() de cada elemento pode ser chamado no loop. O mesmo deve ser feito para o evento de maximização do formulário, fazendo com que todos os elementos do formulário sejam visíveis usando os seus métodos CElement::Show().
Cada evento personalizado requer seu identificador exclusivo. Adicione os identificadores para a minimização/maximização do formulário para o arquivo Defines.mqh:
//--- Eventos #define ON_WINDOW_UNROLL (1) // Maximização do formulário #define ON_WINDOW_ROLLUP (2) // Minimização do formulário
No final da chamada dos métodos CWindow::RollUp() e CWindow::Unroll() a função EventChartCustom() passou por elas:
- O identificador do gráfico.
- O identificador de evento personalizado.
- O identificador do elemento como o terceiro parâmetro (lparam).
- O número da sub-janela do gráfico e onde o programa está localizado como quarto parâmetro (dparam).
O terceiro e o quarto parâmetro são necessários para a verificação adicional no caso deste evento ser enviado por um outro elemento ou outro programa.
Abaixo encontramos as versões curtas dos métodos CWindow::RollUp() e CWindow::Unroll() contendo apenas o que tem de ser adicionado a eles (Todos os comentários foram mantidos):
//+------------------------------------------------------------------+ //| Minimiza a janela | //+------------------------------------------------------------------+ void CWindow::RollUp(void) { //--- Altera o botão //--- Define e armazena o tamanho //--- Desabilita o botão //--- Minimiza o estado do formulário //--- Se este é um indicador com uma altura definida e com a sub-janela no modo minimizado, // define o tamanho do indicador da sub-janela //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_WINDOW_ROLLUP,CElement::Id(),m_subwin,""); } //+------------------------------------------------------------------+ //| Maximiza a janela | //+------------------------------------------------------------------+ void CWindow::Unroll(void) { //--- Altera o botão //--- Define e armazena o tamanho //--- Desabilita o botão //--- Maximiza o estado do formulário //--- Se este é um indicador com uma altura definida e com a sub-janela no modo minimizado, // define o tamanho do indicador da sub-janela //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_WINDOW_UNROLL,CElement::Id(),m_subwin,""); }
Agora, nós precisamos criar os métodos na classe CWndEvents, que irá manipular esses eventos personalizados. Vamos nomeá-los da mesma forma que os macros para os identificadores. Abaixo está o código para a declaração e implementação dos métodos na classe CWndEvents:
class CWndEvents : public CWndContainer { private: //--- Minimizando/maximizando o formulário bool OnWindowRollUp(void); bool OnWindowUnroll(void); //--- }; //+------------------------------------------------------------------+ //| Evento ON_WINDOW_ROLLUP | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowRollUp(void) { //--- Se o sinal for para minimizar o formulário if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_ROLLUP) return(false); //--- Se o identificador da janela e o número da sub-janela coincidirem if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin) { int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) { //--- Esconder todos os elementos, exceto o formulário if(m_wnd[0].m_elements[e].ClassName()!="CWindow") m_wnd[0].m_elements[e].Hide(); } } //--- return(true); } //+------------------------------------------------------------------+ //| Evento ON_WINDOW_UNROLL | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowUnroll(void) { //--- Se o sinal for para maximizar o formulário if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL) return(false); //--- Se o identificador da janela e o número da sub-janela coincidirem if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin) { int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) { //--- Faça todos os elementos visíveis, exceto o formulário e a // lista suspensa if(m_wnd[0].m_elements[e].ClassName()!="CWindow") if(!m_wnd[0].m_elements[e].IsDropdown()) m_wnd[0].m_elements[e].Show(); } } //--- return(true); }
O método CWindow::Show() ainda não foi implementado na classe do formulário. Como este método será chamado em todos os elementos em um loop, a sua implementação é obrigatória, mesmo se uma condição não permite que o programa chegue a classe CWindow, como é mostrado no código acima.
Código do método CWindow::Show():
//+------------------------------------------------------------------+ //| Exibe a janela | //+------------------------------------------------------------------+ void CWindow::Show(void) { //--- Faz com que todos os objetos sejam visíveis for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_ALL_PERIODS); //--- Estado de visibilidade CElement::m_is_visible=true; //--- Zera o foco CElement::MouseFocus(false); m_button_close.MouseFocus(false); m_button_close.State(false); }
Nós vamos chamar esses métodos no método CWndEvents::ChartEventCustom():
//+------------------------------------------------------------------+ //| 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; }
A partir de agora, todos os métodos que manipulam os eventos personalizados estarão localizados no método CWndEvents::ChartEventCustom().
Por sua vez, a chamada do CWndEvents::ChartEventCustom() tem de ser colocado no método CWndEvents::ChartEvent() antes dos métodos onde os eventos do gráfico são tratados:
//+------------------------------------------------------------------+ //| Manipulação de eventos do programa | //+------------------------------------------------------------------+ void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Retorna, se o array está vazio if(CWndContainer::WindowsTotal()<1) return; //--- Inicialização dos campos dos parâmetros de evento InitChartEventsParams(id,lparam,dparam,sparam); //--- Evento personalizado ChartEventCustom(); //--- Eventos de verificação dos elementos de interface CheckElementsEvents(); //--- Evento do movimento do mouse ChartEventMouseMove(); //--- O evento de alteração das propriedades do gráfico ChartEventChartChange(); }
Depois de compilar os arquivos que sofreram alterações e o arquivo principal do programa, carregue-o para o gráfico. Agora, quando o formulário for minimizado, o elemento de menu será oculto e se tornará visível quando o formulário for maximizado.
Nós completamos o desenvolvimento da parte principal da classe CMenuItem. Agora, nós só temos que configurar o manipulador de eventos desse controle. Nós vamos voltar a isto depois de termos implementado a classe para a criação de um menu de contexto para que todas as alterações possam ser testadas de forma consistente e completa. Antes disso, nós vamos desenvolver um outro elemento de interface, que é uma parte de um menu de contexto - a linha de separação.
Conclusão
Neste artigo, discutimos em pormenor o processo de criação do controle do elemento de menu. Nós também introduzimos as adições necessárias à classe de formulário para os controles (CWindow) e classe principal de manipulação de eventos (CWndEvents). No próximo artigo nós vamos criar as classes para criar uma linha de separação e um menu de contexto.
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/2200
- 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