English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas I: Animação na Interface Gráfica (Capítulo 3)

Interfaces Gráficas I: Animação na Interface Gráfica (Capítulo 3)

MetaTrader 5Exemplos | 22 julho 2016, 09:56
1 740 1
Anatoli Kazharski
Anatoli Kazharski

Conteúdo

 

Introdução

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

No artigo anterior da série, nós começamos a desenvolver uma classe de formulário para os controles. Neste artigo, nós vamos continuar a desenvolver esta classe e preenchê-la com os métodos para mover um formulário sobre a área do gráfico. Em seguida, integraremos este componente da interface para o núcleo da biblioteca. Além disso, nós vamos garantir que os controles do formulário mudem de cor quando o cursor do mouse estiver pairando sobre eles.

 

Gestão da Interface Gráfica

O formulário para os controles é para ser anexado ao gráfico. Neste momento, ele está completamente inativo. Nosso objetivo é fazer com que o formulário e seus controles reajam as ações do usuário. Para implementar isso, é necessário controlar a posição do cursor no gráfico. O programa deve "estar consciente" das coordenadas do cursor em qualquer momento. Isto não é possível implementar na aplicação MQL com os parâmetros padrões do gráfico. É necessário habilitar o acompanhando da posição do cursor e os cliques dos botões do mouse. Olhando para a frente, no processo de desenvolvimento, algumas propriedades do gráfico terão que ser usadas. Portanto, vamos incluir a classe CChart da biblioteca padrão no arquivo WndEvents.mqh da nossa biblioteca e criar sua instância no corpo da classe.

//+------------------------------------------------------------------+
//|                                                    WndEvents.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Defines.mqh"
#include "WndContainer.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Classe para a manipulação de eventos                             |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
   CChart            m_chart;
  };

No construtor da classe, anexe o objeto para o gráfico atual, tendo obtido o seu identificador, e permitido o acompanhamento da posição do cursor. No destrutor, retire o objeto do gráfico (destacado em verde no código abaixo). Caso contrário, quando o programa for removido do gráfico, o gráfico em si será fechado.

//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void) : m_chart_id(0),
                               m_subwin(0),
                               m_indicator_name(""),
                               m_program_name(PROGRAM_NAME)
  {
//--- Obter o ID do gráfico atual
   m_chart.Attach();
// --- Ativar o rastreio de eventos do mouse
   m_chart.EventMouseMove(true);
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
//--- Retira do gráfico
   m_chart.Detach();
  }

Como mencionado anteriormente, a classe de cada controle terá o seu próprio processador de eventos. Agora, na classe CWindow vamos criar alguns métodos que nós precisaremos para gerenciar o formulário. Todos eles serão chamados no método CWindow::OnEvent(). Vamos decidir sobre a funcionalidade, o que irá ser adicionado à classe CWindow:

1. A janela em que o cursor está localizado deve ser definida. Isto é porque um gráfico pode consistir de várias partes, que é o gráfico principal e as sub-janelas do indicador, e a aplicação MQL pode ser um indicador, situado numa janela diferente da principal.

2. Se a aplicação MQL for um indicador e ela não está localizada na janela principal do gráfico, a coordenada Y deve ser ajustada.

3. O estado do botão esquerdo do mouse tem de ser verificado, bem como o ponto em que ele foi pressionado. Haverá quatro estados:

  • NOT_PRESSED — O botão não pressionado.
  • PRESSED_OUTSIDE — O botão pressionado fora da área do formulário.
  • PRESSED_INSIDE_WINDOW — O botão pressionado dentro da área do formulário.
  • PRESSED_INSIDE_HEADER — O botão pressionado dentro da área do cabeçalho.

Adicione a enumeração ENUM_WMOUSE_STATE para o arquivo Enums.mqh:

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Enumeração dos estados do botão esquerdo do mouse para o formulário |
//+------------------------------------------------------------------+
enum ENUM_WMOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_OUTSIDE       =1,
   PRESSED_INSIDE_WINDOW =2,
   PRESSED_INSIDE_HEADER =3
  };

4. Se o cursor estiver dentro da área do formulário ou dentro da área de um componente de interface, então, deve-se desativar a rolagem do gráfico e o gerenciamento dos níveis de negociação.

5. Se o cursor estiver dentro da área de captura do cabeçalho e o botão esquerdo do mouse for pressionado, então o programa entra no modo de atualização das coordenadas do formulário.

6. Quando as coordenadas são atualizadas, é realizado uma verificação por deixar a área do gráfico e feito o ajuste correspondente. Tal verificação exige (1) o objeto da classe CChart, (2) variáveis ​​e o método para a obtenção dos tamanhos do gráfico.

7. É necessário também um método para mover todos os objetos do formulário em relação às coordenadas atualizadas. Para isso, o método virtual CWindow::Moving() foi declarado anteriormente. Agora, nós temos de implementá-lo.

8. Há também que ser criado alguns métodos auxiliares para identificar o cursor na área do cabeçalho e para zerar algumas variáveis ​​auxiliares.

9. O acompanhando do foco do mouse irá funcionar para todos os objetos que constituem a janela. Portanto, vamos criar um método em que tal verificação será efetuada.

10. Uma interface gráfica com a capacidade de movê-la sobre o gráfico não é sempre necessária. Por esta razão, nós vamos adicionar um método que permitirá ativar/desativar esse recurso.

 

Funcionalidade para Poder Mover o Formulário

Vamos implementar a funcionalidade acima discutida. No corpo da classe, adicione as declarações dos métodos necessários e algumas variáveis. Inclua o arquivo com a classe CChart e crie sua instância (em amarelo):

//+------------------------------------------------------------------+
//|                                                       Window.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Classe para criar um formulário de controles                     |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   CChart            m_chart;
   // --- Possibilidade de mover uma janela no gráfico
   bool              m_movable;
   //--- Tamanho do gráfico
   int               m_chart_width;
   int               m_chart_height;
   //--- Variáveis, relacionadas com o deslocamento
   int               m_prev_x;        // Ponto fixo X quando se clica
   int               m_prev_y;        // Ponto fixo Y quando se clica
   int               m_size_fixing_x; // Distância da coordenada X para o ponto fixo X
   int               m_size_fixing_y; // Distância da coordenada Y para o ponto fixo Y
   //--- Estado do botão do mouse, considerando a posição em que ela foi clicado
   ENUM_WMOUSE_STATE m_clamping_area_mouse;
   //---
public:
   //--- Possibilidade de mover a janela
   bool              Movable(void)                                     const { return(m_movable);                  }
   void              Movable(const bool flag)                                { m_movable=flag;                     }
   //--- Obter o tamanho do gráfico
   void              SetWindowProperties(void);
   //--- Converte a coordenada Y para uma relativa
   void              YToRelative(const int y);
   //--- Verifica o cursor na área do cabeçalho 
   bool              CursorInsideCaption(const int x,const int y);
   //--- Zerando as variáveis
   void              ZeroPanelVariables(void);
   //--- Verificando o foco do mouse
   void              CheckMouseFocus(const int x,const int y,const int subwin);
   //--- Verificando o estado do botão esquerdo do mouse
   void              CheckMouseButtonState(const int x,const int y,const string state);
   //--- Definir o modo gráfico
   void              SetChartState(const int subwindow_number);
   //--- Atualizando as coordenadas do formulário
   void              UpdateWindowXY(const int x,const int y);
  };

O código dos métodos SetWindowProperties(), YToRelative(), CursorInsideCaption() e ZeroPanelVariables() são bem simples e não requerem explicações adicionais. A única coisa que eu gostaria de chamar a atenção é passagem do número da sub-janela (m_subwin) para os métodos do objeto CChart. Este deve ser o número da sub-janela em que o programa MQL se encontra.

//+------------------------------------------------------------------+
//| Obtendo o tamanho do gráfico                                     |
//+------------------------------------------------------------------+
void CWindow::SetWindowProperties(void)
  {
//--- Obter a largura e altura da janela do gráfico
   m_chart_width  =m_chart.WidthInPixels();
   m_chart_height =m_chart.HeightInPixels(m_subwin);
  }
//+------------------------------------------------------------------+
//| Converte a coordenada Y para uma relativa                        |
//+------------------------------------------------------------------+
int CWindow::YToRelative(const int y)
  {
//--- Obter a distância do topo do gráfico para a sub-janela do indicador
   int chart_y_distance=m_chart.SubwindowY(m_subwin);
//--- Converte a coordenada Y para uma relativa
   return(y-chart_y_distance);
  }
//+------------------------------------------------------------------+
//| Verificando a posição do cursor na área de título da janela      |
//+------------------------------------------------------------------+
bool CWindow::CursorInsideCaption(const int x,const int y)
  {
   return(x>m_x && x<X2()-m_right_limit && y>m_y && y<m_caption_bg.Y2());
  }
//+------------------------------------------------------------------+
//| Zerando as variáveis, ligadas ao deslocamento da janela e        |
//| e o estado do botão esquerdo o mouse                              |
//+------------------------------------------------------------------+
void CWindow::ZeroPanelVariables(void)
  {
   m_prev_x              =0;
   m_prev_y              =0;
   m_size_fixing_x       =0;
   m_size_fixing_y       =0;
   m_clamping_area_mouse =NOT_PRESSED;
  }

A verificação do estado do botão esquerdo do mouse em relação ao formulário ocorre no método CWindow::CheckMouseButtonState(). Para isso, as coordenadas do cursor e o parâmetro de string do modelo de evento do gráfico são passados ​​para este método. O parâmetro de string exibe o estado do botão esquerdo do mouse quando o evento CHARTEVENT_MOUSE_MOVE é tratado. Isto significa que este parâmetro pode ter um valor igual a "0", se o botão do mouse for liberado e igual a "1" se o botão do mouse é pressionado.

Se o botão for liberado, então, todas as variáveis ​​auxiliares serão zeradas e o trabalho do método estará concluído. Se o botão for pressionado, então, uma verificação é realizada no método CWindow::CheckMouseButtonState() para determinar onde isso ocorreu na área do gráfico. Se ele verificar que o botão já tem um estado registrado, ou seja, ele foi pressionado de alguma área do gráfico, então o programa deixará o método (return).

