Download MetaTrader 5

Interfaces Gráficas X: Gestão avançada de listas e tabelas. Otimização do código (build 7)

24 fevereiro 2017, 08:44
Anatoli Kazharski
0
600

Conteúdo


Introdução

A fim de obter uma melhor compreensão do propósito desta biblioteca, leia por favor o primeiro artigo: Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1). 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.

O código da biblioteca juntamente com uma otimização poderia torná-lo mais regularizado e, portanto, mais legível e compreensível para estudar. Além disso, nós vamos continuar a desenvolver os controles criados nos artigos anteriores: listas, tabelas e barras de rolagem. Vamos adicionar métodos que permitem gerenciar programaticamente as propriedades desses controles durante a execução da aplicação MQL. 


Alterações no esquema da biblioteca e otimização do código

Otimizado parcialmente o código em todos os arquivos de biblioteca relacionados aos controles. Os casos com o código frequentemente repetidos foram colocados em métodos separados, e os próprios métodos foram movidos para uma classe separada.

Aqui está como ele foi feito. A classe CElement foi renomeada para CElementBase. Esta é a classe base para todos os controles da biblioteca. Agora, a próxima classe derivada é o nova classe CElement, que contém os métodos frequentemente repetido em todos os controles. Estas incluem:

  • Método para armazenar o ponteiro de formulário, a qual o controle está ligado
  • Verifica a disponibilidade do ponteiro do formulário
  • Verifica o identificador do controle ativado
  • Cálculo das coordenadas absolutas
  • Cálculo das coordenadas relativas da borda do formulário

As classes CElementBase e CElement estão localizadas em arquivos diferentes, ElementBase.mqh e Element.mqh, respectivamente. Portanto, o arquivo ElementBase.mqh com a classe base está incluída no arquivo Element.mqh. Já que o tipo CWindows deve ser definido aqui, inclua o arquivoWindow.mqh também. Isto é apresentado no código a seguir:

//+------------------------------------------------------------------+
//|                                                      Element.mqh |
//|                 Copyright 2016, MetaQuotes Software Corp. |
//|                                                      http://www.mql5.com |
//+------------------------------------------------------------------+
#include "ElementBase.mqh"
#include "Controls\Window.mqh"
//+------------------------------------------------------------------+
//| Classe para obter os parâmetros do mouse                         |
//+------------------------------------------------------------------+
class CElement : public CElementBase
  {
protected:
   //--- Ponteiro para o formulário ao qual o elemento está ligado
   CWindow          *m_wnd;
   //---
public:
                     CElement(void);
                    ~CElement(void);
   //--- Armazena o ponteiro ao formulário
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }
   //---
