English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas IV: Elementos Informativos da Interface (Capítulo 1)

Interfaces Gráficas IV: Elementos Informativos da Interface (Capítulo 1)

MetaTrader 5Exemplos | 2 setembro 2016, 09:22
1 161 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo


Introdução

O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. A lista completa dos links para os artigos se encontra no final de cada capítulo da série. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado. 

No atual estágio de desenvolvimento, a biblioteca para a criação de interfaces gráficas contém um formulário e vários controles que podem ser ligados a ele. Foi mencionado antes que um dos artigos futuros seria dedicado ao modo multi-janela. Agora, nós já temos tudo preparado para considerar tal questão, desse modo, nós vamos lidar com isso no capítulo seguinte. Neste capítulo, nós vamos escrever as classes para criar os elementos de interface barra de status e dica de contexto.


O Elemento Barra de Status

A barra de status pertence aos elementos informativos da interface gráfica. Este elemento é usado para uma entrada rápida de dados importantes, detalhes, valores, etc. Os terminais MetaTrader também possuem uma barra de status. Este é composto por várias seções (itens). O primeiro deles apresenta as informações sobre o lugar em que o cursor do mouse está localizado no terminal ou os nomes dos comandos do programa. Há também os itens que exibem as informações de datas e preços quando o cursor do mouse está se movendo sobre a área do gráfico. Alguns itens contêm um menu de contexto, que se abrem pelo clique esquerdo do mouse. O editor de código MetaEditor possui uma barra de status também. Seus itens também exibem os comandos do programa, a localização do cursor (linha/coluna) e o modo de entrada de texto (INS/OVR). Informações mais detalhadas sobre as barras de status dos terminais e do editor de código podem ser encontrados na seção de Ajuda (F1).

Neste artigo, nós vamos criar uma barra de status simples, sem uma opção de ligar os menus de contexto aos seus itens. Semelhante aos outros elementos de interface, a barra de status será composta de vários objetos primitivos:

  • Fundo.
  • Elementos.
  • Linhas de separação.

Fig. 1. Partes integrantes do elemento barra de status.

Fig. 1. Partes integrantes do elemento barra de status.


Crie o arquivo StatusBar.mqh e inclua-o no arquivo WndContainer.mqh para que ele esteja disponível para uso em toda a biblioteca. O código abaixo exibe a classe CStatusBar com os métodos virtuais padrão, que são utilizados em todas as classes de controles. 

