Interfaces gráficas VII: O Controle Tabela (Capítulo 1)
Anatoli Kazharski | 10 outubro, 2016
Conteúdo
- Introdução
- O controle Tabela com o rótulo de texto
- O Controle Tabela com a Caixa de Edição
- O Controle Tabela Renderizada
- Conclusão
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:
- Fundo.
- Rótulos de texto.
- Barra de rolagem vertical.
- Barra de rolagem horizontal.
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
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:
- Fundo
- Caixas de edição
- Barra de rolagem vertical
- Barra de rolagem horizontal
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
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:
- Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)
- Interfaces Gráficas IV: Elementos Informativos da Interface (Capítulo 1)
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:
- Fundo
- Tabela renderizada
- Barra de rolagem vertical
- Barra de rolagem horizontal
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
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
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:
- Interfaces gráficas VII: O Controle Tabela (Capítulo 1)
- Interfaces Gráficas VII: O Controle Guias (Capítulo 2)