protected:
   //--- Verifica se existe um ponteiro ao formulário
   bool              CheckWindowPointer(void);
   //--- Verifica o identificador do controle ativado
   bool              CheckIdActivatedElement(void);
  
   //--- Cálculo das coordenadas absolutas
   int               CalculateX(const int x_gap);
   int               CalculateY(const int y_gap);
   //--- Cálculo das coordenadas relativas a partir da borda do formulário
   int               CalculateXGap(const int x);
   int               CalculateYGap(const int y);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CElement::CElement(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CElement::~CElement(void)
  {
  }

Todos esses métodos e seu código eram frequentemente repetidos em todas as classes de controles. Movê-los para uma classe separada deixou o código em classes de controles significativamente mais compreensível e legível. O código de todos esses métodos é simples e literalmente se encaixa em uma única linha (veja o código abaixo). O posicionamento de controle em relação a um lado do formulário é considerado no cálculo das coordenadas.

//+------------------------------------------------------------------+
//| Verifica o identificador do controle ativado                     |
//+------------------------------------------------------------------+
bool CElement::CheckIdActivatedElement(void)
  {
   return(m_wnd.IdActivatedElement()==CElementBase::Id());
  }
//+------------------------------------------------------------------+
//| Calcula a coordenada X absoluta                                  |
//+------------------------------------------------------------------+
int CElement::CalculateX(const int x_gap)
  {
   return((CElementBase::AnchorRightWindowSide())? m_wnd.X2()-x_gap : m_wnd.X()+x_gap);
  }
//+------------------------------------------------------------------+
//| Calcula a coordenada Y absoluta                                  |
//+------------------------------------------------------------------+
int CElement::CalculateY(const int y_gap)
  {
   return((CElementBase::AnchorBottomWindowSide())? m_wnd.Y2()-y_gap : m_wnd.Y()+y_gap);
  }
//+------------------------------------------------------------------+
//| Calcula a coordenada X relativa da borda do formulário           |
//+------------------------------------------------------------------+
int CElement::CalculateXGap(const int x)
  {
   return((CElementBase::AnchorRightWindowSide())? m_wnd.X2()-x : x-m_wnd.X());
  }
//+------------------------------------------------------------------+
//| Calcula a coordenada Y relativa da borda do formulário           |
//+------------------------------------------------------------------+
int CElement::CalculateYGap(const int y)
  {
   return((CElementBase::AnchorBottomWindowSide())? m_wnd.Y2()-y : y-m_wnd.Y());
  }

Alguém pode perguntar: "Por que estes métodos não foram colocados na versão antiga da classe CElement? "Isso não era possível: quando estava incluindo o arquivo Window.mqh e tentássemos compilar, ocorria o erro de declaração sem o tipo, e como resultado - muitos outros erros relacionados:

 Fig. 1. Mensagem na compilação que o tipo CElement estava faltando

Fig. 1. Mensagem na compilação que o tipo CElement estava faltando


As tentativas de contornar esta dificuldade, incluindo o Window.mqh depois do corpo da classe CElement, quando um objeto do tipo CWindow já havia sido declarado no corpo dessa classe, levava ao erro de compilação familiar do tipo especificado faltando.

 Fig. 2. Mensagem na compilação que o tipo CWindow estava faltando

Fig. 2. Mensagem na compilação que o tipo CWindow estava faltando


Por isso, decidiu-se criar uma classe intermediária herdada adicional para colocar o código e os métodos frequentemente repetidos para trabalhar com o ponteiro do formulário, ao qual os controles estão ligados. Parte do esquema da biblioteca quanto à relação entre o formulário e os controles aparentam a seguir:

 Fig. 3. Parte do esquema da biblioteca quanto à relação entre o formulário e os controles.

Fig. 3. Parte do esquema da biblioteca quanto à relação entre o formulário e os controles.


Como pode ser visto a partir do esquema acima, a classe CWindow é derivada diretamente da classe CElementBase, já que a classe intermediária CElement é agora supérflua e inconveniente para o formulário. Todas as outras classes de controles são derivadas a partir da classe intermediária CElement

 

Controlando uma barra de rolagem via programação

A necessidade de controlar programaticamente as barras de rolagem surgiram durante a aplicação da biblioteca. Para este objetivo, o método MovingThumb() foi implementado nas classes CScrollV e CScrollH, que podem ser utilizados para deslocar a barra de rolagem para a posição especificada. 

A lista abaixo mostra o código apenas para a barra de rolagem vertical, como ela é virtualmente idêntica à barra de rolagem horizontal. O método tem um argumento, que tem como padrão WRONG_VALUE. Se o método é chamado sem especificar a posição (com o valor padrão), o polegar será movido para a última posição da lista. Isso é útil quando os itens são adicionados à lista, enquanto o programa está em execução, e também permite implementar o deslocamento automático da lista.

//+------------------------------------------------------------------+
//| Classe para a gestão da barra de rolagem vertical                |
//+------------------------------------------------------------------+
class CScrollV : public CScroll
  {
public:
   //--- Move o polegar para a posição especificada
   void              MovingThumb(const int pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| Move o polegar para a posição especificada                       |
//+------------------------------------------------------------------+
void CScrollV::MovingThumb(const int pos=WRONG_VALUE)
  {
//--- Sai, se a barra de rolagem não é necessária
   if(m_items_total<=m_visible_items_total)
      return;
//--- Para verificar a posição do polegar
   uint check_pos=0;
//--- Ajuste no caso do tamanho ser excedido
   if(pos<0 || pos>m_items_total-m_visible_items_total)
      check_pos=m_items_total-m_visible_items_total;
   else
      check_pos=pos;
//--- Armazena a posição do polegar
   CScroll::CurrentPos(check_pos);
//--- Calcula e definir a coordenada Y do polegar da barra de rolagem
   CalculateThumbY();
  }

 

Controlando listas via programação

Uma série de métodos públicos para gerir as listas têm sido implementadas, que executam as seguintes ações:

  • Reconstrução da lista
  • Adicionando um item para o fim da lista
  • Limpar a lista (excluindo todos os itens)
  • Rolar a lista

Além disso, como parte da otimização do código da biblioteca, os métodos privados repetidos do código foram adicionados às classes da lista:

  • Cálculo da coordenada Y do item
  • Cálculo do tamanho dos itens
  • Cálculo do tamanho da lista ao longo do eixo Y

Vamos dar uma olhada mais detalhadamente na estrutura desses métodos na classe CListView. Os métodos privados são apenas métodos auxiliares para o código repetido mais de uma vez em diferentes partes da classe. Eles ocupam apenas uma linha em cada método:

//+------------------------------------------------------------------+
//| Classe para criar uma lista                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
private:
   //--- Cálculo da coordenada Y do item
   int               CalculationItemY(const int item_index=0);
   //--- Calculando a largura dos itens
   int               CalculationItemsWidth(void);
   //--- Cálculo do tamanho da lista ao longo do eixo Y
   int               CalculationYSize(void);
//+------------------------------------------------------------------+
//| Cálculo da coordenada Y do item                                  |
//+------------------------------------------------------------------+
int CListView::CalculationItemY(const int item_index=0)
  {
   return((item_index>0)? m_items[item_index-1].Y2()-1 : CElementBase::Y()+1);
  }
//+------------------------------------------------------------------+
//| Cálculo da largura dos itens                                     |
//+------------------------------------------------------------------+
int CListView::CalculationItemsWidth(void)
  {
   return((m_items_total>m_visible_items_total) ? CElementBase::XSize()-m_scrollv.ScrollWidth()-1 : CElementBase::XSize()-2);
  }
//+------------------------------------------------------------------+
//| Cálculo do tamanho da lista ao longo do eixo Y                   |
//+------------------------------------------------------------------+
int CListView::CalculationYSize(void)
  {
   return(m_item_y_size*m_visible_items_total-(m_visible_items_total-1)+2);
  }

A limpeza da lista fala por si só: todos os itens são removidos da lista. Para fazer isso, use o método CListView::Clear(). Aqui, as primitivas gráficas são removidas primeiro, o array de ponteiros para esses objetos é liberado e os valores padrão são definidos para determinados campos da classe. Depois disso, o tamanho da lista é definido como zero e os parâmetros de barra de rolagem são resetados. No final do método, é necessário adicionar o ponteiro ao fundo da lista para o array de ponteiros novamente, como se ele tivesse sido removido anteriormente pelo método CElementBase::FreeObjectsArray().

//+------------------------------------------------------------------+
//| Classe para criar uma lista                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- Limpa a lista (apaga todos os itens)
   void              Clear(void);
  };
//+------------------------------------------------------------------+
//| Limpa a lista (apaga todos os itens)                             |
//+------------------------------------------------------------------+
void CListView::Clear(void)
  {
//--- Remove os itens de objetos
   for(int r=0; r<m_visible_items_total; r++)
      m_items[r].Delete();
//--- Limpa o array de ponteiros para os objetos
   CElementBase::FreeObjectsArray();
//--- Define os valores padrão
   m_selected_item_text  ="";
   m_selected_item_index =0;
//--- Define o tamanho zero à lista
   ListSize(0);
//--- Reseta os valores de barra de rolagem
   m_scrollv.Hide();
   m_scrollv.MovingThumb(0);
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
//--- Adiciona o fundo da lista ao array de ponteiros para os objetos do controle
   CElementBase::AddToArray(m_area);
  }

Para reconstruir a lista, use o método CListView::Rebuilding(). A reconstrução é uma situação em que é necessário re-criar a lista totalmente. Este método pode ser usado para alterar o número total de itens e o número de itens visíveis. Isto é, o tamanho da lista também mudará, se o número de itens visíveis é definido diferente do valor original.

A lista é limpa no início do método CListView::Rebuilding(). Em seguida, os valores dos argumentos transmitidos são usados ​​para definir os novos tamanhos e para ajustar a altura da lista, se o número de itens visíveis mudar. Em seguida, os tamanhos dos objetos na barra de posicionamento são ajustados. Depois disso, a lista é criada, e se o número total de itens exceder a quantidade especificada de itens visíveis, uma barra de rolagem é exibida. 

//+------------------------------------------------------------------+
//| Classe para criar uma lista                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- Reconstrução da lista
   void              Rebuilding(const int items_total,const int visible_items_total);
  };
//+------------------------------------------------------------------+
//| Reconstrução da lista                                            |
//+------------------------------------------------------------------+
void CListView::Rebuilding(const int items_total,const int visible_items_total)
  {
//--- Limpando a lista
   Clear();
//--- Ajusta o tamanho de exibição da lista e sua parte visível
   ListSize(items_total);
   VisibleListSize(visible_items_total);
//--- Ajusta o tamanho da lista
   int y_size=CalculationYSize();
   if(y_size!=CElementBase::YSize())
     {
      m_area.YSize(y_size);
      m_area.Y_Size(y_size);
      CElementBase::YSize(y_size);
     }
//--- Ajusta o tamanho da barra de rolagem
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
   m_scrollv.ChangeYSize(y_size);
//--- Cria a lista
   CreateList();
//--- Mostra a barra de rolagem, se necessário
   if(m_items_total>m_visible_items_total)
     {
      if(CElementBase::IsVisible())
         m_scrollv.Show();
     }
  }

Um método separado CListView::CreateItem() foi implementado para a criação de um único item, como seu código será utilizado quando adicionar um item à lista no método CListView::AddItem() durante o tempo de execução, e não apenas ao criar uma lista inteira no ciclo dentro do método CListView::CreateList(). 

O método CListView::AddItem() recebe somente um argumento - o texto exibido do item. Ela é uma string vazia por padrão. O texto também pode ser adicionado depois de criar o item usando o método CListView::SetItemValue(). No início do método CListView::AddItem(), o conjunto de itens é incrementado por um elemento. Então, se a quantidade total de elementos no momento não for maior que o número de itens visíveis, isto significa que é necessário criar um objeto gráfico. Se o número da quantidade visível for excedida, então é necessário mostrar a barra de rolagem e ajustar o o tamanho do polegar, bem como ajustar a largura dos itens. 

//+------------------------------------------------------------------+
//| Classe para criar uma lista                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- Adiciona um item à lista
   void              AddItem(const string value="");
  };
//+------------------------------------------------------------------+
//| Adiciona um item à lista                                         |
//+------------------------------------------------------------------+
void CListView::AddItem(const string value="")
  {
//--- Aumenta o tamanho do array por um elemento
   int array_size=ItemsTotal();
   m_items_total=array_size+1;
   ::ArrayResize(m_item_value,m_items_total);
   m_item_value[array_size]=value;
//--- Se o número total de itens for maior do que os visíveis
   if(m_items_total>m_visible_items_total)
     {
      //--- Ajusta o tamanho do polegar e exibe a barra de rolagem
      m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
      if(CElementBase::IsVisible())
         m_scrollv.Show();
      //--- Sai, se o array tem menos do que um elemento
      if(m_visible_items_total<1)
         return;
      //--- Calcula a largura dos itens de exibição da lista
      int width=CElementBase::XSize()-m_scrollv.ScrollWidth()-1;
      if(width==m_items[0].XSize())
         return;
      //--- Define o novo tamanho para os itens da lista
      for(int i=0; i<m_items_total && i<m_visible_items_total; i++)
        {
         m_items[i].XSize(width);
         m_items[i].X_Size(width);
        }
      //---
      return;
     }
//--- Cálculo das coordenadas
   int x=CElementBase::X()+1;
   int y=CalculationItemY(array_size);
//--- Calcula a largura dos itens de exibição da lista
   int width=CalculationItemsWidth();
//--- Cria o objeto
   CreateItem(array_size,x,y,width);
//--- Destaca o item selecionado
   HighlightSelectedItem();
//--- Armazena o texto do item selecionado
   if(array_size==1)
      m_selected_item_text=m_item_value[0];
  }

O método CListView::Scrolling() é projetado para percorrer os itens da lista via programação. O número da posição na lista é tomada por um único argumento. O valor padrão é WRONG_VALUE, o que significa deslocar a lista para a última posição. 

