Interfaces gráficas VII: O Controle Tabela (Capítulo 1)

Anatoli Kazharski | 10 outubro, 2016


Conteúdo

 

 

Introdução

O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) explica 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.

Este artigo lida com três classes que lhe permitem criar diferentes tipos de tabelas para a exibição de conjuntos de dados bidimensionais como parte da interface gráfica das aplicações em MQL5:

  • tabela com o rótulo de texto;
  • tabela com a caixa de seleção;
  • tabela renderizada.

Cada tipo de tabela possui suas características únicas, sendo que suas vantagens são descritas abaixo.

No próximo artigo (parte VII, capítulo 2), nós vamos descrever os seguintes controles:

  • abas;
  • abas com imagens.

 


O controle Tabela com o rótulo de texto

Uma tabela é um controle GUI complexo, uma vez que ele inclui outros controles - barras de rolagem horizontal e vertical. Como os dados podem exceder o espaço disponível da área especificada do controle, as barras de rolagem permitem que você desloque os dados exibidos do array tanto na vertical quanto na horizontal.

As tabelas com o rótulo de texto consistem nos seguintes componentes:

  1. Fundo.
  2. Rótulos de texto.
  3. Barra de rolagem vertical.
  4. Barra de rolagem horizontal.

 Fig. 1. Partes integrantes do controle tabela com o rótulo de texto

Fig. 1. Partes integrantes do controle tabela com o rótulo de texto

Vamos dar uma olhada mais a fundo na classe deste controle.

 


Desenvolvimento da Classe CLabelsTable

Crie o arquivo LabelsTable.mqh e inclua-o na biblioteca (WndContainer.mqh):

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include "LabelsTable.mqh"

Dentro do arquivo LabelsTable.mqh, crie a classe CLabelsTable com o conjunto padrão de métodos que devem estar presentes em cada controle da biblioteca, bem como o método para guardar o ponteiro para o formulário que o controle deve ser conectado. Faça o mesmo para todos os controles mencionados no artigo atual.

//+------------------------------------------------------------------+
//| Classe para criar uma tabela com o rótulo de texto               |
//+------------------------------------------------------------------+
class CLabelsTable : public CElement
  {
private:
   //--- Ponteiro para o formulário ao qual o elemento está anexado
   CWindow          *m_wnd;
   //---
public:
                     CLabelsTable(void);
                    ~CLabelsTable(void);
   //--- (1) Armazena o ponteiro do formulário, (2) retorna os ponteiros para as barras de rolagem
   void              WindowPointer(CWindow &object)               { m_wnd=::GetPointer(object);      }
   //---
public:
   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Move o elemento
   virtual void      Moving(const int x,const int y);
   //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Reseta a cor
   virtual void      ResetColors(void) {}
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CLabelsTable::CLabelsTable(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CLabelsTable::~CLabelsTable(void)
  {
  }

Antes de criar a tabela, especifique algumas de suas propriedades. Vamos nomeá-las.

  • Altura da linha
  • Margem da primeira coluna a partir da borda esquerda do controle
  • Distância entre as colunas
  • Cor de fundo
  • Cor do texto
  • Modo de fixação da primeira linha
  • Modo de fixação da primeira coluna
  • Número total de colunas
  • Número total de linhas
  • Número de colunas da parte visível da tabela
  • Número de linhas da parte visível da tabela

Neste tipo de tabela, as coordenadas do rótulo de texto são calculadas a partir do ponto de ancoragem central (ANCHOR_CENTER). Portanto, a margem da borda esquerda do controle para os rótulos de texto da primeira coluna, bem como a distância entre os rótulos de texto de cada coluna são calculados a partir do centro de cada um deles.

Já para os modos de fixação, pode ser necessário para a primeira linha da tabela e/ou a primeira coluna (onde o usuário pode definir os nomes para cada coluna e/ou de uma série de dados) para permanecer visível em todos os momentos, mesmo quando o deslizador da barra de rolagem vertical e/ou horizontal é deslocado longe da primeira posição. Os modos de fixação do cabeçalho podem ser usados tanto em conjunto como separadamente. 

class CLabelsTable : public CElement
  {
private:
   //--- Altura da linha
   int               m_row_y_size;
   //--- Cor de fundo da tabela
   color             m_area_color;
   //--- Cor padrão do texto da tabela
   color             m_text_color;
   //--- Distância entre o ponto de ancoragem da primeira coluna e a borda esquerda do controle
   int               m_x_offset;
   //--- Distância entre os pontos de ancoragem das colunas
   int               m_column_x_offset;
   //--- Modo de fixação da primeira linha
   bool              m_fix_first_row;
   //--- Modo de fixação da primeira coluna
   bool              m_fix_first_column; 
   //--- Prioridades do clique do botão esquerdo do mouse
   int               m_zorder;
   int               m_area_zorder;
   //---
public:
   //--- (1) A cor do fundo, (2) a cor do texto
   void              AreaColor(const color clr)                   { m_area_color=clr;                }
   void              TextColor(const color clr)                   { m_text_color=clr;                }
   //--- (1) Altura da linha, (2) define a distância entre o ponto de ancoragem da primeira coluna e a borda esquerda da tabela,
   //    (3) define a distância entre os pontos de ancoragem das colunas
   void              RowYSize(const int y_size)                   { m_row_y_size=y_size;             }
   void              XOffset(const int x_offset)                  { m_x_offset=x_offset;             }
   void              ColumnXOffset(const int x_offset)            { m_column_x_offset=x_offset;      }
   //--- (1) Obtém e (2) define o modo de fixação da primeira linha
   bool              FixFirstRow(void)                      const { return(m_fix_first_row);         }
   void              FixFirstRow(const bool flag)                 { m_fix_first_row=flag;            }
   //--- (1) Obtém e (2) define o modo de fixação da primeira coluna
   bool              FixFirstColumn(void)                   const { return(m_fix_first_column);      }
   void              FixFirstColumn(const bool flag)              { m_fix_first_column=flag;         }
  };

Vamos criar os métodos CLabelsTable::TableSize() e CLabelsTable::VisibleTableSize() para definir um número total e visível de linhas e colunas da tabela. Além disso, nós precisamos de arrays dinâmicos bidimensionais na forma de estruturas. Uma das estruturas (LTLabels) cria o array de rótulos de texto para a parte visível da tabela, enquanto que o (LTOptions) armazena todos os valores e propriedades de cada célula da tabela. Em nossa aplicação, ele irá armazenar os valores exibidos (linhas) e a cor do texto.

Devem ser passados dois argumentos (números de colunas e linhas) ​​para os métodos CLabelsTable::TableSize() e CLabelsTable::VisibleTableSize(). Os valores passados são ​​submetidos a uma verificação no início dos métodos no caso do número de colunas ser inferior a um e o número de linhas ser menor que dois. OS tamanhos de todos os arrays são definidos e as propriedades dos arrays são inicializados depois.

Além dos métodos do tamanho da tabela, nós também precisamos dos métodos para receber o tamanho total e visível das colunas e linhas. 

class CLabelsTable : public CElement
  {
private:
   //--- Array de objetos para a parte visível da tabela
   struct LTLabels
     {
      CLabel            m_rows[];
     };
   LTLabels          m_columns[];
   //--- Array de valores da tabela e suas propriedades
   struct LTOptions
     {
      string            m_vrows[];
      color             m_colors[];
     };
   LTOptions         m_vcolumns[];
   //---
public:
   //--- Retorna o número total de (1) linhas e (2) colunas
   int               RowsTotal(void)                        const { return(m_rows_total);            }
   int               ColumnsTotal(void)                     const { return(m_columns_total);         }
   //--- Retorna o número de (1) linhas e (2) colunas da parte visível da tabela
   int               VisibleRowsTotal(void)                 const { return(m_visible_rows_total);    }
   int               VisibleColumnsTotal(void)              const { return(m_visible_columns_total); }

   //--- Define o (1) tamanho da tabela e (2) o tamanho da sua parte visível
   void              TableSize(const int columns_total,const int rows_total);
   void              VisibleTableSize(const int visible_columns_total,const int visible_rows_total);
  };
//+------------------------------------------------------------------+
//| Define o tamanho da tabela                                       |
//+------------------------------------------------------------------+
void CLabelsTable::TableSize(const int columns_total,const int rows_total)
  {
//--- Deve haver pelo menos uma coluna
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Deve haver pelo menos duas linhas
   m_rows_total=(rows_total<2) ? 2 : rows_total;
// --- Define o tamanho do array de colunas
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Ajusta o tamanho dos arrays de linhas
   for(int i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_colors,m_rows_total);
      //--- Inicializa o array de cores de texto com o valor padrão
      ::ArrayInitialize(m_vcolumns[i].m_colors,m_text_color);
     }
  }
//+------------------------------------------------------------------+
//| Define o tamanho da parte visível da tabela                      |
//+------------------------------------------------------------------+
void CLabelsTable::VisibleTableSize(const int visible_columns_total,const int visible_rows_total)
  {
//--- Deve haver pelo menos uma coluna
   m_visible_columns_total=(visible_columns_total<1) ? 1 : visible_columns_total;
//--- Deve haver pelo menos duas linhas
   m_visible_rows_total=(visible_rows_total<2) ? 2 : visible_rows_total;
// --- Define o tamanho do array de colunas
   ::ArrayResize(m_columns,m_visible_columns_total);
//--- Ajusta o tamanho dos arrays de linhas
   for(int i=0; i<m_visible_columns_total; i++)
      ::ArrayResize(m_columns[i].m_rows,m_visible_rows_total);
  }

Todas as propriedades devem ser inicializadas pelos valores padrão antes da criação do controle. Eu recomendo fazer isto direto do construtor da classe:

//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CLabelsTable::CLabelsTable(void) : m_fix_first_row(false),
                                   m_fix_first_column(false),
                                   m_row_y_size(18),
                                   m_x_offset(30),
                                   m_column_x_offset(60),
                                   m_area_color(clrWhiteSmoke),
                                   m_text_color(clrBlack),
                                   m_rows_total(2),
                                   m_columns_total(1),
                                   m_visible_rows_total(2),
                                   m_visible_columns_total(1)
  {
//--- Armazena o nome da classe do elemento na classe base
   CElement::ClassName(CLASS_NAME);
//--- Define as prioridades do botão esquerdo do mouse
   m_zorder      =0;
   m_area_zorder =1;
//--- Ajusta o tamanho da tabela e sua parte visível
   TableSize(m_columns_total,m_rows_total);
   VisibleTableSize(m_visible_columns_total,m_visible_rows_total);
  }

Vamos criar quatro métodos privados e um público para uma chamada externa para desenvolver o controle. A fim de permitir que os usuários configurem as barras de rolagem da tabela, nós devemos adicionar os métodos que retornam os seus ponteiros. 

class CLabelsTable : public CElement
  {
private:
   //--- Objetos para a criação de uma tabela
   CRectLabel        m_area;
   CScrollV          m_scrollv;
   CScrollH          m_scrollh;
   //--- Array de objetos para a parte visível da tabela
   struct LTLabels
     {
      CLabel            m_rows[];
     };
   LTLabels          m_columns[];
   //---
public:
   //--- Retorna os ponteiros para as barras de rolagem
   CScrollV         *GetScrollVPointer(void)                const { return(::GetPointer(m_scrollv)); }
   CScrollH         *GetScrollHPointer(void)                const { return(::GetPointer(m_scrollh)); }
   //--- Métodos para a criação da tabela
   bool              CreateLabelsTable(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateLabels(void);
   bool              CreateScrollV(void);
   bool              CreateScrollH(void);
  };

De todos os métodos de criação de objetos, é exibido aqui somente o CLabelsTable::CreateLabels() para o desenvolvimento do array de rótulo de texto. Tenha certeza de adicionar os índices da linha e da coluna ao criar um nome de objeto. Todos os outros métodos podem ser encontrados nos arquivos anexados abaixo. 

//+------------------------------------------------------------------+
//| Cria um array de rótulos de texto                                |
//+------------------------------------------------------------------+
bool CLabelsTable::CreateLabels(void)
  {
//--- Coordenadas e deslocamento
   int x      =CElement::X();
   int y      =0;
   int offset =0;
//--- Colunas
   for(int c=0; c<m_visible_columns_total; c++)
     {
      //--- Cálculo da tabela de deslocamento
      offset=(c>0) ? m_column_x_offset : m_x_offset;
      //--- Cálculo da coordenada X
      x=x+offset;
      //--- Linhas
      for(int r=0; r<m_visible_rows_total; r++)
        {
         //--- Cria o nome do objeto
         string name=CElement::ProgramName()+"_labelstable_label_"+(string)c+"_"+(string)r+"__"+(string)CElement::Id();
         //--- Calcula a coordenada Y
         y=(r>0) ? y+m_row_y_size-1 : CElement::Y()+10;
         //--- Cria o objeto
         if(!m_columns[c].m_rows[r].Create(m_chart_id,name,m_subwin,x,y))
            return(false);
         //--- Define as propriedades
         m_columns[c].m_rows[r].Description(m_vcolumns[c].m_vrows[r]);
         m_columns[c].m_rows[r].Font(FONT);
         m_columns[c].m_rows[r].FontSize(FONT_SIZE);
         m_columns[c].m_rows[r].Color(m_text_color);
         m_columns[c].m_rows[r].Corner(m_corner);
         m_columns[c].m_rows[r].Anchor(ANCHOR_CENTER);
         m_columns[c].m_rows[r].Selectable(false);
         m_columns[c].m_rows[r].Z_Order(m_zorder);
         m_columns[c].m_rows[r].Tooltip("\n");
         //--- Margens a partir da borda do formulário
         m_columns[c].m_rows[r].XGap(x-m_wnd.X());
         m_columns[c].m_rows[r].YGap(y-m_wnd.Y());
         //--- Coordenadas
         m_columns[c].m_rows[r].X(x);
         m_columns[c].m_rows[r].Y(y);
         //--- Armazena o ponteiro de objeto
         CElement::AddToArray(m_columns[c].m_rows[r]);
        }
     }
//---
   return(true);
  }

Nós vamos precisar dos métodos para alterar os valores e as propriedades de qualquer célula da tabela a qualquer momento após ela ter sido criada. Vamos escrever os métodos CLabelsTable::SetValue() e CLabelsTable::GetValue() para alterar e receber um valor de célula. Para ambos os métodos, os índices da coluna e linha da tabela devem ser passados como os dois primeiros argumentos. O valor a ser colocado dentro do array para os índices especificados devem ser passados como um terceiro argumento no método CLabelsTable::SetValue(). A verificação se o tamanho do array não foi excedido é obrigatório no início dos métodos. 

class CLabelsTable : public CElement
  {
public:
   //--- Define o valor para a célula da tabela especificada
   void              SetValue(const int column_index,const int row_index,const string value);
   //--- Obtém o valor da célula da tabela especificada
   string            GetValue(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Define o valor para a célula da tabela especificada              |
//+------------------------------------------------------------------+
void CLabelsTable::SetValue(const int column_index,const int row_index,const string value)
  {
//--- Verifica se o tamanho da coluna não excedeu
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return;
//--- Verifica se o tamanho da linha não excedeu
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return;
//--- Define o valor
   m_vcolumns[column_index].m_vrows[row_index]=value;
  }
//+------------------------------------------------------------------+
//| Retorna o valor no índice especificado                           |
//+------------------------------------------------------------------+
string CLabelsTable::GetValue(const int column_index,const int row_index)
  {
//--- Verifica se o tamanho da coluna não excedeu
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return("");
//--- Verifica se o tamanho da linha não excedeu
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return("");
//--- Retorna o valor
   return(m_vcolumns[column_index].m_vrows[row_index]);
  }

Além de alterar os valores da tabela, os usuários da biblioteca podem querer alterar a cor do texto. Por exemplo, os valores positivos podem ser exibidos em verde, enquanto que os negativos podem ser exibidos em vermelho. Vamos implementar o método CLabelsTable::TextColor() para isso. Ele é semelhante ao CLabelsTable::SetValue(), a única diferença é que a cor é passada como terceiro argumento. 

class CLabelsTable : public CElement
  {
public:
   //--- Muda a cor do texto na célula da tabela especificada
   void              TextColor(const int column_index,const int row_index,const color clr);
  };
//+------------------------------------------------------------------+
//| Altera a cor nos índices especificados                           |
//+------------------------------------------------------------------+
void CLabelsTable::TextColor(const int column_index,const int row_index,const color clr)
  {
//--- Verifica se o tamanho da coluna não excedeu
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return;
//--- Verifica se o tamanho da linha não excedeu
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return;
//--- Define a cor
   m_vcolumns[column_index].m_colors[row_index]=clr;
  }

As mudanças implementadas são exibidas somente após a atualização da tabela. Para fazer isso, nós devemos criar um método universal que também é para ser usado para deslocar os dados da tabela em relação à posição dos controles deslizantes da barra de rolagem. 

Vamos chamar esse método de CLabelsTable::UpdateTable(). Se os cabeçalhos estão bloqueados, o deslocamento dos dados começam a partir do segundo índice (1) do array para assegurar que a primeira linha esteja sempre na parte superior e/ou na coluna da extrema esquerda. As variáveis ​​t e l são declaradas no início do método, e o valor de 1 ou 0 é atribuído a eles, dependendo do modo atual utilizado.

A fim de definir o índice, a partir do qual o deslocamento/atualização de dados deve ser iniciado, deve-se obter as posições atuais dos controles deslizantes da barra de rolagem. Os cabeçalhos na coluna da esquerda e da linha superior são deslocados em loops separados (se os modos estão habilitados).

Os dados de base da tabela e a cor da célula são deslocados no loop duplo no final do método. 

class CLabelsTable : public CElement
  {
public:
   //--- Atualiza os dados da tabela considerando as mudanças recentes
   void              UpdateTable(void);
  };
//+------------------------------------------------------------------+
//| Atualiza os dados da tabela considerando as mudanças recentes    |
//+------------------------------------------------------------------+
void CLabelsTable::UpdateTable(void)
  {
//--- Desloca o índice por um, se o modo de cabeçalho fixo estiver habilitado
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtém as posições atuais dos controles deslizantes das barras de rolagem vertical e horizontal
   int h=m_scrollh.CurrentPos()+l;
   int v=m_scrollv.CurrentPos()+t;
//--- Deslocamento dos cabeçalhos na coluna da esquerda
   if(m_fix_first_column)
     {
      m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]);
      //--- Linhas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         if(r>=t && r<m_rows_total)
            m_columns[0].m_rows[r].Description(m_vcolumns[0].m_vrows[v]);
         //---
         v++;
        }
     }
//--- Deslocamento dos cabeçalhos na linha superior
   if(m_fix_first_row)
     {
      m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]);
      //--- Colunas
      for(int c=l; c<m_visible_columns_total; c++)
        {
         if(h>=l && h<m_columns_total)
            m_columns[c].m_rows[0].Description(m_vcolumns[h].m_vrows[0]);
         //---
         h++;
        }
     }
//--- Obtém a posição atual do controle deslizante da barra de rolagem horizontal
   h=m_scrollh.CurrentPos()+l;
//--- Colunas
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Obtém a posição atual do controle deslizante da barra de rolagem vertical
      v=m_scrollv.CurrentPos()+t;
      //--- Linhas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Desloca os dados da tabela
         if(v>=t && v<m_rows_total && h>=l && h<m_columns_total)
           {
            //--- Ajuste de cores
            m_columns[c].m_rows[r].Color(m_vcolumns[h].m_colors[v]);
            //--- Ajuste do valor
            m_columns[c].m_rows[r].Description(m_vcolumns[h].m_vrows[v]);
            v++;
           }
        }
      //---
      h++;
     }
  }

Vamos implementar o recuo rápido quando o botão esquerdo do mouse for pressionado sobre os botões da barra de rolagem, semelhante à forma que foi feita para os controles da campo de edição e lista. Não há motivos em exibir o código do método CLabelsTable::FastSwitching() aqui, uma vez que ele é muito semelhante aos métodos de mesmo nome descritos nos artigos anteriores nas classes CListView, CSpinEdit e CCheckBoxEdit

O código dos métodos para o processamento dos eventos do controle podem ser introduzidos, como é mostrado na lista abaixo: 

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CLabelsTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipulação do evento do movimento do cursor
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai se o elemento está oculto
      if(!CElement::IsVisible())
         return;
      //--- Coordenadas e o estado do botão esquerdo do mouse
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Verificando o foco sobre a tabela
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Mova a lista se a gestão do deslizador for habilitada
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state))
         UpdateTable();
      //---
      return;
     }
//--- Manipulando o pressionamento de objetos
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Se o pressionamento foi sobre o botão da barra de rolagem da tabela
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) ||
         m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam))
         //--- Desloca a tabela em relação a barra de rolagem
         UpdateTable();
      //---
      return;
     }
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CLabelsTable::OnEventTimer(void)
  {
//--- Se este é um elemento suspenso
   if(CElement::IsDropdown())
      FastSwitching();
//--- Se este não é um elemento suspenso, leva em consideração a disponibilidade atual do formulário
   else
     {
      //--- Acompanha o avanço rápido da tabela apenas se o formulário não estiver bloqueado
      if(!m_wnd.IsLocked())
         FastSwitching();
     }
  }

Uma tabela é um controle GUI (interface gráfica do usuário) complexo. Portanto, os ponteiros para seus outros controles (representados por barras de rolagem horizontal e vertical, em nosso caso) devem ser incluídos no banco de dados do ponteiro. Nó devemos criar o array pessoal de ponteiros para as tabelas. Você pode encontrar todas essas mudanças no arquivo WndContainer.mqh da classe CWndContainer. Este tópico já foi coberto em outros artigos da série. Por isso, eu vou pular para a parte de teste da tabela com o rótulo de texto. 

 


Teste da Tabela com o Rótulo de Texto

Para fins de teste, nós vamos utilizar o Expert Advisor do artigo anterior somente com o menu principal e a barra de estado. Para adicionar a tabela com o rótulo de texto no gráfico da aplicação MQL, a instância da classe do tipo CLabelsTable, bem como o método e os recuos do ponto extremo do formulário devem ser declarados (veja o código abaixo).

class CProgram : public CWndEvents
  {
private:
   //--- Tabela com o rótulo de texto
   CLabelsTable      m_labels_table;
   //---
private:
   //--- Tabela com o rótulo de texto
#define LABELS_TABLE1_GAP_X   (1)
#define LABELS_TABLE1_GAP_Y   (42)
   bool              CreateLabelsTable(void);
  };

Agora, vamos examinar o código do método CProgram::CreateLabelsTable() em detalhes. Nós precisamos fazer uma tabela que consiste de 21 colunas e 100 linhas. O número de colunas visíveis é igual a 5, enquanto que o número de linhas visíveis é igual a 10. Nós devemos fixar a fila superior e a primeira coluna para evitar que elas sejam movidas. Depois que a tabela é construída, ela é preenchida com valores aleatórios (de -1000 até 1000) e as cores são definidas. Os valores positivos são exibidos em verde, enquanto que os negativas - em vermelho. Tenha certeza de atualizar a tabela para exibir as últimas alterações

//+------------------------------------------------------------------+
//| Cria a tabela com o rótulo de texto                              |
//+------------------------------------------------------------------+
bool CProgram::CreateLabelsTable(void)
  {
#define COLUMNS1_TOTAL (21)
#define ROWS1_TOTAL    (100)
//--- Salva um ponteiro para o formulário
   m_labels_table.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+LABELS_TABLE1_GAP_X;
   int y=m_window1.Y()+LABELS_TABLE1_GAP_Y;
//--- Número de colunas e linhas visíveis
   int visible_columns_total =5;
   int visible_rows_total    =10;
//--- Define as propriedades
   m_labels_table.XSize(400);
   m_labels_table.XOffset(40);
   m_labels_table.ColumnXOffset(75);
   m_labels_table.FixFirstRow(true);
   m_labels_table.FixFirstColumn(true);
   m_labels_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_labels_table.VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Cria a tabela
   if(!m_labels_table.CreateLabelsTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Preenche a tabela:
//    A primeira célula está vazia
   m_labels_table.SetValue(0,0,"-");
//--- Cabeçalhos das colunas
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=0; r<1; r++)
         m_labels_table.SetValue(c,r,"SYMBOL "+string(c));
     }
//--- Cabeçalhos das linhas, o alinhamento do texto é para a direita
   for(int c=0; c<1; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        m_labels_table.SetValue(c,r,"PARAMETER "+string(r));
     }
//--- Formatação de dados e da tabela (cores de fundo e das celulas)
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
         m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000));
     }
//--- Define a cor do texto nas células da tabela
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed);
//--- Atualiza a tabela
   m_labels_table.UpdateTable();
//--- Adiciona o ponteiro do elemento para a base
   CWndContainer::AddToElementsArray(0,m_labels_table);
   return(true);
  }

Além disso, nós devemos testar como os valores da tabela são alterados quando o programa é executado em um gráfico do terminal. Para fazer isso, o código é adicionado ao timer CProgram::OnTimerEvent() de uma classe de aplicação MQL como é mostrado na lista abaixo: 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
// --- Atualize o segundo ponto da linha de estado a cada 500 milissegundos
   static int count=0;
   if(count<500)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//--- Rseta o timer
   count=0;
//--- Altera o valor no segundo ponto da linha de estado
   m_status_bar.ValueToItem(1,::TimeToString(::TimeLocal(),TIME_DATE|TIME_SECONDS));
//--- Preenche a tabela com os dados
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000));
//--- Define a cor do texto nas células da tabela
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed);
//--- Atualiza a tabela
   m_labels_table.UpdateTable();
  }

O método para a criação de uma tabela com o rótulo de texto deve ser chamado no método principal CProgram::CreateExpertPanel() do aplicativo GUI. A versão resumida do método é mostrada abaixo: 

//+------------------------------------------------------------------+
//| Cria o painel do expert                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Cria o formulário 1 para os controles
//--- Cria os controles:
//    Menu principal
//--- Menus de contexto
//--- Cria a linha de estado
//--- Tabela com o rótulo de texto
   if(!CreateLabelsTable())
      return(false);
//--- Redesenha o gráfico
   m_chart.Redraw();
   return(true);
  }

Agora, é hora de compilar o código e iniciar o aplicativo no gráfico. A imagem abaixo mostra o resultado:

 Fig. 2. Testando o controle tabela com o rótulo de texto

Fig. 2. Testando o controle tabela com o rótulo de texto

Tudo está funcionando bem. Agora, nós vamos examinar a classe para a criação do segundo tipo de tabelas. 


 


O Controle Tabela com a Caixa de Edição

Ao contrário da tabela com o rótulo de texto, a caixa de edição proporciona maior flexibilidade e possui mais recursos. Ela permite que você altere a cor do texto e também: 

  • definir o modo de alinhamento do texto na célula (esquerda/direita/centro);
  • alterar a cor de fundo e caixa de edição do quadro;
  •  alterar os valores da caixa de edição manualmente, se o modo apropriado for habilitado.

Tudo isso torna a tabela mais fácil de usar e facilita a sua utilização para uma vasta gama de tarefas. As tabelas com o rótulo de texto consistem nos seguintes componentes:

  1. Fundo
  2. Caixas de edição
  3. Barra de rolagem vertical
  4. Barra de rolagem horizontal

 Fig. 3. Partes integrantes do controle tabela com a caixa de edição

Fig. 3. Partes integrantes do controle tabela com a caixa de edição


Vamos ver como o código da tabela difere da anterior.

 


Desenvolvimento da Classe CTable

Vamos descrever as propriedades da tabela e destacar suas diferenças em relação às tabelas com o rótulo de texto. O array dos objetos gráficos para a parte visível da tabela é de outro tipo - (CEdit). Em outras palavras, ele tem caixas de edição em vez de rótulos de texto (veja o código abaixo).

class CTable : public CElement
  {
private:
   //--- Array de objetos para a parte visível da tabela
   struct TEdits
     {
      CEdit             m_rows[];
     };
   TEdits            m_columns[];
  };

Aqui, nós encontramos mais propriedades únicas para cada célula, uma vez que a tabela permite definir/alterar o método de alinhamento de texto e editar a cor de fundo da caixa além da cor de texto.

class CTable : public CElement
  {
private:
   //--- Arrays dos valores da tabela e suas propriedades
   struct TOptions
     {
      string            m_vrows[];
      ENUM_ALIGN_MODE   m_text_align[];
      color             m_text_color[];
      color             m_cell_color[];
     };
   TOptions          m_vcolumns[];
  };

Abaixo está a lista dos modos e funcionalidades que não estão presentes na tabela com o rótulo de texto.

  • Modo da tabela editável
  • Modo de realce da linha quando o cursor estiver sobre ela
  • Modo da linha selecionável
  • Altura da linha
  • Cor da grade
  • Cor de fundo do cabeçalho
  • Cor do texto do cabeçalho
  • A cor da célula quando o cursor estiver sobre ela
  • A cor da célula de texto padrão
  • Método de alinhamento de texto de célula padrão
  • Cor de fundo da linha realçada
  • Cor de destaque da linha do texto

Este tipo de tabela também permite fixar os cabeçalhos na primeira coluna e linha. Ao mover o controle deslizante da barra de rolagem, eles permanecem no lugar, se os modos estiverem habilitados. A listagem de código abaixo fornece a lista completa de caixas e métodos para definir as propriedades da tabela: 

class CTable : public CElement
  {
private:
   //--- Altura das linhas da tabela
   int               m_row_y_size;
   //--- Cor do (1) fundo e (2) a estrutura de fundo da tabela
   color             m_area_color;
   color             m_area_border_color;
   //--- Cor da grade
   color             m_grid_color;
   //--- Cor de fundo do cabeçalho
   color             m_headers_color;
   //--- Cor do texto de cabeçalho
   color             m_headers_text_color;
   //--- Cor das células em diferentes estados
   color             m_cell_color;
   color             m_cell_color_hover;
   //--- Cor padrão dos textos da celula
   color             m_cell_text_color;
   //--- Cor do (1) plano de fundo e (2) a linha de texto selecionada
   color             m_selected_row_color;
   color             m_selected_row_text_color;
   //--- Modo da tabela editável
   bool              m_read_only;
   //--- Modo de realce quando o cursor estiver pairando sobre a coluna
   bool              m_lights_hover;
   //--- Modo da linha selecionável
   bool              m_selectable_row;
   //--- Modo de fixação da primeira linha
   bool              m_fix_first_row;
   //--- Modo de fixação da primeira coluna
   bool              m_fix_first_column;
   //--- Modo de alinhamento de texto padrão nas caixas de edição
   ENUM_ALIGN_MODE   m_align_mode;
   //---
public:
   //--- Cor de (1) fundo e (2) da estrutura da tabela
   void              AreaColor(const color clr)                        { m_area_color=clr;                }
   void              BorderColor(const color clr)                      { m_area_border_color=clr;         }
   //--- (1) Obtém e (2) define o modo de fixação da primeira linha
   bool              FixFirstRow(void)                           const { return(m_fix_first_row);         }
   void              FixFirstRow(const bool flag)                      { m_fix_first_row=flag;            }
   //--- (1) Obtém e (2) define o modo de fixação da primeira coluna
   bool              FixFirstColumn(void)                        const { return(m_fix_first_column);      }
   void              FixFirstColumn(const bool flag)                   { m_fix_first_column=flag;         }
   //--- Cor de (1) fundo do cabeçalho, (2) o texto do cabeçalho e (3) a grade da tabela
   void              HeadersColor(const color clr)                     { m_headers_color=clr;             }
   void              HeadersTextColor(const color clr)                 { m_headers_text_color=clr;        }
   void              GridColor(const color clr)                        { m_grid_color=clr;                }
   //--- Tamanho das linhas ao longo do eixo Y
   void              RowYSize(const int y_size)                        { m_row_y_size=y_size;             }
   void              CellColor(const color clr)                        { m_cell_color=clr;                }
   void              CellColorHover(const color clr)                   { m_cell_color_hover=clr;          }
   //--- (1) "somente leitura", (2) linha realçada quando o mouse está sobre ela, (3) modos de linha selecionáveis
   void              ReadOnly(const bool flag)                         { m_read_only=flag;                }
   void              LightsHover(const bool flag)                      { m_lights_hover=flag;             }
   void              SelectableRow(const bool flag)                    { m_selectable_row=flag;           }
   //--- Método de alinhamento de texto da célula
   void              TextAlign(const ENUM_ALIGN_MODE align_mode)       { m_align_mode=align_mode;         }
  };

Na lista abaixo nós encontramos as características dos métodos destinados a definir as propriedades e receber os valores da tabela de índices da coluna e linha.

  • Tamanho total da tabela (número total de colunas e linhas)
  • Tamanho visível da tabela (número visível de colunas e linhas)
  • Método de alinhamento do texto da célula (esquerda/direita/centro)
  • Cor do texto
  • Cor de fundo
  • Define/altera o valor
  • Recebe o valor

Não há motivos para visualizar o código para estes métodos uma vez que os métodos semelhantes já foram descritos na seção da tabela com o rótulo de texto. Depois de definir as propriedades, certifique-se de atualizar a tabela, chamando o método CTable::UpdateTable(), de modo que as mudanças implementadas são exibidas. 

class CTable : public CElement
  {
public:
   //--- Define o (1) tamanho da tabela e (2) o tamanho da sua parte visível
   void              TableSize(const int columns_total,const int rows_total);
   void              VisibleTableSize(const int visible_columns_total,const int visible_rows_total);
   //--- Define (1) o modo de alinhamento do texto, (2) a cor do texto e (3) a cor de fundo da célula
   void              TextAlign(const int column_index,const int row_index,const ENUM_ALIGN_MODE mode);
   void              TextColor(const int column_index,const int row_index,const color clr);
   void              CellColor(const int column_index,const int row_index,const color clr);
   //--- Define o valor para a célula da tabela especificada
   void              SetValue(const int column_index,const int row_index,const string value);
   //--- Obtém o valor da célula da tabela especificada
   string            GetValue(const int column_index,const int row_index);
   //--- Atualiza os dados da tabela considerando as mudanças recentes
   void              UpdateTable(void);
  };

Agora, vamos considerar os métodos de gestão da tabela. Todos eles são métodos privados da classe para uso interno. Suas funções incluem:

  • Processamento do clique de uma linha da tabela.
  • Processamento da entrada do valor para uma célula da tabela.
  • Recebe uma ID do nome do objeto.
  • Recupera o índice da coluna a partir do nome do objeto.
  • Recupera um índice da linha a partir do nome do objeto.
  • Destaca a linha selecionada.
  • Altera a cor da linha da tabela quando o cursor passar sobre o botão.
  • Avanço/retrocesso rápido dos dados da tabela.

Vamos começar a partir do método CTable::OnClickTableRow() para processar o clique de uma linha da tabela. Várias verificações devem ser passadas no início do método. O programa sai do método nos seguintes casos:

  • o modo da tabela editável está habilitada;
  • uma das barras de rolagem está ativa (o cursor está em movimento);
  • o evento do clique não se refere a uma célula da tabela. Isto é determinado pela presença do nome do programa ou pela propriedade de adesão da célula da tabela como parte do nome do objeto;
  • o ID de controle não corresponde. O método CTable::IdFromObjectName() é usado para recuperar a identificação do nome do objeto. O método já foi descrito na análise de outros controles.

Agora, é hora de passar por todas as células procurando por aquela que foi pressionada pelo seu nome, considerando o modo atual de cabeçalhos fixos (a primeira linha) e a posição atual da barra de rolagem vertical. Se a célula pressionada foi encontrada, o índice da linha e o valor da célula atual são salvos nos campos da classe.

Se os cabeçalhos (primeira linha) foram pressionados, o programa sai do método. Caso contrário, uma mensagem personalizada é gerada. Ela contém o (1) ID do gráfico, (2) ID do evento (ON_CLICK_LIST_ITEM), (3) ID do controle e (4) o índice da linha selecionada. 

class CTable : public CElement
  {
private:
   //--- Processamento do clique de uma linha da tabela.
   bool              OnClickTableRow(const string clicked_object);
   //--- Obtém o identificador do nome do objeto
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Processamento do clique de uma linha da tabela                   |
//+------------------------------------------------------------------+
bool CTable::OnClickTableRow(const string clicked_object)
  {
//--- Sai se o modo tabela editável estiver habilitado
   if(!m_read_only)
      return(false);
//--- Sai, se a barra de rolagem está ativa
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Sai se o pressionamento não foi na célula da tabela
   if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- Obtém o identificador do nome do objeto
   int id=IdFromObjectName(clicked_object);
//--- Retorna, se o tipo definido não corresponder
   if(id!=CElement::Id())
      return(false);
//--- Busca o índice da linha
   int row_index=0;
//--- Desloca o índice por um, se o modo de cabeçalho fixo estiver habilitado
   int t=(m_fix_first_row) ? 1 : 0;
//--- Colunas
   for(int c=0; c<m_visible_columns_total; c++)
     {
      //--- Obtém a posição atual do controle deslizante da barra de rolagem vertical
      int v=m_scrollv.CurrentPos()+t;
      //--- Linhas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Se a o pressionamento não foi nesta célula
         if(m_columns[c].m_rows[r].Name()==clicked_object)
           {
            //--- Armazena o índice da linha
            m_selected_item=row_index=v;
            //--- Armazena a linha da célula
            m_selected_item_text=m_columns[c].m_rows[r].Description();
            break;
           }
         //--- Aumenta o contador da linha
         if(v>=t && v<m_rows_total)
            v++;
        }
     }
//--- Sai, se o cabeçalho foi pressionado
   if(m_fix_first_row && row_index<1)
      return(false);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElement::Id(),m_selected_item,"");
   return(true);
  }

Vamos escrever o método CTable::OnEndEditCell() para processar a entrada do valor em uma célula no modo tabela editável. O programa também deve passar por um certo número de controles no início do método. A saída do método é realizada nos seguintes casos:

  • o modo da tabela editável está desativada;
  • nome do programa ou a propriedade de adesão da célula da tabela não corresponde;
  • o ID de controle não corresponde.

Se todas as verificações são passadas, nós recebemos a coluna da parte visível da célula da tabela e os índices da linha (índices do array de objetos gráficos) usando os métodos auxiliares CTable::ColumnIndexFromObjectName() e CTable::RowIndexFromObjectName(). Agora, nós devemos adicionar as posições atuais dos controles deslizantes da barra de rolagem para os índices de objeto a fim de receber os índices do array de dados. Em seguida, nós devemos corrigir o índice da linha se o modo do cabeçalho fixo está habilitado e o índice do array de objeto é igual a zero. Então, nós precisamos verificar se o valor da célula foi alterado. Se sim, os novos valores são salvos no array de dados apropriado e a mensagem contendo o (1) ID do gráfico, (2) o ID do evento (ON_END_EDIT), (3) a ID do controle e (4) a linha formada entre os índices de uma coluna, linha e o valor atual da célula são gerados. O símbolo "_" é usado como um separador dentro da linha. 

class CTable : public CElement
  {
private:
   //--- Manipulando a entrada do valor na célula da tabela
   bool              OnEndEditCell(const string edited_object);
   //--- Recupera o índice da coluna a partir do nome do objeto
   int               ColumnIndexFromObjectName(const string object_name);
   //--- Recupera o índice da linha a partir do nome do objeto
   int               RowIndexFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Evento de finalização da edição do valor da célula               |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const string edited_object)
  {
//--- Sai se o modo da tabela editável está desativado
   if(m_read_only)
      return(false);
//--- Sai se o pressionamento não foi na célula da tabela
   if(::StringFind(edited_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- Obtém o identificador do nome do objeto
   int id=IdFromObjectName(edited_object);
//--- Retorna, se o tipo definido não corresponder
   if(id!=CElement::Id())
      return(false);
//--- Obtém os índices da célula da coluna e linha
   int c =ColumnIndexFromObjectName(edited_object);
   int r =RowIndexFromObjectName(edited_object);
//--- Obtém os índices da célula da coluna e linha no array de dados
   int vc =c+m_scrollh.CurrentPos();
   int vr =r+m_scrollv.CurrentPos();
//--- Ajusta o índice da linha, se um cabeçalho foi pressionado
   if(m_fix_first_row && r==0)
      vr=0;
//--- Obter o valor inserido
   string cell_text=m_columns[c].m_rows[r].Description();
//--- Se o valor da célula foi alterado
   if(cell_text!=m_vcolumns[vc].m_vrows[vr])
     {
      //--- Armazenar o valor no array
      SetValue(vc,vr,cell_text);
      //--- Envia uma mensagem sobre ele
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),0,string(vc)+"_"+string(vr)+"_"+cell_text);
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Recupera o índice da coluna a partir do nome do objeto           |
//+------------------------------------------------------------------+
int CTable::ColumnIndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtém o código do separador
   u_sep=::StringGetCharacter("_",0);
//--- Divide a string
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Verifica se o tamanho do array não excedeu
   if(array_size-3<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Retornar o índice do elemento
   return((int)result[array_size-3]);
  }
//+------------------------------------------------------------------+
//| Recupera o índice da linha a partir do nome do objeto            |
//+------------------------------------------------------------------+
int CTable::RowIndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtém o código do separador
   u_sep=::StringGetCharacter("_",0);
//--- Divide a string
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Verifica se o tamanho do array não excedeu
   if(array_size-2<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Retornar o índice do elemento
   return((int)result[array_size-2]);
  }

O método CTable::HighlightSelectedItem() é necessário para destacar a linha quando o modo apropriado é ativado. A linha destacada difere das cores do plano de fundo e de texto. Estas propriedades editáveis ​​já foram examinadas anteriormente.

Existem dois controles no início do método (ver a listagem abaixo). O programa sai a partir daqui se:

  • o modo de edição da célula da tabela está habilitado;
  • o modo de realce da linha está desativada.

Se as verificações foram aprovadas, é realizado o deslocamento por um índice para as colunas e linhas, caso os modos de cabeçalhos fixos estão habilitados. Agora, nós devemos encontrar a linha selecionada e definir a cor apropriada para as células de fundo e texto no loop duplo usando dois contadores (para as colunas e linhas) e considerando a posição atual dos controles deslizantes da barra de rolagem. A cor dos arrays é usada para os objetos a partir de outras linhas.  

class CTable : public CElement
  {
private:
   //--- Realce da linha selecionada
   void              HighlightSelectedItem(void);
  };
//+------------------------------------------------------------------+
//| Realce da linha selecionada                                      |
//+------------------------------------------------------------------+
void CTable::HighlightSelectedItem(void)
  {
//--- Sair se um dos modos ( "somente leitura", "linha selecionável") está desativado
   if(!m_read_only || !m_selectable_row)
      return;
//--- Desloca o índice por um, se o modo de cabeçalho fixo estiver habilitado
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtém a posição atual do controle deslizante da barra de rolagem horizontal
   int h=m_scrollh.CurrentPos()+l;
//--- Colunas
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Obtém a posição atual do controle deslizante da barra de rolagem vertical
      int v=m_scrollv.CurrentPos()+t;
      //--- Linhas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Desloca os dados da tabela
         if(v>=t && v<m_rows_total)
           {
            //--- Ajusta considerando a linha selecionada
            color back_color=(m_selected_item==v) ? m_selected_row_color : m_vcolumns[h].m_cell_color[v];
            color text_color=(m_selected_item==v) ? m_selected_row_text_color : m_vcolumns[h].m_text_color[v];
            //--- Ajusta a cor do texto e do fundo da célula
            m_columns[c].m_rows[r].Color(text_color);
            m_columns[c].m_rows[r].BackColor(back_color);
            v++;
           }
        }
      //---
      h++;
     }
  }

Outro modo útil é destacar a linha da tabela quando o cursor do mouse passa sobre ela. O método CTable::RowColorByHover() é utilizado para isso. Ele também inclui um certo número de controles. Se eles não são passados, o programa sai do método. A saída é realizada nos seguintes casos:

  • o modo da linha destacada quando o cursor do mouse estiver pairando sobre ele, está desativado;
  • o modo de edição de tabela está habilitado;
  • o formulário do controle onde ele está anexado está bloqueado;
  • uma das barras de rolagem está ativa (o deslizador está em movimento).

Se todas as verificações são passados, nós devemos passar por todas as células em um loop de duplo e mudar sua cor dependendo de qual linha da célula o cursor do mouse está. A única exceção é uma linha destacada. Quando ele é atingido, o contador da linha aumenta, mas a cor da célula da linha não é alterada. 

class CTable : public CElement
  {
private:
   //--- Altera a cor da linha da tabela quando o cursor passar sobre ele.
   void              RowColorByHover(const int x,const int y);
  };
//+-------------------------------------------------------------------+
//| Altera a cor da linha da tabela quando o cursor passar sobre ele  |
//+-------------------------------------------------------------------+
void CTable::RowColorByHover(const int x,const int y)
  {
//--- Sai se a linha destacada está desativada ou se o formulário é bloqueado
   if(!m_lights_hover || !m_read_only || m_wnd.IsLocked())
      return;
//--- Sai, se a barra de rolagem está no processo de se mover
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return;
//--- Desloca o índice por um, se o modo de cabeçalho fixo estiver habilitado
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtém a posição atual do controle deslizante da barra de rolagem horizontal
   int h=m_scrollh.CurrentPos()+l;
//--- Colunas
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Obtém a posição atual do controle deslizante da barra de rolagem vertical
      int v=m_scrollv.CurrentPos()+t;
      //--- Linhas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Verifica para evitar o array de exceder  o tamanho permitido
         if(v>=t && v<m_rows_total)
           {
            //--- Pula, se no modo "somente leitura", a seleção da fileira é ativada e o elemento selecionado é alcançável
            if(m_selected_item==v && m_read_only && m_selectable_row)
              {
               v++;
               continue;
              }
            //--- Realce a linha, se foi clicável
            if(x>m_columns[0].m_rows[r].X() && x<m_columns[m_visible_columns_total-1].m_rows[r].X2() &&
               y>m_columns[c].m_rows[r].Y() && y<m_columns[c].m_rows[r].Y2())
              {
               m_columns[c].m_rows[r].BackColor(m_cell_color_hover);
              }
            //--- Restaura a cor padrão, se o cursor estiver fora da área desta linha
            else
              {
               if(v>=t && v<m_rows_total)
                  m_columns[c].m_rows[r].BackColor(m_vcolumns[h].m_cell_color[v]);
              }
            //---
            v++;
           }
        }
      //---
      h++;
     }
  }