//+------------------------------------------------------------------+
//| Verifica o estado do botão do mouse                              |
//+------------------------------------------------------------------+
void CWindow::CheckMouseButtonState(const int x,const int y,const string state)
  {
//--- Se o botão foi liberado
   if(state=="0")
     {
      //--- Zerando as variáveis
      ZeroPanelVariables();
      return;
     }
//--- Se o botão foi pressionado
   if(state=="1")
     {
      //--- Sai se o estado é gravado
      if(m_clamping_area_mouse!=NOT_PRESSED)
         return;
      //--- Fora da área do painel
      if(!CElement::MouseFocus())
         m_clamping_area_mouse=PRESSED_OUTSIDE;
      //--- Dentro da área do painel
      else
        {
         //--- Se está dentro do cabeçalho
         if(CursorInsideCaption(x,y))
           {
            m_clamping_area_mouse=PRESSED_INSIDE_HEADER;
            return;
           }
         //--- Se está dentro da área da janela
         m_clamping_area_mouse=PRESSED_INSIDE_WINDOW;
        }
     }
  }

Os métodos para definir o limite dos objetos são fornecidos no arquivo Objects.mqh de todas as classes de objetos primitivos. Estes métodos permitem a identificação rápida e fácil caso um objeto esteja no foco do mouse. O foco para o formulário e todos os objetos do formulário podem ser verificados na classe CWindow usando o método CheckMouseFocus(). Serão passados para este método as coordenadas atuais do cursor e o número da sub-janela onde o cursor está localizado. Será demonstrado mais tarde uma maneira de obter o número da sub-janela que contém o cursor.

//+------------------------------------------------------------------+
//| Verificando o foco do mouse                                      |
//+------------------------------------------------------------------+
void CWindow::CheckMouseFocus(const int x,const int y,const int subwin)
  {
//--- Se o cursor estiver na área da janela do programa
   if(subwin==m_subwin)
     {
      //--- Se no momento ele não está no modo para deslocar o formulário
      if(m_clamping_area_mouse!=PRESSED_INSIDE_HEADER)
        {
         //--- Verificando a localização do cursor
         CElement::MouseFocus(x>m_x && x<X2() && y>m_y && y<Y2());
         //---
         m_button_rollup.MouseFocus(x>m_button_rollup.X() && x<m_button_rollup.X2() && 
                                    y>m_button_rollup.Y() && y<m_button_rollup.Y2());
         m_button_close.MouseFocus(x>m_button_close.X() && x<m_button_close.X2() && 
                                   y>m_button_close.Y() && y<m_button_close.Y2());
         m_button_unroll.MouseFocus(x>m_button_unroll.X() && x<m_button_unroll.X2() && 
                                    y>m_button_unroll.Y() && y<m_button_unroll.Y2());
        }
     }
   else
     {
      CElement::MouseFocus(false);
     }
  }

O resultado da verificação do foco do formulário e o estado do botão do mouse em relação ao formulário podem indicar se a rolagem do gráfico e a gestão dos níveis de negociação deve ser ativado ou desativado. Se estas propriedades do gráfico não são desativadas quando o cursor estiver sobre o formulário, então, a rolagem do gráfico estará acontecendo juntamente com o deslocamento do formulário e a gestão dos níveis de negociação irá acontecer embaixo do formulário, sendo que isso não é necessário.

//+------------------------------------------------------------------+
//| Define o estado do gráfico                                       |
//+------------------------------------------------------------------+
void CWindow::SetChartState(const int subwindow_number)
  {
//--- Se (o cursor estiver na área do painel e o botão do mouse é liberado) ou
//    o botão do mouse foi pressionado para dentro da área da formulário ou cabeçalho
   if((CElement::MouseFocus() && m_clamping_area_mouse==NOT_PRESSED) || 
      m_clamping_area_mouse==PRESSED_INSIDE_WINDOW ||
      m_clamping_area_mouse==PRESSED_INSIDE_HEADER)
     {
      //--- Desativa a rolagem e gestão dos níveis de negociação
      m_chart.MouseScroll(false);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false);
     }
//--- Ativa a gestão, se o cursor estiver fora da área da janela
   else
     {
      m_chart.MouseScroll(true);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true);
     }
  }

A atualização das coordenadas da janela tem lugar no método CWindow::UpdateWindowXY(). No início, o modo da janela é verificado. Se o formulário estiver no modo fixo, então, o programa deixa o método já que não há nenhum ponto para atualizar as coordenadas. Então, se o botão do mouse é pressionado, as coordenadas atuais são armazenadas, é armazenado a distância do ponto de extremidade do formulário até o cursor, é calculado os limites para sair da área do gráfico, é feito as verificações e, se necessário, o ajuste de coordenadas para o formulário. Você pode estudar o código abaixo:

//+------------------------------------------------------------------+
//| Atualizando as coordenadas da janela                             |
//+------------------------------------------------------------------+
void CWindow::UpdateWindowXY(const int x,const int y)
  {
//--- Se o modo fixo do formulário foi definido
   if(!m_movable)
      return;
//---
   int new_x_point =0; // New X coordinate
   int new_y_point =0; // New Y coordinate
//--- Limites
   int limit_top    =0;
   int limit_left   =0;
   int limit_bottom =0;
   int limit_right  =0;
//--- Se o botão do mouse foi pressionado
   if((bool)m_clamping_area_mouse)
     {
      //--- Armazena as coordenadas XY atuais do cursor
      if(m_prev_y==0 || m_prev_x==0)
        {
         m_prev_y=y;
         m_prev_x=x;
        }
      //--- Armazena a distância do ponto de extremidade do formulário até o cursor
      if(m_size_fixing_y==0 || m_size_fixing_x==0)
        {
         m_size_fixing_y=m_y-m_prev_y;
         m_size_fixing_x=m_x-m_prev_x;
        }
     }
//--- Estabelece os limites
   limit_top    =y-::fabs(m_size_fixing_y);
   limit_left   =x-::fabs(m_size_fixing_x);
   limit_bottom =m_y+m_caption_height;
   limit_right  =m_x+m_x_size;
//--- Se os limites do gráfico não são excedidos para baixo/cima/direita/esquerda
   if(limit_bottom<m_chart_height && limit_top>=0 && 
      limit_right<m_chart_width && limit_left>=0)
     {
      new_y_point =y+m_size_fixing_y;
      new_x_point =x+m_size_fixing_x;
     }
//--- Se os limites do gráfico foram excedidos
   else
     {
      if(limit_bottom>m_chart_height) // > downwards
        {
         new_y_point =m_chart_height-m_caption_height;
         new_x_point =x+m_size_fixing_x;
        }
      if(limit_top<0) // > upwards
        {
         new_y_point =0;
         new_x_point =x+m_size_fixing_x;
        }
      if(limit_right>m_chart_width) // > right
        {
         new_x_point =m_chart_width-m_x_size;
         new_y_point =y+m_size_fixing_y;
        }
      if(limit_left<0) // > left
        {
         new_x_point =0;
         new_y_point =y+m_size_fixing_y;
        }
     }
//--- Atualizar as coordenadas, se houver um deslocamento
   if(new_x_point>0 || new_y_point>0)
     {
      //--- Ajusta as coordenadas do formulário
      m_x =(new_x_point<=0)? 1 : new_x_point;
      m_y =(new_y_point<=0)? 1 : new_y_point;
      //---
      if(new_x_point>0)
         m_x=(m_x>m_chart_width-m_x_size-1) ? m_chart_width-m_x_size-1 : m_x;
      if(new_y_point>0)
         m_y=(m_y>m_chart_height-m_caption_height-1) ? m_chart_height-m_caption_height-2 : m_y;
      //--- Zera os pontos fixos
      m_prev_x=0;
      m_prev_y=0;
     }
  }

No manipulador de eventos do gráfico OnEvent() da classe CWindow , ao manipular o evento do movimento do mouse (CHARTEVENT_MOUSE_MOVE), o número da sub-janela pode ser obtido com a função ChartXYToTimePrice(). Se a função retorna true, então, ela recebe uma coordenada Y relativa usando o método CWindow::YToRelative() criado anteriormente e após isso, iterar sobre todos os métodos listados acima:

//+------------------------------------------------------------------+
//| Manipulador de eventos do gráfico                                |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      int      x      =(int)lparam; // Coordenada do eixo X
      int      y      =(int)dparam; // Coordenada do eixo Y
      int      subwin =WRONG_VALUE; // Número da janela, na qual o cursor está localizado
      datetime time   =NULL;        // Hora correspondente a coordenada X
      double   level  =0.0;         // Nível (preço) correspondente da coordenada Y
      int      rel_y  =0;           // Para identificação da coordenada relativa Y
      //--- Obtém a localização do cursor
      if(!::ChartXYToTimePrice(m_chart_id,x,y,subwin,time,level))
         return;
      //--- Obtém a coordenada Y relativa
      rel_y=YToRelative(y);
      //--- Verifica e armazena o estado do botão do mouse
      CheckMouseButtonState(x,rel_y,sparam);
      //--- Verificando o foco do mouse
      CheckMouseFocus(x,rel_y,subwin);
      //--- Define o estado do gráfico
      SetChartState(subwin);
      //--- Se a gestão é delegada para a janela, então, identifica a sua localização
      if(m_clamping_area_mouse==PRESSED_INSIDE_HEADER)
        {
         //--- Coordenadas da janela de atualização
         UpdateWindowXY(x,rel_y);
        }
      return;
     }
  }

Para testar o movimento do formulário, falta implementar apenas o método CWindow::Moving(). Pode parecer que este método será usado no manipulador de eventos interno da classe logo após o método UpdateWindowXY(), mas isto não é o caso. Se fizermos isso agora, o resultado será excelente quando não tiver outros controles no formulário. Seguindo esta lógica, você terá de enfrentar a necessidade de fazer a mesma coisa em classes de outros controles. Como resultado, quando o formulário possuir muitos controles ligados e está sendo deslocado, todos os controles serão movidos juntamente com o formulário com algum atraso. E este será um resultado ruim.

A razão para isso é que todo o método para mover um controle de interface é implementado no manipulador interno, então, todos os controles serão divididos por várias condições e verificações. Isso vai causar um atraso. Para todos os controlos mover sincronizadamente, não deve haver outras operações entre os métodos Moving(). Isso pode ser implementado na classe CWndEvents já que ela é uma classe derivada da classe CWndContainer e tem um acesso direto a todos os ponteiros de objeto, que serão armazenados lá.

O conteúdo do método CWindow::Moving() consiste de duas partes. Na primeira, as coordenadas de todos os objetos que constituem o formulário são armazenadas e, em seguida, as coordenadas do objeto são atualizadas no gráfico. Da mesma forma, o método Moving() será implementado mais tarde para cada controle.

//+------------------------------------------------------------------+
//| Movendo a janela                                                 |
//+------------------------------------------------------------------+
void CWindow::Moving(const int x,const int y)
  {
//--- Armazenando as coordenadas em variáveis
   m_bg.X(x);
   m_bg.Y(y);
   m_caption_bg.X(x);
   m_caption_bg.Y(y);
   m_icon.X(x+m_icon.XGap());
   m_icon.Y(y+m_icon.YGap());
   m_label.X(x+m_label.XGap());
   m_label.Y(y+m_label.YGap());
   m_button_close.X(x+m_button_close.XGap());
   m_button_close.Y(y+m_button_close.YGap());
   m_button_unroll.X(x+m_button_unroll.XGap());
   m_button_unroll.Y(y+m_button_unroll.YGap());
   m_button_rollup.X(x+m_button_rollup.XGap());
   m_button_rollup.Y(y+m_button_rollup.YGap());
   m_button_tooltip.X(x+m_button_tooltip.XGap());
   m_button_tooltip.Y(y+m_button_tooltip.YGap());
//--- Atualizando as coordenadas dos objetos gráficos
   m_bg.X_Distance(m_bg.X());
   m_bg.Y_Distance(m_bg.Y());
   m_caption_bg.X_Distance(m_caption_bg.X());
   m_caption_bg.Y_Distance(m_caption_bg.Y());
   m_icon.X_Distance(m_icon.X());
   m_icon.Y_Distance(m_icon.Y());
   m_label.X_Distance(m_label.X());
   m_label.Y_Distance(m_label.Y());
   m_button_close.X_Distance(m_button_close.X());
   m_button_close.Y_Distance(m_button_close.Y());
   m_button_unroll.X_Distance(m_button_unroll.X());
   m_button_unroll.Y_Distance(m_button_unroll.Y());
   m_button_rollup.X_Distance(m_button_rollup.X());
   m_button_rollup.Y_Distance(m_button_rollup.Y());
   m_button_tooltip.X_Distance(m_button_tooltip.X());
   m_button_tooltip.Y_Distance(m_button_tooltip.Y());
  }

 

Teste de Movimento do Formulário sobre o Gráfico

Todos os eventos devem ser tratados no método CWndEvents::ChartEvent(), que foi criado anteriormente e, atualmente, está vazio. No início, será realizado a verificação do tamanho do array de ponteiros de janelas. Se ele estiver vazio, então não há nenhum ponto em continuar com o trabalho e o programa irá sair (return). Em seguida, os campos da classe que se referem aos parâmetros de eventos do gráfico serão inicializados. Por enquanto, nós vamos colocar duas funções de manipulação de eventos: (1) para a verificação de eventos nos manipuladores de cada controle CWndEvents::CheckElementsEvents() e (2) para acompanhar o cursor do mouse CWndEvents::ChartEventMouseMove().

Assim, atualmente o método CWndEvents::ChartEvent() deve ter o conteúdo conforme mostrado no código abaixo:

//+------------------------------------------------------------------+
//| Manipulador de eventos do programa                               |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Se o array estiver vazio, retorna
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Inicialização dos campos dos parâmetros de eventos
   InitChartEventsParams(id,lparam,dparam,sparam);
//--- Verificação dos eventos de controle da interface
   CheckElementsEvents();
//--- Evento do movimento do mouse
   ChartEventMouseMove();
  }

Implementa os métodos CWndEvents::CheckElementsEvents() e CWndEvents::ChartEventMouseMove() já que no momento eles estão vazios. A verificação dos eventos dos controles de interface é realizada em um ciclo. Lá, nós chamamos sequencialmente os manipuladores OnEvent() de todos os controles que estão na base. Como atualmente o nosso arquivo de teste contém apenas uma janela, nós vamos usar temporariamente o índice zero dos arrays de janela na classe CWndEvents. Isto terá de ser alterado quando nós passarmos a desenvolver o modo multi-janela.

//+------------------------------------------------------------------+
//| Verificação dos eventos de controle                              |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

Vamos criar um método na classe CWndEvents, onde o deslocamento do controle sobre o gráfico será realizado. Nós vamos chamá-la de MovingWindow(). Primeiramente, neste método, o movimento do formulário será executado e, logo em seguida, o movimento de todos os comandos que estão ligados a ele.

class CWndEvents : public CWndContainer
  {
private:
   //--- Movendo a janela
   void              MovingWindow(void);
  };
//+------------------------------------------------------------------+
//| Movendo a janela                                                 |
//+------------------------------------------------------------------+
void CWndEvents::MovingWindow(void)
  {
//--- Movendo a janela
   int x=m_windows[0].X();
   int y=m_windows[0].Y();
   m_windows[0].Moving(x,y);
//--- Movendo os controles
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].Moving(x,y);
  }

Agora, o código pode ser adicionado ao método CWndEvents::ChartEventMouseMove() como é mostrado abaixo. Não se esqueça de redesenhar o gráfico toda vez após as coordenadas dos objetos serem atualizadas no gráfico, caso contrário, as alterações não terão efeito.