//+------------------------------------------------------------------+
//| Classe para criar uma lista                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- Rolagem da lista
   void              Scrolling(const int pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| Rolagem da lista                                                 |
//+------------------------------------------------------------------+
void CListView::Scrolling(const int pos=WRONG_VALUE)
  {
//--- Sai, se a barra de rolagem não é necessária
   if(m_items_total<=m_visible_items_total)
      return;
//--- Para determinar a posição do polegar
   int index=0;
//--- Índice da última posição
   int last_pos_index=m_items_total-m_visible_items_total;
//--- Ajuste no caso do intervalo ser excedido
   if(pos<0)
      index=last_pos_index;
   else
      index=(pos>last_pos_index)? last_pos_index : pos;
//--- Move o botão de rolagem
   m_scrollv.MovingThumb(index);
//--- Move a lista
   UpdateList(index);
  }

Métodos semelhantes foram implementados para listas do tipo CCheckBoxList

 

Otimização do código para a tabela do tipo CTable

O código da classe CTable também foi otimizada. Ela se tornou mais compacta e de fácil leitura, devido à adição de um número de métodos privados, que contém o código repetido com frequência. Esses métodos são os seguintes:

  • Redimensionar as linhas do array
  • Inicialização das células com valores padrão
  • Cálculo do tamanho da tabela ao longo do eixo X
  • Cálculo do tamanho da tabela ao longo do eixo Y
  • Cálculo da coordenada X da célula
  • Cálculo da coordenada Y da célula
  • Cálculo da largura da coluna
  • Alteração da largura das colunas
  • Alteração do tamanho da tabela ao longo do eixo Y
//+------------------------------------------------------------------+
//| Classe para a criação de uma tabela de caixa de edição           |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- Redimensiona as linhas do array
   void              RowResize(const uint column_index,const uint new_size);
   //--- Inicialização das células com valores padrão
   void              CellInitialize(const uint column_index,const int row_index=WRONG_VALUE);
   //--- Cálculo do tamanho da tabela ao longo do eixo X
   int               CalculationXSize(void);
   //--- Cálculo do tamanho da tabela ao longo do eixo Y
   int               CalculationYSize(void);
   //--- Cálculo da coordenada X da célula
   int               CalculationCellX(const int column_index=0);
   //--- Cálculo da coordenada Y da célula
   int               CalculationCellY(const int row_index=0);
   //--- Cálculo da largura da coluna
   int               CalculationColumnWidth(const bool is_last_column=false);
   //--- Alteração da largura das colunas
   void              ColumnsXResize(void);
   //--- Alteração do tamanho da tabela ao longo do eixo Y
   void              YResize(void);
  };

O método CTable::CalculationColumnWidth() é destinado para calcular a largura das colunas da tabela, possuindo apenas um argumento com o valor false. O valor padrão é usado para calcular a largura total de todas as colunas. Se o valor passado for true, então a largura para a última coluna será calculada. Neste caso, é usado a chamada de um método recursivo. A divisão no cálculo da largura total e a largura da última coluna é necessária, como no cálculo geral, a extremidade da direita da última coluna pode não coincidir com a borda da direita da tabela.

//+------------------------------------------------------------------+
//| Cálculo da largura da coluna                                     |
//+------------------------------------------------------------------+
int CTable::CalculationColumnWidth(const bool is_last_column=false)
  {
   int width=0;
//--- Verifica a presença de uma barra de rolagem vertical
   bool is_scrollv=m_rows_total>m_visible_rows_total;
//---
   if(!is_last_column)
     {
      if(m_visible_columns_total==1)
         width=(is_scrollv)? m_x_size-m_scrollv.ScrollWidth() : width=m_x_size-2;
      else
        {
         if(is_scrollv)
            width=(m_x_size-m_scrollv.ScrollWidth())/int(m_visible_columns_total);
         else
            width=m_x_size/(int)m_visible_columns_total+1;
        }
     }
   else
     {
      width=CalculationColumnWidth();
      int last_column=(int)m_visible_columns_total-1;
      int w=m_x_size-(width*last_column-last_column);
      width=(is_scrollv) ? w-m_scrollv.ScrollWidth()-1 : w-2;
     }
//---
   return(width);
  }

O método CTable::ColumnsXResize() é chamado quando uma tabela é criada ou quando a largura da tabela é alterada. Aqui o método CTable::CalculationColumnWidth() é chamado para o cálculo das larguras da coluna, que foi discutido acima. No final do processo, se a tabela está ordenada, é necessário ajustar a posição da seta de sinal da tabela ordenada

//+------------------------------------------------------------------+
//| Alteração da largura das colunas                                 |
//+------------------------------------------------------------------+
void CTable::ColumnsXResize(void)
  {
//--- Cálculo da largura das colunas
   int width=CalculationColumnWidth();
//--- Colunas
   for(uint c=0; c<m_columns_total && c<m_visible_columns_total; c++)
     {
      //--- Cálculo da coordenada X
      int x=CalculationCellX(c);
      //--- Ajusta a largura da última coluna
      if(c+1>=m_visible_columns_total)
         width=CalculationColumnWidth(true);

      //--- Linhas
      for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
        {
         //--- Coordenadas
         m_columns[c].m_rows[r].X(x);
         m_columns[c].m_rows[r].X_Distance(x);
         //--- Largura
         m_columns[c].m_rows[r].XSize(width);
         m_columns[c].m_rows[r].X_Size(width);
         //--- Margens a partir da borda do painel
         m_columns[c].m_rows[r].XGap(CalculateXGap(x));
        }
     }
//--- Sai, se a tabela não está ordenada
   if(m_is_sorted_column_index==WRONG_VALUE)
      return;
//--- Desloca por um índice, se o modo de cabeçalho fixo está habilitado
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtém as posições atuais dos polegares das barras de rolagem vertical e horizontal
   int h=m_scrollh.CurrentPos()+l;
//--- Se não exceder o intervalo do array
   if(m_is_sorted_column_index>=h && m_is_sorted_column_index<(int)m_visible_columns_total)
     {
      //--- Desloca a seta para a coluna de tabela ordenada
      ShiftSortArrow(m_is_sorted_column_index);
     }
  }

Você pode facilmente estudar o código dos outros métodos privados, fornecidos na lista no início desta seção, já que eles não contêm qualquer coisa complexa, o que poderia causar perguntas.

Além dos métodos acima descritos, no âmbito da otimização, um método privado separado CTable::CreateCell() foi implementado para a criação das células da tabela. Outra adição útil para as tabelas do tipo CTable nesta atualização é a formatação automática do estilo da zebra. Anteriormente, se um usuário da biblioteca precisasse fazer uma tabela listrada para uma melhor compreensão do conjunto de dados, o método CTable::CellColor() tem de ser usado. Ou seja, é necessário atribuir cores para cada célula da tabela individualmente. Isso é inconveniente e demorado. Agora, para fazer a tabela listrada, basta chamar o método CTable::IsZebraFormatRows() antes de criar o controle, passando a segunda cor como um único argumento. O valor definido pelo método CTable::CellColor() para todas as células da tabela (padrão - branco) é usado como a primeira cor. 

//+------------------------------------------------------------------+
//| Classe para a criação de uma tabela de caixa de edição           |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- Modo de colorir listrado estilo zebra da tabela
   color             m_is_zebra_format_rows;
   //---
public:
   //--- Modo de linhas de formatação no estilo zebra
   void              IsZebraFormatRows(const color clr)                         { m_is_zebra_format_rows=clr;      }
  };

Se a segunda cor para a formatação no estilo Zebra for especificada, então o método privado CTable::ZebraFormatRows() é chamado sempre que necessário. 

//+------------------------------------------------------------------+
//| Classe para a criação de uma tabela de caixa de edição           |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- Formata a tabela no estilo zebra
   void              ZebraFormatRows(void);
  };
//+------------------------------------------------------------------+
//| Formata a tabela no estilo zebra                                 |
//+------------------------------------------------------------------+
void CTable::ZebraFormatRows(void)
  {
//--- Sai, se o modo está desativado
   if(m_is_zebra_format_rows==clrNONE)
      return;
//--- A cor padrão
   color clr=m_cell_color;
//---
   for(uint c=0; c<m_columns_total; c++)
     {
      for(uint r=0; r<m_rows_total; r++)
        {
         if(m_fix_first_row)
           {
            if(r==0)
               continue;
            //---
            clr=(r%2==0)? m_is_zebra_format_rows : m_cell_color;
           }
         else
            clr=(r%2==0)? m_cell_color : m_is_zebra_format_rows;
         //--- Armazena a cor de fundo das células no array comum
         m_vcolumns[c].m_cell_color[r]=clr;
        }
     }
  }

 

Controlando uma tabela do tipo CTable via programação

Nesta atualização da biblioteca, somente a tabela do tipo CTable recebe um controle programático. Vários métodos públicos foram implementadas para executar as seguintes ações:

  • Reconstrução da tabela
  • Adicionar uma coluna
  • Adicionar uma linha
  • Limpar a tabela (excluindo todas as colunas e linhas)
  • Rolagem horizontal e vertical da tabela
//+------------------------------------------------------------------+
//| Classe para a criação de uma tabela de caixa de edição           |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
public:
   //--- Reconstrução da tabela
   void              Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total);
   //--- Adiciona uma coluna à tabela
   void              AddColumn(void);
   //--- Adiciona uma linha à tabela
   void              AddRow(void);
   //--- Limpa a tabela (exclui todas as linhas e colunas)
   void              Clear(void);
   //--- Rolagem da tabela: (1) vertical e (2) horizontal
   void              VerticalScrolling(const int pos=WRONG_VALUE);
   void              HorizontalScrolling(const int pos=WRONG_VALUE);
  };

O método CTable::Clear() para limpar a tabela não será mencionada: é praticamente idêntico aquela nas listas, que foi mencionado nas seções anteriores deste artigo.

Para reconstruir a tabela, é necessário chamar o método CTable::Rebuilding(), em que o número total de colunas e linhas, bem como a sua quantidade visível deve ser passada como os argumentos. Aqui, no início do método, a tabela é apagada. Ou seja, todas as suas colunas e linhas são excluídas. Em seguida, as novas dimensões são definidas por array, com base nos valores dos parâmetros passados. As barras de deslocamento são ajustadas de acordo com o número atual total de linhas e colunas em relação à sua quantidade visível. Depois que todos os cálculos forem feitos, as células da tabela são criadas e, se necessário, as barras de rolagem são visíveis. 

