Interfaces Gráficas X: Novos recursos para a tabela Renderizada (build 9)

Anatoli Kazharski | 4 abril, 2017


Conteúdo


Introdução

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

Até agora, o tipo mais avançado de tabelas já desenvolvido em nossa biblioteca foi a CTable. Esta tabela é montada a partir de caixas de edição do tipo OBJ_EDIT, e seu posterior desenvolvimento tornou-se problemático. Por exemplo, é difícil de implementar o redimensionamento manual das colunas ao arrastar as bordas do cabeçalho, uma vez que não é possível gerenciar as áreas visíveis dos objetos gráficos de forma individual da tabela. O limite foi atingido aqui.

Portanto, no atual estágio de desenvolvimento da biblioteca, é mais razoável passar para o desenvolvimento das tabelas renderizadas do tipo CCanvasTable. As informações sobre as versões anteriores e atualizações da tabela renderizada podem ser encontradas aqui:

A imagem mostra a aparência da versão mais recente da tabela renderizada. Como você pode observar, ela está completamente inerte. Ela é simplesmente uma grade composta de células da tabela contendo dados. O método de alinhamento pode ser especificado para as células. Exceto pelas barras de rolagem e o ajuste automático do tamanho do formulário, esta tabela não possui nenhuma interatividade.

 Fig. 1. A versão anterior da tabela renderizada.

Fig. 1. A versão anterior da tabela renderizada.

Para corrigir a situação, vamos complementar a tabela renderizada com novas funcionalidades. Os recursos a seguir serão incluídos na atualização atual:

  • Formatação no estilo Zebra
  • Selecionar uma linha da tabela e remover a seleção quando ela for clicada novamente
  • Adição de cabeçalhos para as colunas com a possibilidade de alterar a cor quando o mouse estiver em cima e ser clicado
  • Ajuste automático de texto para a largura da coluna no caso de não existir espaço suficiente na célula
  • Possibilidade de alterar a largura do cabeçalho de cada coluna pelo arraste de sua borda

Formatação no estilo Zebra

A formatação no estilo Zebra foi adicionada à tabela CTable em algum dos artigos recentes. Ela melhora a navegação da tabela quando ela contém uma grande quantidade de células. Vamos implementar este modo na tabela renderizada também.

Use o método CCanvasTable::IsZebraFormatRows() para ativar este modo. Será passado a ela a segunda cor do estilo, enquanto que a cor da célula geral servirá como a primeira cor.

//+------------------------------------------------------------------+
//| Classe para a criação de uma tabela renderizada                  |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Modo de formatação no estilo Zebra
   color             m_is_zebra_format_rows;
   //---
public:
   //--- Formatação no estilo Zebra
   void              IsZebraFormatRows(const color clr)   { m_is_zebra_format_rows=clr;      }
      };

Os métodos para a visualização deste estilo se diferem nas tabelas de tipos diferentes. No caso da CCanvasTable, no modo normal, o fundo da tabela (tela para desenho) é completamente preenchida com uma cor comum de célula. Quando o modo Zebra é ativado, é iniciado um ciclo. Cada uma de suas iterações calculam as coordenadas para cada linha, e suas áreas são coloridas em duas cores de forma alternada. Isso é feito pelo método FillRectangle(), que é usado para desenhar retângulos preenchidos

class CCanvasTable : public CElement
  {
public:
   //--- Desenha o fundo das linhas da tabela
   void              DrawRows(void);
  };
//+------------------------------------------------------------------+
//| Desenha o fundo das linhas da tabela                             |
//+------------------------------------------------------------------+
void CCanvasTable::DrawRows(void)
  {
//--- Se o modo de formatação no estilo Zebra está desativado
   if(m_is_zebra_format_rows==clrNONE)
     {
      //--- Preenche a tela com uma cor
      m_table.Erase(::ColorToARGB(m_cell_color));
      return;
     }
//--- Coordenadas dos cabeçalhos
   int x1=0,x2=m_table_x_size;
   int y1=0,y2=0;
//--- Formatação no estilo Zebra
   for(int r=0; r<m_rows_total; r++)
     {
      //--- Calcula as coordenadas
      y1=(r*m_cell_y_size)-r;
      y2=y1+m_cell_y_size;
      //--- Cor da linha
      uint clr=::ColorToARGB((r%2!=0)? m_is_zebra_format_rows : m_cell_color);
      //--- Desenha o fundo da linha
      m_table.FillRectangle(x1,y1,x2,y2,clr);
     }
      }

As cores da linha podem ser ajustadas conforme o seu gosto. Como resultado, a tabela renderizada no modo Zebra ficará da seguinte forma:

 Fig. 2. Tabela renderizada no modo de formatação no estilo Zebra.

Fig. 2. Tabela renderizada no modo de formatação no estilo Zebra. 

 


Seleção e remoção da seleção das linhas da tabela

Para a seleção das linhas será necessário os campos adicionais e os métodos para armazenamento e configuração:

  • A cor do fundo e do texto da linha selecionada
  • Índice e texto

class CCanvasTable : public CElement
  {
private:
   //--- Cor do (1) plano de fundo e (2) a linha de texto selecionada
   color             m_selected_row_color;
   color             m_selected_row_text_color;
   //--- (1) Índice e (2) o texto da linha selecionada
   int               m_selected_item;
   string            m_selected_item_text;
   //---
public:
   //--- Retorna o (1) índice e o (2) texto da linha selecionada na tabela
   int               SelectedItem(void)             const { return(m_selected_item);         }
   string            SelectedItemText(void)         const { return(m_selected_item_text);    }
   //---
private:
   //--- Desenha o fundo das linhas da tabela
   void              DrawRows(void);
      };

O modo de linha selecionável pode ser ativado/desativado pelo método CCanvasTable::SelectableRow():