//+------------------------------------------------------------------+
//|                                                    StatusBar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Classe para criar a barra de status                              |
//+------------------------------------------------------------------+
class CStatusBar : public CElement
  {
private:
   //--- Ponteiro para o formulário ao qual o elemento está anexado
   CWindow          *m_wnd;
   //---
public:
                     CStatusBar(void);
                    ~CStatusBar(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);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CStatusBar::CStatusBar(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CStatusBar::~CStatusBar(void)
  {
  }
//+------------------------------------------------------------------+

Os métodos para o tratamento de eventos OnEvent() e OnEventTimer() não serão utilizados nesta versão para manter a consistência com outras classes de controle. 

Agora, nós vamos considerar as propriedades da barra de status. Já que é para ser utilizado os arrays de objetos, haverá propriedades únicas e comuns para eles. Das propriedades únicas temos apenas a largura dos elementos. A lista de propriedades comuns é mais longa e ela é apresentada abaixo.

Propriedades comuns:

  • Cor do fundo e do fundo do quadro.
  • Cor do texto.
  • Cores para a linha de separação.
  • Prioridade do botão esquerdo do mouse.

Inicializamos os campos de propriedades dentro do construtor pelos valores predefinidos. No momento da criação do elemento, o usuário pode redefini-los usando os métodos públicos da classe. 

class CStatusBar : public CElement
  {
private:
   //--- Propriedades:
   //    Arrays de propriedades únicas
   int               m_width[];
   //--- (1) Cor do fundo e (2) do fundo do quadro
   color             m_area_color;
   color             m_area_border_color;
   //--- Cor do texto
   color             m_label_color;
   //--- Prioridade do botão esquerdo do mouse
   int               m_zorder;
   //--- Cores para as linhas de separação.
   color             m_sepline_dark_color;
   color             m_sepline_light_color;
   //---
public:
   //--- (1) Cor do fundo e (2) do fundo do quadro e (3) do texto
   void              AreaColor(const color clr)                       { m_area_color=clr;             }
   void              AreaBorderColor(const color clr)                 { m_area_border_color=clr;      }
   void              LabelColor(const color clr)                      { m_label_color=clr;            }
   //--- Cores para as linhas de separação.
   void              SeparateLineDarkColor(const color clr)           { m_sepline_dark_color=clr;     }
   void              SeparateLineLightColor(const color clr)          { m_sepline_light_color=clr;    }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CStatusBar::CStatusBar(void) : m_area_color(C'240,240,240'),
                               m_area_border_color(clrSilver),
                               m_label_color(clrBlack),
                               m_sepline_dark_color(C'160,160,160'),
                               m_sepline_light_color(clrWhite)
  {
//--- Armazena o nome da classe do elemento na classe base  
   CElement::ClassName(CLASS_NAME);
//--- Define as prioridades do botão esquerdo do mouse
   m_zorder=2;
  }

Vamos considerar os métodos para criar a barra de status. Para criar o fundo, nós vamos usar um objeto primitivo do tipo CRectLabel. O array dinâmico de objetos primitivos do tipo CEdit tem de ser declarado para criar os elementos. A classe CSeparateLine foi escrita anteriormente para a criação das linhas de separação. Esta classe pode ser usada como um elemento de interface independente. Neste caso, ela será usada como parte integrante da barra de status e nós vamos precisar de um conjunto de tais elementos.

Os elementos devem ser adicionados usando o método CStatusBar::AddItem() antes de criar a barra de status, caso contrário, a criação da interface gráfica será encerrada. O número de elementos pode ser obtido chamando o método CStatusBar::ItemsTotal(). 

//+------------------------------------------------------------------+
//|                                                    StatusBar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "SeparateLine.mqh"
//+------------------------------------------------------------------+
//| Classe para criar a barra de status                              |
//+------------------------------------------------------------------+
class CStatusBar : public CElement
  {
private:
   //--- Objeto para a criação de um botão
   CRectLabel        m_area;
   CEdit             m_items[];
   CSeparateLine     m_sep_line[];
   //---
public:
   //--- Métodos para a criação da barra de status
   bool              CreateStatusBar(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateItems(void);
   bool              CreateSeparateLine(const int line_number,const int x,const int y);
   //---
public:
   //--- Número de elementos
   int               ItemsTotal(void)                           const { return(::ArraySize(m_items)); }

   //--- Adiciona o elemento com propriedades as especificadas antes de criar a barra de status
   void              AddItem(const int width);
  };

Nós vamos discutir em detalhes apenas o método CStatusBar::CreateItems() para criar os elementos da barra de status. Os demais métodos não possuem nada de novo que nós não tenhamos estudado nas classes de outros controles.

Os elementos são definidos dentro da área de fundo de maneira que eles não obscurecem seu quadro. É por isso que uma margem de pixels é adicionada às coordenadas no início. Em seguida, é realizado uma verificação no número de elementos. Se nenhum elemento foi definido, será impresso uma mensagem no registro e na criação da interface gráfica será encerrada. 

Nós faremos com que se a largura do primeiro elemento não tiver sido definida, ele será calculado automaticamente. Supõe-se que a largura da barra de status seja igual à largura do formulário ao qual ele está anexado (apenas dois pixels a menos para ficar dentro da área do formulário). Para obter a largura do primeiro elemento, o tamanho (largura) de outros elementos é para ser adicionado em um loop e este valor é para ser deduzido do valor da largura do formulário. 

Em seguida, segue o loop onde os elementos da barra de status são criados e, em seguida, o loop para criar as linhas de separação. As coordenadas para as linhas de separação são calculadas em relação às coordenadas de cada elemento. Uma linha de separação não é criada para o primeiro elemento. Desta forma, o número de linhas de separação sempre será um objeto a menos que o número de elementos.  

//+------------------------------------------------------------------+
//| Cria uma lista de elementos de barra de status                   |
//+------------------------------------------------------------------+
bool CStatusBar::CreateItems(void)
  {
   int l_w=0;
   int l_x=m_x+1;
   int l_y=m_y+1;
//--- Obtém o número de elementos
   int items_total=ItemsTotal();
//--- Se não houver elementos no grupo, reporta e sai
   if(items_total<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "se um grupo conter pelo menos um elemento! Use the CStatusBar::AddItem() method");
      return(false);
     }
//--- Se a largura do primeiro elemento não está definida, então...
   if(m_width[0]<1)
     {
      //--- ...calcula ele em relação à largura comum dos outros elementos
      for(int i=1; i<items_total; i++)
         l_w+=m_width[i];
      //---
      m_width[0]=m_wnd.XSize()-l_w-(items_total+2);
     }
//--- Cria o número especificado de elementos
   for(int i=0; i<items_total; i++)
     {
      //--- Elaborando o nome do objeto
      string name=CElement::ProgramName()+"_statusbar_edit_"+string(i)+"__"+(string)CElement::Id();
      //--- Coordenada X
      l_x=(i>0)? l_x+m_width[i-1] : l_x;
      //--- Cria um objeto
      if(!m_items[i].Create(m_chart_id,name,m_subwin,l_x,l_y,m_width[i],m_y_size-2))
         return(false);
      //--- Definindo as propriedades
      m_items[i].Description("");
      m_items[i].TextAlign(ALIGN_LEFT);
      m_items[i].Font(FONT);
      m_items[i].FontSize(FONT_SIZE);
      m_items[i].Color(m_label_color);
      m_items[i].BorderColor(m_area_color);
      m_items[i].BackColor(m_area_color);
      m_items[i].Corner(m_corner);
      m_items[i].Anchor(m_anchor);
      m_items[i].Selectable(false);
      m_items[i].Z_Order(m_zorder);
      m_items[i].ReadOnly(true);
      m_items[i].Tooltip("\n");
      //--- Margens da borda do painel
      m_items[i].XGap(l_x-m_wnd.X());
      m_items[i].YGap(l_y-m_wnd.Y());
      //--- Coordenadas
      m_items[i].X(l_x);
      m_items[i].Y(l_y);
      //--- Tamanho
      m_items[i].XSize(m_width[i]);
      m_items[i].YSize(m_y_size-2);
      //--- Armazena o ponteiro de objeto
      CElement::AddToArray(m_items[i]);
     }
//--- Criação das linhas de separação
   for(int i=1; i<items_total; i++)
     {
      //--- Coordenada X
      l_x=m_items[i].X();
      //--- Criação de uma linha
      CreateSeparateLine(i,l_x,l_y+2);
     }
//---
   return(true);
  }

Nós também vamos precisar de um método público para a mudar o texto de cada elemento. Vamos criar tal método e chamá-lo de CStatusBar::ValueToItem(). Ele vai receber como parâmetro o número de índice do elemento e a linha que dever ser refletida nele. 

class CStatusBar : public CElement
  {
public:
   //--- Define o valor do índice especificado
   void              ValueToItem(const int index,const string value);
  };
//+------------------------------------------------------------------+
//| Define o valor do índice especificado                            |
//+------------------------------------------------------------------+
void CStatusBar::ValueToItem(const int index,const string value)
  {
//--- Verifica se o tamanho do array não excedeu
   int array_size=::ArraySize(m_items);
   if(array_size<1 || index<0 || index>=array_size)
      return;
//--- Define o texto passado
   m_items[index].Description(value);
  }

 


Teste da Barra de Status

Agora, nós temos tudo pronto para testar o elemento barra de status. Para o teste, nós usaremos o primeiro EA da parte anterior (3) da série. Mantenha o menu principal e os cinco botões do tipo CIconButton e exclua todos os outros controles. O arquivo StatusBar.mqh já está incluído na biblioteca e sua instância e o método para criação da barra de status podem ser criados na classe personalizada CProgram.

Crie a barra de status que consiste de dois elementos. Nós não iremos definir a largura do primeiro elemento, ele será calculado automaticamente. Após a criação da barra de status, defina o texto «For Help, press F1» no primeiro elemento, como exemplo.

//+------------------------------------------------------------------+
//| Classe para a criação de um aplicativo                           |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Barra de status
   CStatusBar        m_status_bar;
   //---
private:
   //--- Barra de status
#define STATUSBAR1_GAP_X         (1)
#define STATUSBAR1_GAP_Y         (175)
   bool              CreateStatusBar(void);
  };
//+------------------------------------------------------------------+
//| Cria o painel de negociação                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Criação do formulário 1 para os controles
//--- Criação dos controles:
//    Menu principal
//--- Menus de contexto
//--- Criando a barra de status
   if(!CreateStatusBar())
      return(false);
//--- Redesenho do gráfico
   m_chart.Redraw();
   return(true);
  }
//+------------------------------------------------------------------+
//| Cria a barra de status                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateStatusBar(void)
  {
#define STATUS_LABELS_TOTAL 2
//--- Passa o objeto do painel
   m_status_bar.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+STATUSBAR1_GAP_X;
   int y=m_window1.Y()+STATUSBAR1_GAP_Y;
//--- Largura
   int width[]={0,110};
//--- Define as propriedades antes da criação
   m_status_bar.YSize(24);
//--- Especifica o número de componentes e define suas propriedades
   for(int i=0; i<STATUS_LABELS_TOTAL; i++)
      m_status_bar.AddItem(width[i]);
//--- Cria um controle
   if(!m_status_bar.CreateStatusBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Define o texto no primeiro elemento da barra de status
   m_status_bar.ValueToItem(0,"For Help, press F1");
//--- Adiciona um objeto ao array comum de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_status_bar);
   return(true);
  }

O segundo elemento da barra de status mostrará a hora local. Por isso, adicione o código ao timer da aplicação, como é mostrado no código abaixo. Isto significa que o elemento será atualizado a cada 500 milissegundos.

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();

//--- Atualiza o segundo elemento da barra de status a cada 500 milissegundos
   static int count=0;
   if(count<500)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//---
   count=0;
   m_status_bar.ValueToItem(1,TimeToString(TimeLocal(),TIME_DATE|TIME_SECONDS));
   m_chart.Redraw();
  }

Se tudo foi feito corretamente, você deve obter o resultado como é mostrado na imagem abaixo:

Fig. 2. Teste do elemento barra de status.

Fig. 2. Teste do elemento barra de status.

 

Nós completamos o desenvolvimento da classe para a criação do elemento barra de status. A versão completa pode ser encontrada nos arquivos anexados a este artigo.  

 


O Elemento Dica de Contexto

Agora, nós vamos desenvolver a classe para a criação das dicas de contexto. No quarto capítulo da primeira parte da série, ao considerar as funções para os botões de formulário, foi explicado em detalhes o porquê desse elemento necessitar de uma classe separada. Em suma, as dicas de contexto são necessárias sem limitar o número de símbolos e com a opção de destacar algumas palavras. Para implementar isso, nós vamos usar uma classe para desenhar os elementos. Anteriormente, foi mostrado um exemplo de como os elementos de interface podem ser desenhados. A classe CSeparateLine para criar o elemento linha de separação foi escrito no segundo capítulo da segunda parte desta série. Agora, seguindo o mesmo princípio, iremos desenvolver uma classe para as dicas de contexto.

Fig. 3. Exemplo de uma dica de contexto no Word.

Fig. 3. Exemplo de uma dica de contexto no Word.

 

Crie o arquivo Tooltip.mqh e inclua-o no arquivo WndContainer.mqh no qual todos os elementos da interface estão inclusos. Então, crie neste novo arquivo uma classe com os métodos padrão, que já são conhecidos, para todos os elementos da interface. Nesta classe, além do ponteiro do formulário, nós precisaremos do ponteiro para o elemento onde a dica de contexto será ligada e de um método para armazenar esse ponteiro. 

//+------------------------------------------------------------------+
//|                                                      Tooltip.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Classe para a criação de uma dica de contexto                    |
//+------------------------------------------------------------------+
class CTooltip : public CElement
  {
private:
   //--- Ponteiro para o formulário ao qual o elemento está anexado
   CWindow          *m_wnd;
   //--- Ponteiro para o elemento ao qual a dica de contexto está ligada
   CElement         *m_element;
   //---
public:
   //--- (1) Armazena o ponteiro do formulário, (2) armazena o ponteiro do elemento
   void              WindowPointer(CWindow &object)   { m_wnd=::GetPointer(object);     }
   void              ElementPointer(CElement &object) { m_element=::GetPointer(object); }
   //---
public:
   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Move o elemento
   virtual void      Moving(const int x,const int y);
   //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CTooltip::CTooltip(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CTooltip::~CTooltip(void)
  {
  }

Propriedades em que o usuário pode definir antes de criar a dica de contexto:

  • Cabeçalho.
  • Array de linhas.

Outras propriedades terão os valores predefinidos. Eles são:

  • As cores do gradiente do fundo da dica de contexto. 
  • Cor do quadro de fundo.
  • Cor do cabeçalho.
  • Cor do texto.
  • Canal alfa. Para gerenciar a transparência da dica de contexto.

O array de linha pode ser adicionado linha por linha antes de criar a dica de contexto o método CTooltip::AddString()

class CTooltip : public CElement
  {
private:
   //--- Propriedades:
   //    Cabeçalho
   string            m_header;
   //--- Array de linhas do texto da dica de contexto
   string            m_tooltip_lines[];
   //--- Valor do canal alfa (transparência da dica de contexto)
   uchar             m_alpha;
   //--- Cores do (1) texto, (2) do cabeçalho e do (3) fundo do quadro
   color             m_text_color;
   color             m_header_color;
   color             m_border_color;
   //--- Array do fundo de gradiente
   color             m_array_color[];
   //---
public:
   //--- Cabeçalho da dica de contexto
   void              Header(const string text)        { m_header=text;                  }
   //--- Adiciona a linha para a dica de contexto
   void              AddString(const string text);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CTooltip::CTooltip(void) : m_header(""),
                           m_alpha(0),
                           m_text_color(clrDimGray),
                           m_header_color(C'50,50,50'),
                           m_border_color(C'118,118,118'),
                           m_gradient_top_color(clrWhite),
                           m_gradient_bottom_color(C'208,208,235')
  {
//--- Armazena o nome da classe do elemento na classe base  
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Adiciona a linha                                                 |
//+------------------------------------------------------------------+
void CTooltip::AddString(const string text)
  {
//--- Aumenta o tamanho do array por um elemento
   int array_size=::ArraySize(m_tooltip_lines);
   ::ArrayResize(m_tooltip_lines,array_size+1);
//--- Armazenar o valor dos parâmetros passados
   m_tooltip_lines[array_size]=text;
  }

Semelhante à classe do elemento linha de separação, nós usaremos a classe CRectCanvas e dois métodos (públicos e privados) para a criação da dica de contexto. 

class CTooltip : public CElement
  {
private:
   //--- Objeto para a criação da dica de contexto
   CRectCanvas       m_canvas;
   //---
public:
   //--- Método para criar a dica de contexto
   bool              CreateTooltip (const long chart_id,const int subwin);
   //---
private:
   //--- Cria a tela (canvas) para a dica de contexto
   bool              CreateCanvas(void);
  };

Além da verificação do ponteiro para o formulário onde o elemento irá ser ligado, o método público CTooltip::CreateTooltip() também contém uma verificação do ponteiro para o elemento que foi designado esta dica de contexto. Se o ponteiro do elemento estiver presente, as coordenadas para a dica de contexto são calculadas em relação às coordenadas deste elemento. No nosso caso, este é um pixel abaixo do limite inferior do elemento.

//+------------------------------------------------------------------+
//| Cria o objeto dica de contexto                                   |
//+------------------------------------------------------------------+
bool CTooltip::CreateTooltip(const long chart_id,const int subwin)
  {
//--- Retorna se não há nenhum ponteiro do formulário
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de criar a dica de contexto, a classe deve ser passada "
              "para o ponteiro do formulário: CTooltip::WindowPointer(CWindow &object).");
      return(false);
     }
//--- Retorna se não há nenhum ponteiro do formulário
   if(::CheckPointer(m_element)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de criar a dica de contexto, a classe deve ser passada "
              "para o ponteiro do elemento: CTooltip::ElementPointer(CElement &object).");
      return(false);
     }
//--- Inicialização dos ponteiros
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =subwin;
   m_x        =m_element.X();
   m_y        =m_element.Y2()+1;
//--- Margens da borda
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Cria a dica de contexto
   if(!CreateTooltip())
      return(false);
//---
   return(true);
  }

Ao contrário do método semelhante na classe CSeparateLine, no método privado CTooltip::CreateCanvas() para a criação da tela de desenho, as seguintes ações são realizadas em vez de desenhar após a criação de um objeto e a definição de suas propriedades.

  • Vamos definir o tamanho do array de gradiente para o fundo da dica de contexto. O tamanho do array é igual ao número de pixels da altura do objeto (o tamanho do eixo Y).
  • Inicialização do array de gradiente. 
  • Limpeza da tela. Defina a transparência em 100%. O valor do canal alfa é zero.

No final do método, o objeto é adicionado ao array comum de objetos do elemento. O código deste método é mostrado abaixo. 

//+------------------------------------------------------------------+
//| Cria a tela canvas para desenho                                  |
//+------------------------------------------------------------------+
bool CTooltip::CreateCanvas(void)
  {
//--- Elaborando o nome do objeto
   string name=CElement::ProgramName()+"_help_tooltip_"+(string)CElement::Id();
//--- Cria a tela
   if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
//--- Anexa ao gráfico
   if(!m_canvas.Attach(m_chart_id,name,m_subwin,1))
      return(false);
//--- Define as propriedades
   m_canvas.Background(false);
//--- Margens da borda
   m_canvas.XGap(m_x-m_wnd.X());
   m_canvas.YGap(m_y-m_wnd.Y());
//--- Define o tamanho do array de gradiente do fundo da dica de contexto
   CElement::GradientColorsTotal(m_y_size);
   ::ArrayResize(m_array_color,m_y_size);
//--- Inicialização do array de gradiente
   CElement::InitColorArray(m_gradient_top_color,m_gradient_bottom_color,m_array_color);
//--- Limpeza da tela
   m_canvas.Erase(::ColorToARGB(clrNONE,0));
   m_canvas.Update();
   m_alpha=0;
//--- Armazena o ponteiro de objeto
   CElement::AddToArray(m_canvas);
   return(true);
  }

Vamos considerar os métodos para desenhar o gradiente vertical e o quadro. Para o gradiente, já existe um array inicializado com os valores necessários no momento de criação da tela. É por isso, que nós precisamos apenas desenhar linha após linha com a cor especificada a partir do array em um loop com o número de iterações igual à altura da tela. Para desenhar o quadro, no início do método, os arrays com as coordenadas de cada lado da tela são declarados e inicializados. Após o quadro ser desenhado, os cantos são arredondados por um pixel e, em seguida, adicionado mais quatro pixels nos quatro cantos internos da tela. 

Ambos os métodos possuem apenas um parâmetro - o canal alfa. Isto significa que o grau de transparência do gradiente e do quadro pode ser definido ao chamar esses métodos. 

//+------------------------------------------------------------------+
//| Gradiente vertical                                               |
//+------------------------------------------------------------------+
void CTooltip::VerticalGradient(const uchar alpha)
  {
//--- Coordenadas X
   int x1=0;
   int x2=m_x_size;
//--- Desenha o gradiente
   for(int y=0; y<m_y_size; y++)
      m_canvas.Line(x1,y,x2,y,::ColorToARGB(m_array_color[y],alpha));
  }
//+------------------------------------------------------------------+
//| Quadro                                                           |
//+------------------------------------------------------------------+
void CTooltip::Border(const uchar alpha)
  {
//--- Cor do quadro
   color clr=m_border_color;
//--- Limites
   int x_size =m_canvas.X_Size()-1;
   int y_size =m_canvas.Y_Size()-1;
//--- Coordenadas: topo/direita/fundo/esquerda
   int x1[4]; x1[0]=0;      x1[1]=x_size; x1[2]=0;      x1[3]=0;
   int y1[4]; y1[0]=0;      y1[1]=0;      y1[2]=y_size; y1[3]=0;
   int x2[4]; x2[0]=x_size; x2[1]=x_size; x2[2]=x_size; x2[3]=0;
   int y2[4]; y2[0]=0;      y2[1]=y_size; y2[2]=y_size; y2[3]=y_size;
//--- Desenha o quadro pelas coordenadas especificadas
   for(int i=0; i<4; i++)
      m_canvas.Line(x1[i],y1[i],x2[i],y2[i],::ColorToARGB(clr,alpha));
//--- Arredonda os cantos por um pixel
   clr=clrBlack;
   m_canvas.PixelSet(0,0,::ColorToARGB(clr,0));
   m_canvas.PixelSet(0,m_y_size-1,::ColorToARGB(clr,0));
   m_canvas.PixelSet(m_x_size-1,0,::ColorToARGB(clr,0));
   m_canvas.PixelSet(m_x_size-1,m_y_size-1,::ColorToARGB(clr,0));
//--- Adiciona os pixels através das coordenadas especificadas
   clr=C'180,180,180';
   m_canvas.PixelSet(1,1,::ColorToARGB(clr,alpha));
   m_canvas.PixelSet(1,m_y_size-2,::ColorToARGB(clr,alpha));
   m_canvas.PixelSet(m_x_size-2,1,::ColorToARGB(clr,alpha));
   m_canvas.PixelSet(m_x_size-2,m_y_size-2,::ColorToARGB(clr,alpha));
  }

Vamos fazer com que quando o cursor do mouse estiver sobre o elemento, uma dica de contexto irá emergir imediatamente e quando o cursor deixar a área do elemento, a dica de contexto irá desaparecendo gradualmente. 

Vamos criar o método CTooltip::showtooltip() para exibir a dica de contexto. No início deste método, haverá uma verificação no valor do campo m_alpha (canal alfa). Se o valor do canal alfa for igual ou maior do que 255, isso significa que o programa já esteve aqui e a dica de contexto está 100% visível por isso não há necessidade de seguir em frente. Caso contrário, o próximo passo é definir as coordenadas, a margem para o cabeçalho da dica de contexto e desenhar o gradiente e o quadro. Caso o usuário não tenha especificado o texto para o cabeçalho, o cabeçalho não será desenhado. Se o texto para o cabeçalho está presente, então os parâmetros de fonte são definidos e o cabeçalho é desenhado. 

Após isso, as coordenadas para o texto principal da dica de contexto são definidos tendo em conta a presença do cabeçalho. Em seguida, os parâmetros para o texto principal são definidos. Ao contrário da fonte em negrito do texto do cabeçalho (FW_BLACK), a fonte da principal será menos proeminente (FW_THIN). Em seguida, o texto da dica de contexto é impresso na tela em um loop desde o array inicializado pelo usuário. A coordenada Y é ajustada para cada linha para o valor especificado em cada iteração. No final do processo, a tela é atualizada para exibir as mudanças e há a indicação que a dica de contexto está completamente visível. O código deste método é mostrado abaixo. 

//+------------------------------------------------------------------+
//| Exibe a dica de contexto                                         |
//+------------------------------------------------------------------+
void CTooltip::ShowTooltip(void)
  {
//--- Sai, se a dica de contexto é 100% visível
   if(m_alpha>=255)
      return;
//--- Coordenadas e as margens para o cabeçalho
   int  x        =5;
   int  y        =5;
   int  y_offset =15;
//--- Desenha o gradiente
   VerticalGradient(255);
//--- Desenha o quadro
   Border(255);
//--- Desenha o cabeçalho (se especificado)
   if(m_header!="")
     {
      //--- Define os parâmetros da fonte
      m_canvas.FontSet(FONT,-80,FW_BLACK);
      //--- Desenha o texto do cabeçalho
      m_canvas.TextOut(x,y,m_header,::ColorToARGB(m_header_color),TA_LEFT|TA_TOP);
     }
//--- Coordenadas para o texto principal da dica de contexto (considerando a presença do cabeçalho)
   x=(m_header!="")? 15 : 5;
   y=(m_header!="")? 25 : 5;
//--- Define os parâmetros da fonte
   m_canvas.FontSet(FONT,-80,FW_THIN);
//--- Desenha o texto principal da dica de contexto
   int lines_total=::ArraySize(m_tooltip_lines);
   for(int i=0; i<lines_total; i++)
     {
      m_canvas.TextOut(x,y,m_tooltip_lines[i],::ColorToARGB(m_text_color),TA_LEFT|TA_TOP);
      y=y+y_offset;
     }
//--- Atualiza a tela
   m_canvas.Update();
//--- Indicação de uma dica de contexto completamente visível
   m_alpha=255;
  }

Vamos escrever um método para o desvanecimento gradual da dica de contexto e nomeá-la CTooltip::FadeOutTooltip(). No início deste método, é realizado uma verificação de reversão para o valor do canal alfa. Isto significa que, se a transparência completa foi alcançada, não há necessidade de seguir em frente. Se este não for o caso, a tela é redesenhada em um loop com um passo especificado para o desvanecimento até que a transparência completa seja alcançada. Uma versão completa deste método é mostrado no código abaixo.

//+------------------------------------------------------------------+
//| Desvanecimento gradual da dia de contexto                        |
//+------------------------------------------------------------------+
void CTooltip::FadeOutTooltip(void)
  {
//--- Sai, se a dica de contexto está 100% oculta
   if(m_alpha<1)
      return;
//--- Margem para o cabeçalho
   int y_offset=15;
//--- Passo da transparência
   uchar fadeout_step=7;
//--- Desvanecimento gradual da dica de contexto
   for(uchar a=m_alpha; a>=0; a-=fadeout_step)
     {
      //--- Se o próximo passo torna-a negativa, pare o loop
      if(a-fadeout_step<0)
        {
         a=0;
         m_canvas.Erase(::ColorToARGB(clrNONE,0));
         m_canvas.Update();
         m_alpha=0;
         break;
        }
      //--- Coordenadas para o cabeçalho
      int x =5;
      int y =5;
      //--- Desenha o gradiente e o quadro
      VerticalGradient(a);
      Border(a);
      //--- Desenha o cabeçalho (se especificado)
      if(m_header!="")
        {
         //--- Define os parâmetros da fonte
         m_canvas.FontSet(FONT,-80,FW_BLACK);
         //--- Desenha o texto do cabeçalho
         m_canvas.TextOut(x,y,m_header,::ColorToARGB(m_header_color,a),TA_LEFT|TA_TOP);
        }
      //--- Coordenadas para o texto principal da dica de contexto (considerando a presença do cabeçalho)
      x=(m_header!="")? 15 : 5;
      y=(m_header!="")? 25 : 5;
      //--- Define os parâmetros da fonte
      m_canvas.FontSet(FONT,-80,FW_THIN);
      //--- Desenha o texto principal da dica de contexto
      int lines_total=::ArraySize(m_tooltip_lines);
      for(int i=0; i<lines_total; i++)
        {
         m_canvas.TextOut(x,y,m_tooltip_lines[i],::ColorToARGB(m_text_color,a),TA_LEFT|TA_TOP);
         y=y+y_offset;
        }
      //--- Atualiza a tela
      m_canvas.Update();
     }
  }

Agora, nós temos todos os métodos necessários para a criação e o desenho da dica de contexto. Eles podem ser chamados no manipulador de evento do elemento. Para descobrir se o botão para exibir as dicas de contexto foi pressionado no formulário, crie o método >CWindow::TooltipBmpState() na classe CWindow.

class CWindow : public CElement
  {
public:
   //--- Verifica o modo de exibição da dica de contexto
   bool              TooltipBmpState(void)                             const { return(m_button_tooltip.State());   }
  };

Agora, se o foco está sobre o elemento, a dica de contexto será exibida desde que o modo de exibição da mesma esteja habilitado. Se o cursor do mouse estiver fora da área do elemento ou o formulário está bloqueado, então a dica de contexto será ocultada.

//+------------------------------------------------------------------+
//| Manipulador de eventos do gráfico                                |
//+------------------------------------------------------------------+
void CTooltip::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;
      //--- Sai, se o botão dica de contexto estiver desativado
      if(!m_wnd.TooltipBmpState())
         return;
      //--- Se o formulário está bloqueado
      if(m_wnd.IsLocked())
        {
         //--- Oculta a dica de contexto
         FadeOutTooltip();
         return;
        }
      //--- Se o foco está sobre o elemento
      if(m_element.MouseFocus())
         //--- Exibe a dica de contexto
         ShowTooltip();
      //--- Se não houver foco
      else
      //--- Oculta a dica de contexto
         FadeOutTooltip();
      //---
      return;
     }
  }

 