//+------------------------------------------------------------------+
//| Reconstrução da tabela                                           |
//+------------------------------------------------------------------+
void CTable::Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total)
  {
//--- Limpa a tabela
   Clear();
//--- Ajusta o tamanho da tabela e sua parte visível
   TableSize(columns_total,rows_total);
   VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Ajusta os tamanhos das barras de rolagem
   m_scrollv.ChangeThumbSize(rows_total,visible_rows_total);
   m_scrollh.ChangeThumbSize(columns_total,visible_columns_total);
//--- Verifica a presença de uma barra de rolagem vertical
   bool is_scrollv=m_rows_total>m_visible_rows_total;
//--- Verifica a presença de uma barra de rolagem horizontal
   bool is_scrollh=m_columns_total>m_visible_columns_total;
//--- Calcula o tamanho da tabela ao longo do eixo Y
   int y_size=CalculationYSize();
//--- Redimensiona a barra de rolagem vertical
   m_scrollv.ChangeYSize(y_size);
//--- Redimensiona a tabela
   m_y_size=(is_scrollh)? y_size+m_scrollh.ScrollWidth()-1 : y_size;
   m_area.YSize(m_y_size);
   m_area.Y_Size(m_y_size);
//--- Ajusta a localização da barra de rolagem horizontal ao longo do eixo Y
   m_scrollh.YDistance(CElementBase::Y2()-m_scrollh.ScrollWidth());
//--- Se uma barra de rolagem horizontal é necessária
   if(is_scrollh)
     {
      //--- Define o tamanho de acordo com a presença de uma barra de rolagem vertical
      if(!is_scrollv)
         m_scrollh.ChangeXSize(m_x_size);
      else
        {
         //--- Calcula e altera a largura da barra de rolagem horizontal
         int x_size=m_area.XSize()-m_scrollh.ScrollWidth()+1;
         m_scrollh.ChangeXSize(x_size);
        }
     }
//--- Cria as células da tabela
   CreateCells();
//--- Mostra a barra de rolagem, se necessário
   if(rows_total>visible_rows_total)
     {
      if(CElementBase::IsVisible())
         m_scrollv.Show();
     }
   if(columns_total>visible_columns_total)
     {
      if(CElementBase::IsVisible())
         m_scrollh.Show();
     }
  }

Os métodos para a adição de uma coluna CTable::AddColumn() e linha CTable::AddRow() são muito semelhantes nos seus algoritmos, por conseguinte, apenas um deles serão considerados aqui. 

No início do método CTable::AddColumn(), o tamanho do array de linhas e colunas é definido naquela coluna. Em seguida, a inicialização das células com valores padrão para a coluna adicionada é realizada usando o método CTable::CellInitialize(). Depois disso, se o número total de colunas não for maior do que o valor especificado visível: 

  1. As larguras das colunas são calculadas
  2. Um certo número de objetos gráficos (células da tabela) são criados para as colunas adicionadas
  3. Se necessário, a tabela é formatada no estilo zebra
  4. E no final do método, a tabela é atualizada

Se acontecer do número total de colunas e linhas for maior do que o valor especificado visível após o aumento das colunas e linhas do array, então é necessário mostrar uma barra de deslocamento horizontal e, assim, ajustar a altura da tabela. Depois disso, a tabela é formatada no estilo zebra e o programa deixa o método. 

//+------------------------------------------------------------------+
//| Adiciona uma coluna à tabela                                     |
//+------------------------------------------------------------------+
void CTable::AddColumn(void)
  {
//--- Aumenta o tamanho do array por um elemento
   uint array_size=ColumnsTotal();
   m_columns_total=array_size+1;
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Ajusta o tamanho das linhas do array
   RowResize(array_size,m_rows_total);
//--- Inicializa os arrays com os valores padrão
   CellInitialize(array_size);
//--- Se o número total de colunas for maior do que a quantidade visível
   if(m_columns_total>m_visible_columns_total)
     {
      //--- Ajusta o tamanho da tabela ao longo do eixo Y
      YResize();
      //--- Se não houver nenhuma barra de rolagem vertical, faça a barra de rolagem horizontal ocupar toda a largura da tabela
      if(m_rows_total<=m_visible_rows_total)
         m_scrollh.ChangeXSize(m_x_size);
      //--- Ajusta o tamanho do polegar e exibe a barra de rolagem
      m_scrollh.ChangeThumbSize(m_columns_total,m_visible_columns_total);
      //--- Mostra a barra de rolagem
      if(CElementBase::IsVisible())
         m_scrollh.Show();
      //--- Formatação das linhas em estilo zebra
      ZebraFormatRows();
      //--- Atualiza a tabela
      UpdateTable();
      return;
     }
//--- Cálculo da largura das colunas
   int width=CalculationColumnWidth();
//--- Ajusta a largura da última coluna
   if(m_columns_total>=m_visible_columns_total)
      width=CalculationColumnWidth(true);
//--- Cálculo da coordenada X
   int x=CalculationCellX(array_size);
//---
   for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
     {
      //--- Cálculo da coordenada Y
      int y=CalculationCellY(r);
      //--- Criando o objeto
      CreateCell(array_size,r,x,y,width);
      //--- Define a cor correspondente ao cabeçalho
      if(m_fix_first_row && r==0)
         m_columns[array_size].m_rows[r].BackColor(m_headers_color);
     }
//--- Formatação das linhas em estilo zebra
   ZebraFormatRows();
//--- Atualiza a tabela
   UpdateTable();
  }

Os métodos CTable::VerticalScrolling() e CTable::HorizontalScrolling() para a rolagem da tabela são virtualmente idênticos aos discutidos na seção listas, portanto, seu código não será fornecido aqui. Você pode encontrá-los nos arquivos anexados a este artigo.

Agora, vamos criar um aplicativo MQL de teste, que demonstram os novos recursos das listas e tabelas do tipo CTable

 

Aplicação para testar os controles

Para fins de teste, vamos criar um uma aplicação MQL semelhante, que permitiria ver de imediato o funcionamento dos métodos adicionados às classes das listas e tabelas do tipo CTable. Crie duas guias na interface gráfica desta aplicação. A primeira guia irá conter uma tabela do tipo CTable e controles para gerenciar as propriedades da tabela, localizada acima da tabela. Aqueles consistirá de dois botões e quatro caixas de edição numéricos:

  • Botão «CLEAR TABLE» para limpar a tabela (excluindo todas as colunas e linhas)
  • Botão «REBUILD TABLE» para reconstruir a tabela com base nos parâmetros especificados nas caixas de edição numéricos
  • Caixa de edição «Rows total» para inserir o número total de linhas da tabela
  • Caixa de edição «Columns total» para inserir o número total de colunas da tabela
  • Caixa de edição «Visible rows total» para inserir o número de linhas da tabela visível
  • Caixa de edição «Visible columns total» para inserir o número de colunas da tabela visível

A imagem abaixo mostra a sua aparência:

 Fig. 4. Grupo de controles na primeira guia.

Fig. 4. Grupo de controles na primeira guia


A segunda guia irá conter duas listas (Ver a lista e lista de caixas de seleção). Para demonstrar a gestão programática das listas, os seguintes controles estarão presentes:

  • Botão «CLEAR LISTS» para limpar as listas (excluindo todos os itens)
  • Botão «REBUILD LISTS» para reconstruir as listas com base nos parâmetros especificados nas caixas de edição numéricos
  • Caixa de edição «Items total» para inserir o número total de itens da lista
  • Caixa de edição «Itens visíveis no total» para inserir o número de itens da lista visível

 A imagem abaixo mostra os controles na segunda guia. Como um complemento, mais dois controles foram adicionados a ele: os controles calendário suspenso e Horário.

 Fig. 5. Grupo de controles na segunda guia.

Fig. 5. Grupo de controles na segunda guia


Antes de prosseguir com a demonstração de características das listas e tabelas implementadas nesta atualização, vamos debruçar sobre uma outra adição, o que facilitará o trabalho do desenvolvedor MQL no timer de sua aplicação MQL. Esta é a classe CTimeCounter. Ela pode ser usada para gerenciar a atualização (redesenhar) com frequência para separar grupos de controles de interface gráfica nos intervalos de tempo especificados. A classe CTimeCounter contém apenas três campos e dois métodos (ver o código abaixo).

//+------------------------------------------------------------------+
//|                                                  TimeCounter.mqh |
//|                 Copyright 2016, MetaQuotes Software Corp. |
//|                                                      http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Contador de tempo                                                |
//+------------------------------------------------------------------+
class CTimeCounter
  {
private:
   //--- Passo do contador
   uint              m_step;
   //--- Intervalo de tempo
   uint              m_pause;
   //--- Contador de tempo
   uint              m_time_counter;
   //---
public:
                     CTimeCounter(void);
                    ~CTimeCounter(void);
   //--- Ajuste do passo e do intervalo de tempo
   void              SetParameters(const uint step,const uint pause);
   //--- Verifica se o intervalo de tempo especificado terminou
   bool              CheckTimeCounter(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CTimeCounter::CTimeCounter(void) : m_step(16),
                                   m_pause(1000),
                                   m_time_counter(0)
                                  
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CTimeCounter::~CTimeCounter(void)
  {
  }

O método CTimeCounter::SetParameters() pode ser utilizado para definir e incrementar o contador de intervalo de tempo para uma pausa:

//+------------------------------------------------------------------+
//| Ajuste do passo e intervalo de tempo                             |
//+------------------------------------------------------------------+
void CTimeCounter::SetParameters(const uint step,const uint pause)
  {
   m_step  =step;
   m_pause =pause;
  }

O método CTimeCounter::CheckTimeCounter() é projetado para verificar se o intervalo de tempo especificado nos parâmetros de classe terminaram. Se o intervalo de tempo terminou, o método retorna true.

//+------------------------------------------------------------------+
//| Verifica se o intervalo de tempo especificado terminou           |
//+------------------------------------------------------------------+
bool CTimeCounter::CheckTimeCounter(void)
  {
//--- Aumenta o contador, se o intervalo de tempo especificado não tiver decorrido
   if(m_time_counter<m_pause)
     {
      m_time_counter+=m_step;
      return(false);
     }
//--- Zera o contador
   m_time_counter=0;
   return(true);
  }

Antes de prosseguir, deve-se notar que a localização dos arquivos nas listas da biblioteca desenvolvidos também foram alterados. Apenas os arquivos que contêm as classes de controles estão localizados agora na pasta «MetaTrader 5\MQL5\Include\EasyAndFastGUI\Controls» Todos os outros arquivos foram movidos para a pasta raiz da biblioteca: «MetaTrader 5\MQL5\Include\EasyAndFastGUI». Portanto, para incluir a biblioteca em um arquivo de uma classe personalizada, é necessário especificar o caminho, como mostrado na lista abaixo. Também mostra como incluir um arquivo com a classe CTimeCounter (Irá ser utilizado em exemplos de teste). 

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                 Copyright 2016, MetaQuotes Software Corp. |
//|                                                      http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndEvents.mqh>
#include <EasyAndFastGUI\TimeCounter.mqh>

Os parâmetros dos marcadores temporais serão definidos no construtor do contador personalizado:

//+------------------------------------------------------------------+
//| Classe para a criação de uma aplicação                           |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- Marcadores de tempo
   CTimeCounter      m_counter1; // para atualizar a barra de status
   CTimeCounter      m_counter2; // Para atualizar as listas e tabelas
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
//--- Define os parâmetros de configuração para os contadores de tempo
   m_counter1.SetParameters(16,500);
   m_counter2.SetParameters(16,150);
  }

A demonstração de adicionar itens a listas e adicionar colunas e linhas para a tabela após uma purga completa desses controles, será implementado um temporizador. Após o intervalo de tempo especificado, se o número de itens/linhas/colunas for menor que o especificado nas caixas de edição correspondentes, então, eles serão adicionados neste bloco (veja o código abaixo). Para demonstrar a gestão programática da barra de rolagem, os polegares das barras de rolagem serão transferidos para o fim das listas cada vez que um item for adicionado às listas. 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
...
//--- Pausa entre as atualizações dos controles
   if(m_counter2.CheckTimeCounter())
     {
      //--- Adicione uma linha à tabela, se a quantidade total for menor do que o especificado
      if(m_table.RowsTotal()<m_spin_edit1.GetValue())
         m_table.AddRow();
      //--- Adiciona uma coluna à tabela, se o montante total for menor do que o especificado
      if(m_table.ColumnsTotal()<m_spin_edit2.GetValue())
         m_table.AddColumn();
      //--- Adiciona um item à lista, se o montante total for menor do que o especificado
      if(m_listview.ItemsTotal()<m_spin_edit5.GetValue())
        {
         m_listview.AddItem("SYMBOL "+string(m_listview.ItemsTotal()));
         //--- Move o polegar da barra de rolagem para o final da lista
         m_listview.Scrolling();
        }
      //--- Adiciona um item à lista de caixas de seleção, se o montante total é menor do que o especificado
      if(m_checkbox_list.ItemsTotal()<m_spin_edit5.GetValue())
        {
         m_checkbox_list.AddItem("Checkbox "+string(m_checkbox_list.ItemsTotal()));
         //--- Move o polegar da barra de rolagem para o final da lista
         m_checkbox_list.Scrolling();
        }
      //--- Redesenha o gráfico
      m_chart.Redraw();
     }
  }