Nós examinamos todos os métodos de gestão da tabela. Você pode ver os detalhes do manipulador de eventos do controle CTable::OnEvent() no código abaixo. Por favor note que o método CTable::HighlightSelectedItem() também é utilizado após o processamento do movimento do controle deslizante da barra de rolagem vertical.

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipulação do evento do movimento do cursor
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Sai se o elemento está oculto
      if(!CElement::IsVisible())
         return;
      //--- Coordenadas e o estado do botão esquerdo do mouse
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Se a barra de rolagem está ativa
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state))
         //--- Desloca a tabela
         UpdateTable();
      //--- Realce da linha selecionada
      HighlightSelectedItem();
      //--- Altera a cor da linha da tabela quando o cursor passar sobre ele.
      RowColorByHover(x,y);
      return;
     }
//--- Manipulando o pressionamento de objetos
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Se linha da tabela é pressionada
      if(OnClickTableRow(sparam))
        {
         //--- Realce da linha selecionada
         HighlightSelectedItem();
         return;
        }
      //--- Se o botão da barra de rolagem foi pressionado
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) ||
         m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam))
        {
         //--- Atualiza os dados da tabela considerando as mudanças recentes
         UpdateTable();
         //--- Realce da linha selecionada
         HighlightSelectedItem();
         return;
        }
      return;
     }
//--- Lidando com a alteração do valor no evento campo de entrada
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      OnEndEditCell(sparam);
      //--- Reseta as cores da tabela
      ResetColors();
      return;
     }
  }

 


Teste da Tabela com a Caixa de Edição

Tudo está preparado para testar a tabela com a caixa de edição. Vamos fazer uma cópia do EA de teste anterior e eliminar todos os elementos relacionados com a tabela com o rótulo de texto. Agora, crie a instância da classe de tipo CTable na classe personalizada CProgram e declare o método para criar a tabela e recuos a partir do ponto extremo do formulário:

class CProgram : public CWndEvents
  {
private:
   //--- A tabela com a caixa de edição
   CTable            m_table;
   //---
private:
   //--- A tabela com a caixa de edição
#define TABLE1_GAP_X          (1)
#define TABLE1_GAP_Y          (42)
   bool              CreateTable(void);
  };

Vamos fazer uma tabela que consiste de 100 colunas e 1000 linhas. O número de colunas visíveis é 6, enquanto que o número de linhas visíveis é 15. Vamos fixar os cabeçalhos da tabela (a primeira linha e coluna), para que eles permaneçam no lugar quando os controles deslizantes da barra de rolagem estiverem se movendo. Nós devemos permitir que os modos de seleção da linha e destaque da linha quando o cursor passar sobre ele. 

Depois de criar o controle, preencha os arrays da tabela com os dados e formate-o. Por exemplo, vamos aplicar o método de alinhamento ALIGN_RIGHT para a primeira coluna de texto da célula. Deixe o fundo da linha da célula ser colorida no estilo "zebra", enquanto que o texto da coluna será colorido alternando entre as cores vermelho e azul. Tenha certeza de atualizar a tabela para exibir as alterações. 

//+------------------------------------------------------------------+
// Cria a tabela                                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(void)
  {
#define COLUMNS1_TOTAL (100)
#define ROWS1_TOTAL    (1000)
//--- Salva o ponteiro para o formulário
   m_table.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+TABLE1_GAP_X;
   int y=m_window1.Y()+TABLE1_GAP_Y;
//--- Número de colunas e linhas visíveis
   int visible_columns_total =6;
   int visible_rows_total    =15;
//--- Define as propriedades antes da criação
   m_table.XSize(600);
   m_table.RowYSize(20);
   m_table.FixFirstRow(true);
   m_table.FixFirstColumn(true);
   m_table.LightsHover(true);
   m_table.SelectableRow(true);
   m_table.TextAlign(ALIGN_CENTER);
   m_table.HeadersColor(C'255,244,213');
   m_table.HeadersTextColor(clrBlack);
   m_table.GridColor(clrLightGray);
   m_table.CellColorHover(clrGold);
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Cria o elemento do controle
   if(!m_table.CreateTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Preenche a tabela:
//    A primeira célula está vazia
   m_table.SetValue(0,0,"-");
//--- Cabeçalhos das colunas
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=0; r<1; r++)
         m_table.SetValue(c,r,"SYMBOL "+string(c));
     }
//--- Cabeçalhos das linhas, o alinhamento do texto é para a direita
   for(int c=0; c<1; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        {
         m_table.SetValue(c,r,"PARAMETER "+string(r));
         m_table.TextAlign(c,r,ALIGN_RIGHT);
        }
     }
//--- Formatação dos dados e da tabela (fundo e cores da célula)
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        {
         m_table.SetValue(c,r,string(c)+":"+string(r));
         m_table.TextColor(c,r,(c%2==0)? clrRed : clrRoyalBlue);
         m_table.CellColor(c,r,(r%2==0)? clrWhiteSmoke : clrWhite);
        }
     }
//--- Atualiza a tabela para exibir as alterações
   m_table.UpdateTable();
//--- Adiciona o objeto para o array total de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

O método CProgram::CreateTable() deve ser chamado no método principal da GUI do aplicativo (consulte a versão resumida abaixo): 

//+------------------------------------------------------------------+
//| Cria o painel do expert                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Cria o formulário 1 para os controles
//--- Cria os controles:
//    Menu principal
//--- Menus de contexto
//--- Cria a linha de estado
//--- Tabela com a caixa de edição
   if(!CreateTable())
      return(false);
//--- Redesenha o gráfico
   m_chart.Redraw();
   return(true);
  }

Compile o programa e execute-o em um gráfico. Se tudo foi feito corretamente, você deve obter o resultado como é mostrado na imagem abaixo:

 Fig. 4. Teste do controle tabela com caixa de edição

Fig. 4. Teste do controle tabela com caixa de edição

Tudo está funcionando bem. No entanto, se você colocar um texto superior a 63 caracteres em uma única célula, o texto não é exibido na íntegra. Todos os objetos gráficos do terminal capazes de exibir um texto tem uma limitação de 63 símbolos. Use a classe CCanvas para desenhar e ignorá-lo. Esta classe contém os métodos de exibição de texto sem quaisquer limitações. Nós já aplicamos isso ao desenhar alguns controles (linha de separação e menu de contexto) nos seguintes artigos:

Já que os 63 caracteres podem muitas vezes ser insuficientes para a exibição de dados, o terceiro tipo da tabela é desenhado usando a classe CCanvas

 


O Controle Tabela Renderizada

A tabela renderizada não tem limitações quanto ao número de caracteres em cada célula. Além disso, a largura de cada coluna pode ser configurada sem alterar a largura da tabela visível, o que é bastante difícil de alcançar quando se trabalha com outros tipos de tabelas que foram discutidas acima. Assim, nós já podemos notar algumas vantagens de uma tabela renderizada:

  • sem limitações quanto ao número de símbolos em cada célula;
  • a largura de cada coluna pode ser definida separadamente;
  • apenas um objeto (OBJ_BITMAP_LABEL) é usado para criar uma tabela em vez de múltiplos objetos como etiquetas de texto (OBJ_LABEL) ou caixas de edição (OBJ_EDIT).

As tabelas com o rótulo de texto consistem nos seguintes componentes:

  1. Fundo
  2. Tabela renderizada
  3. Barra de rolagem vertical
  4. Barra de rolagem horizontal

Fig. 5. Partes integrantes do controle tabela renderizada
 

Fig. 5. Partes integrantes do controle tabela renderizada

Vamos examinar o código da classe para a criação de tal tabela.

 


Desenvolvimento da Classe CCanvasTable

Vamos criar a estrutura CTOptions para armazenar os valores da tabela e suas propriedades:

//+------------------------------------------------------------------+
//| Classe para criar uma tabela renderizada                         |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Array de valores e propriedades da tabela
   struct CTOptions
     {
      string            m_vrows[];
      int               m_width;
      ENUM_ALIGN_MODE   m_text_align;
     };
   CTOptions         m_vcolumns[];
  };

Os campos de estrutura CTOptions são inicializados usando os valores padrão quando o tamanho básico da tabela (número total de colunas e linhas) é definido. Nós vamos definir a largura de todas as colunas de 100 pixels, enquanto que o método de alinhamento de texto na coluna das células será ALIGN_CENTER

class CCanvasTable : public CElement
  {
public:
   //--- Define o tamanho da tabela
   void              TableSize(const int columns_total,const int rows_total);
  };
//+------------------------------------------------------------------+
//| Define o tamanho da tabela                                       |
//+------------------------------------------------------------------+
void CCanvasTable::TableSize(const int columns_total,const int rows_total)
  {
//--- Deve haver pelo menos uma coluna
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Deve haver pelo menos duas linhas
   m_rows_total=(rows_total<2) ? 2 : rows_total;
// --- Define o tamanho do array de colunas
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Ajusta o tamanho dos arrays de linhas
   for(int i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      //--- Inicializa as propriedades da coluna com os valores padrão
      m_vcolumns[i].m_width      =100;
      m_vcolumns[i].m_text_align =ALIGN_CENTER;
     }
  }

Agora, vamos criar os métodos que permitem alterar a largura ou o método de alinhamento de texto de algumas colunas (se necessário), após o tamanho da tabela já ter sido definido. Para fazer isso, você precisa inicializar o array e simplesmente passá-lo para o método correspondente. O exemplo é mostrado abaixo. 

class CCanvasTable : public CElement
  {
public:
   //--- Define (1) o modo de alinhamento de texto e (2) a largura para cada coluna
   void              TextAlign(const ENUM_ALIGN_MODE &array[]);
   void              ColumnsWidth(const int &array[]);
  };