//+------------------------------------------------------------------+
//| Evento CHARTEVENT MOUSE MOVE                                     |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventMouseMove(void)
  {
//--- Retorna se este não é um evento de deslocamento do cursor
   if(m_id!=CHARTEVENT_MOUSE_MOVE)
      return;
//--- Movendo a janela
   MovingWindow();
//--- Redesenha o gráfico
   m_chart.Redraw();
  }

O processo de desenvolvimento da biblioteca está em sua fase final, podendo ser testado o deslocamento da janela no gráfico. No corpo do método CWindow::OnEvent(), anexe o código para exibir os dados no canto superior esquerdo do gráfico, onde visualizaremos em tempo real o seguinte:

  • coordenadas do cursor;
  • coordenada relativa a Y;
  • coordenadas do formulário;
  • número da janela do gráfico onde o cursor está localizado;
  • número da janela do gráfico onde o programa MQL está localizado;
  • modo de botão esquerdo do mouse.

Este código deve ser excluído após o teste.

::Comment("x: ",x,"\n",
                "y: ",y,"\n",
                "rel_y: ",rel_y,"\n",
                "w.x: ",m_x,"\n",
                "w.y: ",m_y,"\n",
                "subwin: ",subwin,"\n",
                "m_subwin: ",m_subwin,"\n",
                "clamping mode: ",m_clamping_area_mouse);

Compile os arquivos do projeto e carregue o programa para o gráfico. Para verificar se o programa está rastreando corretamente a janela do gráfico em que o cursor está localizado, carregue qualquer indicador que não processa na janela do gráfico principal.

Eu estou quase certo de que você não será capaz de mover a janela após o programa ser carregado para o gráfico. A razão disso é que no construtor da classe CWindow , a variável m_movable é inicializada pelo valor false, significando que o usuário não poderá mover o formulário no gráfico. É por isso que o desenvolvedor do aplicativo MQL deve especificar no código a necessidade de deslocar o formulário.

Na classe CProgram do método CreateWindow(), adicione uma linha como mostrado no código abaixo, compile os arquivos e tentar testar o aplicativo novamente.

//+------------------------------------------------------------------+
//| Cria o formulário para os controles                              |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Adiciona um ponteiro de janela para o array da janela
   CWndContainer::AddWindow(m_window);
//--- Coordenadas
   int x=1;
   int y=1;
//--- Propriedades
   m_window.Movable(true);
   m_window.XSize(200);
   m_window.YSize(200);
//--- Criação de um formulário
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Agora, não deve haver nada que possa interromper o movimento do formulário:

Fig. 1. Teste de deslocamento do formulário no gráfico.

Fig. 1. Teste de Movimento do Formulário sobre o Gráfico

Às vezes, acontece que o tamanho da janela do gráfico tem que ser alterado. Em tais momentos, é gerado o evento para alterar as propriedades do gráfico CHARTEVENT_CHART_CHANGE. No momento, isto não é controlado de qualquer maneira e uma situação pode ocorrer quando o formulário ultrapassar parcialmente ou completamente os limites da janela do gráfico. Para evitar isso, este tipo de evento deve também ser verificado no manipulador do gráfico de evento CWindow::OnEvent().

Uma vez que este evento é gerado também durante a rolagem de um gráfico, para evitar a execução de uma série de ações desnecessárias, uma verificação é necessária se o botão esquerdo do mouse foi clicado no momento em que o evento aconteceu. Se o botão esquerdo do mouse foi liberado, então nós obtemos o tamanho do gráfico e se os limites da janela do gráfico foram ultrapassados, então nós ajustamos as coordenadas. Abaixo está o código que tem de ser adicionado no final do método CWindow::OnEvent() após a manipulação de outros eventos:

//+------------------------------------------------------------------+
//| Manipulador de eventos do gráfico                                |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento de alteração das propriedades do gráfico
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Se o botão foi liberado
      if(m_clamping_area_mouse==NOT_PRESSED)
        {
         //--- Obter o tamanho da janela do gráfico
         SetWindowProperties();
         //--- Ajuste das coordenadas
         UpdateWindowXY(m_x,m_y);
        }
      return;
     }
  }

O resultado concreto do trabalho descrito acima é a possibilidade de mover o formulário para os controles. Então, nós precisamos incorporar a funcionalidade nos botões para minimizar e maximizar as janelas. Além disso, estes botões também são destinados a reagir ao movimento do cursor do mouse, assim o usuário final entende que eles não são apenas ícones ou elementos de desenho.

 

Alteração da Aparência do Componente da Interface quando o Cursor estiver pairando sobre ele

Anteriormente, nós consideramos a implementação da classe CElement, que é classe base para todos os controles. Nós criamos o método CElement::ChangeObjectColor() para alterar a cor do objeto ao passar o cursor sobre um de seus membros. Este é o momento para criarmos um mecanismo que nos permitirá usá-lo em nosso trabalho. Para adicionar essa funcionalidade é necessário um timer. Isto é desabilitado nas configurações do aplicativo MQL por padrão. Cabe ao desenvolvedor do aplicativo decidir se o timer será habilitado, dependendo das metas estabelecidas.