A manipulação dos eventos de pressionamento do botão de limpeza e reconstrução das listas e a tabela de consulta são da seguinte forma: 

//+------------------------------------------------------------------+
//| Manipulador de eventos do gráfico                                |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- O evento de pressionamento do botão
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //--- Evento do primeiro botão
      if(lparam==m_simple_button1.Id())
        {
         //--- Limpa a tabela
         m_table.Clear();
         return;
        }
      //--- Evento do segundo botão
      if(lparam==m_simple_button2.Id())
        {
         //--- Reconstrói a tabela
         m_table.Rebuilding((int)m_spin_edit3.GetValue(),(int)m_spin_edit4.GetValue(),
                            (int)m_spin_edit1.GetValue(),(int)m_spin_edit2.GetValue());
         //--- Inicializa a tabela
         InitializeTable();
         //--- Atualiza a tabela para mostrar as mudanças
         m_table.UpdateTable();
         return;
        }
      //--- Evento do terceiro botão
      if(lparam==m_simple_button3.Id())
        {
         //--- Limpar as listas
         m_listview.Clear();
         m_checkbox_list.Clear();
         return;
        }
      //--- Evento do segundo botão
      if(lparam==m_simple_button4.Id())
        {
         //--- Reconstrói as listas
         m_checkbox_list.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
         m_listview.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
         //--- Escolhe o oitavo item na exibição da lista
         m_listview.SelectItem(7);
         //--- Preenchendo a exibição da lista com dados
         int items_total=m_listview.ItemsTotal();
         for(int i=0; i<items_total; i++)
            m_listview.SetItemValue(i,"SYMBOL "+string(i));
         //--- Preenche a lista de caixas de seleção com os dados, assinala cada segundo da caixa de seleção
         items_total=m_checkbox_list.ItemsTotal();
         for(int i=0; i<items_total; i++)
           {
            m_checkbox_list.SetItemValue(i,"Checkbox "+string(i));
            m_checkbox_list.SetItemState(i,(i%2!=0)? true : false);
           }
         //---
         return;
        }
      //---
      return;
     }
  }

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

 

Conclusão

A esquemática da biblioteca para a criação das interfaces gráficas no atual estágio de desenvolvimento é parecido com a imagem abaixo:

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

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


Na próxima versão da biblioteca, os controles existentes serão melhorados e complementados com novas funcionalidades. Abaixo, você pode baixar a versão mais recente da biblioteca e seus arquivos de teste.

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. 

Traduzido do russo por MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/2943

Arquivos anexados |
Interfaces Gráficas X: Os Controles Horário, Lista de Caixas de Seleção e Tabela Ordenada (build 6) Interfaces Gráficas X: Os Controles Horário, Lista de Caixas de Seleção e Tabela Ordenada (build 6)

O desenvolvimento da biblioteca para a criação de interfaces gráficas continua. Os controles Horário e a Lista de Caixas de Seleção serão discutidos neste momento. Além disso, agora a classe CTable fornece a capacidade de classificar os dados em ordem crescente ou decrescente.

Modelagem 3D em MQL5 Modelagem 3D em MQL5

As séries temporais são um sistema dinâmico em que os valores de uma variável aleatória chegam de forma consistente, isto é, de forma contínua ou em intervalos. A transição para a análise 3D de mercado fornece um novo olhar sobre os complexos processos e fenômenos de interesse para os investigadores. Este artigo descreve as funções de visualização para representações em 3D de dados bidimensionais.

Canal universal com GUI Canal universal com GUI

Todos os indicadores de canais apresentam três linhas, isto é: central, superior e inferior. A linha central, quanto à sua plotagem, é idêntica à média móvel. Na maioria dos casos, para a plotagem do canal, é utilizada a média móvel. As linhas superior e inferior são equidistantes da linha central. Esta distância pode ser determinada simplesmente em pontos, em porcentagem do preço (indicador Envelopes), pode ser usado o valor do desvio padrão (bandas de Bollinger), pode ser empregado o valor do indicador de ATR (canal Keltner).

Visualize isto! Biblioteca gráfica em linguagem MQL5 como equivalente a plot de R Visualize isto! Biblioteca gráfica em linguagem MQL5 como equivalente a plot de R

A exibição visual usando gráficos desempenha um importante papel na exploração e estudo de padrões regulares. Nas populares linguagens de programação entre a comunidade científica, tais como R e Python, a função especial plot é destinada para visualização. Com ela você pode desenhar linhas, gráficos de dispersão e histogramas para visualizar padrões. Em linguagem MQL5 você pode fazer a mesma coisa usando a classe CGraphics.