Teste da Dica de Contexto

Agora, nós podemos testar como tudo está funcionando. Atualmente, existem cinco botões no formulário do EA de teste. Vamos criar uma dica de contexto para cada botão. Declare cinco instâncias da classe do tipo CTooltip e cinco métodos. Nós vamos mostrar a implementação para apenas um deles como um exemplo.

class CProgram : public CWndEvents
  {
private:
   CTooltip          m_tooltip1;
   CTooltip          m_tooltip2;
   CTooltip          m_tooltip3;
   CTooltip          m_tooltip4;
   CTooltip          m_tooltip5;
   //---
private:
   bool              CreateTooltip1(void);
   bool              CreateTooltip2(void);
   bool              CreateTooltip3(void);
   bool              CreateTooltip4(void);
   bool              CreateTooltip5(void);
  };
//+------------------------------------------------------------------+
//| Cria a dica de contexto 5                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTooltip5(void)
  {
#define TOOLTIP5_LINES_TOTAL 3
//--- Armazena o ponteiro da janela
   m_tooltip5.WindowPointer(m_window1);
//--- Armazenar o ponteiro do elemento
   m_tooltip5.ElementPointer(m_icon_button5);
//--- Array com o texto dica de contexto
   string text[]=
     {
      "Controle \"Botão com ícone\" (5).",
      "Esta é a segunda linha da dica de contexto.",
      "Esta é a terceira linha da dica de contexto."
     };
//--- Define as propriedades antes da criação
   m_tooltip5.Header("Icon Button 5");
   m_tooltip5.XSize(250);
   m_tooltip5.YSize(80);
//--- Adiciona o texto linha por linha
   for(int i=0; i<TOOLTIP5_LINES_TOTAL; i++)
      m_tooltip5.AddString(text[i]);
//--- Cria um controle
   if(!m_tooltip5.CreateTooltip(m_chart_id,m_subwin))
      return(false);
//--- Adiciona um objeto ao array comum de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_tooltip5);
   return(true);
  }

