Interfaces Gráficas III: Botões Simples e Multifuncionais (Capítulo 1)

Anatoli Kazharski | 23 agosto, 2016

Conteúdo

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.

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.

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.

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.

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:

  1. Fundo.
  2. Ícone.
  3. Rótulo de texto.

Fig. 3. Partes compostas do controle botão com ícone.

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.

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):

  1. Fundo.
  2. Ícone.
  3. Texto.
  4. Fundo do botão adicional.
  5. Indício do menu suspenso.

 

Fig. 5. Partes compostas do controle botão de divisão.

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.

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: