Interfaces Gráficas III: Botões Simples e Multifuncionais (Capítulo 1)
Conteúdo
- Introdução
- Desenvolvimento da Classe para Criar um Botão Simples
- Desenvolvimento da Classe para Criar um Botão com Ícone
- Desenvolvimento da Classe para Criar um Botão de Divisão
- Conclusão
Introdução
Nas duas partes anteriores desta série, nós estudamos os temas relativos ao desenvolvimento da estrutura da biblioteca para a criação das interfaces gráficas e dos mecanismos básicos para o gerenciamento de objetos.
- Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1)
- Interfaces Gráficas I: Formulário para os Controles (Capítulo 2)
- Interfaces Gráficas I: Animação na Interface Gráfica (Capítulo 3)
- Interfaces gráficas I: Funções para os Botões do Formulário e Remoção dos Elementos da Interface (Capítulo 4)
- Interfaces gráficas I: Biblioteca de Testes em Programas de Diferentes Tipos e no Terminal MetaTrader 4 (Capítulo 5)
Na segunda parte desta série, foi estudado em detalhes um exemplo de como criar um controle e vinculá-lo ao motor gráfico da biblioteca. Esse exemplo foi bem difícil. É reconhecível que o menu principal e o de contexto são alguns dos controles mais complexos.
- 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)
Esse artigo será significativamente mais simples que os anteriores. Aqui, nós vamos estudar o controle chamado botão.
O botão é o controle mais simples na interface gráfica que um usuário pode interagir. Ao mesmo tempo, pode haver várias opções de implementação. Neste artigo, nós vamos criar três classes para a criação de botões de diferentes níveis de complexidade.
- Botão simples. A classe CSimpleButton.
- Botão com Ícone. A classe CIconButton.
- Botão de divisão (split button). A classe CSplitButton.
Nós também vamos implementar outras três classes para permitir a criação de grupos de botões interligados.
- Grupo de botões simples. A classe CButtonsGroup.
- Grupo de botões com ícones. A classe CIconButtonsGroup.
- Grupo de botões de radio. A classe CRadioButtons.
Nós também vamos introduzir incrementos para enriquecer a funcionalidade do menu de contexto com mais um modo. A classe de formulário CWindow receberá mais um campo com um método que permitirá definir exatamente qual o controle que bloqueou o formulário no momento da sua ativação. Isto irá permitir criar condições em que o formulário poderá ser desbloqueado apenas pelo controle que o bloqueou.
Nós não vamos discutir os métodos característicos de todos os controles já que eles foram bem estudados nos artigos anteriores. Tais métodos serão mostrados no código apenas como uma declaração no corpo da classe.
Desenvolvimento da Classe para Criar um Botão Simples
Vamos começar com um botão simples. Nós já preparamos uma classe para criar um objeto primitivo de controle do tipo CButton no arquivo Objects.mqh. A CChartObjectButton é a sua classe base, que pode ser utilizada para criar um objeto gráfico do tipo OBJ_BUTTON. As propriedades desse objeto já implicam em dois estados - ligado (pressionado) e desligado (solto). A sua representação gráfica também pode ter duas opções, ela depende se a exibição do quadro do objeto está ativada ou não. Em ambos os modos, a cor do botão é ligeiramente mais escura quando ele está pressionado do que em seu estado solto
Este objeto pode ser ligado ao gráfico manualmente a partir do menu principal: Inserir -> Objetos -> Objetos gráficos -> Botão. Os parâmetros também podem ser alterados manualmente a partir da janela de configurações do objeto gráfico:
Fig. 1. A janela de configurações do objeto gráfico botão.
Em seguida, na pasta Controls que contém outros arquivos com as classes dos controles de nossa biblioteca, crie o arquivo SimpleButton.mqh. Neste arquivo, crie a classe CSimpleButton com os campos e métodos padrão para todos os controles, que foram considerados em detalhes nos artigos anteriores.
//+------------------------------------------------------------------+ //| SimpleButton.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Classe para criar um botão simples | //+------------------------------------------------------------------+ class CSimpleButton : public CElement { private: //--- Ponteiro para o formulário ao qual o elemento está anexado CWindow *m_wnd; //--- public: CSimpleButton(void); ~CSimpleButton(void); //--- Armazena o ponteiro do formulário void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- 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); //--- Move o elemento virtual void Moving(const int x,const int y); //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse virtual void SetZorders(void); virtual void ResetZorders(void); };
Vamos definir quais propriedades podem ser configuradas antes de criar o botão
- Tamanho.
- Cores de fundo para diferentes estados e em relação à localização do cursor do mouse.
- Cores do quadro para os diferentes estados.
- Cores do texto.
Às vezes, é necessário um botão que retorna automaticamente ao seu estado inicial (solto) depois dele ter sido pressionado. Acontece também, situações em que o botão é para ficar pressionado após ter sido solto e tornar-se solto após ele ter sido pressionado. Nós vamos fazer com que o botão possa ser trabalhado com os dois modos de escolha do usuário. O método IsPressed() será necessário para identificar se o botão está atualmente pressionado ou solto.
É necessário também a possibilidade de bloquear/desbloquear um botão no caso do desenvolvedor da aplicação precisar usá-lo. Por exemplo, um botão será bloqueado se não forem satisfeitas as condições necessárias para a utilização da função embutida no botão. O botão estará disponível logo que estas condições necessárias se satisfizerem. Mais tarde, esses exemplos serão estudados neste artigo.
Vamos adicionar os métodos para a criação de um botão para a classe. A princípio, não há nada novo em relação aos métodos que nós já estudamos e você pode encontrar o código deles nos arquivos anexados neste artigo.
class CSimpleButton : public CElement { private: //--- Objeto para a criação de um botão CButton m_button; //--- Propriedades do botão: // (1) Texto, (2) tamanho string m_button_text; int m_button_x_size; int m_button_y_size; //--- Cor de fundo color m_back_color; color m_back_color_off; color m_back_color_hover; color m_back_color_pressed; color m_back_color_array[]; //--- Cor do quadro color m_border_color; color m_border_color_off; //--- Cor do texto color m_text_color; color m_text_color_off; color m_text_color_pressed; //--- Prioridade para o clique esquerdo do mouse int m_button_zorder; //--- Modo do botão de dois estados bool m_two_state; //--- Disponível/bloqueado bool m_button_state; //--- public: //--- Métodos para a criação de um botão simples bool CreateSimpleButton(const long chart_id,const int subwin,const string button_text,const int x,const int y); //--- private: bool CreateButton(void); //--- public: //--- (1) configurando o modo do botão, // (2) estado geral do botão (disponível/bloqueado) void TwoState(const bool flag) { m_two_state=flag; } bool IsPressed(void) const { return(m_button.State()); } bool ButtonState(void) const { return(m_button_state); } void ButtonState(const bool state); //--- Tamanho do botão void ButtonXSize(const int x_size) { m_button_x_size=x_size; } void ButtonYSize(const int y_size) { m_button_y_size=y_size; } //--- (1) Retorna o texto do botão, (2) configura a cor do texto do botão string Text(void) const { return(m_button.Description()); } void TextColor(const color clr) { m_text_color=clr; } void TextColorOff(const color clr) { m_text_color_off=clr; } void TextColorPressed(const color clr) { m_text_color_pressed=clr; } //--- Configurando a cor de fundo do botão void BackColor(const color clr) { m_back_color=clr; } void BackColorOff(const color clr) { m_back_color_off=clr; } void BackColorHover(const color clr) { m_back_color_hover=clr; } void BackColorPressed(const color clr) { m_back_color_pressed=clr; } //--- Configurando a cor do quadro do botão void BorderColor(const color clr) { m_border_color=clr; } void BorderColorOff(const color clr) { m_border_color_off=clr; } //--- }; //+------------------------------------------------------------------+ //| Altera o estado do botão | //+------------------------------------------------------------------+ void CSimpleButton::ButtonState(const bool state) { m_button_state=state; m_button.State(false); m_button.Color((state)? m_text_color : m_text_color_off); m_button.BackColor((state)? m_back_color : m_back_color_off); m_button.BorderColor((state)? m_border_color : m_border_color_off); }
Nós vamos considerar a lógica da manipulação de eventos quando um botão é pressionado. Adicione mais um identificador ON_CLICK_BUTTON ao arquivo Defines.mqh para gerar um evento personalizado. Ele será usado em todas as classes dedicadas à criação de um botão.
#define ON_CLICK_BUTTON (8) // Pressionando o botão
Agora, crie o método CSimpleButton::OnClickButton() para o tratamento do pressionamento do botão. Dois controles são exigidos no início do método: um para o nome do objeto pressionado e outro para o estado atual do objeto. O resultado negativo destas verificações levará a saída do método. Se as verificações são passadas, então a manipulação é realizada considerando-se qual o modo que o botão está. Se ela se tornar "solta" por si só, então, ele retorna ao seu estado inicial e sua cor correspondente. Para o modo com dois estados, cada estado tem dois grupos de cores. No final do método, uma mensagem é enviada com o identificador ON_CLICK_BUTTON, o identificador do elemento, o índice do elemento e o nome do botão.
class CSimpleButton : public CElement { private: //--- Manipulando o pressionamento de um botão bool OnClickButton(const string clicked_object); }; //+------------------------------------------------------------------+ //| Manipulador de evento | //+------------------------------------------------------------------+ void CSimpleButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do botão esquerdo do mouse sobre o objeto if(id==CHARTEVENT_OBJECT_CLICK) { if(OnClickButton(sparam)) return; } } //+------------------------------------------------------------------+ //| Manipulando o pressionamento de um botão | //+------------------------------------------------------------------+ bool CSimpleButton::OnClickButton(const string clicked_object) { //--- Verifica o nome do objeto if(m_button.Name()!=clicked_object) return(false); //--- Se o botão está bloqueado if(!m_button_state) { m_button.State(false); return(false); } //--- Se o modo do botão tem um estado if(!m_two_state) { m_button.State(false); m_button.Color(m_text_color); m_button.BackColor(m_back_color); } //--- Se o modo do botão tem dois estados else { //--- Se o botão foi pressionado if(m_button.State()) { //--- Altera a cor do botão m_button.State(true); m_button.Color(m_text_color_pressed); m_button.BackColor(m_back_color_pressed); CElement::InitColorArray(m_back_color_pressed,m_back_color_pressed,m_back_color_array); } //--- Se o botão é solto else { //--- Altera a cor do botão m_button.State(false); m_button.Color(m_text_color); m_button.BackColor(m_back_color); CElement::InitColorArray(m_back_color,m_back_color_hover,m_back_color_array); } } //--- Emite um evento ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),CElement::Index(),m_button.Description()); return(true); }
Nós já podemos testar a colocação dos botões no gráfico com vinculação ao formulário e receber mensagens no manipulador da classe personalizada da aplicação. Vamos copiar o EA de teste do artigo anterior. Deixe apenas o menu principal lá, com os menus de contexto ligados aos seus itens. O arquivo com a classe CSimpleButton deve ser incluída no arquivo WndContainer.mqh para que ele possa ser usado na classe original.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "SimpleButton.mqh"
Após isso, as instâncias da classe CSimpleButton e os métodos para a criação dos botões podem ser declarados na classe personalizada CProgram da aplicação. Vamos criar três botões como exemplo. Dois deles irá se "soltar" depois que eles forem pressionados e o terceiro terá uma opção de ser fixado, ou seja terá dois estados (pressionado/solto).
class CProgram : public CWndEvents { private: //--- Botões simples CSimpleButton m_simple_button1; CSimpleButton m_simple_button2; CSimpleButton m_simple_button3; //--- private: #define BUTTON1_GAP_X (7) #define BUTTON1_GAP_Y (50) bool CreateSimpleButton1(const string text); #define BUTTON2_GAP_X (128) #define BUTTON2_GAP_Y (50) bool CreateSimpleButton2(const string text); #define BUTTON3_GAP_X (7) #define BUTTON3_GAP_Y (75) bool CreateSimpleButton3(const string text); };
Nós vamos considerar o código apenas para um deles. Por favor, note a linha destacada do código onde o modo do botão com dois estados está habilitado.
//+------------------------------------------------------------------+ //| Cria um botão simples 3 | //+------------------------------------------------------------------+ bool CProgram::CreateSimpleButton3(string button_text) { //--- Passa o objeto do painel m_simple_button3.WindowPointer(m_window); //--- Coordenadas int x=m_window.X()+BUTTON3_GAP_X; int y=m_window.Y()+BUTTON3_GAP_Y; //--- Configura as propriedades antes da criação m_simple_button3.TwoState(true); m_simple_button3.ButtonXSize(237); m_simple_button3.TextColor(clrBlack); m_simple_button3.TextColorPressed(clrBlack); m_simple_button3.BackColor(clrLightGray); m_simple_button3.BackColorHover(C'193,218,255'); m_simple_button3.BackColorPressed(C'153,178,215'); //--- Criação de um botão if(!m_simple_button3.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y)) return(false); //--- Adiciona o objeto ao array comum dos grupos de objetos CWndContainer::AddToElementsArray(0,m_simple_button3); return(true); }
Todo o método para a criação dos elementos de interface gráfica são chamados no método CProgram::CreateTradePanel():
//+------------------------------------------------------------------+ //| Cria o painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação de um formulário para os controles //--- Criação dos controles: // Menu principal //--- Menus de contexto //--- Botões simples if(!CreateSimpleButton1("Simple Button 1")) return(false); if(!CreateSimpleButton2("Simple Button 2")) return(false); if(!CreateSimpleButton3("Simple Button 3")) return(false); //--- Redesenho do gráfico m_chart.Redraw(); return(true); }
A mensagem com o identificador do evento ON_CLICK_BUTTON será recebido no manipulador de eventos CProgram::OnEvent() como mostrado no código abaixo. Como exemplo, nós vamos implementar o bloco de código onde o nome do botão será verificado. Se acontecer do terceiro botão ser pressionado, seu estado atual definirá o estado do segundo botão. Isto é, se o terceiro botão for pressionado, o segundo será bloqueado e vice-versa.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Pressionando o botão de evento if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- if(sparam==m_simple_button3.Text()) { if(m_simple_button3.IsPressed()) m_simple_button1.ButtonState(false); else m_simple_button1.ButtonState(true); } } }
As imagens abaixo mostram o resultado da compilação e da colocação do programa ao gráfico. Na imagem da esquerda, o Botão Simples 3 foi solto e o Botão Simples 1 está disponível. Na imagem à direita, o Botão simples 3 foi pressionado e o Botão Simples 1 é bloqueado.
Fig. 2. Teste da colocação do controle botão ao gráfico. Botões em diferentes estados.
A implementação atual está faltando uma nuance na qual acrescentaria um realismo à interação com o botão. Nós precisamos fazer com que o botão mude de cor logo após ele ter sido pressionado. Uma verificação se o botão do mouse foi pressionado pode ser realizada pelo evento do movimento do cursor CHARTEVENT_MOUSE_MOVE , portanto, adicione o código correspondente ao manipulador de eventos CSimpleButton::OnEvent().
Nos casos em que (1) o elemento estiver oculto, (2) o formulário está bloqueado, (3) o botão esquerdo do mouse está solto (4) e o botão está bloqueado, o programa deixa o manipulador. Se todas estas verificações são passadas, então, a cor correspondente é configurada dependendo do foco e do estado atual do botão.
//+------------------------------------------------------------------+ //| Manipulador de evento | //+------------------------------------------------------------------+ void CSimpleButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do movimento do cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto if(!CElement::IsVisible()) return; //--- Identificador do foco int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Sai, se o formulário for bloqueado if(m_wnd.IsLocked()) return; //--- Sai, se o botão do mouse não está pressionado if(sparam=="0") return; //--- Sai, se o botão está bloqueado if(!m_button_state) return; //--- Se não houver um foco if(!CElement::MouseFocus()) { //--- Se o botão é solto if(!m_button.State()) { m_button.Color(m_text_color); m_button.BackColor(m_back_color); } //--- return; } //--- Se houver foco else { m_button.Color(m_text_color_pressed); m_button.BackColor(m_back_color_pressed); return; } //--- return; } }
Agora, tudo funcionará como projetado. O desenvolvimento da classe para a criação de um simples botão está finalizado. Você pode ver o seu código em detalhes nos arquivos anexados ao artigo. Agora, nós vamos estudar a classe para os botões com funcionalidade estendida.
Desenvolvimento da Classe para Criar um Botão com Ícone
Um botão com ícone será composto por três objetos primitivos gráficos:
- Fundo.
- Ícone.
- Rótulo de texto.
Fig. 3. Partes compostas do controle botão com ícone.
Um rótulo de texto é necessário para o posicionamento livre do texto do botão. Por exemplo, o botão pode ser composto de maneira que o seu texto fique na parte inferior e o seu ícone fique na parte superior, e vice-versa. Mais tarde nós mostraremos estes exemplos.
A classe para criar esse controle conterá os mesmos campos e métodos como a classe CSimpleButton para a criação de um botão simples. Além das propriedades que se referem ao tamanho do botão e sua cor, nós vamos exigir campos e métodos para estabelecer as margens para o ícone e o rótulo de texto em relação as coordenadas do controle, bem como os rótulos dos ícones no estado ativo e bloqueado. Vamos também adicionar uma opção para a criação de um botão com somente um ícone. Tal botão será composto de apenas um objeto do tipo OBJ_BITMAP_LABEL.
Em seguida, crie o arquivo IconButton.mqh e a classe CIconButton nele. Inclua no arquivo WndContainer.mqh da mesma maneira que nos outros controles. O código a seguir mostra apenas os campos e métodos da classe CIconButton que faz com que ela seja diferente da classe CSimpleButton para a criação de um botão simples.
class CIconButton : public CElement { private: //--- Objetos para a criação de um botão CButton m_button; CBmpLabel m_icon; CLabel m_label; //--- Propriedades do botão: // Ícones para o botão nos estados ativo e bloqueado string m_icon_file_on; string m_icon_file_off; //--- Margens do ícone int m_icon_x_gap; int m_icon_y_gap; //--- Texto e margens do rótulo de texto string m_label_text; int m_label_x_gap; int m_label_y_gap; //--- O modo somente ícone se o botão for composto apenas do objeto BmpLabel bool m_only_icon; //--- public: //--- Métodos para a criação de um botão bool CreateIconButton(const long chart_id,const int subwin,const string button_text,const int x,const int y); //--- private: bool CreateButton(void); bool CreateIcon(void); bool CreateLabel(void); //--- public: //--- Configurando o modo somente ícone void OnlyIcon(const bool flag) { m_only_icon=flag; } //--- Estabelecer os ícones para o botão nos estados ativo e bloqueado void IconFileOn(const string file_path) { m_icon_file_on=file_path; } void IconFileOff(const string file_path) { m_icon_file_off=file_path; } //--- Margens do ícone void IconXGap(const int x_gap) { m_icon_x_gap=x_gap; } void IconYGap(const int y_gap) { m_icon_y_gap=y_gap; } //--- Margens do rótulo de texto void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } };
Todos os campos da classe devem ser inicializados com os valores padrões:
//+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CIconButton::CIconButton(void) : m_icon_x_gap(4), m_icon_y_gap(3), m_label_x_gap(25), m_label_y_gap(4), m_icon_file_on(""), m_icon_file_off(""), m_button_state(true), m_two_state(false), m_only_icon(false), m_button_y_size(18), m_back_color(clrLightGray), m_back_color_off(clrLightGray), m_back_color_hover(clrSilver), m_back_color_pressed(clrBlack), m_border_color(clrWhite), m_border_color_off(clrDarkGray), m_label_color(clrBlack), m_label_color_off(clrDarkGray), m_label_color_hover(clrBlack), m_label_color_pressed(clrBlack) { //--- Armazena o nome da classe do elemento na classe base CElement::ClassName(CLASS_NAME); //--- Configura as prioridades para o botão esquerdo do mouse m_button_zorder =1; m_zorder =0; }
Os métodos para a criação de todos os objetos do botão irão conter uma verificação para o modo somente ícone. Se foi estabelecido que o botão deve ser composto de apenas um ícone, então o programa irá sair no início do método para a criação do fundo e o rótulo de texto.
//+------------------------------------------------------------------+ //| Cria o botão de fundo | //+------------------------------------------------------------------+ bool CIconButton::CreateButton(void) { //--- Sai, se o modo somente ícone está habilitado if(m_only_icon) return(true); //--- ... etc. } //+------------------------------------------------------------------+ //| Cria o botão de texto | //+------------------------------------------------------------------+ bool CIconButton::CreateLabel(void) { //--- Sai, se o modo somente ícone está habilitado if(m_only_icon) return(true); //--- ... etc. }
Quando um botão é para ser composto apenas por um ícone, o método para criar o último irá conter mais uma verificação quanto à presença dos ícones. Se um ícone não foi definido pelo usuário, então, a criação da interface gráfica será terminada nesta fase e o registro receberá uma mensagem de que a presença dos ícones neste modo é obrigatória. O método CIconButton::CreateIcon() para a criação de um botão com ícone é apresentado no código abaixo:
//+------------------------------------------------------------------+ //| Cria um botão com ícone | //+------------------------------------------------------------------+ bool CIconButton::CreateIcon(void) { //--- Se o modo somente ícone está desativado if(!m_only_icon) { //--- Sai, se não é necessário o ícone para o botão if(m_icon_file_on=="" || m_icon_file_off=="") return(true); } //--- Se o modo somente ícone está habilitado else { //--- Se o ícone não foi definido, imprime a mensagem e retorna if(m_icon_file_on=="" || m_icon_file_off=="") { ::Print(__FUNCTION__," > O ícone deve ser definido no modo \"Icon only\"."); return(false); } } //--- Formando o nome do objeto string name=CElement::ProgramName()+"_icon_button_bmp_"+(string)CElement::Id(); //--- Coordenadas int x =(!m_only_icon)? m_x+m_icon_x_gap : m_x; int y =(!m_only_icon)? m_y+m_icon_y_gap : m_y; //--- Configura o ícone if(!m_icon.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- Configura as propriedades m_icon.BmpFileOn("::"+m_icon_file_on); m_icon.BmpFileOff("::"+m_icon_file_off); m_icon.State(true); m_icon.Corner(m_corner); m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor); m_icon.Selectable(false); m_icon.Z_Order((!m_only_icon)? m_zorder : m_button_zorder); m_icon.Tooltip((!m_only_icon)? "\n" : m_label_text); //--- Armazena as coordenadas m_icon.X(x); m_icon.Y(y); //--- Armazena o tamanho m_icon.XSize(m_icon.X_Size()); m_icon.YSize(m_icon.Y_Size()); //--- Margens da borda 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); }
A classe CIconButton não tem quaisquer outras diferenças a partir do que foi considerado na classe CSimpleButton. Você pode encontrar a versão completa nos arquivos anexados deste artigo.
Agora, nós vamos testar os botões do tipo CIconButton. Para demonstrar a capacidade, crie cinco desses botões de diferentes modos, tamanhos e estados no EA de teste. Crie cinco instâncias da classe CIconButton na classe personalizada do EA de teste e declare cinco métodos para a criação dos botões, como mostrado no código abaixo.
class CProgram : public CWndEvents { private: //--- Botões com Ícone CIconButton m_icon_button1; CIconButton m_icon_button2; CIconButton m_icon_button3; CIconButton m_icon_button4; CIconButton m_icon_button5; //--- private: //--- Botões com Ícone #define ICONBUTTON1_GAP_X (7) #define ICONBUTTON1_GAP_Y (105) bool CreateIconButton1(const string text); #define ICONBUTTON2_GAP_X (128) #define ICONBUTTON2_GAP_Y (105) bool CreateIconButton2(const string text); #define ICONBUTTON3_GAP_X (7) #define ICONBUTTON3_GAP_Y (130) bool CreateIconButton3(const string text); #define ICONBUTTON4_GAP_X (88) #define ICONBUTTON4_GAP_Y (130) bool CreateIconButton4(const string text); #define ICONBUTTON5_GAP_X (169) #define ICONBUTTON5_GAP_Y (130) bool CreateIconButton5(const string text); };
Nós vamos utilizar a implementação de apenas um desses métodos, como exemplo, porque todos eles são iguais, com uma única exceção dos valores dos parâmetros do botão que são definidos:
//+------------------------------------------------------------------+ //| Cria o botão com ícone 5 | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\gold.bmp" #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\gold_colorless.bmp" //--- bool CProgram::CreateIconButton5(const string button_text) { //--- Passa o objeto do painel m_icon_button5.WindowPointer(m_window); //--- Coordenadas int x=m_window.X()+ICONBUTTON5_GAP_X; int y=m_window.Y()+ICONBUTTON5_GAP_Y; //--- Configura as propriedades antes da criação m_icon_button5.ButtonXSize(76); m_icon_button5.ButtonYSize(87); m_icon_button5.LabelXGap(6); m_icon_button5.LabelYGap(69); m_icon_button5.LabelColor(clrBlack); m_icon_button5.LabelColorPressed(clrBlack); m_icon_button5.BackColor(clrGainsboro); m_icon_button5.BackColorHover(C'193,218,255'); m_icon_button5.BackColorPressed(C'210,210,220'); m_icon_button5.BorderColor(C'150,170,180'); m_icon_button5.BorderColorOff(C'178,195,207'); m_icon_button5.IconXGap(6); m_icon_button5.IconYGap(3); m_icon_button5.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp64\\gold.bmp"); m_icon_button5.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp64\\gold_colorless.bmp"); //--- Cria o controle if(!m_icon_button5.CreateIconButton(m_chart_id,m_subwin,button_text,x,y)) return(false); //--- Adiciona o ponteiro do controle para a base CWndContainer::AddToElementsArray(0,m_icon_button5); return(true); }
Coloque as chamadas destes métodos no método principal para criar a interface gráfica do programa.
Adicione o rastreamento do pressionamento do Botão com Ícone 2 ao manipulador de eventos. Na versão do EA, que está anexado no final deste artigo, este botão funciona em dois modos (pressionado/solto). A disponibilidade do Botão com Ícone e do Botão com Ícone 4 depende do estado do Botão com Ícone 2. Se este botão é solto, então, todos os botões dependentes dele estão bloqueados e vice-versa.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Pressionando o botão de evento if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- if(sparam==m_simple_button3.Text()) { if(m_simple_button3.IsPressed()) m_simple_button1.ButtonState(false); else m_simple_button1.ButtonState(true); } //--- if(sparam==m_icon_button2.Text()) { if(m_icon_button2.IsPressed()) { m_icon_button1.ButtonState(true); m_icon_button4.ButtonState(true); } else { m_icon_button1.ButtonState(false); m_icon_button4.ButtonState(false); } } } }
Depois de compilar os arquivos e carregar o EA de teste ao gráfico, você deve ver o resultado como é mostrado na imagem abaixo:
Fig. 4. Teste do controle botão com ícone.
Nós completamos o desenvolvimento da classe CIconButton para a criação de um botão com a funcionalidade estendida. Os ícones nos botões que você vê na imagem acima podem ser baixados no final deste artigo. Agora, nós vamos discutir a classe para criar o botão de divisão (split button)
Desenvolvimento da Classe para Criar um Botão de Divisão
O que é um botão de divisão? O botão de divisão é um botão composto por duas partes funcionais:
- A primeira parte é um botão com a função principal embutida.
- A segunda parte é um botão que abre um menu de contexto com funções adicionais.
Este tipo de controle é frequentemente usado em interfaces gráficas de muitos programas. Esses botões são usados quando várias funções devem ser agrupadas densamente e todos elas pertencem à mesma categoria.
Um botão de divisão será composto por cinco objetos (primitivas gráficas) e um elemento acoplável (menu de contexto):
- Fundo.
- Ícone.
- Texto.
- Fundo do botão adicional.
- Indício do menu suspenso.
Fig. 5. Partes compostas do controle botão de divisão.
Nós podemos ver que o menu de contexto (objeto da classe CContextMenu) não será ligado ao objeto da classe CMenuItem. Isto significa que nós precisamos de um modo adicional, quando um menu de contexto pode ser uma parte de qualquer outro elemento ou até mesmo ser desanexado.
Além disso, nós precisamos também de um ponto de referência para o ajuste qualitativo da interação entre os controles que são ativados temporariamente pelo usuário (elementos suspensos). Deve-se fazer com que o formulário possa ser desbloqueado apenas pelo elemento que o bloqueou. Se isso não for feito, os elementos podem entrar em conflito uns com os outros já que o estado do formulário pode definir o estado de outros controles. Mais tarde nós vamos ilustrar isto com um exemplo. Um teste poderá ser realizado para uma melhor compreensão de quais situações exatamente estes conflitos podem ocorrer. Para implementar esta funcionalidade, um campo e um método para armazenar e obter elementos ativados devem ser adicionados à classe CWindow do formulário como é mostrado no código abaixo.
class CWindow : public CElement { private: //--- Identificador do controle ativado int m_id_activated_element; //--- public: //--- Métodos para armazenar e obter o id do elemento ativado int IdActivatedElement(void) const { return(m_id_activated_element); } void IdActivatedElement(const int id) { m_id_activated_element=id; } };
Se durante a ativação do elemento ele estiver bloqueando o formulário, o identificador deste elemento é para ser armazenado no formulário. No local onde o formulário é desbloqueado, deve-se efetuar uma verificação durante o identificador do elemento que o bloqueou. Não é necessário a verificação do identificador do elemento que bloqueou o formulário quando há uma verificação do nome do objeto pressionado antes de desbloquear o formulário.
Então, nós vamos introduzir incrementos à classe CContextMenu. Eles vão permitir a habilitação e a manipulação do modo do menu de contexto separado, conforme é mostrado no código abaixo. O modo do menu de contexto com a ligação ao nó anterior é definido por padrão:
class CContextMenu : public CElement { //--- O modo do menu de contexto separado. Isto significa que não há nenhuma ligação ao nó anterior. bool m_free_context_menu; //--- public: //--- Definir o modo do menu de contexto separado void FreeContextMenu(const bool flag) { m_free_context_menu=flag; } }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CContextMenu::CContextMenu(void) : m_free_context_menu(false) { }
Identificadores internos serão diferentes para a manipulação do pressionamento sobre o evento do elemento de menu a partir do menu de contexto ligado e separado. Tal divisão irá tornar o código inequívoco, diminua o número de condições e permita gerenciar eventos dos elementos em modos diferentes de uma forma mais flexível.
Adicione um novo identificador (ON_CLICK_FREEMENU_ITEM) para a geração de um evento de um menu de contexto separado para o arquivo Defines.mqh:
#define ON_CLICK_FREEMENU_ITEM (9) // Clicar no elemento de um menu de contexto separado
Condições adicionais com a verificação do modo de menu de contexto separado devem ser adicionados nos próximos lugares da classe CContextMenu. Abaixo encontramos as versões reduzidas dos métodos. Os comentários forma deixados para orientação.
1. No manipulador de eventos:
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do movimento do cursor do mouse if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto //--- Obtém o foco //--- Sai, se este é um menu de contexto separado if(m_free_context_menu) return; //--- Se o menu de contexto está habilitado e o botão esquerdo do mouse for pressionado //--- Verifica as condições para fechar todos os menus de contexto que estavam abertos abaixo desse return; } //--- Manipulação do evento do botão esquerdo do mouse sobre o objeto //--- Manipulação do evento ON_CLICK_MENU_ITEM if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM) { //--- Sai, se este é um menu de contexto separado if(m_free_context_menu) return; //--- Receber a mensagem a partir do elemento de menu para manipulação return; } }
2. No método para criar um menu de contexto:
//+------------------------------------------------------------------+ //| Cria um menu de contexto | //+------------------------------------------------------------------+ bool CContextMenu::CreateContextMenu(const long chart_id,const int subwin,const int x=0,const int y=0) { //--- Retorna se não há nenhum ponteiro do formulário //--- Se este é um menu de contexto anexado if(!m_free_context_menu) { //--- Retorna se não houver nenhum ponteiro para o nó anterior if(::CheckPointer(m_prev_node)==POINTER_INVALID) { ::Print(__FUNCTION__," > Antes de criar um menu de contexto, ele deve ser passado " "para o ponteiro do nó anterior utilizando o método CContextMenu::PrevNodePointer(CMenuItem &object)."); return(false); } } //--- Inicialização das variáveis //--- Se as coordenadas não foram especificadas //--- Se as coordenadas foram especificadas //--- Margens da borda //--- Criação de um menu de contexto //--- Oculta o elemento return(true); }
3. No método para a criação de uma lista de elementos do menu:
//+------------------------------------------------------------------+ //| Cria uma lista de elementos de menu | //+------------------------------------------------------------------+ bool CContextMenu::CreateItems(void) { int s =0; // Para identificar a localização das linhas de separação int x =m_x+1; // coordenada X int y =m_y+1; // coordenada Y. Será calculado em um loop para cada elemento do menu. //--- Número de linhas de separação //--- int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Cálculo da coordenada Y //--- Armazena o ponteiro do formulário //--- Se o menu de contexto tem um anexo, adicione o ponteiro para o nó anterior if(!m_free_context_menu) m_items[i].PrevNodePointer(m_prev_node); //--- Configura as propriedades //--- Margens da borda do painel //--- Criando o elemento de menu //--- Move para o próximo, se todas as linhas de separação foram definidas //--- Se os índices correspondem, define uma linha de separação após este elemento de menu } return(true); }
4. Nos métodos para mostrar e ocultar um menu de contexto:
//+------------------------------------------------------------------+ //| Exibe o menu de contexto | //+------------------------------------------------------------------+ void CContextMenu::Show(void) { //--- Sai, se esse controle já é visível //--- Mostra os objetos do menu de contexto //--- Mostra os elementos de menu //--- Atribui o estado de um controle visível //--- Estado do menu de contexto //--- Registra o estado no nó anterior if(!m_free_context_menu) m_prev_node.ContextMenuState(true); //--- Bloqueia o formulário } //+------------------------------------------------------------------+ //| Oculta um menu de contexto | //+------------------------------------------------------------------+ void CContextMenu::Hide(void) { //--- Sai se o elemento está oculto //--- Oculta os objetos do menu de contexto //--- Oculta os elementos de menu //--- Zera o foco //--- Atribui o estado de um elemento oculto //--- Estado do menu de contexto //--- Registra o estado no nó anterior if(!m_free_context_menu) m_prev_node.ContextMenuState(false); }
5. No método para o tratamento do pressionamento do elemento de menu. No novo bloco, é realizado uma verificação em um loop para o nome do objeto pressionado no modo do menu de contexto separado. Se um objeto é encontrado, então, um evento é emitido com o identificador ON_CLICK_FREEMENU_ITEM. Mais tarde, este evento terá que ser monitorado nos manipuladores de eventos desses controles que contêm um menu de contexto (isto será ilustrado com o exemplo de um botão de divisão).
//+------------------------------------------------------------------+ //| Manipulando o pressionamento de um elemento de menu | //+------------------------------------------------------------------+ bool CContextMenu::OnClickMenuItem(const string clicked_object) { //--- Sai, se este menu de contexto tem um nó anterior e já está aberto if(!m_free_context_menu && m_context_menu_state) return(true); //--- Sai, se o pressionamento não foi no elemento de menu if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0) return(false); //--- Obtém o identificador e o índice a partir do nome do objeto int id =IdFromObjectName(clicked_object); int index =IndexFromObjectName(clicked_object); //--- Se o menu de contexto tem um nó anterior if(!m_free_context_menu) { //--- 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(); } //--- Se este é um menu de contexto separado else { //--- Localiza em um loop o elemento de menu que foi pressionado int total=ItemsTotal(); for(int i=0; i<total; i++) { if(m_items[i].Object(0).Name()!=clicked_object) continue; //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_FREEMENU_ITEM,CElement::Id(),i,DescriptionByIndex(i)); break; } } //--- return(true); }
Tudo está pronto para o desenvolvimento da classe do botão de divisão. Crie o arquivo SplitButton.mqh com a classe CSplitButton e os métodos padrões para todos os controles na pasta Controls:
//+------------------------------------------------------------------+ //| SplitButton.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "ContextMenu.mqh" //+------------------------------------------------------------------+ //| Classe para criar um botão de divisão | //+------------------------------------------------------------------+ class CSplitButton : public CElement { private: //--- Ponteiro para o formulário ao qual o elemento está anexado CWindow *m_wnd; //--- public: CSplitButton(); ~CSplitButton(); //--- Armazena o ponteiro do formulário void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- Manipulador de eventos do gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Timer virtual void OnEventTimer(void); //--- Move o elemento virtual void Moving(const int x,const int y); //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse virtual void SetZorders(void); virtual void ResetZorders(void); };
Além das propriedades e métodos característicos dos botões de todos os tipos que já foram descritos neste artigo, nós precisamos de outros adicionais para a criação de botões com um menu suspenso:
- Tamanho. Nesta versão, nós vamos utilizar apenas a largura. A altura será igual à do botão principal.
- Prioridade para o clique do botão esquerdo do mouse. Um botão com um menu suspenso deve ter uma prioridade maior do que um botão simples.
- Ícones e margens para o ícone do botão.
- Estado do menu de contexto do botão (visível/oculto).
class CSplitButton : public CElement { private: //--- Tamanho e a prioridade do clique esquerdo do mouse para o botão com um menu suspenso int m_drop_button_x_size; int m_drop_button_zorder; //--- Margens do ícone int m_drop_arrow_x_gap; int m_drop_arrow_y_gap; //--- Ícones do botão com um menu suspenso nos estados ativo e bloqueado string m_drop_arrow_file_on; string m_drop_arrow_file_off; //--- Estado do menu de contexto bool m_drop_menu_state; //--- public: //--- Tamanho do botão com um menu suspenso void DropButtonXSize(const int x_size) { m_drop_button_x_size=x_size; } //--- Estabelecer os ícones para o botão com um menu suspenso nos estados ativo e bloqueado void DropArrowFileOn(const string file_path) { m_drop_arrow_file_on=file_path; } void DropArrowFileOff(const string file_path) { m_drop_arrow_file_off=file_path; } //--- Margens do ícone void DropArrowXGap(const int x_gap) { m_drop_arrow_x_gap=x_gap; } void DropArrowYGap(const int y_gap) { m_drop_arrow_y_gap=y_gap; } };
Como mencionado anteriormente, a criação de um botão de divisão requer cinco objetos primitivos e um menu de contexto. Vamos declarar as instâncias de classes e os métodos necessários para a sua criação. Nós precisaremos também de métodos para a formação de um menu de contexto (adicionando os elementos de menu e as linhas de separação). Desde que as propriedades do menu de contexto são configuradas pelo usuário, é necessário de um método para obter o ponteiro para o menu de contexto do botão.
class CSplitButton : public CElement { private: //--- Objetos para a criação de um botão CButton m_button; CBmpLabel m_icon; CLabel m_label; CEdit m_drop_button; CBmpLabel m_drop_arrow; CContextMenu m_drop_menu; //--- public: //--- Métodos para a criação de um botão bool CreateSplitButton(const long chart_id,const string button_text,const int window,const int x,const int y); //--- private: bool CreateButton(void); bool CreateIcon(void); bool CreateLabel(void); bool CreateDropButton(void); bool CreateDropIcon(void); bool CreateDropMenu(void); //--- public: //--- Obtendo o ponteiro do menu de contexto, CContextMenu *GetContextMenuPointer(void) const { return(::GetPointer(m_drop_menu)); } //--- Adiciona um elemento de menu com propriedades especificadas antes da criação do menu de contexto void AddItem(const string text,const string path_bmp_on,const string path_bmp_off); //--- Adiciona uma linha de separação após o elemento especificado antes da criação do menu de contexto void AddSeparateLine(const int item_index); };
Os métodos para a criação de objetos de elemento não possuem diferenças importantes em relação as que já foram consideradas antes. A única nuance importante aqui é que, no método para a criação de um menu de contexto o identificador do botão de divisão deve ser configurado como parte do controle, como é mostrado no código abaixo. Mais tarde, isso permitirá estabelecer pelo identificador qual elemento de menu que a mensagem ON_CLICK_FREEMENU_ITEM veio.
//+------------------------------------------------------------------+ //| Cria um menu suspenso | //+------------------------------------------------------------------+ bool CSplitButton::CreateDropMenu(void) { //--- Passa o objeto do painel m_drop_menu.WindowPointer(m_wnd); //--- Menu de contexto separado m_drop_menu.FreeContextMenu(true); //--- Coordenadas int x=m_x; int y=m_y+m_y_size; //--- Configura as propriedades m_drop_menu.Id(CElement::Id()); m_drop_menu.XSize((m_drop_menu.XSize()>0)? m_drop_menu.XSize() : m_button_x_size); //--- Configura um menu de contexto if(!m_drop_menu.CreateContextMenu(m_chart_id,m_subwin,x,y)) return(false); //--- return(true); }
Nós vamos considerar os métodos de interação com um botão de divisão. Uma vez que um botão deste tipo possuir duas partes, dois métodos separados são necessários para o tratamento do pressionamento de ambos. Estes métodos serão chamados no manipulador da classe no bloco do evento CHARTEVENT_OBJECT_CLICK:
class CSplitButton : public CElement { private: //--- Manipulando o pressionamento de um botão bool OnClickButton(const string clicked_object); //--- Manipulando o pressionamento do botão com um menu suspenso bool OnClickDropButton(const string clicked_object); }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do botão esquerdo do mouse sobre o objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Pressionando o botão simples if(OnClickButton(sparam)) return; //--- Pressionando o botão com um menu suspenso if(OnClickDropButton(sparam)) return; } }
No método CSplitButton::OnClickButton() que manipula o pressionamento do botão principal, primeiramente, é realizado a verificação do nome do objeto. Se este for o nome do objeto desta instância de classe, então, o estado do botão é verificado. Se o botão for bloqueado, então, o programa sai do método. O botão principal pode estar apenas em um estado. Isso significa que ele deve retornar ao estado "solto" após de ter sido pressionado. Se ele passou em todas as verificações, então (1) o menu de contexto deve ser oculto caso esteja visível, (2) deve ser ajustado o seu estado correspondente e a cor do menu e do botão, (3) o formulário deve ser desbloqueado e o identificador do elemento de ativação deve ser zerado em sua memória.
No final do método uma mensagem é enviada. Ele pode ser recebido na classe original. Essa mensagem irá conter (1) o identificador do evento ON_CLICK_BUTTON, (2) o identificador do elemento, (3) o índice do elemento e (4) a descrição exibida no botão.
//+------------------------------------------------------------------+ //| Pressionando o botão | //+------------------------------------------------------------------+ bool CSplitButton::OnClickButton(const string clicked_object) { //--- Sai, se o nome do objeto não corresponder if(clicked_object!=m_button.Name()) return(false); //--- Sai, se o botão está bloqueado if(!m_button_state) { //--- Solta o botão m_button.State(false); return(false); } //--- Oculta o menu m_drop_menu.Hide(); m_drop_menu_state=false; //--- Solta o botão e define a cor do foco m_button.State(false); m_button.BackColor(m_back_color_hover); m_drop_button.BackColor(m_back_color_hover); //--- Desbloqueia o formulário m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
O método CSplitButton::OnClickDropButton(), que realiza a manipulação do pressionamento do botão com um menu suspenso, também possui dois controles no início. Eles verificam (1) o nome e (2) a disponibilidade do botão. Em seguida, o programa vai para um dos dois blocos de código para ocultar ou exibir o botão, isso vai depender do estado da visibilidade atual do botão de menu de contexto.
//+------------------------------------------------------------------+ //| Pressionando o botão com um menu suspenso | //+------------------------------------------------------------------+ bool CSplitButton::OnClickDropButton(const string clicked_object) { //--- Sai, se o nome do objeto não corresponder if(clicked_object!=m_drop_button.Name()) return(false); //--- Sai, se o botão está bloqueado if(!m_button_state) { //--- Solta o botão m_button.State(false); return(false); } //--- Se a lista é mostrada, oculta ela if(m_drop_menu_state) { m_drop_menu_state=false; m_drop_menu.Hide(); m_button.BackColor(m_back_color_hover); m_drop_button.BackColor(m_back_color_hover); //--- Desbloqueia o formulário e zera o id do elemento de ativação m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } //--- Se a lista está oculta, mostra ela else { m_drop_menu_state=true; m_drop_menu.Show(); m_button.BackColor(m_back_color_hover); m_drop_button.BackColor(m_back_color_pressed); //--- Bloqueia o formulário e armazenar o elemento de ativação m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); } //--- return(true); }
No início deste artigo nós melhoramos a classe do menu de contexto CContextMenu com um incremento, que no modo livre facilita o envio de um evento para o uso interno com o identificador ON_CLICK_FREEMENU_ITEM. Esta mensagem será recebida no manipulador da classe CSplitButton de um botão de divisão. Para identificar que a mensagem foi enviada de um menu de contexto relativo, o identificador do elemento deve ser verificado. Ele está contido no parâmetro lparam. Se os identificadores corresponderem, (1) o menu deve ser oculto, (2) as cores correspondentes ao estado do botão devem ser configuradas e (3) o formulário deve ser desbloqueado se esse elemento for o ativador. Depois disso, uma mensagem com o identificador ON_CLICK_CONTEXTMENU_ITEM é enviado. Esta mensagem pode ser recebida na classe original.
Além disso, nós iremos criar um método adicional CSplitButton::HideDropDownMenu() para uso múltiplo. O objetivo deste método é ocultar o menu e desbloquear o formulário zerando o identificador do elemento de ativação.
class CSplitButton : public CElement { private: //--- Oculta o menu suspenso void HideDropDownMenu(void); }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipula o evento de pressionamento do elemento de menu livre if(id==CHARTEVENT_CUSTOM+ON_CLICK_FREEMENU_ITEM) { //--- Sai, se os identificadores não corresponderem if(CElement::Id()!=lparam) return; //--- Oculta o menu suspenso HideDropDownMenu(); //--- Envia uma mensagem ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,lparam,dparam,sparam); return; } } //+------------------------------------------------------------------+ //| Esconde o menu suspenso | //+------------------------------------------------------------------+ void CSplitButton::HideDropDownMenu(void) { //--- Esconde o menu e configura as indicações correspondentes m_drop_menu.Hide(); m_drop_menu_state=false; m_button.BackColor(m_back_color); m_drop_button.BackColor(m_back_color); //--- Desbloqueia a formulário se os identificadores do formulário e deste elemento corresponderem if(m_wnd.IdActivatedElement()==CElement::Id()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } }
Agora, nós temos que configurar a reação do botão de divisão no local do cursor do mouse e o estado do botão esquerdo do mouse quando o cursor estiver sobre o botão. Isso exigirá um outro método que será chamado de CSplitButton::CheckPressedOverButton(). Este método tem apenas um parâmetro - o estado do botão esquerdo do mouse. No início há duas verificações. Se acontecer do (1) cursor do mouse se encontrar fora da área do botão e (2) o formulário estiver bloqueado quando este não é o elemento de ativação, então o programa deixará o método. Se ele passou nas verificações, então o programa define as cores relevantes dependendo do estado do botão esquerdo do mouse e qual parte do botão do cursor ele está.
class CSplitButton : public CElement { private: //--- Verifica o pressionamento do botão esquerdo do mouse sobre um botão de divisão void CheckPressedOverButton(const bool mouse_state); }; //+------------------------------------------------------------------+ //| Verifica o pressionamento do botão esquerdo do mouse sobre um botão de divisão | //+------------------------------------------------------------------+ void CSplitButton::CheckPressedOverButton(const bool mouse_state) { //--- Sai, se estiver fora da área do elemento if(!CElement::MouseFocus()) return; //--- Sai, se o formulário for bloqueado e seus identificadores não corresponderem com este elemento if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return; //--- Botão do mouse pressionado if(mouse_state) { //--- Na área do botão de menu if(m_drop_button.MouseFocus()) { m_button.BackColor(m_back_color_hover); m_drop_button.BackColor(m_back_color_pressed); } else { m_button.BackColor(m_back_color_pressed); m_drop_button.BackColor(m_back_color_pressed); } } //--- Botão do mouse solto else { if(m_drop_menu_state) { m_button.BackColor(m_back_color_hover); m_drop_button.BackColor(m_back_color_pressed); } } }
O método CSplitButton::CheckPressedOverButton() é chamado no manipulador de evento do movimento do cursor do mouse. São feitas várias verificações antes de chamar este método, tais como (1) se o elemento está oculto, (2) o foco, (3) se o elemento está disponível e (4) se o cursor se encontra fora da área do elemento, na qual todos podem fazer com que o menu possa ser oculto e o manipulador poder sair antes de chamar o método CSplitButton::CheckPressedOverButton().
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do movimento do cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto if(!CElement::IsVisible()) return; //--- Identificador do foco int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_drop_button.MouseFocus(x>m_drop_button.X() && x<m_drop_button.X2() && y>m_drop_button.Y() && y<m_drop_button.Y2()); //--- Sai, se o botão está bloqueado if(!m_button_state) return; //--- Fora da área do elemento e com o botão do mouse pressionado if(!CElement::MouseFocus() && sparam=="1") { //--- Sai, se o foco estiver no menu de contexto if(m_drop_menu.MouseFocus()) return; //--- Oculta o menu suspenso HideDropDownMenu(); return; } //--- Verifica o pressionamento do botão esquerdo do mouse sobre um botão de divisão CheckPressedOverButton(bool((int)sparam)); return; } }
A classe do controle botão de divisão está pronta para o teste. Para que ele funcione corretamente, ele deve ser incorporado na estrutura da biblioteca corretamente. Isto deve ser feito toda vez que um controle complexo (composto) for criado. No caso dos botões do tipo CSimpleButton e CIconButton não será necessário ter novos incrementos. Para o botão de divisão, a situação é diferente pois além do botão atual, há também um menu de contexto que deve chegar ao array privado correspondente na base de identificadores do controle. O usuário final da biblioteca estará usando sua versão final e não irá lidar com o código. Ele não terá ideia de como isso funciona. O principal objetivo do desenvolvedor da biblioteca é fazer uso da biblioteca de maneira muito simples, que é assegurar que a criação da interface gráfica do programa leve a um número mínimo de ações.
Inclua o arquivo com a classe CSplitButton para o arquivo WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "SplitButton.mqh"
Em seguida, declare e implemente o método para adicionar o ponteiro ao menu de contexto com um botão de divisão para o array privado, que foi criado anteriormente:
//+------------------------------------------------------------------+ //| Classe para armazenar todos os objetos da interface | //+------------------------------------------------------------------+ class CWndContainer { private: //--- Armazena os ponteiros aos elementos do botão de divisão na base bool AddSplitButtonElements(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| Armazena os ponteiros aos elementos do botão de divisão na base | //+------------------------------------------------------------------+ bool CWndContainer::AddSplitButtonElements(const int window_index,CElement &object) { //--- Sai, se isso não for um botão de divisão if(object.ClassName()!="CSplitButton") return(false); //--- Obtém o ponteiro do botão de divisão CSplitButton *sb=::GetPointer(object); //--- 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 menu de contexto CContextMenu *cm=sb.GetContextMenuPointer(); //--- Armazena o elemento e os objetos na base m_wnd[window_index].m_elements[size]=cm; AddToObjectsArray(window_index,cm); //--- 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 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 para 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); }
Como você deve se lembrar, a chamada de um dos métodos como o CWndContainer::AddSplitButtonElements(), deve ser realizado no método CWndContainer::AddToElementsArray, como é mostrado na versão reduzida deste método no código abaixo.
//+------------------------------------------------------------------+ //| Adiciona um ponteiro ao array de elemento | //+------------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElement &object) { //--- Se a base não há nenhum formulário para os controles / --- Se a solicitação for para um formulário que não existe //--- Adiciona ao array comum de elementos //--- Adiciona os objetos do elemento para o array comum de objetos //--- Armazena o ID do último elemento em todos os formulários //--- Aumenta o contador de identificadores do elemento //--- Armazena os ponteiros para os objetos do menu de contexto na base //--- Armazena os ponteiros para os objetos do menu principal na base //--- Armazena os ponteiros aos elementos do botão de divisão na base if(AddSplitButtonElements(window_index,object)) return; }
Tudo está preparado para testar os botões de divisão. Vamos criar quatro desses botões no EA de teste. Declare as instâncias da classe CSplitButton e os métodos para a criação dos botões com as margens do ponto superior esquerdo do formulário. Coloque a sua chamada no método principal para criar a interface gráfica do programa.
class CProgram : public CWndEvents { private: //--- Botões de divisão CSplitButton m_split_button1; CSplitButton m_split_button2; CSplitButton m_split_button3; CSplitButton m_split_button4; //--- private: //--- Botões de divisão #define SPLITBUTTON1_GAP_X (7) #define SPLITBUTTON1_GAP_Y (225) bool CreateSplitButton1(const string text); #define SPLITBUTTON2_GAP_X (128) #define SPLITBUTTON2_GAP_Y (225) bool CreateSplitButton2(const string text); #define SPLITBUTTON3_GAP_X (7) #define SPLITBUTTON3_GAP_Y (250) bool CreateSplitButton3(const string text); #define SPLITBUTTON4_GAP_X (128) #define SPLITBUTTON4_GAP_Y (250) bool CreateSplitButton4(const string text); }; //+------------------------------------------------------------------+ //| Cria o painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação de um formulário para os controles //--- Criação dos controles: // Menu principal //--- Menus de contexto //--- Botões simples //--- Botões com Ícone //--- Botões de divisão if(!CreateSplitButton1("Split Button 1")) return(false); if(!CreateSplitButton2("Split Button 2")) return(false); if(!CreateSplitButton3("Split Button 3")) return(false); if(!CreateSplitButton4("Split Button 4")) return(false); //--- Redesenho do gráfico m_chart.Redraw(); return(true); }
Nós vamos usar um deles como exemplo e mostrá-lo no código abaixo. Atenção: para configurar as propriedades do menu de contexto, primeiro deve-se obter o ponteiro usando o método CSplitButton::GetContextMenuPointer().
//+------------------------------------------------------------------+ //| Cria o botão de divisão 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSplitButton1(const string button_text) { //--- Três elementos do menu de contexto #define CONTEXTMENU_ITEMS5 3 //--- Passa o objeto do painel m_split_button1.WindowPointer(m_window); //--- Coordenadas int x=m_window.X()+SPLITBUTTON1_GAP_X; int y=m_window.Y()+SPLITBUTTON1_GAP_Y; //--- Array de nome dos elementos string items_text[]= { "Item 1", "Item 2", "Item 3" }; //--- Array de ícones para o modo disponível string items_bmp_on[]= { "Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp", "Images\\EasyAndFastGUI\\Icons\\bmp16\\line_chart.bmp", "Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp" }; //--- Array de ícones para o modo bloqueado string items_bmp_off[]= { "Images\\EasyAndFastGUI\\Icons\\bmp16\\coins_colorless.bmp", "Images\\EasyAndFastGUI\\Icons\\bmp16\\line_chart_colorless.bmp", "Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_colorless.bmp" }; //--- Configura as propriedades antes da criação m_split_button1.ButtonXSize(116); m_split_button1.ButtonYSize(22); m_split_button1.DropButtonXSize(16); m_split_button1.LabelColor(clrBlack); m_split_button1.LabelColorPressed(clrBlack); m_split_button1.BackColor(clrGainsboro); m_split_button1.BackColorHover(C'193,218,255'); m_split_button1.BackColorPressed(C'190,190,200'); m_split_button1.BorderColor(C'150,170,180'); m_split_button1.BorderColorOff(C'178,195,207'); m_split_button1.BorderColorHover(C'150,170,180'); m_split_button1.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp"); m_split_button1.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp"); //--- Obtém o ponteiro para o menu de contexto do botão CContextMenu *cm=m_split_button1.GetContextMenuPointer(); //--- Configura as propriedades do menu de contexto cm.AreaBackColor(C'240,240,240'); cm.AreaBorderColor(clrSilver); cm.ItemBackColor(C'240,240,240'); cm.ItemBorderColor(C'240,240,240'); cm.LabelColor(clrBlack); cm.LabelColorHover(clrWhite); cm.SeparateLineDarkColor(C'160,160,160'); cm.SeparateLineLightColor(clrWhite); //--- Adiciona os elementos ao menu de contexto for(int i=0; i<CONTEXTMENU_ITEMS5; i++) m_split_button1.AddItem(items_text[i],items_bmp_on[i],items_bmp_off[i]); //--- Linha de separação após o primeiro elemento de menu m_split_button1.AddSeparateLine(1); //--- Cria o controle if(!m_split_button1.CreateSplitButton(m_chart_id,button_text,m_subwin,x,y)) return(false); //--- Adiciona o ponteiro do controle para a base CWndContainer::AddToElementsArray(0,m_split_button1); return(true); }
Você deverá ver o seguinte resultado após compilar os arquivos e executar o programa no gráfico:
Fig. 6. Teste do controle botão de divisão.
O desenvolvimento da classe do controle botão de divisão está concluído. Você pode baixar a versão do EA apresentado na imagem acima nos arquivos anexados deste artigo. No próximo artigo, nós vamos analisar o desenvolvimento das classes para criar os grupos de botões, que são os botões interconectados entre si.
Conclusão
Este artigo foi dedicado a criação dos botões simples e multifuncionais. No próximo artigo, nós vamos enriquecer a nossa biblioteca com as classes que criam os grupos de botões.
Os arquivos abaixo com os arquivos da biblioteca no atual estágio de desenvolvimento, imagens e os arquivos dos programas considerados neste artigo (o EA, indicadores e o script) podem ser baixados para a realização de testes nos terminais MetaTrader. 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 terceira parte:
- Interfaces Gráficas III: Botões Simples e Multifuncionais (Capítulo 1)
- Interfaces Gráficas III: Grupos de Botões Simples e Multifuncionais (Capítulo 2)
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2296
- 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