A criação das dicas de contexto devem ser conduzidas por último no método de criação da interface gráfica de modo que elas se encontram no final do array de elemento na base. Desta forma, as dicas de contexto estarão sempre acima de outros objetos no gráfico. 

//+------------------------------------------------------------------+
//| Cria o painel de negociação                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Criação do formulário 1 para os controles
//--- Criação dos controles:
//    Menu principal
//--- Menus de contexto
//--- Criando a barra de status
//--- Botões com Ícone

//--- Dicas de contexto
   if(!CreateTooltip1())
      return(false);
   if(!CreateTooltip2())
      return(false);
   if(!CreateTooltip3())
      return(false);
   if(!CreateTooltip4())
      return(false);
   if(!CreateTooltip5())
      return(false);
//--- Redesenho do gráfico
   m_chart.Redraw();
   return(true);
  }

Para exibir o botão dica de contexto sobre o formulário, utilize o método CWindow::UseTooltipsButton() no momento de sua criação.

//+------------------------------------------------------------------+
//| Cria o formulário 1 para os controles                            |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow1(const string caption_text)
  {
//--- Adiciona o ponteiro da janela para o array de janela
   CWndContainer::AddWindow(m_window1);
//--- Coordenadas
   int x=1;
   int y=20;
//--- Propriedades
   m_window1.Movable(true);
   m_window1.XSize(251);
   m_window1.YSize(200);
   m_window1.UseTooltipsButton();
   m_window1.CaptionBgColor(clrCornflowerBlue);
   m_window1.CaptionBgColorHover(C'150,190,240');
//--- Cria o formulário
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Você deve obter o resultado como é mostrado na imagem abaixo.

Fig. 4. Teste da dica de contexto.

Fig. 4. Teste da dica de contexto.

 

Ele tem um boa aparência e parece estar funcionando bem! Agora, este elemento requer um array privado no ponteiro base. Quando estivermos desenvolvendo o modo multi-janela, será ilustrado quais casos nós podemos precisar de tal array.  

 


Array Privado para a Dica de Contexto

Então, vá para a classe CWndContainer e melhore-o com os novos membros. Um array privado para as dicas de contexto é para ser adicionado à estrutura WindowElements. Nós também vamos criar (1) o método CWndContainer::TooltipsTotal() para a obtenção da quantidade de dicas de contexto e (2) o método CWndContainer::AddTooltipElements() para adicionar o ponteiro da dica de contexto ao array privado.

class CWndContainer
  {
protected:
   //--- Estrutura de arrays de elementos
   struct WindowElements
     {
      //--- Dicas de contexto
      CTooltip         *m_tooltips[];
     };
   //--- Array de arrays de elementos para cada janela
   WindowElements    m_wnd[];
   //---
public:
   //--- Quantidade das dicas de contexto
   int               TooltipsTotal(const int window_index);
   //---
private:
   //--- Armazena os ponteiros aos elementos dicas de contexto na base
   bool              AddTooltipElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Retorna a quantidade de dicas de contexto pelo índice da janela especificada|
//+------------------------------------------------------------------+
int CWndContainer::TooltipsTotal(const int window_index)
  {
   if(window_index>=::ArraySize(m_wnd))
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return(::ArraySize(m_wnd[window_index].m_tooltips));
  }
//+------------------------------------------------------------------+
//| Armazena o ponteiro dica de contexto no array privado            |
//+------------------------------------------------------------------+
bool CWndContainer::AddTooltipElements(const int window_index,CElement &object)
  {
//--- Retorna, se este não for uma dica de contexto
   if(object.ClassName()!="CTooltip")
      return(false);
//--- Obtém o ponteiro dica de contexto
   CTooltip *t=::GetPointer(object);
//--- Adiciona o ponteiro para o array privado
   AddToRefArray(t,m_wnd[window_index].m_tooltips);
   return(true);
  }

o método CWndContainer::AddTooltipElements() deve ser chamado no método público CWndContainer::AddToElementsArray(), que será utilizado na classe personalizada quando se adiciona o elemento para a base. A sua versão reduzida é apresentada no código abaixo.

//+------------------------------------------------------------------+
//| Adiciona o ponteiro ao array de elemento                         |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Se a base não contém formulários para os controles
/ --- Se a solicitação for para um formulário que não existe
//--- Adiciona ao array comum de elementos
//--- Adiciona os objetos do elemento para o array comum de objetos
//--- Armazena o ID do último elemento em todos os formulários
//--- Aumenta o contador de identificadores de 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 

//--- Armazena os ponteiros aos elementos dicas de contexto na base
   if(AddTooltipElements(window_index,object))
      return;
  }

O desenvolvimento da classe para a criação das dicas de contexto está finalizado. Você pode baixar a versão completa nos arquivos anexados deste artigo. 

 


Conclusão

Neste artigo, nós discutimos o desenvolvimento da barra de status e a dica de contexto da interface informativa. No próximo capítulo, nós vamos implementar a possibilidade de criar interfaces gráficas de multi-janelas e discutir o sistema de gestão de prioridades do botão esquerdo do mouse.

Você pode encontrar e baixar o material da primeira parte ou toda a série nos arquivos anexados, assim, você pode testar o seu funcionamento. 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 quarta parte:


Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2307

Arquivos anexados |
Interfaces Gráficas IV: O Modo Multi-Janela e o Sistema de Prioridades (Capítulo 2) Interfaces Gráficas IV: O Modo Multi-Janela e o Sistema de Prioridades (Capítulo 2)
Neste capítulo, nós vamos estender a implementação da biblioteca para possibilitar a criação de interfaces de multi-janela para as aplicações MQL. Nós também vamos desenvolver um sistema de prioridades para clique esquerdo do mouse sobre os objetos gráficos. Isso se faz necessário para evitar problemas quando os elementos não respondem as ações do usuário.
Teste de estratégias de negociação em ticks reiais Teste de estratégias de negociação em ticks reiais
Neste artigo mostraremos os resultados de teste de uma estratégia de negociação simples em três modos: "OHLC em M1", "Todos os ticks" e "Cada tick baseado em ticks reais" usando os ticks gravados a partir do histórico.
Mais uma vez vamos falar sobre mapas de Kohonen Mais uma vez vamos falar sobre mapas de Kohonen
O artigo descreve as técnicas para trabalhar com mapas de Kohonen. Ele vai ser do interesse tanto para exploradores do mercado, com habilidades básicas nas plataformas MQL4 e MQL5, quanto para programadores experientes que enfrentam dificuldades com a conexão dos mapas de Kohonen aos seus projetos.
Como adicionar rapidamente paneis de controle a indicadores e conselheiros (EA) Como adicionar rapidamente paneis de controle a indicadores e conselheiros (EA)
Você deseja adicionar ao seu indicador ou conselheiro um painel gráfico para um controle fácil e rápido, mas não sabe como fazê-lo? Neste artigo, vou mostrar passo a passo como "atar" o painel de diálogo com os parâmetros de entrada do seu programa MQL4/MQL5.