//+------------------------------------------------------------------+
//| Preenche o array de modos de alinhamento de texto                |
//+------------------------------------------------------------------+
void CCanvasTable::TextAlign(const ENUM_ALIGN_MODE &array[])
  {
   int total=0;
   int array_size=::ArraySize(array);
//--- Sai se um array de tamanho zero foi aprovado
   if(array_size<1)
      return;
//--- Ajuste o valor para evitar que o array exceda a faixa
   total=(array_size<m_columns_total)? array_size : m_columns_total;
//--- Armazena os valores na estrutura
   for(int c=0; c<total; c++)
      m_vcolumns[c].m_text_align=array[c];
  }
//+------------------------------------------------------------------+
//| Preenche o array de largura da coluna                            |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnsWidth(const int &array[])
  {
   int total=0;
   int array_size=::ArraySize(array);
//--- Sai se um array de tamanho zero foi aprovado
   if(array_size<1)
      return;
//--- Ajuste o valor para evitar que o array exceda a faixa 
   total=(array_size<m_columns_total)? array_size : m_columns_total;
//--- Armazena os valores na estrutura
   for(int c=0; c<total; c++)
      m_vcolumns[c].m_width=array[c];
  }

Antes de começar a criar a tabela, nós devemos definir a sua dimensão global, bem como o tamanho de sua parte visível, em relação aos parâmetros especificados (largura de todas as colunas, altura de todas as linhas e presença de barras de rolagem). Para fazer isso, nós vamos escrever o método CCanvasTable::CalculateTableSize() mostrado na lista abaixo: 

class CCanvasTable : public CElement
  {
private:
   //--- Tamanho total e tamanho da parte visível da tabela
   int               m_table_x_size;
   int               m_table_y_size;
   int               m_table_visible_x_size;
   int               m_table_visible_y_size;
//---
private:
   //--- Calcula o tamanho da tabela
   void              CalculateTableSize(void);
  };
//+------------------------------------------------------------------+
//| Calcula o tamanho da tabela                                      |
//+------------------------------------------------------------------+
void CCanvasTable::CalculateTableSize(void)
  {
//--- Calcula a largura total da tabela
   m_table_x_size=0;
   for(int c=0; c<m_columns_total; c++)
      m_table_x_size=m_table_x_size+m_vcolumns[c].m_width;
//--- Largura da tabela com uma barra de rolagem vertical
   int x_size=(m_rows_total>m_visible_rows_total) ? m_x_size-m_scrollh.ScrollWidth() : m_x_size-2;
//--- Se a largura de todas as colunas é inferior à largura da tabela, utiliza a largura da tabela
   if(m_table_x_size<m_x_size)
      m_table_x_size=x_size;
//--- Calcula a altura total da tabela
   m_table_y_size=m_cell_y_size*m_rows_total-(m_rows_total-1);
//--- Define o tamanho da estrutura para exibir um fragmento da imagem (parte visível da tabela)
   m_table_visible_x_size=x_size;
   m_table_visible_y_size=m_cell_y_size*m_visible_rows_total-(m_visible_rows_total-1);
//--- Se houver uma barra de rolagem horizontal, ajuste o tamanho do controle ao longo do eixo Y
   int y_size=m_cell_y_size*m_visible_rows_total+2-(m_visible_rows_total-1);
   m_y_size=(m_table_x_size>m_table_visible_x_size) ? y_size+m_scrollh.ScrollWidth()-1 : y_size;
  }

Depois de calcular o tamanho da tabela e criar a tela, nós precisamos desenvolver os métodos para desenhar a grade da tabela (frame) e o texto da célula. Para desenhar a grade, nós vamos escrever o método CCanvasTable::DrawGrid(). Em primeiro lugar, as linhas da grade horizontal são desenhadas no primeiro loop, em seguida, as linhas verticais são desenhadas no segundo loop.  

class CCanvasTable : public CElement
  {
private:
   //--- Cor da grade
   color             m_grid_color;
   //--- Tamanho (altura) das células
   int               m_cell_y_size;
//---
public:
   //--- Cor da grade
   void              GridColor(const color clr)           { m_grid_color=clr;                }
//---
private:
   //--- Desenha a grade
   void              DrawGrid(void);
  };
//+------------------------------------------------------------------+
//| Desenha a grade                                                  |
//+------------------------------------------------------------------+
void CCanvasTable::DrawGrid(void)
  {
//--- Cor da grade
   uint clr=::ColorToARGB(m_grid_color,255);
//--- Tamanho da tela para desenho
   int x_size =m_canvas.XSize()-1;
   int y_size =m_canvas.YSize()-1;
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=0;
//--- Linhas horizontais
   x1=0;
   y1=0;
   x2=x_size;
   y2=0;
   for(int i=0; i<=m_rows_total; i++)
     {
      m_canvas.Line(x1,y1,x2,y2,clr);
      y2=y1+=m_cell_y_size-1;
     }
//--- Linhas verticais
   x1=0;
   y1=0;
   x2=0;
   y2=y_size;
   for(int i=0; i<m_columns_total; i++)
     {
      m_canvas.Line(x1,y1,x2,y2,clr);
      x2=x1+=m_vcolumns[i].m_width;
     }
//--- Direita
   x1=x_size;
   y1=0;
   x2=x_size;
   y2=y_size;
   m_canvas.Line(x1,y1,x2,y2,clr);
  }

O método CCanvasTable::DrawText() para a elaboração de um texto é mais complicado que o método de desenho da grade. Não devemos considerar apenas o método de alinhamento de texto da coluna atual, mas também o da coluna anterior para calcular os recuos corretamente. Nós vamos definir o recuo de 10 pixels a partir da extremidade da célula para o alinhamento da direita e esquerda. O recuo a partir da borda da célula superior será de 3 pixels. O código do método é fornecido em detalhes abaixo: 

class CCanvasTable : public CElement
  {
private:
   //--- Cor do texto
   color             m_cell_text_color;
//---
public:
   //--- Cor do texto da tabela
   void              TextColor(const color clr)           { m_cell_text_color=clr;           }
//---
private:
   //--- Desenha o texto
   void              DrawText(void);
  };
//+------------------------------------------------------------------+
//| Desenha o texto                                                  |
//+------------------------------------------------------------------+
void CCanvasTable::DrawText(void)
  {
//--- Para calcular as coordenadas e os deslocamentos
   int  x             =0;
   int  y             =0;
   uint text_align    =0;
   int  column_offset =0;
   int  cell_x_offset =10;
   int  cell_y_offset =3;
//--- Cor do texto
   uint clr=::ColorToARGB(m_cell_text_color,255);
//--- Propriedades da fonte
   m_canvas.FontSet(FONT,-80,FW_NORMAL);
//--- Colunas
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Cálculo do deslocamento para a primeira coluna
      if(c==0)
        {
         //--- Alinhamento do texto nas células com base na definição do modo de cada coluna
         switch(m_vcolumns[0].m_text_align)
           {
            //--- Centro
            case ALIGN_CENTER :
               column_offset=column_offset+m_vcolumns[0].m_width/2;
               x=column_offset;
               break;
               //--- Direita
            case ALIGN_RIGHT :
               column_offset=column_offset+m_vcolumns[0].m_width;
               x=column_offset-cell_x_offset;
               break;
               //--- Esquerda
            case ALIGN_LEFT :
               x=column_offset+cell_x_offset;
               break;
           }
        }
      //--- Cálculo dos deslocamentos para todas as colunas, exceto a primeira
      else
        {
         //--- Alinhamento do texto nas células com base na definição do modo de cada coluna
         switch(m_vcolumns[c].m_text_align)
           {
            //--- Centro
            case ALIGN_CENTER :
               //--- Cálculo do deslocamento em relação ao alinhamento da coluna anterior
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+(m_vcolumns[c].m_width/2);
                     break;
                  case ALIGN_RIGHT :
                     column_offset=column_offset+(m_vcolumns[c].m_width/2);
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width+(m_vcolumns[c].m_width/2);
                     break;
                 }
               //---
               x=column_offset;
               break;
               //--- Direita
            case ALIGN_RIGHT :
               //--- Cálculo do deslocamento em relação ao alinhamento da coluna anterior
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                  case ALIGN_RIGHT :
                     column_offset=column_offset+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                 }
               //---
               break;
               //--- Esquerda
            case ALIGN_LEFT :
               //--- Cálculo do deslocamento em relação ao alinhamento da coluna anterior
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2);
                     x=column_offset+cell_x_offset;
                     break;
                  case ALIGN_RIGHT :
                     x=column_offset+cell_x_offset;
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width;
                     x=column_offset+cell_x_offset;
                     break;
                 }
               //---
               break;
           }
        }
      //--- Linhas
      for(int r=0; r<m_rows_total; r++)
        {
         //---
         y+=(r>0) ? m_cell_y_size-1 : cell_y_offset;
         //---
         switch(m_vcolumns[c].m_text_align)
           {
            case ALIGN_CENTER :
               text_align=TA_CENTER|TA_TOP;
               break;
            case ALIGN_RIGHT :
               text_align=TA_RIGHT|TA_TOP;
               break;
            case ALIGN_LEFT :
               text_align=TA_LEFT|TA_TOP;
               break;
           }
         //--- Desenha o texto
         m_canvas.TextOut(x,y,m_vcolumns[c].m_vrows[r],clr,text_align);
        }
      //--- Zera a coordenada Y para o próximo ciclo
      y=0;
     }
  }