Para ativar o timer, a linguagem MQL possui duas funções com frequências diferentes: EventSetTimer() e EventSetMillisecondTimer(). A primeira permite definir um intervalo não inferior a um segundo. Isso não se adequa com os nossos propósitos já que um segundo é um intervalo muito alto para o controle alterar sua aparência quando o cursor estiver passando sobre ele. A mudança deve acontecer instantaneamente, sem atrasos. Nós vamos utilizar a função EventSetMillisecondTimer(), que suporta a definição de um timer com intervalos medidos em milissegundos. De acordo com a referência MQL, o intervalo mínimo que pode ser definido utilizando esta função é de 10^-16 milissegundos. Isto é o suficiente para realizarmos o nosso plano.

Adicione uma constante com o passo do timer necessário para a biblioteca trabalhar no arquivo Defines.mqh Arquivo:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Passo do timer (milissegundos)
#define TIMER_STEP_MSC (16)

Podem surgir perguntas bem lógicas como "Isto não é muito frequente?" e "o programa não irá consumir recursos demais?". Nós vamos responder a estas perguntas quando testarmos essa ideia.

Ative o timer na classe CWndEvents, que é o núcleo da biblioteca em desenvolvimento. Para isso, adicione estas linhas de código em seu construtor e destrutor como mostrado abaixo:

//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
//--- Ativar o timer
   if(!::MQLInfoInteger(MQL_TESTER))
      ::EventSetMillisecondTimer(TIMER_STEP_MSC);
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
//--- Remover o timer
   ::EventKillTimer();
  }

No construtor da classe CWndEvents, o timer será ativado apenas se o programa não estiver no modo do testador de estratégia. Nossa biblioteca não irá funcionar no testador agora já que atualmente ela possui algumas limitações para se trabalhar com objetos gráficos. Além disso, o intervalo mínimo para o timer no testador é igual a um segundo, mesmo usando a função EventSetMillisecondTimer(). No destrutor o timer é desativado com a função EventKillTimer().

Na classe de cada controle haverá sua própria implementação do método OnEventTimer(), e para isso na classe CElement já existe um método virtual com este nome. Semelhante ao método CWndEvents::CheckElementsEvents(), em que nós iteramos sobre os métodos OnEvent() de cada controle, nós temos que criar um método em que o programa também irá interagir sobre os métodos OnEventTimer(). Vamos chamá-lo de CheckElementsEventsTimer().

A declaração e implementação do método CWndEvents::CheckElementsEventsTimer():

class CWndEvents : public CWndContainer
  {
private:
   //--- Verificação dos eventos de todos os controles pelo timer
   void              CheckElementsEventsTimer(void);
  };
//+------------------------------------------------------------------+
//| Verificação dos eventos de todos os controles pelo timer         |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEventsTimer(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEventTimer();
  }

Então, este método tem de ser chamado no método CWndEvents::OnTimerEvent() com a verificação do tamanho do array da janela no início, assim como foi feito no método CWndEvents::ChartEvent().

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWndEvents::OnTimerEvent(void)
  {
//--- Se o array estiver vazio, retorna  
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Verificação dos eventos de todos os controles pelo timer
   CheckElementsEventsTimer();
//--- Redesenha o gráfico
   m_chart.Redraw();
  }

Na classe CProgram, onde o desenvolvedor da aplicação MQL estará criando a interface gráfica, no método CProgram::OnTimerEvent(), que está conectado com o arquivo principal do programa, simplesmente chamará o método de mesmo nome da classe base, como é mostrado no código abaixo:

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

Desta forma, as ações de todos os controles que estão nos métodos OnEventTimer() estarão disponíveis no fluxo de eventos do timer do programa.

Não ainda não temos todo o código para a realização dos testes. A biblioteca que nós estamos desenvolvendo atualmente contém apenas um componente de interface - um formulário para os controles, a classe CWindow. Nós vamos criar uma funcionalidade necessária na mesma, adicionando a sua versão local do método OnEventTimer().

O nome do método para alteração da cor dos objetos será chamado de CWindow::ChangeObjectsColor(). Abaixo está a sua declaração e implementação na classe CWindow:

class CWindow: public CElement
  {
private:
   //--- Alterando a cor dos objetos do formulário
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Alterando a cor dos objetos ao pairar o cursor sobre ele         |
//+------------------------------------------------------------------+
void CWindow::ChangeObjectsColor(void)
  {
//--- Alterando os ícones nos botões
   m_button_rollup.State(m_button_rollup.MouseFocus());
   m_button_unroll.State(m_button_unroll.MouseFocus());
   m_button_close.State(m_button_close.MouseFocus());
//--- Alterando a cor no cabeçalho
   CElement::ChangeObjectColor(m_caption_bg.Name(),CElement::MouseFocus(),OBJPROP_BGCOLOR,
                               m_caption_bg_color,m_caption_bg_color_hover,m_caption_color_bg_array);
  }

Como você pode ver a partir do código, não há nada complicado no método CWindow::ChangeObjectsColor(). Os métodos MouseFocus() retornam o foco do cursor sobre todos os objetos, que é verificado no método CWindow::CheckMouseFocus() quando o cursor se desloca sobre o gráfico. Anteriormente, dois ícones foram carregados nos objetos que desempenham aqui um papel de botões. Eles podem ser alternados através da definição do seu estado utilizando o método CChartObjectBmpLabel::State(). O método CElement::ChangeObjectColor() trabalha com a cor da parte indicada do objeto.

Neste caso, a cor de fundo do cabeçalho será alterada quando o cursor estiver na área da janela. É possível alterar também a cor do quadro de cabeçalho, o plano de fundo juntamente com o quadro ou até mesmo a cor de outros objetos se o método CElement::ChangeObjectColor() for chamado sequencialmente, especificando no primeiro parâmetro o nome do objeto para cada um deles.

A única coisa que resta agora é fazer uma chamada para este método no timer local:

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWindow::OnEventTimer(void)
  {
//--- Alterando a cor dos objetos do formulário
   ChangeObjectsColor();
  }

Compile todos os arquivos do projeto que foram alterados e carregue o programa para realizar os testes no gráfico. Agora, quando o cursor estiver sobre algum objeto no formulário onde a funcionalidade é exigida, a cor do objeto será alterada

Fig. 2. Teste da reação do objeto quando o cursor do mouse estiver sobre ele.

Fig. 2. Teste da reação do objeto quando o cursor do mouse estiver sobre ele.

 

Conclusão

Neste artigo, nós introduzimos algumas alterações adicionais, que nos permite deslocar um formulário sobre o gráfico. Os controles do formulário agora reagem ao movimento do cursor do mouse. No próximo artigo, nós vamos continuar o desenvolvimento da classe CWindow. Ela será ampliada com os métodos, que permitirão gerir o formulário mediante os cliques em seus controles.

Você pode baixar para o seu computador todo o material da primeira parte da série para você testá-lo. 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 primeira parte:

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

Arquivos anexados |
Últimos Comentários | Ir para discussão (1)
Samuel Manoel De Souza
Samuel Manoel De Souza | 31 mar 2020 em 00:43

Qual é a forma de processar o movimento do mouse usando a Biblioteca Padrão? Eu testei com o arquivo localizado em Experts\Examples\Controls. Modificando a classe CListView da biblioteca padrão.
Ocorre que o evento ON_MOUSE_FOCUS_SET não é processado quando uso somente a função .ChartEvent(...).
Quando uso a função .OnEvent(...)  o evento ON_MOUSE_FOCUS_SET é processado, mas não consigo mover a janela.
Quando uso as duas funções, primeiro chamo .ChartEvent() e depois .OnEvent() o evento ON_MOUSE_FOCUS_SET é processado, porém quando clico no botão de incremento e decremento, está alterando mais de um passo.


   virtual bool      OnItemFocusSet(const int index);
   //--- redraw
   bool              Redraw(void);
   bool              RowState(const int index,const bool select);
   bool              CheckView(void);
  };
//+------------------------------------------------------------------+
//| Common handler of chart events                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CListView)
ON_INDEXED_EVENT(ON_CLICK,m_rows,OnItemClick)
ON_INDEXED_EVENT(ON_MOUSE_FOCUS_SET,m_rows,OnItemFocusSet)
EVENT_MAP_END(CWndClient)
bool CListView::OnItemFocusSet(const int index)
  {
//--- select "row"
   Select(index+m_offset);
//--- send notification
   EventChartCustom(CONTROLS_SELF_MESSAGE,ON_CHANGE,m_id,0.0,m_name);
//--- handled
   return(true);
  }
Expert Advisor Universal: Negociação em Grupo e Gestão de uma Carteira de Estratégias (Parte 4) Expert Advisor Universal: Negociação em Grupo e Gestão de uma Carteira de Estratégias (Parte 4)
Na última parte da série de artigos sobre o mecanismo de negociação CStrategy, vamos considerar a operação simultânea de vários algoritmos de negociação, aprenderemos a carregar estratégias de arquivos XML, e apresentaremos um painel simples para selecionar Expert Advisors partir de um único módulo executável e gerenciar os seus modos de negociação.
Interfaces Gráficas I: Formulário para os Controles (Capítulo 2) Interfaces Gráficas I: Formulário para os Controles (Capítulo 2)
Neste artigo, nós vamos criar o primeiro e o principal elemento da interface gráfica - o formulário para os controles. Vários controles podem ser anexados a este formulário, podendo ser de qualquer lugar e qualquer combinação.
Expert Advisor Universal: Ordens Pendentes e Suporte para Cobertura de Risco (Parte 5) Expert Advisor Universal: Ordens Pendentes e Suporte para Cobertura de Risco (Parte 5)
Este artigo fornece uma descrição mais detalhada do mecanismo de negociação CStrategy. Por demanda popular dos usuários, nós adicionamos funções de apoio as ordem pendente no mecanismo de negociação. Além disso, a versão mais recente do MetaTrader 5 agora oferece suporte a contas com a opção de cobertura (hedge). O mesmo suporte foi adicionado ao CStrategy. O artigo fornece uma descrição detalhada de algoritmos para o uso de ordens pendentes, bem como dos princípios de funcionamento da classe CStrategy nas contas com a opção de cobertura (hedge) habilitada.
Expert Advisor Universal: O Modelo de Evento e o Protótipo da Estratégia de Negociação (Parte 2) Expert Advisor Universal: O Modelo de Evento e o Protótipo da Estratégia de Negociação (Parte 2)
Este artigo continua a série de publicações do modelo universal de um Expert Advisor. Esta parte descreve em detalhes o modelo de eventos original, baseado no processamento de dados centralizado e considera a estrutura da classe base CStrategy.