class CCanvasTable : public CElement
  {
private:
   //--- Modo da linha selecionável
   bool              m_selectable_row;
   //---
public:
   //--- Seleção da linha
   void              SelectableRow(const bool flag)       { m_selectable_row=flag;           }
      };

Para selecionar uma linha, será necessário um método separado para o desenho de uma área definida pelo usuário. O código do método CCanvasTable::DrawSelectedRow() é apresentado abaixo. Ele calcula as coordenadas da área selecionada na tela, que são utilizadas para desenhar o retângulo preenchido

class CCanvasTable : public CElement
  {
private:
   //--- Desenha uma linha selecionada
   void              DrawSelectedRow(void);
  };
//+------------------------------------------------------------------+
//| Desenha uma linha selecionada                                    |
//+------------------------------------------------------------------+
void CCanvasTable::DrawSelectedRow(void)
  {
//--- Define as coordenadas iniciais para verificar a condição
   int y_offset=(m_selected_item*m_cell_y_size)-m_selected_item;
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=0;
//---
   x1=0;
   y1=y_offset;
   x2=m_table_x_size;
   y2=y_offset+m_cell_y_size-1;
//--- Desenha um retângulo preenchido
   m_table.FillRectangle(x1,y1,x2,y2,::ColorToARGB(m_selected_row_color));
      }

O método auxiliar CCanvasTable::TextColor() é usado para redesenhar o texto, que determina a cor do texto nas células: 

class CCanvasTable : public CElement
  {
private:
   //--- Retorna a cor do texto da célula
   uint              TextColor(const int row_index);
  };
//+------------------------------------------------------------------+
//| Retorna a cor do texto da célula                                 |
//+------------------------------------------------------------------+
uint CCanvasTable::TextColor(const int row_index)
  {
   uint clr=::ColorToARGB((row_index==m_selected_item)? m_selected_row_text_color : m_cell_text_color);
//--- Retornar a cor do cabeçalho
   return(clr);
      }

Para selecionar uma linha da tabela, é necessário dar um clique duplo nele. Isso exigirá o método CCanvasTable::OnClickTable(), que será chamado no processador de eventos do controle pelo identificador CHARTEVENT_OBJECT_CLICK.

Várias verificações devem ser passadas no início do método. O programa deixa o método se:

  • o modo de seleção da linha está desativado;
  • a barra de rolagem está ativo;
  • o clique não foi sobre a tabela. 

Se as verificações são passadas, então, a fim de calcular as coordenadas do clique, é necessário obter o deslocamento atual a partir da borda da tela e a coordenada Y do cursor do mouse. Depois disso, determinar a linha clicada em um loop. Uma vez que a linha é encontrada, é necessário verificar se ela está selecionada no momento, e se estiver — remove a seleção. Se a linha é selecionada, é necessário armazenar o índice e o texto da primeira coluna. A tabela é redesenhada após a busca da linha no loop ter sido finalizada. Uma mensagem é enviada, contendo:

  • identificador do evento ON_CLICK_LIST_ITEM
  • identificador do controle
  • índice da linha selecionada
  • texto da linha selecionada. 
class CCanvasTable : public CElement
  {
private:
   //--- Manipulação do clique sobre o elemento
   bool              OnClickTable(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Manipulação do clique no controle                                |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickTable(const string clicked_object)
  {
//--- Sai, se o modo de seleção de linha está desativada
   if(!m_selectable_row)
      return(false);
//--- Sai, se a barra de rolagem está ativa
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Sai, se ele tem um nome de objeto diferente
   if(m_table.Name()!=clicked_object)
      return(false);
//--- Obtém o deslocamento ao longo do eixos X e Y
   int xoffset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int yoffset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Determina a coordenada da caixa de edição de texto abaixo do cursor do mouse
   int y=m_mouse.Y()-m_table.Y()+yoffset;
//--- Determina a linha clicada
   for(int r=0; r<m_rows_total; r++)
     {
      //--- Define as coordenadas iniciais para verificar a condição
      int y_offset=(r*m_cell_y_size)-r;
      //--- Verificação das condições ao longo do eixo Y
      bool y_pos_check=(y>=y_offset && y<y_offset+m_cell_y_size);
      //--- Se o clique não foi nesta linha, ir para a próxima
      if(!y_pos_check)
         continue;
      //--- Se clicou em um linha selecionada, remove a seleção
      if(r==m_selected_item)
        {
         m_selected_item      =WRONG_VALUE;
         m_selected_item_text ="";
         break;
        }
      //--- Armazena o índice da linha 
      m_selected_item      =r;
      m_selected_item_text =m_vcolumns[0].m_vrows[r];
      break;
     }
//--- Desenhar a tabela
   DrawTable();
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElementBase::Id(),m_selected_item,m_selected_item_text);
   return(true);
      }

A tabela renderizada com uma linha selecionada possui o seguinte aspecto:

Fig. 3. Demonstração da seleção e remoção da seleção de uma linha da tabela renderizada.

Fig. 3. Demonstração da seleção e remoção da seleção de uma linha da tabela renderizada. 

 

Cabeçalhos para as colunas

Qualquer tabela sem cabeçalhos se assemelha com um espaço em branco. Os cabeçalhos serão desenhados neste tipo de tabela, porém, em uma tela separada. Para fazer isso, inclua outro exemplo da classe CRectCanvas na classe CCanvasTable e crie um método separado para a criação de uma tela. O código deste método não será fornecido aqui: ele é quase o mesmo que o de criação de uma tabela. As únicas diferenças são os tamanhos predefinidos e a localização do objeto.

class CCanvasTable : public CElement
  {
private:
   //--- Objetos para a criação de uma tabela
   CRectCanvas       m_headers;
   //---
private:
   bool              CreateHeaders(void);
      };

Agora, considere as propriedades relacionadas com os cabeçalhos das colunas. Eles podem ser configurados antes da criação da tabela.

  • Modo de exibição dos cabeçalhos da tabela.
  • Tamanho (altura) dos cabeçalhos.
  • Cor de fundo dos cabeçalhos em diferentes estados.
  • Encabeçamento da cor de texto.

Campos e métodos relacionados com estas propriedades: 

class CCanvasTable : public CElement
  {
private:
   //--- Modo de exibição dos cabeçalhos de tabela
   bool              m_show_headers;
   //--- Tamanho (altura) dos cabeçalhos
   int               m_header_y_size;
   //--- Cor dos cabeçalhos (fundo) em diferentes estados
   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;
   //--- Cor do texto de cabeçalho
   color             m_headers_text_color;
   //---
public:
   //--- (1) Modo de exibição dos cabeçalhos, altura dos (2) cabeçalhos
   void              ShowHeaders(const bool flag)         { m_show_headers=flag;             }
   void              HeaderYSize(const int y_size)        { m_header_y_size=y_size;          }
   //--- (1) Cor de fundo e (2) do texto dos cabeçalhos
   void              HeadersColor(const color clr)        { m_headers_color=clr;             }
   void              HeadersColorHover(const color clr)   { m_headers_color_hover=clr;       }
   void              HeadersColorPressed(const color clr) { m_headers_color_pressed=clr;     }
   void              HeadersTextColor(const color clr)    { m_headers_text_color=clr;        }
      };

Para definir os nomes do cabeçalho, é necessário termos um método. Além disso, é necessário também ter um array para armazenar estes valores. O tamanho do array é igual ao número de colunas e será definido no mesmo método CCanvasTable::TableSize() ao definir o tamanho da tabela. 

class CCanvasTable : public CElement
  {
private:
   //--- Texto dos cabeçalhos
   string            m_header_text[];
   //---
public:
   //--- Define o texto para o cabeçalho especificado
   void              SetHeaderText(const int column_index,const string value);
  };
//+------------------------------------------------------------------+
//| Preenche o array de cabeçalhos no índice especificado            |
//+------------------------------------------------------------------+
void CCanvasTable::SetHeaderText(const uint column_index,const string value)
  {
//--- Verifica se o tamanho da coluna não excedeu
   uint csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index>=csize)
      return;
//--- Armazena o valor no array
   m_header_text[column_index]=value;
      }

O alinhamento do texto nas células e cabeçalhos serão realizados utilizando um método comum CCanvasTable::TextAlign(). O método de alinhamento ao longo do eixo X nas células da tabela correspondem ao de cabeçalhose o alinhamento ao longo do eixo Y é definido pelo valor passado. Nesta versão, o texto do cabeçalho ao longo do eixo Y será posicionado no centro — TA_VCENTER, e as células vão ter o deslocamento a partir da borda superior da célula ajustada — TA_TOP

class CCanvasTable : public CElement
  {
private:
   //--- Retorna o modo de alinhamento de texto na coluna especificada
   uint              TextAlign(const int column_index,const uint anchor);
  };
//+------------------------------------------------------------------+
//| Retorna o modo de alinhamento de texto na coluna especificada    |
//+------------------------------------------------------------------+
uint CCanvasTable::TextAlign(const int column_index,const uint anchor)
  {
   uint text_align=0;
//--- Alinhamento do texto para a coluna atual
   switch(m_vcolumns[column_index].m_text_align)
     {
      case ALIGN_CENTER :
         text_align=TA_CENTER|anchor;
         break;
      case ALIGN_RIGHT :
         text_align=TA_RIGHT|anchor;
         break;
      case ALIGN_LEFT :
         text_align=TA_LEFT|anchor;
         break;
     }
//--- Retorna o tipo de alinhamento
   return(text_align);
      }

Em muitas tabelas e no ambiente do SO, o ponteiro muda quando o cursor passa a borda entre os dois cabeçalhos. A imagem abaixo ilustra tal situação, o exemplo da tabela na janela Caixa de Ferramentas do terminal de negociação MetaTrader 5. Se este ponteiro que apareceu recentemente for clicado, ele alterna o modo de alteração da largura da coluna. A cor de fundo desta coluna muda também.

Fig. 4. Ponteiro do mouse quando está em cima das bordas articuladas do cabeçalho.

Fig. 4. Ponteiro do mouse quando está em cima das bordas articuladas do cabeçalho.

 

Vamos preparar a mesma imagem para a biblioteca desenvolvida. O anexo no final do artigo contém uma pasta com todas as imagens para os controles da biblioteca. Adicione os novos identificadores para a enumeração dos ponteiros ENUM_MOUSE_POINTER no arquivo Enums.mqh para redimensionamento ao longo dos eixos X e Y

//+------------------------------------------------------------------+
//| Enumeração dos tipos de ponteiros                                |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,
   MP_X_RESIZE_RELATIVE =5,
   MP_Y_RESIZE_RELATIVE =6,
   MP_X_SCROLL          =7,
   MP_Y_SCROLL          =8,
   MP_TEXT_SELECT       =9
      };

As adições correspondentes têm de ser feitas para a classe CPointer para fazer com que este tipo de ponteiro esteja disponível para uso nas classes dos controles. 

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Recursos
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp"
//+------------------------------------------------------------------+
//| Classe para a criação do cursor do mouse                         |
//+------------------------------------------------------------------+
class CPointer : public CElement
  {
private:
   //--- Define as imagens para o cursor do mouse
   void              SetPointerBmp(void);
  };
//+------------------------------------------------------------------+
//| Define os ícones do cursor com base no tipo de cursor            |
//+------------------------------------------------------------------+
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      ...
      case MP_X_RESIZE_RELATIVE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         break;
      case MP_Y_RESIZE_RELATIVE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         break;
      ...
     }
//--- Se o tipo personalizado (MP_CUSTOM) foi especificado
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Ambas as imagens devem ser definidas para o cursor!");
      }

Campos adicionais serão necessários aqui:

  • Para determinar o momento de arrastar a borda do cabeçalho
  • Para determinar o momento em que o cursor do mouse se move a partir da área de um cabeçalho para a área de outro cabeçalho. Isso é necessário para economizar recursos, de modo que os cabeçalhos são redesenhados somente quando os limites das áreas adjacentes estão cruzados.

class CCanvasTable : public CElement
  {
private:
   //--- Para determinar o momento de transição do cursor do mouse de um cabeçalho para o outro
   int               m_prev_header_index_focus;
   //--- O estado do arraste da borda do cabeçalho para alterar a largura da coluna
   int               m_column_resize_control;
      };

O método CCanvasTable::HeaderColorCurrent() permite obter a cor atual do cabeçalho, dependendo do modo atual, a localização do cursor do mouse e o estado do botão esquerdo do mouse. O foco sobre o cabeçalho será determinado no método CCanvasTable::DrawHeaders(), que é designado para desenhar os fundos de cabeçalho, e será passada aqui como o resultado de uma verificação.

class CCanvasTable : public CElement
  {
private:
   //--- Retorna a cor de fundo do cabeçalho atual
   uint              HeaderColorCurrent(const bool is_header_focus);
  };
//+------------------------------------------------------------------+
//| Retorna a cor de fundo do cabeçalho atual                        |
//+------------------------------------------------------------------+
uint CCanvasTable::HeaderColorCurrent(const bool is_header_focus)
  {
   uint clr=clrNONE;
//--- Se não houver foco
   if(!is_header_focus || !m_headers.MouseFocus())
      clr=m_headers_color;
   else
     {
      //--- Se o botão esquerdo do mouse é pressionado e não no processo de alterar a largura da coluna
      bool condition=(m_mouse.LeftButtonState() && m_column_resize_control==WRONG_VALUE);
      clr=(condition)? m_headers_color_pressed : m_headers_color_hover;
     }
//--- Retornar a cor do cabeçalho
   return(::ColorToARGB(clr));
      }

O código do método CCanvasTable::DrawHeaders() é apresentado abaixo. Aqui, se o cursor do mouse não está na área de cabeçalhos, toda a tela é preenchida com a cor especificada. Se o foco está nos cabeçalhos, então é necessário determinar qual deles tem o foco. Para fazer isso, é necessário determinar as coordenadas relativas do cursor do mouse, e verificar se cada cabeçalho está em foco durante o cálculo das coordenadas do cabeçalho em um loop. Além disso, o modo de alterar a largura da coluna foi considerado aqui. O deslocamento adicional é usado nos cálculos para este modo. Se o foco for encontrado, o índice da coluna deve ser armazenado

class CCanvasTable : public CElement
  {
private:
   //--- Desloca das bordas das linhas de separação para exibir o ponteiro do mouse no modo de alteração da largura da coluna
   int               m_sep_x_offset;
   //---
private:
   //--- Desenha os cabeçalhos
   void              DrawHeaders(void);
  };
//+------------------------------------------------------------------+
//| Desenha o fundo dos cabeçalhos                                   |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeaders(void)
  {
//--- Se não estiver em foco, redefine as cores do cabeçalho
   if(!m_headers.MouseFocus())
     {
      m_headers.Erase(::ColorToARGB(m_headers_color));
      return;
     }
//--- Para verificar o foco sobre os cabeçalhos
   bool is_header_focus=false;
//--- Coordenadas do cursor do mouse
   int x=0;
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=m_header_y_size;
//--- Obtém as coordenadas relativas ao cursor do mouse
   if(::CheckPointer(m_mouse)!=POINTER_INVALID)
     {
      //--- Obtém o deslocamento ao longo do eixo X
      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //--- Determina as coordenadas do cursor do mouse
      x=m_mouse.X()-m_headers.X()+xoffset;
     }
//--- Limpa o fundo dos cabeçalhos
   m_headers.Erase(::ColorToARGB(clrNONE,0));
//--- Desloca considerando o modo de alteração das larguras da coluna
   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;
//--- Desenha o fundo dos cabeçalhos
   for(int i=0; i<m_columns_total; i++)
     {
      //--- Calcula as coordenadas
      x2+=m_vcolumns[i].m_width;
      //--- Verifica o foco
      if(is_header_focus=x>x1+((i!=0)? sep_x_offset : 0) && x<=x2+sep_x_offset)
         m_prev_header_index_focus=i;
      //--- Desenha o fundo do cabeçalho
      m_headers.FillRectangle(x1,y1,x2,y2,HeaderColorCurrent(is_header_focus));
      //--- Calcula o deslocamento para o próximo cabeçalho
      x1+=m_vcolumns[i].m_width;
     }
      }

Uma vez que o fundo de cabeçalhos é desenhado, é necessário chamar a grade (quadros de cabeçalho). O método CCanvasTable::DrawHeadersGrid() é utilizado para esta finalidade. Primeiro, a estrutura comum é desenhada, e, em seguida, as linhas de separação são aplicadas em um loop.

class CCanvasTable : public CElement
  {
private:
   //--- Desenha a grade dos cabeçalhos da tabela
   void              DrawHeadersGrid(void);
  };
//+------------------------------------------------------------------+
//| Desenha a grade dos cabeçalhos de tabela                         |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeadersGrid(void)
  {
//--- Cor da grade
   uint clr=::ColorToARGB(m_grid_color);
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=0;
   x2=m_table_x_size-1;
   y2=m_header_y_size-1;
//--- Desenha o quadro
   m_headers.Rectangle(x1,y1,x2,y2,clr);
//--- Linhas de separação
   x2=x1=m_vcolumns[0].m_width;
   for(int i=1; i<m_columns_total; i++)
     {
      m_headers.Line(x1,y1,x2,y2,clr);
      x2=x1+=m_vcolumns[i].m_width;
     }
      }

E, finalmente, desenha o texto dos cabeçalhos. Esta tarefa é realizada pelo método CCanvasTable::DrawHeadersText(). Aqui, é necessário passar por cada cabeçalho em um loop, determinando a coordenada para o texto e o modo de alinhamento em cada iteração. O nome do cabeçalho é aplicado como a última operação do loop. O ajuste de texto em relação à largura da coluna também é utilizado aqui. O método CCanvasTable::CorrectingText() é usado para esta finalidade. Ele é descrito em mais detalhe na seção seguinte do artigo. 

class CCanvasTable : public CElement
  {
private:
   //--- Desenha o texto dos cabeçalhos da tabela
   void              DrawHeadersText(void);
  };
//+------------------------------------------------------------------+
//| Desenha o texto dos cabeçalhos de tabela                         |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeadersText(void)
  {
//--- Para calcular as coordenadas e os deslocamentos
   int x=0,y=m_header_y_size/2;
   int column_offset =0;
   uint text_align   =0;
//--- Cor do texto
   uint clr=::ColorToARGB(m_headers_text_color);
//--- Propriedades da fonte
   m_headers.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Desenha o texto
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Obtém a coordenada X do texto
      x=TextX(c,column_offset);
      //--- Obtém o modo de alinhamento do texto
      text_align=TextAlign(c,TA_VCENTER);
      //--- Desenha o nome da coluna
      m_headers.TextOut(x,y,CorrectingText(c,0,true),clr,text_align);
     }
      }

Todos os métodos listados para desenhar os cabeçalhos são chamados no método comum CCanvasTable::DrawTableHeaders(). A entrada para este método é bloqueada se o modo de exibição do cabeçalho estiver desativado

class CCanvasTable : public CElement
  {
private:
   //--- Desenha os cabeçalhos da tabela
   void              DrawTableHeaders(void);
  };
//+------------------------------------------------------------------+
//| Desenha os cabeçalhos da tabela                                  |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTableHeaders(void)
  {
//--- Sai, se os cabeçalhos são desativados
   if(!m_show_headers)
      return;
//--- Desenha os cabeçalhos
   DrawHeaders();
//--- Desenha a grade
   DrawHeadersGrid();
//--- Desenha o texto dos cabeçalhos
   DrawHeadersText();
      }

O foco no cabeçalho é verificado usando o método CCanvasTable::CheckHeaderFocus(). O programa deixa o método em dois casos:

  • se o modo de exibição do cabeçalho é desativado
  • ou se o processo de alteração da largura da coluna foi iniciado.

Depois disso, são obtidas as coordenadas relativas do cursor sobre a tela. O loop busca por um foco em qualquer cabeçalho e verifica se ele mudou desde a última chamada para este método. Se um novo foco foi registrado (o momento de cruzar as bordas do cabeçalho), então é necessário resetar o índice de cabeçalho previamente guardado e interromper o loop.

class CCanvasTable : public CElement
  {
private:
   //--- Verificação do foco no cabeçalho
   void              CheckHeaderFocus(void);
  };
//+------------------------------------------------------------------+
//| Verificação do foco no cabeçalho                                 |
//+------------------------------------------------------------------+
void CCanvasTable::CheckHeaderFocus(void)
  {
//--- Sai, se (1) os cabeçalhos estão desativados ou (2) se a alteração da largura da coluna foi iniciada
   if(!m_show_headers || m_column_resize_control!=WRONG_VALUE)
      return;
//--- Coordenadas dos cabeçalhos
   int x1=0,x2=0;
//--- Obtém o deslocamento ao longo do eixo X
   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
//--- Obtém as coordenadas relativas ao cursor do mouse
   int x=m_mouse.X()-m_headers.X()+xoffset;
//--- Desloca considerando o modo de alteração das larguras da coluna
   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;
//--- Busca o foco
   for(int i=0; i<m_columns_total; i++)
     {
      //--- Calcula coordenada da direita
      x2+=m_vcolumns[i].m_width;
      //--- Se o foco do cabeçalho mudou
      if((x>x1+sep_x_offset && x<=x2+sep_x_offset) && m_prev_header_index_focus!=i)
        {
         m_prev_header_index_focus=WRONG_VALUE;
         break;
        }
      //--- Calcule a coordenada da esquerda
      x1+=m_vcolumns[i].m_width;
     }
      }

Por sua vez, os cabeçalhos são redesenhados apenas quando as bordas são cruzadas. Isso poupa os recursos do CPU. O método CCanvasTable::ChangeHeadersColor() foi projetado para esta tarefa. Aqui, o programa deixa o método se o modo de exibição do cabeçalho está desativado ou se está ocorrendo o processo de alteração de sua largura. Se são passadas pelas verificações no início do método, então o foco sobre os cabeçalhos é verificado e eles são redesenhados. 

class CCanvasTable : public CElement
  {
private:
   //--- Altera a cor dos cabeçalhos
   void              ChangeHeadersColor(void);
  };
//+------------------------------------------------------------------+
//| Altera a cor dos cabeçalhos                                      |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeHeadersColor(void)
  {
//--- Sai, se os cabeçalhos são desativados
   if(!m_show_headers)
      return;
//--- Se o cursor estiver ativado
   if(m_column_resize.IsVisible() && m_mouse.LeftButtonState())
     {
      //--- Armazena o índice da coluna arrastada
      if(m_column_resize_control==WRONG_VALUE)
         m_column_resize_control=m_prev_header_index_focus;
      //---
      return;
     }
//--- Se não estiver em foco
   if(!m_headers.MouseFocus())
     {
      //--- Se ainda não indicou que não está no foco
      if(m_prev_header_index_focus!=WRONG_VALUE)
        {
         //--- Reseta o foco
         m_prev_header_index_focus=WRONG_VALUE;
         //--- Altera a cor
         DrawTableHeaders();
         m_headers.Update();
        }
     }
//--- Se está em foco
   else
     {
      //--- Verifica o foco sobre os cabeçalhos
      CheckHeaderFocus();
      //--- Se não houver foco
      if(m_prev_header_index_focus==WRONG_VALUE)
        {
         //--- Altera a cor
         DrawTableHeaders();
         m_headers.Update();
        }
     }
      }