Nos dois primeiros tipos de tabelas que nós examinamos, é realizado o deslocamento de dados através das barras de rolagem, alterando os valores do objeto (rótulos de texto e caixas de edição) da parte visível da tabela. Aqui nós vamos deslocar o escopo da visibilidade retangular da imagem. Em outras palavras, o tamanho da tabela (imagem) é inicialmente igual à soma de todas as colunas e a altura de todas as linhas. Este é o tamanho da imagem original, em que o escopo da visibilidade pode ser movido. O tamanho do escopo da visibilidade pode ser alterado a qualquer momento, mas aqui eles são definidos logo após o controle ser criado no método CCanvasTable::CreateCells(). 

O escopo da visibilidade pode ser definido por meio das propriedades OBJPROP_XSIZE e OBJPROP_YSIZE, enquanto que o deslocamento do escopo (dentro da faixa da imagem original) pode ser realizado através das propriedades OBJPROP_XOFFSET e OBJPROP_YOFFSET (veja o exemplo no código abaixo): 

//--- Define o tamanho da área visível
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_XSIZE,m_table_visible_x_size);
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_YSIZE,m_table_visible_y_size);
//--- Set the frame offset within the image along the X and Y axes
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_XOFFSET,0);
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_YOFFSET,0);

Vamos desenvolver o método simples CCanvasTable::ShiftTable() para mudar o escopo da visibilidade em relação à posição atual dos controles deslizantes da barra de rolagem. O passo do deslocamento vertical é igual à altura da linha, enquanto que o horizontal é realizada em pixels (veja o código abaixo): 

class CCanvasTable : public CElement
  {
public:
   //--- Desloca a tabela em relação às posições das barras de rolagem
   void              ShiftTable(void);
  };
//+------------------------------------------------------------------+
//| Desloca a tabela em relação a barra de rolagem                   |
//+------------------------------------------------------------------+
void CCanvasTable::ShiftTable(void)
  {
//--- Obtém as posições atuais dos controles deslizantes das barras de rolagem vertical e horizontal
   int h=m_scrollh.CurrentPos();
   int v=m_scrollv.CurrentPos();
//--- Cálculo da posição da tabela em relação aos controles deslizantes da barra de rolagemm
   long c=h;
   long r=v*(m_cell_y_size-1);
//--- Deslocamento da tabela
   ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_XOFFSET,c);
   ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_YOFFSET,r);
  }

O método geral do código CCanvasTabl::DrawTable() para desenho da tabela vai ter a aparência mostrada abaixo: 

class CCanvasTable : public CElement
  {
public:
   //--- Desenha a tabela com a consideração das mudanças recentes
   void              DrawTable(void);
  };
//+------------------------------------------------------------------+
//| Desenha a tabela                                                 |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTable(void)
  {
//--- Faz o fundo transparentem
   m_canvas.Erase(::ColorToARGB(clrNONE,0));
//--- Desenha a grade
   DrawGrid();
//--- Desenha o texto
   DrawText();
//--- Exibe as mudanças mais recentes extraídas
   m_canvas.Update();
//--- Coloque a tabela relativa às barras de rolagem
   ShiftTable();
  }

Agora, tudo está pronto para testar este tipo de tabela. 

 


Teste da Tabela Renderizada

Vamos fazer uma cópia do EA de teste anterior e excluir todos os elementos relacionados com a tabela do tipo CTable. Agora, crie a instância da classe de tipo CCanvasTable na classe personalizada CProgram e declare o (1) método CProgram::CreateCanvasTable() para a criação da tabela, bem como os (2) recuos a partir do ponto extremo do formulário como é mostrado no código a seguir:

class CProgram : public CWndEvents
  {
private:
   //--- Tabela renderizada
   CCanvasTable      m_canvas_table;
   //---
private:
   //--- Tabela renderizada
#define TABLE1_GAP_X          (1)
#define TABLE1_GAP_Y          (42)
   bool              CreateCanvasTable(void);
  };

Vamos fazer uma tabela que consiste de 15 colunas e 1000 linhas. O número de linhas visíveis será de 16. Nós não precisamos definir o número de colunas visíveis já que o deslocamento horizontal é realizada em pixels. Neste caso, a largura da área visível da tabela deve ser especificada explicitamente. Vamos configurá-la para 601 pixels. 

Como exemplo, vamos definir a largura de todas as colunas (exceto para os dois primeiros) para 70 pixels. A largura da primeira e da segunda coluna são definidas para 100 e 90 os pixels em conformidade. Em todas as colunas (exceto para os três primeiros), o texto é centrado. Na primeira e terceira coluna, ela está alinhada para a direita, enquanto que no segundo está alinhada à esquerda. O código completo do método CProgram::CreateCanvasTable() é fornecido abaixo:

//+------------------------------------------------------------------+
// Cria a tabela renderizada                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateCanvasTable(void)
  {
#define COLUMNS1_TOTAL 15
#define ROWS1_TOTAL    1000
//--- Salva o ponteiro para o formulário
   m_canvas_table.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+TABLE1_GAP_X;
   int y=m_window1.Y()+TABLE1_GAP_Y;
//--- Número de linhas visíveis
   int visible_rows_total=16;
//--- Array de largura da coluna
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,70);
   width[0]=100;
   width[1]=90;
//--- Array para o alinhamento do texto em colunas
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_RIGHT;
   align[1]=ALIGN_LEFT;
   align[2]=ALIGN_RIGHT;
//--- Define as propriedades antes da criação
   m_canvas_table.XSize(601);
   m_canvas_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_canvas_table.VisibleTableSize(0,visible_rows_total);
   m_canvas_table.TextAlign(align);
   m_canvas_table.ColumnsWidth(width);
   m_canvas_table.GridColor(clrLightGray);
//--- Preenche a tabela com os dados
   for(int c=0; c<COLUMNS1_TOTAL; c++)
      for(int r=0; r<ROWS1_TOTAL; r++)
         m_canvas_table.SetValue(c,r,string(c)+":"+string(r));
//--- Cria o controle
   if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Adiciona o objeto para o array total de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_canvas_table);
   return(true);
  }

A chamada é efetuada no método de criação do GUI principal: 

//+------------------------------------------------------------------+
//| Cria o painel de negociação                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Cria o formulário 1 para os controles
//--- Cria os controles:
//    Menu principal
//--- Menus de contexto
//--- Cria a linha de estado
//--- Cria a tabela renderizada
   if(!CreateCanvasTable())
      return(false);
//--- Redesenha o gráfico
   m_chart.Redraw();
   return(true);
  }

Agora, vamos compilar o programa e executá-lo em um gráfico. O resultado é mostrado na imagem abaixo:

 Fig. 6. Testando o controle tabela renderizada no EA

Fig. 6. Testando o controle tabela renderizada no EA

No quinto capítulo da primeira parte da série, testou-se o uso das formas em scripts. As tabelas sem barras de deslocamento podem ser usadas neste tipo de aplicações MQL. Por exemplo, vamos adicionar a tabela renderizada aos dados do formulário no script e atualizar a cada 250 milissegundos. Adicione o código da tabela para a classe personalizada da aplicação como é mostrado anteriormente. Além disso, o código deve ser adicionado para manipulador de eventos de script CProgram::OnEvent() como é exibido na listagem abaixo. Agora, os dados da segunda coluna mudará continuamente depois de um intervalo de tempo especificado. 

//+------------------------------------------------------------------+
//| Eventos                                                          |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int milliseconds)
  {
   static int count =0;  // Counter
   string     str   =""; // Header row
//--- Formar o cabeçalho exibindo o processo de
   switch(count)
     {
      case 0 : str="SCRIPT PANEL";     break;
      case 1 : str="SCRIPT PANEL .";   break;
      case 2 : str="SCRIPT PANEL ..";  break;
      case 3 : str="SCRIPT PANEL ..."; break;
     }
//--- Atualiza a linha de cabeçalho
   m_window.CaptionText(str);
//--- Altera o primeiro dados da coluna
   for(int r=0; r<13; r++)
      m_canvas_table.SetValue(1,r,string(::rand()));
//--- Mostra a nova tabela de dados
   m_canvas_table.DrawTable();
//--- Redesenha o gráfico
   m_chart.Redraw();
//--- Aumenta o contador
   count++;
//--- Defina para zero, se superior a três
   if(count>3)
      count=0;
//--- Pausa
   ::Sleep(milliseconds);
  }

Compilar o programa e execute o script no gráfico. Você deve ver o seguinte: 

 Fig. 7. Testando o controle tabela renderizada no script

Fig. 7. Testando o controle tabela renderizada no script

Nós completamos o desenvolvimento da classe CCanvasTable para criar uma tabela renderizada. Agora, está na hora para alguns resultados provisórios. 

 


Conclusão

Neste artigo, discutimos as rês classes para criar os controles de interface importantes - Tabelas. Cada uma dessas classes tem suas próprias características que são mais adequadas para tarefas específicas. Por exemplo, a classe CTable permite desenvolver uma tabela com caixas editáveis, proporcionando uma oportunidade para formatá-la de modo mais amigável. A classe CCanvasTable permite evitar a limitação do número de caracteres nas células, enquanto que o deslocamento na tabela ao longo do escopo de visibilidade da imagem torna possível definir a largura específica para cada uma das colunas. Estas não são as versões finais das tabelas. Se necessário, elas poderão ser melhoradas.

No próximo artigo, nós vamos examinar em detalhes as classes para o desenvolvimento do controle Guia que também é usado ​​frequentemente nas interfaces gráficas.

Você pode baixar o material da parte VII e testar o seu funcionamento. Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo.

Lista de artigos (capítulos) da sétima parte: