English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas X: O Controle Gráfico Padrão (build 4)

Interfaces Gráficas X: O Controle Gráfico Padrão (build 4)

MetaTrader 5Exemplos | 6 dezembro 2016, 12:42
1 236 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. Você irã encontrar uma lista de artigos com os links 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.

Vamos considerar um outro controle, que não poderia ficar de fora da biblioteca em desenvolvimento. Quando o usuário inicia o terminal de negociação, aparecem os gráficos de preços. Seria conveniente ter uma ferramenta que permitisse gerenciar os gráficos com maior facilidade. No artigo anterior chamado Guia Prático do MQL5: Monitoramento de Múltiplos Períodos de Tempo em uma Única Janela foi demonstrado uma das possíveis variantes de tal ferramenta. Desta vez, nós vamos escrever uma classe para a criação de um controle, que será simples e fácil de utilizar nas interfaces gráficas das aplicações personalizadas em MQL. Ao contrário da versão anterior fornecida no link acima, esta aplicação permitirá percorrer horizontalmente o conteúdo dos objetos gráficos, como na janela principal do gráfico convencional.

Além disso, nós continuaremos a otimizar o código da biblioteca para reduzir o consumo de recursos do CPU. Mais detalhes serão fornecidos no decorrer do artigo.

 

Desenvolvimento de uma Classe para a Criação do Controle Gráfico Padrão

Antes de iniciar o desenvolvimento da classe CStandardChart para criar o controle gráfico padrão, a classe base CSubChartcom as propriedades adicionais (veja o código abaixo) devem ser adicionadas ao arquivo Object.mqh. Isto foi feito anteriormente para todos os tipos de objetos gráficos, que são utilizados durante a criação da biblioteca de controles. A classe base para a classe CSubChart é uma classe da biblioteca padrão - CChartObjectSubChart

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Lista de classes no arquivo para uma navegação rápida (Alt+G)
...
class CSubChart;
//+------------------------------------------------------------------+
//| Classe com propriedades adicionais para o objeto Subchart        |
//+------------------------------------------------------------------+
class CSubChart : public CChartObjectSubChart
  {
protected:
   int               m_x;
   int               m_y;
   int               m_x2;
   int               m_y2;
   int               m_x_gap;
   int               m_y_gap;
   int               m_x_size;
   int               m_y_size;
   bool              m_mouse_focus;
   //---
public:
                     CSubChart(void);
                    ~CSubChart(void);
   //--- Coordenadas
   int               X(void)                      { return(m_x);           }
   void              X(const int x)               { m_x=x;                 }
   int               Y(void)                      { return(m_y);           }
   void              Y(const int y)               { m_y=y;                 }
   int               X2(void)                     { return(m_x+m_x_size);  }
   int               Y2(void)                     { return(m_y+m_y_size);  }
   //--- Margens do ponto da margem (XY)
   int               XGap(void)                   { return(m_x_gap);       }
   void              XGap(const int x_gap)        { m_x_gap=x_gap;         }
   int               YGap(void)                   { return(m_y_gap);       }
   void              YGap(const int y_gap)        { m_y_gap=y_gap;         }
   //--- Tamanho
   int               XSize(void)                  { return(m_x_size);      }
   void              XSize(const int x_size)      { m_x_size=x_size;       }
   int               YSize(void)                  { return(m_y_size);      }
   void              YSize(const int y_size)      { m_y_size=y_size;       }
   //--- Foco
   bool              MouseFocus(void)             { return(m_mouse_focus); }
   void              MouseFocus(const bool focus) { m_mouse_focus=focus;   }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CSubChart::CSubChart(void) : m_x(0),
                             m_y(0),
                             m_x2(0),
                             m_y2(0),
                             m_x_gap(0),
                             m_y_gap(0),
                             m_x_size(0),
                             m_y_size(0),
                             m_mouse_focus(false)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CSubChart::~CSubChart(void)
  {
  }

A classe CChartObjectSubChart contém o método para a criação de um objeto gráfico, bem como os métodos para modificar as propriedades do gráfico que são utilizadas com maior frequência. A lista inclui os métodos para definir e obter tais propriedades como: 

  • coordenadas e dimensões
  • símbolo, tempo gráfico e escala
  • exibição do preço e das escalas de tempo.  
//+------------------------------------------------------------------+
//|                                          ChartObjectSubChart.mqh |
//|                   Copyright 2009-2013, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "ChartObject.mqh"
//+------------------------------------------------------------------+
//| Classe CChartObjectSubChart.                                     |
//| Objetivo: Classe do objeto gráfico "SubChart".                   |
//|          Deriva da classe CChartObject.                          |
//+------------------------------------------------------------------+
class CChartObjectSubChart : public CChartObject
  {
public:
                     CChartObjectSubChart(void);
                    ~CChartObjectSubChart(void);
   //--- métodos de acesso as propriedades do objeto
   int               X_Distance(void) const;
   bool              X_Distance(const int X) const;
   int               Y_Distance(void) const;
   bool              Y_Distance(const int Y) const;
   ENUM_BASE_CORNER  Corner(void) const;
   bool              Corner(const ENUM_BASE_CORNER corner) const;
   int               X_Size(void) const;
   bool              X_Size(const int size) const;
   int               Y_Size(void) const;
   bool              Y_Size(const int size) const;
   string            Symbol(void) const;
   bool              Symbol(const string symbol) const;
   int               Period(void) const;
   bool              Period(const int period) const;
   int               Scale(void) const;
   bool              Scale(const int scale) const;
   bool              DateScale(void) const;
   bool              DateScale(const bool scale) const;
   bool              PriceScale(void) const;
   bool              PriceScale(const bool scale) const;
   //--- a alteração das coordenadas de tempo/preço está bloqueada
   bool              Time(const datetime time) const { return(false); }
   bool              Price(const double price) const { return(false); }
   //--- método de criação do objeto
   bool              Create(long chart_id,const string name,const int window,
                            const int X,const int Y,const int sizeX,const int sizeY);
   //--- método para identificar o objeto
   virtual int       Type(void) const { return(OBJ_CHART); }
   //--- métodos para trabalhar com arquivos
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
  };

Agora nós podemos criar o arquivo StandardChart.mqh com o a classe CStandardChart, onde os métodos padrão de todos os controles da biblioteca podem ser especificados no conteúdo base, conforme mostrado no código abaixo. Já que o controle contará com o modo de deslocamento horizontal, será necessário um ícone para o cursor do mouse informar ao usuário que o modo de deslocamento está ativado e os dados do objeto gráfico serão deslocados conforme o cursor do mouse é movido horizontalmente. Para alterar o ícone, inclua o arquivo Pointer.mqh contendo a classe CPointer, considerada anteriormente no artigo Interfaces Gráficas VIII: O Controle Lista Hierárquica (Capítulo 2). Para o ícone do ponteiro do mouse, nós vamos usar uma cópia daquele que está ativado com o deslocamento horizontal do gráfico principal (seta dupla preta com um contorno branco). Duas versões deste ícone (setas preta e azul) serão anexadas no final do artigo. 

Por conseguinte, a enumeração dos ponteiros do mouse (ENUM_MOUSE_POINTER) foi suplementada com outro identificador (MP_X_SCROLL): 

//+------------------------------------------------------------------+
//| Enumeração dos tipos de ponteiros                                |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM     =0,
   MP_X_RESIZE   =1,
   MP_Y_RESIZE   =2,
   MP_XY1_RESIZE =3,
   MP_XY2_RESIZE =4,
   MP_X_SCROLL   =5
  };