Abaixo está o código do método CCanvasTable::CheckColumnResizeFocus(). Ele é necessária para determinar o foco nas bordas entre os cabeçalhos e é responsável por mostrar/ocultar o cursor para alterar a largura das colunas. Há duas verificações no início do método. O programa deixa o método se o modo de alteração da largura da coluna está desativado. Se ele estiver habilitado e a alteração da largura da coluna está em andamento, então é necessário atualizar as coordenadas do cursor do mouse e deixar o método.

Se ainda não começou o processo de alteração da largura da coluna, então, se o cursor está na área dos cabeçalhos, ele tenta determinar o foco na borda de um deles em um loop. Se o foco for encontrado, atualiza as coordenadas do cursor do mouse, torná-o visível e deixa o método. Se o foco não foi encontrado, então, o ponteiro deve ser ocultado

class CCanvasTable : public CElement
  {
private:
   //--- Verifica o foco sobre as bordas dos cabeçalhos para alterar suas larguras
   void              CheckColumnResizeFocus(void);
  };
//+---------------------------------------------------------------------------+
//| Verifica o foco sobre as bordas dos cabeçalhos para alterar suas larguras |
//+---------------------------------------------------------------------------+
void CCanvasTable::CheckColumnResizeFocus(void)
  {
//--- Sai, se o modo de alteração das larguras das colunas é desativada
   if(!m_column_resize_mode)
      return;
//--- Sai, se começou a alteração da largura da coluna
   if(m_column_resize_control!=WRONG_VALUE)
     {
      //--- Atualiza as coordenadas do cursor e torná-o visível
      m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
      return;
     }
//--- Para verificar o foco nas bordas dos cabeçalhos
   bool is_focus=false;
//--- Se o cursor do mouse está na área dos cabeçalhos
   if(m_headers.MouseFocus())
     {
      //--- Coordenadas dos cabeçalhos
      int x1=0,x2=0;
      //--- Obtém o deslocamento ao longo do eixo X
      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //--- Obtém as coordenadas relativas ao cursor do mouse
      int x=m_mouse.X()-m_headers.X()+xoffset;
      //--- Busca o foco
      for(int i=0; i<m_columns_total; i++)
        {
         //--- Calcula as coordenadas
         x1=x2+=m_vcolumns[i].m_width;
         //--- Verificando o foco
         if(is_focus=x>x1-m_sep_x_offset && x<=x2+m_sep_x_offset)
            break;
        }
      //--- Se houver um foco
      if(is_focus)
        {
         //--- Atualiza as coordenadas do cursor e torná-o visível
         m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
         //--- Mostra o cursor
         m_column_resize.Show();
         return;
        }
     }
//--- Oculta o ponteiro, se não está em foco
   if(!m_headers.MouseFocus() || !is_focus)
      m_column_resize.Hide();
      }

O resultado final é o seguinte:

 Fig. 5. Cabeçalhos de colunas.

Fig. 5. Cabeçalhos de colunas.

 

 


Ajuste do comprimento da string em relação à largura da coluna

Anteriormente, para fazer com que o texto não se sobreponha as células adjacentes, foi necessário selecionar manualmente a largura da coluna e recompilar o arquivo para ver o resultado. Naturalmente, isto é inconveniente.

Vamos fazer com que o tamanho da string se ajuste automaticamente caso ele não se encaixe na célula da tabela. As strings ajustadas anteriormente não serão reajustadas quando a tabela for redesenhada. Adicione outro array para a estrutura das propriedades da tabela a fim de armazenar essas strings.

class CCanvasTable : public CElement
  {
private:
   //--- Array de valores e propriedades da tabela
   struct CTOptions
     {
      string            m_vrows[];
      string            m_text[];
      int               m_width;
      ENUM_ALIGN_MODE   m_text_align;
     };
   CTOptions         m_vcolumns[];
      };

Como resultado, o m_vrows[] irá armazenar o texto completo, enquanto que o array m_text[] irá armazenar a versão ajustada do texto.

O método CCanvasTable::CorrectingText() será responsável em ajustar o tamanho da string tanto nos cabeçalhos quanto nas células da tabela. Depois de identificar o texto para trabalhar com ele, obtenha a sua largura. Em seguida, verifique se o texto completo da string se encaixa na célula com a consideração de todos os deslocamentos das bordas das células. Se ele se encaixa, salve-o no array m_text[] e deixe o método. Na versão atual, o texto ajustado é guardado apenas para as células, mas não para cabeçalhos.

Se o texto não se encaixa, então os caracteres excessivos devem ser cortados e uma reticência ('...') deve ser adicionada. As reticências irão indicar que o texto apresentado foi encurtado. Este procedimento é fácil de implementar:

1) Obtenha o tamanho da string.

2) Em seguida, itere sobre todos os caracteres em um loop a partir do último caractere, exclua o último caractere e salve o texto cortado em uma variável temporária.

Se não houver caracteres restantes, retornar uma string vazia.

4) Enquanto existem algum caractere restante, obtenha a largura da string resultante, incluindo as reticências.

5) Verifique se a string se encaixa na célula da tabela desta forma, com a consideração dos deslocamentos especificados a partir das bordas das celulas.

6) Se a string se encaixa, então armazene-os a uma variável local do método e pare o ciclo.

7) Depois disso, armazene a sequência ajustada para o array m_text[] e retorne-o do método. 

class CCanvasTable : public CElement
  {
private:
   //--- Retorna o texto ajustado para a largura da coluna
   string            CorrectingText(const int column_index,const int row_index,const bool headers=false);
  };
//+------------------------------------------------------------------+
//| Retorna o texto ajustado para a largura da coluna                |
//+------------------------------------------------------------------+
string CCanvasTable::CorrectingText(const int column_index,const int row_index,const bool headers=false)
  {
//--- Obtém o texto atual
   string corrected_text=(headers)? m_header_text[column_index]: m_vcolumns[column_index].m_vrows[row_index];
//--- Deslocamentos a partir das bordas da célula ao longo do eixo X
   int x_offset=m_text_x_offset*2;
//--- Obtém o ponteiro para o objeto da tela
   CRectCanvas *obj=(headers)? ::GetPointer(m_headers) : ::GetPointer(m_table);
//--- Obtém a largura do texto
   int full_text_width=obj.TextWidth(corrected_text);
//--- Se ele se encaixa na célula, salva o texto ajustado em um array separado e retorna ele
   if(full_text_width<=m_vcolumns[column_index].m_width-x_offset)
     {
      //--- Se esses não são cabeçalhos, salva o texto ajustado
      if(!headers)
         m_vcolumns[column_index].m_text[row_index]=corrected_text;
      //---
      return(corrected_text);
     }
//--- Se o texto não couber na célula, é necessário ajustar o texto (cortar os excessivos caracteres e adicionar as reticências)
   else
     {
      //--- Para trabalhar com uma string
      string temp_text="";
      //--- Obtém o comprimento da string
      int total=::StringLen(corrected_text);
      //--- Remove os caracteres da string, um por um, até que a largura do texto desejado seja alcançado
      for(int i=total-1; i>=0; i--)
        {
         //--- Exclui um caractere
         temp_text=::StringSubstr(corrected_text,0,i);
         //--- Se não sobrou nada, deixa uma string vazia
         if(temp_text=="")
           {
            corrected_text="";
            break;
           }
         //--- Adiciona reticências antes da verificação
         int text_width=obj.TextWidth(temp_text+"...");
         //--- Se encaixa na célula
         if(text_width<m_vcolumns[column_index].m_width-x_offset)
           {
            //--- Salva o texto e para o ciclo
            corrected_text=temp_text+"...";
            break;
           }
        }
     }
//--- Se esses não são cabeçalhos, salva o texto ajustado
   if(!headers)
      m_vcolumns[column_index].m_text[row_index]=corrected_text;
//--- Retorna o texto ajustado
   return(corrected_text);
      }

Usando as strings ajustadas enquanto a tabela é redesenhada é especialmente relevante durante o processo de alteração da largura da coluna. Em vez de ajustar o texto em todas as células da tabela repetidamente, é suficiente fazer isso apenas nas células da coluna, onde a largura está sendo alterada. Isso poupa os recursos do CPU.

O método CCanvasTable::Text() irá determinar se é necessário ajustar o texto para a coluna especificada ou se é suficiente enviar a versão previamente ajustada. Seu código é da seguinte maneira: 
class CCanvasTable : public CElement
  {
private:
   //--- Retorna o texto
   string            Text(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Retorna o texto                                                  |
//+------------------------------------------------------------------+
string CCanvasTable::Text(const int column_index,const int row_index)
  {
   string text="";
//--- Ajusta o texto, se não no modo de alteração da largura da coluna
   if(m_column_resize_control==WRONG_VALUE)
      text=CorrectingText(column_index,row_index);
//--- Se no modo de alteração da largura da coluna, então...
   else
     {
      //--- ...ajusta o texto apenas para a coluna com a largura sendo alterada
      if(column_index==m_column_resize_control)
         text=CorrectingText(column_index,row_index);
      //--- Para todos os outros, use o text previamente ajustado
      else
         text=m_vcolumns[column_index].m_text[row_index];
     }
//--- Retorna o texto
   return(text);
      }

Abaixo está o código do método CCanvasTable::ChangeColumnWidth(), que foi designado para modificar a largura da coluna.

A largura mínima da coluna é definida para 30 píxels. O programa deixa o método se a exibição do cabeçalho é desativada. Se a verificação é passada, o foco é verificado nas bordas dos cabeçalhos. Se esta verificação determina que o processo não foi iniciado/concluído, as variáveis ​​auxiliares são zeradas e o programa deixa o método. Se o processo estiver em execução, então, obtém a coordenada relativa X do cursor. E se o processo apenas começou, então é necessário armazenar a coordenada X atual do cursor (a variável x_fixed) e a largura da coluna arrastada (a variável prev_width). As variáveis ​​locais destinadas para este fim são estáticas. Portanto, cada vez que este método for inserido, os valores serão armazenados, até que eles sejam zerados quando o processo for concluído. 

Agora, calcula a nova largura para a coluna. Se acontecer de atingir a largura mínima da coluna, o programa deixa o método. Caso contrário, a nova largura é armazenada na estrutura das propriedades da tabela na coluna especificada. Depois disso, as dimensões da tabela são recalculadas e reaplicada, e a tabela é redesenhada no final do método. 

class CCanvasTable : public CElement
  {
private:
   //--- A largura mínima das colunas
   int               m_min_column_width;
   //---
private:
   //--- Altera a largura da coluna arrastada
   void              ChangeColumnWidth(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_min_column_width(30)
  {
   ...
  }
//+------------------------------------------------------------------+
//| Altera a largura da coluna arrastada                             |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeColumnWidth(void)
  {
//--- Sai, se os cabeçalhos são desativados
   if(!m_show_headers)
      return;
//--- Verifica o foco nas bordas dos cabeçalhos
   CheckColumnResizeFocus();
//--- Variáveis ​​auxiliares
   static int x_fixed    =0;
   static int prev_width =0;
//--- Se concluída, redefine o valor
   if(m_column_resize_control==WRONG_VALUE)
     {
      x_fixed    =0;
      prev_width =0;
      return;
     }
//--- Obtém o deslocamento ao longo do eixo X
   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
//--- Obtém as coordenadas relativas ao cursor do mouse
   int x=m_mouse.X()-m_headers.X()+xoffset;
//--- Se o processo de alterar a largura da coluna está apenas começando
   if(x_fixed<1)
     {
      //--- Armazena a coordenada X atual e largura da coluna
      x_fixed    =x;
      prev_width =m_vcolumns[m_column_resize_control].m_width;
     }
//--- Calcula a nova largura para a coluna
   int new_width=prev_width+(x-x_fixed);
//--- Deixa inalterado, se for menor do que o limite especificado
   if(new_width<m_min_column_width)
      return;
//--- Salve a nova largura da coluna
   m_vcolumns[m_column_resize_control].m_width=new_width;
//--- Calcula o tamanho da tabela
   CalculateTableSize();
//--- Redimensiona a tabela
   ChangeTableSize();
//--- Desenhar a tabela
   DrawTable();
      }

O resultado é o seguinte:

 Fig. 5. Ajuste do comprimento da string em relação à largura variável da coluna.

Fig. 5. Ajuste do comprimento da string em relação à largura variável da coluna. 

 


Manipulação de Eventos

O gerenciamento de cores dos objetos da tabela e a alteração das larguras de suas colunas é realizado pelo manipulador do controle do evento de movimento do mouse (CHARTEVENT_MOUSE_MOVE). 

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CCanvasTable::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 controle está oculto
      if(!CElementBase::IsVisible())
         return;
      //--- Sai, se os números de sub-janelas não correspondem
      if(!CElementBase::CheckSubwindowNumber())
         return;
      //--- Verificando o foco sobre os elementos
      CElementBase::CheckMouseFocus();
      m_headers.MouseFocus(m_mouse.X()>m_headers.X() && m_mouse.X()<m_headers.X2() &&
                           m_mouse.Y()>m_headers.Y() && m_mouse.Y()<m_headers.Y2());
      //--- Se a barra de rolagem está ativa
      if(m_scrollv.ScrollBarControl() || m_scrollh.ScrollBarControl())
        {
         ShiftTable();
         return;
        }
      //--- Altera as cores do objeto
      ChangeObjectsColor();
      //--- Altera a largura da coluna arrastada
      ChangeColumnWidth();
      return;
     }
   ...
      }

Outro identificador de evento novo será necessário, a fim de determinar o momento de alterar o estado do botão esquerdo do mouse. É necessário se livrar das verificações repetidas e o tratamento simultâneo em vários blocos de código do manipulador de eventos. Adicione o identificador ON_CHANGE_MOUSE_LEFT_BUTTON para o arquivo Define.mqh

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
  #define ON_CHANGE_MOUSE_LEFT_BUTTON (33) // Altera o estado do botão esquerdo do mouse

Além disso, o método CMouse::CheckChangeLeftButtonState() foi adicionado à classe para obter os parâmetros atuais do mouse (CMouse). Ele permite determinar o momento da alteração no estado do botão esquerdo do mouse. Este método é chamado no manipulador da classe. Se o estado do botão esquerdo do mouse mudou, o método envia uma mensagem com o identificador ON_CHANGE_MOUSE_LEFT_BUTTON. Esta mensagem pode mais tarde ser recebida e processada em qualquer controle. 

//+------------------------------------------------------------------+
//| Classe para obter os parâmetros do mouse                         |
//+------------------------------------------------------------------+
class CMouse
  {
private:
   //--- Verificação da mudança no estado do botão esquerdo do mouse
   bool              CheckChangeLeftButtonState(const string mouse_state);
  };
//+------------------------------------------------------------------+
//| Manipular de eventos de movimento do cursor do mouse             |
//+------------------------------------------------------------------+
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipulação do evento do movimento do cursor
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Coordenadas e o estado do botão esquerdo do mouse
      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =CheckChangeLeftButtonState(sparam);
      ...
     }
  }
//+------------------------------------------------------------------+
//| Verificação da mudança no estado do botão esquerdo do mouse      |
//+------------------------------------------------------------------+
bool CMouse::CheckChangeLeftButtonState(const string mouse_state)
  {
   bool left_button_state=(bool)int(mouse_state);
//--- Envia uma mensagem sobre uma mudança no estado do botão esquerdo do mouse
   if(m_left_button_state!=left_button_state)
      ::EventChartCustom(m_chart.ChartId(),ON_CHANGE_MOUSE_LEFT_BUTTON,0,0.0,"");
//---
   return(left_button_state);
      }

A manipulação dos eventos com o identificador ON_CHANGE_MOUSE_LEFT_BUTTON é necessário na classe CCanvasTable:

  •  para zerar certos campos da classe;
  •  para ajustar as barras de rolagem;
  •  para redesenhar a tabela
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Altera o estado do botão esquerdo do mouse
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      //--- Sai, se os cabeçalhos são desativados
      if(!m_show_headers)
         return;
      //--- Se o botão esquerdo do mouse foi liberado
      if(!m_mouse.LeftButtonState())
        {
         //--- Reseta o modo de alteração da largura
         m_column_resize_control=WRONG_VALUE;
         //--- Oculta o cursor
         m_column_resize.Hide();
         //--- Ajusta a barra de rolagem levando em consideração as recentes alterações
         HorizontalScrolling(m_scrollh.CurrentPos());
        }
      //--- Reseta o índice do último foco em um cabeçalho
      m_prev_header_index_focus=WRONG_VALUE;
      //--- Altera as cores do objeto
      ChangeObjectsColor();
     }
      }

As imagens animadas do artigo demonstram o resultado da operação da aplicação em MQL, que pode ser baixada usando o link abaixo para estudos ainda mais detalhados.

 

Conclusão

A atualização atual da biblioteca melhora a tabela renderizada do tipo CCanvasTable. Esta não é a versão final da tabela. Ela continuará a ser desenvolvida e novos recursos serão adicionados a ela.

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.

 

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.