Além disso, é necessário incluir os ícones como recursos para este tipo de cursor no arquivo Pointer.mqh, e uma construção switch no método CPointer::SetPointerBmp() deve ser expandido com outro bloco do tipo case

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//--- Recursos
...
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp"
//+------------------------------------------------------------------+
//| Define as imagens do cursor com base no tipo do cursor           |
//+------------------------------------------------------------------+
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      case MP_X_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs.bmp";
         break;
      case MP_Y_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs.bmp";
         break;
      case MP_XY1_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs.bmp";
         break;
      case MP_XY2_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs.bmp";
         break;
      case MP_X_SCROLL :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp";
         break;
     }
//--- Se o tipo personalizado (MP_CUSTOM) foi especificado
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Ambas as imagens devem ser definidas para o cursor!");
  }

Deve-se notar também que o método Moving() também pode ser utilizado em dois modos, podendo ser definidos pelo terceiro argumento do método. O valor padrão desse argumento é false, significando que o controle pode ser movido apenas no caso do formulário, que está anexado a ele, também se encontrar no modo de movimento. 

//+------------------------------------------------------------------+
//|                                                StandardChart.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "Pointer.mqh"
//+------------------------------------------------------------------+
//| Classe para a criação do gráfico padrão                          |
//+------------------------------------------------------------------+
class CStandardChart : public CElement
  {
private:
   //--- Ponteiro para o formulário que este elemento está anexado
   CWindow          *m_wnd;
   //---
public:
   //--- 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);
   //--- Move o elemento
   virtual void      Moving(const int x,const int y,const bool moving_mode=false);
   //--- (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);
   //---
private:
   //--- Altera a largura da margem direita da janela
   virtual void      ChangeWidthByRightWindowSide(void);
   //--- Altera a altura na borda inferior da janela
   virtual void      ChangeHeightByBottomWindowSide(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CStandardChart::CStandardChart(void)
  {
//--- Armazena o nome da classe do elemento na classe base
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CStandardChart::~CStandardChart(void)
  {
  }

Se o valor do terceiro argumento no método Moving() for definido como true, então o controle será movido de forma forçada após a chamada do método, independentemente do fato do formulário estar no modo de movimento. Em alguns casos, isto reduz significativamente o consumo de recursos do CPU. 

Para verificar se o formulário está no modo de movimento, foi adicionado o método ClampingAreaMouse() à classe CWindow. Ela retorna a área onde o botão esquerdo do mouse foi pressionado: 

//+------------------------------------------------------------------+
//| Classe para criar um formulário de controles                     |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
public:
   //--- Retorna a área onde o botão esquerdo do mouse foi pressionado
   ENUM_MOUSE_STATE  ClampingAreaMouse(void)                           const { return(m_clamping_area_mouse);          }
  };

O método CWindow::ClampingAreaMouse() só pode ser acessado através do ponteiro do formulário em cada controle anexado à ele. Por tudo funcionar como foi descrito acima, um bloco de código deve ser inserido ao método Moving() de cada controle, como é mostrado abaixo (veja o destaque em amarelo). Como um exemplo, é exibido o método da classe CSimpleButton (Versão reduzida). 

//+------------------------------------------------------------------+
//| Deslocamento dos controles                                       |
//+------------------------------------------------------------------+
void CSimpleButton::Moving(const int x,const int y,const bool moving_mode=false)
  {
//--- Sai se o elemento está oculto
   if(!CElement::IsVisible())
      return;
//--- Se a gestão é delegada para a janela, então, identifica a sua localização
   if(!moving_mode)
      if(m_wnd.ClampingAreaMouse()!=PRESSED_INSIDE_HEADER)
         return;
//--- Se está ancorado à direita
//--- Se está ancorado à esquerda
//--- Se está ancorado no canto inferior
//--- Se está ancorado no canto superior
//--- Atualizando as coordenadas dos objetos gráficos
   m_button.X_Distance(m_button.X());
   m_button.Y_Distance(m_button.Y());
  }

Um exemplo de uso do método Moving() poderá ser visto abaixo, que demonstra o código do método CSimpleButton::Show(). Neste caso, as coordenadas do controle deveriam ser atualizadas à força, portanto, nós teremos como terceiro argumento o valou igual a true. As modificações apropriadas foram feitas em todas as classes da biblioteca, que utilizam o método Moving().  

//+------------------------------------------------------------------+
//| Exibe o botão                                                    |
//+------------------------------------------------------------------+
void CSimpleButton::Show(void)
  {
//--- Retorna, se o elemento já for visível
   if(CElement::IsVisible())
      return;
//--- Faz com que todos os objetos sejam visíveis
   m_button.Timeframes(OBJ_ALL_PERIODS);
//--- Estado de visibilidade
   CElement::IsVisible(true);
//--- Atualiza a posição dos objetos
   Moving(m_wnd.X(),m_wnd.Y(),true);
  }

A otimização da biblioteca desenvolvida será discutida mais adiante neste artigo, por enquanto, consideraremos apenas o controle gráfico padrão.

Vamos tornar possível a criação de um array de objetos gráficos colocados em uma linha. Para isso, é necessário declarar arrays dinâmicos para os objetos que representam os gráficos, bem como para certas propriedades, tais como: (1) Identificador do gráfico, (2) símbolo e (3) tempo gráfico. Antes de criar o controle Gráfico Padrão, é necessário usar o método CStandardChart::AddSubChart(), onde o símbolo e o tempo do gráfico devem ser passados. No início deste método, antes de adicionar os elementos aos arrays e inicializá-los com os valores passados, deve-se fazer uma verificação de sua disponibilidade do símbolo usando o método CStandardChart::CheckSymbol().  

class CStandardChart : public CElement
  {
private:
   //--- Objetos para criar o controle
   CSubChart         m_sub_chart[];
   //--- Propriedades do gráfico:
   long              m_sub_chart_id[];
   string            m_sub_chart_symbol[];
   ENUM_TIMEFRAMES   m_sub_chart_tf[];
   //---
public:
   //--- Adiciona um gráfico com as propriedades especificadas antes de sua criação
   void              AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf);
   //---
private:
   //--- Verificação do símbolo
   bool              CheckSymbol(const string symbol);
  };

O método CStandardChart::CheckSymbol() verifica primeiro se o símbolo especificado está disponível na janela do Market Watch. Se o símbolo não for encontrado, ele tenta localizar o símbolo na lista geral. Se o símbolo for encontrado, então, ele deve ser adicionado ao Market Watch. Caso contrário, será impossível criar um objeto gráfico com este símbolo (será criado em seu lugar um objeto gráfico com o símbolo da janela principal do gráfico). 

Se bem sucedido, o método CStandardChart::CheckSymbol() retorna true. Se o símbolo especificado não for encontrado, o método irá retornar false e o objeto gráfico não será adicionado (os arrays terão o mesmo tamanho), uma mensagem sobre isso será exibido no registro.  

//+------------------------------------------------------------------+
//| Adiciona um gráfico                                              |
//+------------------------------------------------------------------+
void CStandardChart::AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf)
  {
//--- Verifica se o símbolo está disponível no servidor
   if(!CheckSymbol(symbol))
     {
      ::Print(__FUNCTION__," > O símbolo "+symbol+" não está disponível no servidor!");
      return;
     }
//--- Aumenta o tamanho do array por um elemento
   int array_size=::ArraySize(m_sub_chart);
   int new_size=array_size+1;
   ::ArrayResize(m_sub_chart,new_size);
   ::ArrayResize(m_sub_chart_id,new_size);
   ::ArrayResize(m_sub_chart_symbol,new_size);
   ::ArrayResize(m_sub_chart_tf,new_size);
//--- Armazenar o valor dos parâmetros passados
   m_sub_chart_symbol[array_size] =symbol;
   m_sub_chart_tf[array_size]     =tf;
  }
//+------------------------------------------------------------------+
//| Verifica a disponibilidade do símbolo                            |
//+------------------------------------------------------------------+
bool CStandardChart::CheckSymbol(const string symbol)
  {
   bool flag=false;
//--- Verifica se o símbolo está no Market Watch
   int symbols_total=::SymbolsTotal(true);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Se o símbolo estiver disponível, interrompe o loop
      if(::SymbolName(i,true)==symbol)
        {
         flag=true;
         break;
        }
     }
//--- Se o símbolo não está disponível na janela do Market Watch
   if(!flag)
     {
      //--- ... tenta encontrá-lo na lista geral
      symbols_total=::SymbolsTotal(false);
      for(int i=0; i<symbols_total; i++)
        {
         //--- Se este símbolo está disponível
         if(::SymbolName(i,false)==symbol)
           {
            //--- ... adiciona ele a janela do Market Watch e interrompe o loop
            ::SymbolSelect(symbol,true);
            flag=true;
            break;
           }
        }
     }
//--- Retorna os resultados da pesquisa
   return(flag);
  }

A criação do controle gráfico padrão irá exigir três métodos: um método público que será principal e dois privados, sendo que um deles se refere ao ícone para o cursor do mouse no modo de deslocamento horizontal. O método público CStandardChart::SubChartsTotal() foi adicionado à classe como um método auxiliar para recuperar o número de objetos gráficos. 

class CStandardChart : public CElement
  {
private:
   //--- Objetos para criar o controle
   CSubChart         m_sub_chart[];
   CPointer          m_x_scroll;
   //---
public:
   //--- Métodos para a criação de um gráfico padrão
   bool              CreateStandardChart(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateSubChart(void);
   bool              CreateXScrollPointer(void);
   //---
public:
   //--- Retorna o tamanho do array de gráficos
   int               SubChartsTotal(void)              const { return(::ArraySize(m_sub_chart)); }
  };

Vamos considerar o método CStandardChart::CreateSubCharts() para a criação dos objetos gráficos. Logo no início deste método, nós encontramos uma verificação do número de gráficos adicionados ao array antes da criação do controle. Se não foi adicionado nenhum, o programa simplesmente deixará o método e mostrará uma mensagem relevante no registro.

Se os gráficos foram adicionados, então, a largura é calculada para cada objeto. A largura total de um controle deve ser definida pelo usuário na classe personalizada da aplicação MQL, que foi desenvolvida. Caso seja necessário criar vários gráficos, basta dividir a largura total do controle pelo número de gráficos, para obter a largura de cada objeto.

Em seguida, os objetos são criados em um loop. Isso leva em conta o posicionamento do controle (pontos de ancoragem para um dos lados do formulário). Este tópico tem sido exaustivamente descrito no artigo anterior, por isso ele não será discutido aqui. 

Depois de criar o objeto gráfico, o identificador do gráfico criado é obtido e armazenado no array, as suas propriedades são ajustadas e armazenadas. 

//+------------------------------------------------------------------+
//| Cria os gráficos                                                 |
//+------------------------------------------------------------------+
bool CStandardChart::CreateSubCharts(void)
  {
//--- Obtém o número de gráficos
   int sub_charts_total=SubChartsTotal();
//--- Se não houver um gráfico no grupo, relata
   if(sub_charts_total<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "se um grupo conter pelo menos um gráfico! Use o método CStandardChart::AddSubChart()");
      return(false);
     }
//--- Calcula as coordenadas e o tamanho
   int x=m_x;
   int x_size=(sub_charts_total>1)? m_x_size/sub_charts_total : m_x_size;
//--- Cria o número especificado de gráficos
   for(int i=0; i<sub_charts_total; i++)
     {
      //--- Elaborando o nome do objeto
      string name=CElement::ProgramName()+"_sub_chart_"+(string)i+"__"+(string)CElement::Id();
      //--- Cálculo da coordenada X
      x=(i>0)?(m_anchor_right_window_side)? x-x_size+1 :  x+x_size-1 : x;
      //--- Ajusta a largura do último gráfico
      if(i+1>=sub_charts_total)
         x_size=m_x_size-(x_size*(sub_charts_total-1)-(sub_charts_total-1));
      //--- Configura um botão
      if(!m_sub_chart[i].Create(m_chart_id,name,m_subwin,x,m_y,x_size,m_y_size))
         return(false);
      //--- Obtém e armazena o identificador do gráfico criado
      m_sub_chart_id[i]=m_sub_chart[i].GetInteger(OBJPROP_CHART_ID);
      //--- Define as propriedades
      m_sub_chart[i].Symbol(m_sub_chart_symbol[i]);
      m_sub_chart[i].Period(m_sub_chart_tf[i]);
      m_sub_chart[i].Z_Order(m_zorder);
      m_sub_chart[i].Tooltip("\n");
      //--- Armazena o tamanho
      m_sub_chart[i].XSize(x_size);
      m_sub_chart[i].YSize(m_y_size);
      //--- Margens da borda
      m_sub_chart[i].XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
      m_sub_chart[i].YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y());
      //--- Armazena o ponteiro de objeto
      CElement::AddToArray(m_sub_chart[i]);
     }
//---
   return(true);
  }

Após a sua criação é possível alterar qualquer propriedade dos objetos gráficos contidos no controle Gráfico Padrão. Isso é feito por meio do ponteiro que pode ser obtido com a ajuda do método CStandardChart::GetSubChartPointer(). Se um índice incorreto for passado por acidente, ele será corrigido a fim de impedir que exceda o tamanho do array. 

class CStandardChart : public CElement
  {
public:
   //--- Retorna o ponteiro para o objeto gráfico através do índice especificado
   CSubChart        *GetSubChartPointer(const uint index);
  };
//+------------------------------------------------------------------+
//| Retorna o ponteiro para o gráfico através do índice especificado |
//+------------------------------------------------------------------+
CSubChart *CStandardChart::GetSubChartPointer(const uint index)
  {
   uint array_size=::ArraySize(m_sub_chart);
//--- Se não houver nenhum gráfico, relata
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "se um grupo conter pelo menos um gráfico!);
     }
//--- Ajuste no caso do tamanho exceder
   uint i=(index>=array_size)? array_size-1 : index;
//--- Retorna o ponteiro
   return(::GetPointer(m_sub_chart[i]));
  }

Um ícone para o cursor do mouse é criado apenas se está habilitado o modo de deslocamento horizontal para os dados nos objetos gráficos. Ele deve ser ativado antes da criação do controle, usando o método CStandardChart::XScrollMode().  

class CStandardChart : public CElement
  {
private:
   //--- Modo de deslocamento horizontal
   bool              m_x_scroll_mode;
   //---
public:
   //--- Modo de deslocamento horizontal
   void              XScrollMode(const bool mode) { m_x_scroll_mode=mode; }
  };
//+------------------------------------------------------------------+
//| Cria o cursor de deslocamento horizontal                         |
//+------------------------------------------------------------------+
bool CStandardChart::CreateXScrollPointer(void)
  {
//--- Sai, se o deslocamento horizontal não for necessário
   if(!m_x_scroll_mode)
      return(true);
//--- Define as propriedades
   m_x_scroll.XGap(0);
   m_x_scroll.YGap(-20);
   m_x_scroll.Id(CElement::Id());
   m_x_scroll.Type(MP_X_SCROLL);
//--- Criação de um elemento
   if(!m_x_scroll.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Para resumir o que foi exposto acima. Se o modo de deslocamento horizontal está habilitado, a sua operação utiliza o método CStandardChart::HorizontalScroll(), que será chamado no manipulador de eventos do controle quando o evento CHARTEVENT_MOUSE_MOVE é disparado. No caso do botão esquerdo do mouse ser pressionado, dependendo do que foi pressionado ou se forem chamadas repetidas para o método, durante o processo de deslocamento horizontal, o método calcula a distância percorrida pelo cursor do mouse em pixels a partir do ponto que houve o pressionamento. Aqui: 

  • O formulário está bloqueado.
  • As coordenadas para o ícone do cursor do mouse são calculadas.
  • O ícone é mostrado.

O valor calculado para o deslocamento de dados nos objetos gráficos pode ser negativo, como o deslocamento irá ser realizado em relação à última barra - o método ::ChartNavigate() com o valor CHART_END (o segundo argumento) da enumeração ENUM_CHART_POSITION. Se o valor de deslocamento for positivo, o programa deixa o método. Se é passado na verificação, então, o valor atual do deslocamento é armazenado para a iteração seguinte. Em seguida, o "Deslocamento Automático" (CHART_AUTOSCROLL) e o "Deslocar o final do gráfico a partir da borda direita" (CHART_SHIFT) são desativados para todos os objetos gráficos sendo que o deslocamento é realizado de acordo com o valor calculado.

Se o botão esquerdo do mouse for liberado, o formulário será desbloqueado, e o ícone do cursor do mouse, que indica o processo de deslocamento horizontal será ocultado. Depois disso, o programa sairá do método. 

class CStandardChart : public CElement
  {
private:
   //--- As variáveis ​​relacionadas ao deslocamento horizontal do gráfico
   int               m_prev_x;
   int               m_new_x_point;
   int               m_prev_new_x_point;
   //---
private:
   //--- Deslocamento horizontal
   void              HorizontalScroll(void);
  };
//+------------------------------------------------------------------+
//| Deslocamento horizontal do gráfico                               |
//+------------------------------------------------------------------+
void CStandardChart::HorizontalScroll(void)
  {
//--- Sai, se o deslocamento horizontal dos gráficos está desativado
   if(!m_x_scroll_mode)
      return;
//--- Se o botão do mouse foi pressionado
   if(m_mouse.LeftButtonState())
     {
      //--- Armazena as coordenadas X atuais do cursor
      if(m_prev_x==0)
        {
         m_prev_x      =m_mouse.X()+m_prev_new_x_point;
         m_new_x_point =m_prev_new_x_point;
        }
      else
         m_new_x_point=m_prev_x-m_mouse.X();
      //--- Bloqueia o formulário
      if(!m_wnd.IsLocked())
        {
         m_wnd.IsLocked(true);
         m_wnd.IdActivatedElement(CElement::Id());
        }
      //--- Atualiza as coordenadas do ponteiro e torná-o visível
      int l_x=m_mouse.X()-m_x_scroll.XGap();
      int l_y=m_mouse.Y()-m_x_scroll.YGap();
      m_x_scroll.Moving(l_x,l_y);
      //--- Mostra o ponteiro
      m_x_scroll.Show();
      //--- Define a visibilidade do sinalizador
      m_x_scroll.IsVisible(true);
     }
   else
     {
      m_prev_x=0;
      //--- Desbloqueia o formulário
      if(m_wnd.IdActivatedElement()==CElement::Id())
        {
         m_wnd.IsLocked(false);
         m_wnd.IdActivatedElement(WRONG_VALUE);
        }
      //--- Oculta o ponteiro
      m_x_scroll.Hide();
      //--- Define a visibilidade do sinalizador
      m_x_scroll.IsVisible(false);
      return;
     }
//--- Sai, se houver um valor positivo
   if(m_new_x_point>0)
      return;
//--- Armazena a posição atual
   m_prev_new_x_point=m_new_x_point;
//--- Aplica à todos os gráficos
   int symbols_total=SubChartsTotal();
//--- Desativa o deslocamento automáticos e o deslocamento a partir da borda direita
   for(int i=0; i<symbols_total; i++)
     {
      if(::ChartGetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL))
         ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false);
      if(::ChartGetInteger(m_sub_chart_id[i],CHART_SHIFT))
         ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false);
     }
//--- Reseta o último erro
   ::ResetLastError();
//--- Desloca os gráficos
   for(int i=0; i<symbols_total; i++)
      if(!::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point))
         ::Print(__FUNCTION__," > erro: ",::GetLastError());
  }

O método CStandardChart::ZeroHorizontalScrollVariables() será usado para repor as variáveis ​​auxiliares do modo de deslocamento horizontal para os dados dos objetos gráficos. É possível também que seja necessário ir até a última barra mediante programação. Para isto, o método público CStandardChart::ResetCharts() é usado. 

class CStandardChart : public CElement
  {
public:
   //--- Redefine os gráficos
   void              ResetCharts(void);
   //---
private:
   //--- Reseta as variáveis ​​de deslocamento horizontal
   void              ZeroHorizontalScrollVariables(void);
  };
//+------------------------------------------------------------------+
//| Reseta os gráficos                                               |
//+------------------------------------------------------------------+
void CStandardChart::ResetCharts(void)
  {
   int sub_charts_total=SubChartsTotal();
   for(int i=0; i<sub_charts_total; i++)
      ::ChartNavigate(m_sub_chart_id[i],CHART_END);
//--- Reseta as variáveis ​​auxiliares para o deslocamento horizontal dos gráficos
   ZeroHorizontalScrollVariables();
  }
//+------------------------------------------------------------------+
//| Reseta as variáveis ​​de deslocamento horizontal                   |
//+------------------------------------------------------------------+
void CStandardChart::ZeroHorizontalScrollVariables(void)
  {
   m_prev_x           =0;
   m_new_x_point      =0;
   m_prev_new_x_point =0;
  }

Também pode ser necessário monitorar o evento de pressionamento do botão esquerdo do mouse sobre um objeto gráfico do controle "Gráfico Padrão". Então, adicione o novo identificador ON_CLICK_SUB_CHART para o arquivo Defines.mqh

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Identificadores de eventos
...
#define ON_CLICK_SUB_CHART         (28) // Ao clicar no objeto gráfico

Para determinar o clique no objeto gráfico, implemente o método CStandardChart::OnClickSubChart(). Se as verificações do nome e do identificador forem bem sucedidos (veja abaixo), então ele gera uma mensagem com o (1) identificador do evento ON_CLICK_SUB_CHART, (2) identificador do controle, (3) o índice do objeto gráfico e (4) o nome do símbolo. 

class CStandardChart : public CElement
  {
private:
   //--- Manipulando o pressionamento do objeto gráfico
   bool              OnClickSubChart(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Manipulando o pressionamento de um botão                         |
//+------------------------------------------------------------------+
bool CStandardChart::OnClickSubChart(const string clicked_object)
  {
//--- Sai, se o pressionamento não foi no elemento de menu
   if(::StringFind(clicked_object,CElement::ProgramName()+"_sub_chart_",0)<0)
      return(false);
//--- Obtém o identificador e o índice a partir do nome do objeto
   int id=CElement::IdFromObjectName(clicked_object);
//--- Retorna, se o tipo definido não corresponder
   if(id!=CElement::Id())
      return(false);
//--- Obtém o índice
   int group_index=CElement::IndexFromObjectName(clicked_object);
//--- Envia um sinal sobre ele
   ::EventChartCustom(m_chart_id,ON_CLICK_SUB_CHART,CElement::Id(),group_index,m_sub_chart_symbol[group_index]);
   return(true);
  }

Suponha que você precise de outra maneira de navegar nos objetos gráficos, semelhante à forma como ele foi implementado ao gráfico principal por meio do terminal. Se a tecla «Space» ou «Enter» for pressionada no terminal MetaTrader, uma caixa de edição é ativada no canto inferior esquerdo do gráfico (veja a imagem abaixo). Este é um tipo de linha de comando, onde uma data poderá ser inserida, a fim de saltar para essa data no gráfico. Essa linha de comando também pode ser usada para alterar o símbolo e o tempo gráfico do gráfico.  

 Fig. 1. Linha de comando do gráfico no canto esquerdo.

Fig. 1. Linha de comando do gráfico no canto esquerdo.


A propósito, um novo recurso para o gerenciamento da linha de comando foi adicionado na última atualização do terminal de negociação (build 1455).

8. MQL5: A nova propriedade CHART_QUICK_NAVIGATION permite ativar/desativar a barra de navegação rápida no gráfico. Se você precisar modificar e acessar o estado de propriedade, use as funções ChartSetInteger e ChartGetInteger.

A barra de navegação é aberta pressionando Enter ou Space. Ela permite que você se mova rapidamente até a data especificada no gráfico, bem como para alterar o símbolo e o tempo gráfico. Se os seu programa em MQL5 processa o pressionamento das teclas Enter ou Space, desative a propriedade CHART_QUICK_NAVIGATION, para evitar a interceptação destes eventos pelo terminal. A barra de navegação rápida ainda pode ser aberta por um duplo clique.

… 

Dentro da interface gráfica, tudo pode ser feito de forma mais fácil e cômoda. A biblioteca Easy And Fast já contém o controle Calendário (a classe CCalendar). A navegação do gráfico e do objeto gráfico principal pode ser implementada simplesmente escolhendo uma data no calendário. Vamos simplificar tudo para um único método com um argumento. O valor deste argumento será a data em que o gráfico precisa ser deslocado. Este método será chamado de CStandardChart::SubChartNavigate(), o código a seguir mostra a versão atual deste método.

Os modos "Deslocamento Automático" e "Deslocar o final do gráfico a partir da borda direita" do gráfico principal estão desativados no início do método. Então, se a variável passada para o método for maior do que a do início do dia atual, simplesmente vá para a última barra e deixe o método. Se a data for menor, em seguida, é necessário calcular o número de barras para o deslocamento à esquerda. Primeiro, o cálculo é realizado para o gráfico principal:

  • Obtenha o número total de barras disponíveis para o símbolo atual e o tempo gráfico desde o início do dia atual até a data especificada.
  • Obtenha o número de barras visíveis no gráfico.
  • Obtenha o número de barras a partir do início do dia atual + duas barras como um recuo adicional.
  • Calcule o número de barras para o deslocamento a partir da última barra.

Depois disso, o gráfico principal é deslocado para a esquerda, e tudo é repetido para os objetos gráficos. 

class CStandardChart : public CElement
  {
public:
   //--- Pula para a data especificada
   void              SubChartNavigate(const datetime date);
  };
//+------------------------------------------------------------------+
//| Pula para a data especificada                                    |
//+------------------------------------------------------------------+
void CStandardChart::SubChartNavigate(const datetime date)
  {
//--- (1) A data do gráfico atual e (2) a nova data selecionada no calendário
   datetime current_date  =::StringToTime(::TimeToString(::TimeCurrent(),TIME_DATE));
   datetime selected_date =date;
//--- Desativa o deslocamento automáticos e o deslocamento a partir da borda direita
   ::ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,false);
   ::ChartSetInteger(m_chart_id,CHART_SHIFT,false);
//--- Se a data selecionada no calendário for maior do que a data atual
   if(selected_date>=current_date)
     {
      //--- Vai para a data atual em todos os gráficos
      ::ChartNavigate(m_chart_id,CHART_END);
      ResetCharts();
      return;
     }
//--- Obtém o número de barras a partir da data especificada
   int  bars_total    =::Bars(::Symbol(),::Period(),selected_date,current_date);
   int  visible_bars  =(int)::ChartGetInteger(m_chart_id,CHART_VISIBLE_BARS);
   long seconds_today =::TimeCurrent()-current_date;
   int  bars_today    =int(seconds_today/::PeriodSeconds())+2;
//--- Define o avanço da margem direita para todos os gráficos
   m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today);
   ::ChartNavigate(m_chart_id,CHART_END,m_new_x_point);
//---
   int sub_charts_total=SubChartsTotal();
   for(int i=0; i<sub_charts_total; i++)
     {
      //--- Desativa o deslocamento automáticos e o deslocamento a partir da borda direita
      ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false);
      ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false);
      //--- Obtém o número de barras a partir da data especificada
      bars_total   =::Bars(m_sub_chart[i].Symbol(),(ENUM_TIMEFRAMES)m_sub_chart[i].Period(),selected_date,current_date);
      visible_bars =(int)::ChartGetInteger(m_sub_chart_id[i],CHART_VISIBLE_BARS);
      bars_today   =int(seconds_today/::PeriodSeconds((ENUM_TIMEFRAMES)m_sub_chart[i].Period()))+2;
      //--- Recuo da margem direita do gráfico
      m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today);
      ::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point);
     }
  }

O desenvolvimento da classe CStandardChart para a criação do controlo Gráfico Padrão foi concluída. Agora, vamos escrever um aplicação para ver como ela funciona. 


Aplicação para testar o controle

Para fins de teste, você pode usar o Expert Advisor do artigo anterior. Remova todos os controles, exceto o menu principal, barra de estado e guias. Faça com que cada guia tenha um grupo separado de objetos gráficos. Cada grupo irá conter uma determinada moeda; portanto, cada guia terá uma descrição correspondente:

  • A primeira guia - EUR (Euro).
  • A segunda guia - GBP (Libra Esterlina).
  • A terceira guia - AUD (Dólar Australiano).
  • A quarta guia - CAD (Dólar Canadense).
  • A quinta guia - JPY (Iene Japonês).

Os objetos gráficos estarão localizados estritamente na área de trabalho das guias e elas serão redimensionadas automaticamente sempre que o formulário for redimensionado. A borda direita da área de trabalho nas guias sempre terá um recuo de 173 pixels da borda direita do formulário. Este espaço será preenchido com os controles para definir essas propriedades como:

  • Exibição da escala de tempo (Date time).
  • Exibição da escala de preços (Price scale).
  • Alteração do período do gráfico (Timeframes).
  • Navegação dos dados do gráfico através do calendário.

Como um exemplo, basta mostrar o código para a criação de um único controle do Gráfico Padrão (CStandardChart). Lembre-se que o deslocamento horizontal dos gráficos é desativado por padrão, de modo que no caso de ser necessário, o método CStandardChart::XScrollMode() pode ser utilizado. O método CStandardChart::AddSubChart() é usado para a adição de gráficos ao grupo.

//+------------------------------------------------------------------+
//| Classe para a criação de uma aplicação                           |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- Gráfico Padrão
   CStandardChart    m_sub_chart1;
   //---
protected:
   //--- Gráfico Padrão
   bool              CreateSubChart1(const int x_gap,const int y_gap);
  };
//+------------------------------------------------------------------+
//| Cria um gráfico padrão 1                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateSubChart1(const int x_gap,const int y_gap)
  {
//--- Armazena o ponteiro da janela
   m_sub_chart1.WindowPointer(m_window);
//--- Anexa à primeira guia
   m_tabs.AddToElementsArray(0,m_sub_chart1);
//--- Coordenadas
   int x=m_window.X()+x_gap;
   int y=m_window.Y()+y_gap;
//--- Define as propriedades antes da criação
   m_sub_chart1.XSize(600);
   m_sub_chart1.YSize(200);
   m_sub_chart1.AutoXResizeMode(true);
   m_sub_chart1.AutoYResizeMode(true);
   m_sub_chart1.AutoXResizeRightOffset(175);
   m_sub_chart1.AutoYResizeBottomOffset(25);
   m_sub_chart1.XScrollMode(true);
//--- Adiciona os gráficos
   m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1);
   m_sub_chart1.AddSubChart("EURGBP",PERIOD_D1);
   m_sub_chart1.AddSubChart("EURAUD",PERIOD_D1);
//--- Cria o controle
   if(!m_sub_chart1.CreateStandardChart(m_chart_id,m_subwin,x,y))
      return(false);
//--- Adiciona o objeto ao array comum dos grupos de objetos
   CWndContainer::AddToElementsArray(0,m_sub_chart1);
   return(true);
  }

A imagem abaixo mostra o resultado final. Neste exemplo, os dados do objeto gráfico pode ser deslocado horizontalmente, de forma semelhante como ele foi implementado ao gráfico principal. Além disso, a navegação entre objetos gráficos funciona através do calendário, incluindo o avanço rápido das datas.

 Fig. 2. Teste do controle Gráfico Padrão.

Fig. 2. Teste do controle Gráfico Padrão.


O aplicativo de teste apresentado no artigo pode ser baixado usando o link abaixo para estudá-lo ainda mais. 

 

Otimização do timer e do manipulador de eventos do motor da biblioteca

Mais cedo, a biblioteca Easy And Fast só tinha sido testada no sistema operacional Windows 7 x64. Após a atualização para o Windows 10 x64, foi verificado que o uso do CPU aumenta significativamente. Os processos da biblioteca consumiam até 10% dos recursos da CPU mesmo no modo de repouso, quando não havia nenhuma interação com a interface gráfica. As imagens abaixo mostram o consumo dos recursos da CPU antes e depois de anexar a aplicação MQL de teste ao gráfico.

Fig. 3. Consumo dos recursos da CPU antes de anexar a aplicação MQL de teste ao gráfico.

Fig. 3. Consumo dos recursos da CPU antes de anexar a aplicação MQL de teste ao gráfico.


Fig. 4. Consumo dos recursos da CPU após anexar a aplicação MQL de teste ao gráfico.

Fig. 4. Consumo dos recursos da CPU após anexar a aplicação MQL de teste ao gráfico.


Descobriu-se que o problema estava no timer do motor da biblioteca, onde o gráfico é atualizado a cada 16ms, como é mostrado no código abaixo:

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

O consumo de CPU aumenta ainda mais, se somarmos os movimentos do cursor do mouse dentro da área do gráfico e a interação ativa com a interface gráfica da aplicação MQL. A tarefa é reduzir o funcionamento do timer do motor da biblioteca e eliminar o redesenho do gráfico cada vez que o evento movimento do mouse for acionado. Como isso pode ser feito?

Remova a linha responsável por redesenhar o gráfico (destacado em vermelho) do método CWndEvents::ChartEventMouseMove(): 

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

Como para o timer do motor da biblioteca, seu propósito atual se resume a mudança de cor dos controles quando eles estão abaixo do mouse e executam um avanço rápido de diferentes controles (listas, tabelas, calendário, etc.) Portanto, ele precisa ser atualizado frequentemente. Para economizar recursos, o timer será ativado quando o cursor do mouse começar a se mover, e logo que o movimento terminar, a sua operação será bloqueada após uma breve pausa. 

A fim de implementar a ideia, é necessário fazer algumas adições à classe CMouse. As adições incluem um contador de chamadas para o timer do sistema e o CMouse::GapBetweenCalls(), que retorna a diferença entre as chamadas para os eventos de movimento do mouse. 

class CMouse
  {
private:
   //--- Contador de chamadas
   ulong             m_call_counter;
   //---
public:   
   //--- Retorna (1) o valor do contador armazenado durante a última chamada e (2) a diferença entre as chamadas para o manipulador de eventos de movimento do mouse
   ulong             CallCounter(void)     const { return(m_call_counter);                  }
   ulong             GapBetweenCalls(void) const { return(::GetTickCount()-m_call_counter); }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CMouse::CMouse(void) : m_call_counter(::GetTickCount())
  {
  }

A lógica é simples. Logo que o cursor do mouse começar a se mover, o processador de eventos da classe CMouse armazena o valor atual do timer do sistema

//+------------------------------------------------------------------+
//| Manipular de eventos de movimento do cursor do mouse             |
//+------------------------------------------------------------------+
void CMouse::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)
     {
      //--- Coordenadas e o estado do botão esquerdo do mouse
      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =(bool)int(sparam);
      //--- Armazena o valor do contador de chamadas
      m_call_counter=::GetTickCount();
      //--- Obtém a localização do cursor
      if(!::ChartXYToTimePrice(0,m_x,m_y,m_subwin,m_time,m_level))
         return;
      //--- Obtém a coordenada Y relativa
      if(m_subwin>0)
         m_y=m_y-m_chart.SubwindowY(m_subwin);
     }
  }

O timer do motor da biblioteca (a classe CWndEvents) deve conter uma condição: se o cursor do mouse não for movido por mais de 500ms, o gráfico não deve ser redesenhado. O botão esquerdo do mouse deve ser liberado naquele tempo, para evitar a situação em que a opção de avanço rápido para os controles funcione somente para 500ms. 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWndEvents::OnTimerEvent(void)
  {
//--- Sai, se o cursor do mouse está em repouso (diferença entre as chamadas for maior do que 500ms) e com o botão esquerdo do mouse é liberado
   if(m_mouse.GapBetweenCalls()>500 && !m_mouse.LeftButtonState())
      return;
//--- Retorna, se o array está vazio  
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Verificação de eventos de todos os elementos pelo timer
   CheckElementsEventsTimer();
//--- Redesenha o gráfico
   m_chart.Redraw();
  }

Problema resolvido. Desabilitar o redesenho sempre que o cursor do mouse se mover não tem efeito sobre a qualidade do movimento de um formulário com controles, já que o intervalo de 16ms do timer é suficiente para redesenhar. O problema foi resolvido da maneira mais simples, mas não é a única maneira possível. A otimização do código da biblioteca será discutida nos próximos artigos da série, uma vez que existem outros métodos e opções que podem ajudar a reduzir o consumo da CPU de forma mais eficiente.


Otimização dos controles Lista Hierárquica e Navegador de Arquivos

Verificou-se que a inicialização levou muito tempo para a lista hierárquica (CTreeView), contendo um grande número de elementos. Isso também ocorreu no navegador de arquivos (CFileNavigator), que utiliza esse tipo de lista. Para resolver este problema, é necessário especificar o tamanho da reserva para o array como terceiro parâmetro da função ::ArrayResize() quando adicionar os elementos nos arrays. 

Citação da referência da função ::ArrayResize():

Com a alocação de memória frequente, é recomendado o uso de um terceiro parâmetro que define uma reserva para reduzir o número de atribuições de memória física. Todas as chamadas subsequentes de ArrayResize não conduzem a realocação de memória física, mas apenas altera o tamanho da primeira dimensão do array dentro da memória reservada. Devemos lembrar que o terceiro parâmetro será usado somente durante a alocação de memória física...

Para efeito de comparação, abaixo estão os resultados dos testes com diferentes valores do tamanho da reserva para os arrays em uma lista hierárquica. O número de arquivos para o teste excede 15 000.

 Fig. 5. Resultados dos testes para a formação de arrays com os valores do tamanho da reserva.

Fig. 5. Resultados dos testes para a formação de arrays com os valores do tamanho da reserva.


Define o tamanho da reserva para os arrays da lista hierárquica iguais a 10 000. As alterações apropriadas foram feitas para as classes CTreeView e CFileNavigator.

 

Novos ícones para as pastas e arquivos no navegador de arquivos.

Foi adicionado novos ícones para as pastas e arquivos do navegador de arquivos (a classe CFileNavigator), que são análogos aos utilizados no navegador de arquivos do sistema operacional Windows 10. Seu design elegante é mais adequado para as interfaces gráficas da biblioteca em desenvolvimento, mas se necessário, versões personalizadas podem ser usadas também.

 Fig. 6. Novos ícones para as pastas e arquivos no navegador de arquivos.

Fig. 6. Novos ícones para as pastas e arquivos no navegador de arquivos. 

 

Estas imagens estão disponíveis no final deste artigo.

 

Conclusão

A biblioteca para a criação de interfaces gráficas no atual estágio de desenvolvimento se parece com o esquema abaixo.

 Fig. 6. Estrutura da biblioteca no atual estágio de desenvolvimento.

Fig. 6. Estrutura da biblioteca no atual estágio de desenvolvimento.


Os próximos artigos sobre interfaces gráficas continuarão com o desenvolvimento da biblioteca Easy And Fast. A biblioteca será expandida com controles adicionais, que possam ser necessárias para o desenvolvimento das aplicações MQL. Os controles existentes sofrerão melhorias e será adicionado novas funcionalidades. 

Abaixo, você pode baixar a quarta versão (build 4) da biblioteca Easy And Fast.Se você estiver interessado, poderá contribuir para o desenvolvimento mais rápido deste projeto, sugerindo soluções para algumas tarefas nos comentários do artigo ou através de mensagens privadas.

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

Arquivos anexados |
LifeHack para traders: relatório comparativo de vários testes LifeHack para traders: relatório comparativo de vários testes
No artigo, é tratada a execução simultânea do teste de Experts em quatro símbolos diferentes. A comparação final dos quatro relatórios respetivos é realizada numa tabela, como seria feito durante a seleção de produtos numa loja. Uma vantagem adicional consiste na geração automática de gráficos de distribuição para cada símbolo.
MQL5 Programações Básicas: Arquivos MQL5 Programações Básicas: Arquivos
Este artigo de orientação prática se concentra em trabalhar com arquivos no MQL5. Ele oferece uma série de tarefas simples, o qual nos permite compreender os conceitos básicos e aprimorar suas habilidades.
Oscilador universal com interface gráfica do usuário Oscilador universal com interface gráfica do usuário
No artigo, descreve-se a criação de um indicador universal baseado em todos os osciladores do terminal com uma interface gráfica do usuário própria. Isto permite rápida e facilmente alterar os parâmetros de cada oscilador individual diretamente a partir da janela do gráfico (em vez de abrir a janela de opções), comparar seu desempenho e selecionar a melhor opção para uma tarefa específica.
ZigZag universal ZigZag universal
O Zigzag é um dos indicadores mais populares entre os usuários MetaTrader 5. No artigo, foram analisadas as possibilidades de criar diferentes variações do ZigZag. Como resultado, obtivemos um indicador universal com amplas possibilidades para estender recursos de fácil uso durante o desenvolvimento de Expert Advisor e outros indicadores.