Interfaces Gráficas X: Atualizações para a tabela Renderizada e otimização do código (build 10)
Conteúdo
- Introdução
- Coordenadas relativas do cursor do mouse na tela especificada
- Mudanças na estrutura da tabela
- Determinação do intervalo das linhas na parte visível
- Ícones nas células da tabela
- Realce das linhas da tabela quando o mouse estiver em cima
- Métodos para redesenhar rapidamente as células da tabela
- Aplicação para testar os controles
- Conclusão
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.
Nós continuamos a complementar a tabela renderizada (CCanvasTable) com novas funcionalidades. Os seguintes recursos serão adicionados neste momento.
- Realce das linhas da tabela quando o mouse estiver em cima.
- Possibilidade de adicionar um array de ícones para cada célula e um método para trocá-los.
- Possibilidade de definir e modificar o texto na célula durante a execução do programa.
Além disso, o código e alguns algoritmos foram otimizados para que a tabela fosse redesenhada de forma mais rápida.
Coordenadas relativas ao cursor na tela especificada
Para eliminar o código duplicado em muitas classes e métodos ao calcular as coordenadas relativas na tela, os métodos CMouse::RelativeX() e CMouse::RelativeY() foram adicionados à classe CMouse para recuperar as coordenadas. Uma referência a um objeto do tipo CRectCanvas deve ser passada para estes métodos, a fim de calcular a coordenada relativa levando em consideração o atual deslocamento da parte visível da tela.
//+------------------------------------------------------------------+ //| Classe para obter os parâmetros do mouse | //+------------------------------------------------------------------+ class CMouse { public: //--- Retorna as coordenadas relativas ao cursor do mouse do objeto de tela passado int RelativeX(CRectCanvas &object); int RelativeY(CRectCanvas &object); }; //+------------------------------------------------------------------+ //| Retorna a coordenada X relativa ao cursor do mouse | //| do objeto de tela passado | //+------------------------------------------------------------------+ int CMouse::RelativeX(CRectCanvas &object) { return(m_x-object.X()+(int)object.GetInteger(OBJPROP_XOFFSET)); } //+------------------------------------------------------------------+ //| Retorna a coordenada Y relativa ao cursor do mouse | //| do objeto de tela passado | //+------------------------------------------------------------------+ int CMouse::RelativeY(CRectCanvas &object) { return(m_y-object.Y()+(int)object.GetInteger(OBJPROP_YOFFSET)); }
Na continuação do desenvolvimento da biblioteca, estes métodos serão usados para se obter as coordenadas relativas de todos os controles desenhados.
Mudanças na estrutura da tabela
Para otimizar a execução do código da tabela renderizada o máximo possível, foi necessário modificar um pouco e complementar a estrutura da tabela do tipo CTOptions, adicionando novas estruturas que permitem a construção de arrays multidimensionais. A tarefa aqui é fazer com que certos fragmentos da tabela se redesenhem com base nos valores calculados anteriormente. Por exemplo, estes podem ser as coordenadas das colunas da tabela e as bordas das linhas.
Por exemplo, o cálculo e armazenamento da coordenada X das bordas da coluna é conveniente somente no método CCanvasTable::DrawGrid(), que é utilizado para desenhar a grade, e somente quando se desenha a tabela inteira. E quando o usuário seleciona uma linha da tabela, pode-se utilizar os valores predeterminados. O mesmo se aplica para o realce das linhas da tabela quando o mouse estiver em cima (isto irá ser discutido mais adiante no artigo).
Crie uma estrutura separada (CTRowOptions) e declare um array de suas instâncias para armazenar a coordenada Y das linhas da tabela, e possivelmente, outras propriedades da linha no futuro. A coordenada Y das linhas são calculadas no método CCanvasTable::DrawRows(), designado para desenhar o fundo das linhas. Como este método é chamado antes de desenhar a grade, o método CCanvasTable::DrawGrid() usa os valores pré-calculados a partir da estrutura CTRowOptions.
Crie uma estrutura separada do tipo CTCell para armazenar as propriedades das células da tabela. O array de instâncias na estrutura CTRowOptions é declarada com este tipo, como um array de linhas da tabela. Esta estrutura irá armazenar:
- Array de ícones
- Arrays dos tamanhos dos ícones
- Índice do ícone selecionado (exibido) na célula
- Texto completo
- Texto reduzido
- Cor do texto
Como cada ícone é um array de pixels, será necessário uma estrutura separada (CTImage) com um array dinâmico para armazená-los. O código destas estruturas podem ser encontrados na lista abaixo:
class CCanvasTable : public CElement { private: //--- Array de pixels do ícone struct CTImage { uint m_image_data[]; }; //--- Propriedades das células da tabela struct CTCell { CTImage m_images[]; // Array de ícones uint m_image_width[]; // Array da largura dos ícones uint m_image_height[]; // Array da altura dos ícones int m_selected_image; // Índice do ícone selecionado (exibido) string m_full_text; // Texto completo string m_short_text; // Texto reduzido color m_text_color; // Cor do texto }; //--- Array de linhas e propriedades das colunas da tabela struct CTOptions { int m_x; // Coordenada X da borda esquerda da coluna int m_x2; // Coordenada X da borda direita da coluna int m_width; // Largura da coluna ENUM_ALIGN_MODE m_text_align; // Modo de alinhamento do texto nas células da coluna int m_text_x_offset; // Deslocamento do texto string m_header_text; // Texto do cabeçalho da coluna CTCell m_rows[]; // Array de linhas da tabela }; CTOptions m_columns[]; //--- Array das propriedades da linha da tabela struct CTRowOptions { int m_y; // Coordenada Y da borda superior da linha int m_y2; // Coordenada Y da borda inferior da linha }; CTRowOptions m_rows[]; };
As alterações apropriadas foram feitas para todos os métodos de onde esses tipos de dados são usados.
Determinação do intervalo das linhas na parte visível
Já que uma tabela pode ter várias linhas, a busca do foco em uma linha seguido pelo redesenho da tabela pode desacelerar significativamente o processo. O mesmo aplica-se a seleção de uma linha e ajustando o comprimento do texto durante a alteração da largura da coluna manualmente. A fim de evitar atraso, é necessário determinar o primeiro e o último índice na parte visível da tabela e organizar um ciclo para iterar apenas nesse intervalo. O método CCanvasTable::VisibleTableIndexes() foi implementado para essa finalidade. Primeiro ele determina os limites da parte visível. O limite superior é o deslocamento da parte visível ao longo do eixo Y e o limite inferior é definido como a parte superior + o tamanho da parte visível ao longo do eixo Y.
Agora é conveniente dividir os valores das bordas obtidos pela altura da linha definida nas configurações da tabela a fim de determinar os índices das linhas superior e inferior da parte visível. No caso de exceder o intervalo da última linha da tabela, o ajuste é realizado no final do método.
class CCanvasTable : public CElement { private: //--- Para determinar os índices da parte visível da tabela int m_visible_table_from_index; int m_visible_table_to_index; //--- private: //--- Determinando os índices da parte visível da tabela void VisibleTableIndexes(void); }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CCanvasTable::CCanvasTable(void) : m_visible_table_from_index(WRONG_VALUE), m_visible_table_to_index(WRONG_VALUE) { ... } //+------------------------------------------------------------------+ //| Determinando os índices da parte visível da tabela | //+------------------------------------------------------------------+ void CCanvasTable::VisibleTableIndexes(void) { //--- Determinando os limites que levam em conta o deslocamento da parte visível da tabela int yoffset1 =(int)m_table.GetInteger(OBJPROP_YOFFSET); int yoffset2 =yoffset1+m_table_visible_y_size; //--- Determinando o primeiro e os últimos índices da parte visível da tabela m_visible_table_from_index =int(double(yoffset1/m_cell_y_size)); m_visible_table_to_index =int(double(yoffset2/m_cell_y_size)); //--- Aumenta o índice inferior por um, se ele não estiver fora do intervalo m_visible_table_to_index=(m_visible_table_to_index+1>m_rows_total)? m_rows_total : m_visible_table_to_index+1; }
Os índices serão determinados no método CCanvasTable::DrawTable(). Neste método pode ser passado um argumento para especificar que é necessário redesenhar somente a parte visível da tabela. O valor padrão do argumento é false, na qual indica o redesenho da tabela inteira. O código a seguir mostra a versão resumida deste método.
//+------------------------------------------------------------------+ //| Desenha a tabela | //+------------------------------------------------------------------+ void CCanvasTable::DrawTable(const bool only_visible=false) { //--- Se não foi indicado para redesenhar apenas a parte visível da tabela if(!only_visible) { //--- Define os índices da linha de toda a tabela desde o início até o fim m_visible_table_from_index =0; m_visible_table_to_index =m_rows_total; } //--- Obtém os índices da linha da parte visível da tabela else VisibleTableIndexes(); //--- Desenha o fundo das linhas da tabela //--- Desenha uma linha selecionada //--- Desenha a grade //--- Desenha o ícone //--- Desenha o texto //--- Exibe as mudanças mais recentes extraídas //--- Atualiza cabeçalhos, se eles estão habilitados //--- Ajusta a tabela em relação às barras de rolagem }
É necessário realizar no método uma chamada à CCanvasTable::VisibleTableIndexes() para determinar o foco sobre as linhas da tabela:
//+------------------------------------------------------------------+ //| Verificação do foco sobre as linhas da tabela | //+------------------------------------------------------------------+ int CCanvasTable::CheckRowFocus(void) { int item_index_focus=WRONG_VALUE; //--- Obtém a coordenada Y relativa abaixo do cursor do mouse int y=m_mouse.RelativeY(m_table); //--- Obtém os índices do local da tabela VisibleTableIndexes(); //--- Busca o foco for(int i=m_visible_table_from_index; i<m_visible_table_to_index; i++) { //--- Se o foco da linha mudou if(y>m_rows[i].m_y && y<=m_rows[i].m_y2) { item_index_focus=i; break; } } //--- Retorna o índice da linha com o foco return(item_index_focus); }
Ícones nas células da tabela
Vários ícones podem ser atribuídos a cada célula, que podem ser alterados durante a execução do programa. Adiciona os campos e métodos para definir os deslocamentos do ícone a partir das bordas superior e esquerda da célula:
class CCanvasTable : public CElement { private: //--- Deslocamentos das bordas das celulas int m_image_x_offset; int m_image_y_offset; //--- public: //--- Deslocamentos das bordas das celulas void ImageXOffset(const int x_offset) { m_image_x_offset=x_offset; } void ImageYOffset(const int y_offset) { m_image_y_offset=y_offset; } };
Para atribuir os ícones para a célula especificada, é necessário passar um array com os seus caminhos na pasta local do terminal. Antes disso, eles devem ser incluídos na aplicação MQL como um recurso (#resource). O método CCanvasTable::SetImages() é projetado para esta finalidade. Aqui, se um array vazio é passado ou for detectado que seu tamanho foi excedido, o programa sai do método.
Se ele passar nas verificações, os arrays da célula são redimensionadas. Depois disso, o método ::ResourceReadImage() é utilizado em um loop para ler o conteúdo do ícone para um array unidimensional, armazenando a cor de cada pixel no array. Os tamanhos dos ícones são armazenados aos arrays correspondentes. Eles serão necessários para organizar os loops para desenhar os ícones na tela. O primeiro ícone do array será selecionado na célula por padrão.
class CCanvasTable : public CElement { public: //--- Ajusta os ícones para a célula especificada void SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[]); }; //+------------------------------------------------------------------+ //| Ajusta os ícones para a célula especificada | //+------------------------------------------------------------------+ void CCanvasTable::SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[]) { int total=0; //--- Sai se um array de tamanho zero foi aprovado if((total=CheckArraySize(bmp_file_path))==WRONG_VALUE) return; //--- Verifica se o tamanho do array não excedeu if(!CheckOutOfRange(column_index,row_index)) return; //--- Redimensiona os arrays ::ArrayResize(m_columns[column_index].m_rows[row_index].m_images,total); ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_width,total); ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_height,total); //--- for(int i=0; i<total; i++) { //--- O primeiro ícone do array é selecionado por padrão m_columns[column_index].m_rows[row_index].m_selected_image=0; //--- Escreve o ícone passado para o array e armazena o seu tamanho if(!ResourceReadImage(bmp_file_path[i],m_columns[column_index].m_rows[row_index].m_images[i].m_image_data, m_columns[column_index].m_rows[row_index].m_image_width[i], m_columns[column_index].m_rows[row_index].m_image_height[i])) { Print(__FUNCTION__," > error: ",GetLastError()); return; } } }
Para descobrir quantos ícones uma célula em particular tem, use o método CCanvasTable::ImagesTotal():
class CCanvasTable : public CElement { public: //--- Retorna o número total de ícones na célula especificada int ImagesTotal(const uint column_index,const uint row_index); }; //+------------------------------------------------------------------+ //| Retorna o número total de ícones na célula especificada | //+------------------------------------------------------------------+ int CCanvasTable::ImagesTotal(const uint column_index,const uint row_index) { //--- Verifica se o tamanho do array não excedeu if(!CheckOutOfRange(column_index,row_index)) return(WRONG_VALUE); //--- Retorna o tamanho do array de ícones return(::ArraySize(m_columns[column_index].m_rows[row_index].m_images)); }
Agora, considere os métodos que serão utilizados para desenhar os ícones. Primeiramente, um novo método CColors::BlendColors() foi adicionado a classe CColors, que permite misturar corretamente as cores superiores e inferiores levando em consideração a transparência do ícone de sobreposição. Bem como um método auxiliar CColors::GetA() para obter o valor de transparência da cor passada.
No método CColors::BlendColors(), as cores passadas são divididas primeiro em componentes RGB, e o canal alfa é extraído a partir de sua cor superior. O canal alfa é convertido para um valor entre zero e um. Se a cor passada não conter transparência, a mistura não é executada. No caso de haver transparência, então, cada componente das duas cores passadas será misturado tendo em conta a transparência da cor superior. Depois disso, os valores dos componentes obtidos são ajustadas, no caso deles estarem fora do intervalo (255).
//+------------------------------------------------------------------+ //| Classe para trabalhar com as cores | //+------------------------------------------------------------------+ class CColors { public: double GetA(const color aColor); color BlendColors(const uint lower_color,const uint upper_color); }; //+------------------------------------------------------------------+ //| Obtendo o valor do componente A | //+------------------------------------------------------------------+ double CColors::GetA(const color aColor) { return(double(uchar((aColor)>>24))); } //+------------------------------------------------------------------+ //| Misturando duas cores considerando a transparência da cor superior | //+------------------------------------------------------------------+ color CColors::BlendColors(const uint lower_color,const uint upper_color) { double r1=0,g1=0,b1=0; double r2=0,g2=0,b2=0,alpha=0; double r3=0,g3=0,b3=0; //--- Converte as cores em formato ARGB uint pixel_color=::ColorToARGB(upper_color); //--- Obtém os componentes das cores inferior e superior ColorToRGB(lower_color,r1,g1,b1); ColorToRGB(pixel_color,r2,g2,b2); //--- Obtém o percentual de transparência de 0.00 até 1.00 alpha=GetA(upper_color)/255.0; //--- Se não houver transparência if(alpha<1.0) { //--- Mistura os componentes, levando em consideração o canal alfa r3=(r1*(1-alpha))+(r2*alpha); g3=(g1*(1-alpha))+(g2*alpha); b3=(b1*(1-alpha))+(b2*alpha); //--- Ajuste dos valores obtidos r3=(r3>255)? 255 : r3; g3=(g3>255)? 255 : g3; b3=(b3>255)? 255 : b3; } else { r3=r2; g3=g2; b3=b2; } //--- Combina os componentes obtidos e retorna a cor return(RGBToColor(r3,g3,b3)); }
Agora é fácil escrever um método para desenhar os ícones. O código do método CCanvasTable::DrawImage() é apresentado abaixo. Os índices das células da tabela de devem ser passados, onde o ícone será desenhado. No início do método, as coordenadas do ícone são obtidas levando em consideração os desvios, bem como o índice da célula selecionada e do seu tamanho. Em seguida, um loop duplo gera o pixel do ícone por pixel. Se o pixel especificado está vazio (não tem cor), então, o loop passa para o próximo pixel. Se há uma cor, então, a cor de fundo da célula e a cor do pixel atual são determinados, em seguida, estas duas cores são misturadas levando em consideração a transparência da cor de sobreposição. A cor resultante é desenhada na tela.
class CCanvasTable : public CElement { private: //--- Desenha um ícone na célula especificada void DrawImage(const int column_index,const int row_index); }; //+------------------------------------------------------------------+ //| Desenha um ícone na célula especificada | //+------------------------------------------------------------------+ void CCanvasTable::DrawImage(const int column_index,const int row_index) { //--- Calcula as coordenadas int x =m_columns[column_index].m_x+m_image_x_offset; int y =m_rows[row_index].m_y+m_image_y_offset; //--- O ícone selecionado na célula e seu tamanho int selected_image =m_columns[column_index].m_rows[row_index].m_selected_image; uint image_height =m_columns[column_index].m_rows[row_index].m_image_height[selected_image]; uint image_width =m_columns[column_index].m_rows[row_index].m_image_width[selected_image]; //--- Desenha for(uint ly=0,i=0; ly<image_height; ly++) { for(uint lx=0; lx<image_width; lx++,i++) { //--- Se não há cor, vai para o próximo pixel if(m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i]<1) continue; //--- Obtém a cor da camada inferior (fundo da célula) e a cor do pixel especificado do ícone uint background =(row_index==m_selected_item)? m_selected_row_color : m_table.PixelGet(x+lx,y+ly); uint pixel_color =m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i]; //--- Mistura as cores uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color)); //--- Desenha o pixel da sobreposição do ícone m_table.PixelSet(x+lx,y+ly,foreground); } } }
O método CCanvasTable::DrawImages() é designado para extrair todos os ícones da tabela de uma só vez, levando em consideração quando é necessário desenhar apenas a parte visível da tabela. Na versão atual da tabela, os ícones podem ser desenhados somente se o texto nas colunas estiver alinhado à esquerda. Além disso, cada iteração verifica se um ícone é atribuído à célula, e também se o array de seus pixels é vazio. Se ele é passado em todas as verificações, o método CCanvasTable::DrawImage() é chamado para desenhar o ícone.
class CCanvasTable : public CElement { private: //--- Desenha todos os ícones da tabela void DrawImages(void); }; //+------------------------------------------------------------------+ //| Desenha todos os ícones da tabela | //+------------------------------------------------------------------+ void CCanvasTable::DrawImages(void) { //--- Para calcular as coordenadas int x=0,y=0; //--- Colunas for(int c=0; c<m_columns_total; c++) { //--- Se o texto não está alinhado à esquerda, vá para a próxima coluna if(m_columns[c].m_text_align!=ALIGN_LEFT) continue; //--- Linhas for(int r=m_visible_table_from_index; r<m_visible_table_to_index; r++) { / --- Vá para a próxima, se esta célula não contém ícones if(ImagesTotal(c,r)<1) continue; //--- O ícone selecionado na célula (o primeiro [0] é selecionada por padrão) int selected_image=m_columns[c].m_rows[r].m_selected_image; //--- Vá para a próxima, se o array de pixels for vazio if(::ArraySize(m_columns[c].m_rows[r].m_images[selected_image].m_image_data)<1) continue; //--- Desenha o ícone DrawImage(c,r); } } }
A imagem abaixo demonstra um exemplo de uma tabela com ícones nas células:
Fig. 1. Tabela com ícones nas células.
Realce das linhas da tabela quando o mouse estiver em cima
Serão requeridos campos e métodos adicionais para as linhas da tabela renderizada que serão realçadas quando o mouse estiver em cima. Use o método CCanvasTable::LightsHover() para ativar o modo de realce. A cor da linha pode ser definida com a ajuda do método CCanvasTable::CellColorHover().
class CCanvasTable : public CElement { private: //--- Cor das células em diferentes estados color m_cell_color; color m_cell_color_hover; //--- Modo de realce quando o cursor estiver pairando sobre a coluna bool m_lights_hover; //--- public: //--- Cor das células em diferentes estados void CellColor(const color clr) { m_cell_color=clr; } void CellColorHover(const color clr) { m_cell_color_hover=clr; } //--- Modo de realce quando o cursor estiver pairando sobre a coluna void LightsHover(const bool flag) { m_lights_hover=flag; } };
O realce de uma linha não requer o redesenho de toda a tabela novamente quando o cursor do mouse se move. Além disso, é altamente recomendável não fazer isso, pois ele diminui drasticamente o desempenho da aplicação, consumindo muitos recursos da CPU. Na primeira/nova entrada do cursor do mouse para a área da tabela, é conveniente olhar para o foco apenas uma vez (iteração sobre todo o array de linhas). O método CCanvasTable::CheckRowFocus() é utilizado para esta finalidade. Uma vez que o foco é encontrado e o índice da linha é armazenado, basta verificar se o foco da linha com o índice armazenado se alterou quando o cursor é movido. O algoritmo descrito é implementado no método CCanvasTable::ChangeRowsColor(), mostrado na lista abaixo. O método CCanvasTable::RedrawRow() é usado para mudar a cor da linha, seu código será apresentado mais tarde. O método CCanvasTable::ChangeRowsColor() é chamado no método CCanvasTable::ChangeObjectsColor() para alterar as cores dos objetos da tabela.
class CCanvasTable : public CElement { private: //--- Para determinar o foco da linha int m_item_index_focus; //--- Para determinar o momento de transição do cursor do mouse de uma linha para outra int m_prev_item_index_focus; //--- private: //--- Mudando a cor da linha quando o mouse estiver em cima void ChangeRowsColor(void); }; //+------------------------------------------------------------------+ //| Mudando a cor da linha quando o mouse estiver em cima | //+------------------------------------------------------------------+ void CCanvasTable::ChangeRowsColor(void) { //--- Sai, se o realce da linha quando o mouse estiver em cima está desativado if(!m_lights_hover) return; //--- Se não estiver em foco if(!m_table.MouseFocus()) { //--- Se ainda não indicou que não está no foco if(m_prev_item_index_focus!=WRONG_VALUE) { m_item_index_focus=WRONG_VALUE; //--- Altera a cor RedrawRow(); m_table.Update(); //--- Reseta o foco m_prev_item_index_focus=WRONG_VALUE; } } //--- Se está em foco else { //--- Verifica o foco nas linhas if(m_item_index_focus==WRONG_VALUE) { //--- Obtém o índice da linha com o foco m_item_index_focus=CheckRowFocus(); //--- Muda a cor da linha RedrawRow(); m_table.Update(); //--- Armazena como o índice anterior em foco m_prev_item_index_focus=m_item_index_focus; return; } //--- Obtém a coordenada Y relativa abaixo do cursor do mouse int y=m_mouse.RelativeY(m_table); //--- Verificando o foco bool condition=(y>m_rows[m_item_index_focus].m_y && y<=m_rows[m_item_index_focus].m_y2); //--- Se o foco mudou if(!condition) { //--- Obtém o índice da linha com o foco m_item_index_focus=CheckRowFocus(); //--- Muda a cor da linha RedrawRow(); m_table.Update(); //--- Armazena como o índice anterior em foco m_prev_item_index_focus=m_item_index_focus; } } }
O método CCanvasTable::RedrawRow() para o redesenho rápido das linhas da tabela opera em dois modos:
- quando se seleciona uma fileira
- no modo de realce de uma linha quando o mouse estiver em cima.
O método precisa ser passado para o argumento correspondente para especificar o modo desejado. Por padrão, o argumento é definido como false, indicando a utilização do método no modo de realce das linhas da tabela. A classe contém os campos especiais para ambos os modos para determinar a linha da tabela selecionada/realçada atual e anterior. Assim, marcando outra linha exige somente o redesenho das linhas anterior e atual, e não da tabela inteira.
O programa deixa o método se os índices não estão definidos (WRONG_VALUE). Em seguida, é necessário determinar quantos índices serão definidos. Se esta é a primeira entrada na tabela e apenas um índice (atual) foi definido, então, a cor será alterada apenas na linha atual. Se entrou novamente, a cor será alterada em duas linhas (a atual e a anterior).
Agora é necessário determinar a sequência para alterar as cores da linha. Se o índice da linha atual for maior que o anterior, isso significa que o cursor se moveu para baixo. Primeiro altere a cor no índice anterior, e depois no atual. Numa situação inversa, fazer o oposto. O método também considera o momento de sair da área da tabela, quando o índice da linha atual não estiver definida, enquanto o índice da linha anterior ainda está presente.
Uma vez que todas as variáveis locais para a operação são inicializados, o fundo das linhas, a grade, os ícones e o texto são desenhadas em sequência estrita.
class CCanvasTable : public CElement { private: //--- Redesenha a linha da tabela especificada de acordo com o modo especificado void RedrawRow(const bool is_selected_row=false); }; //+----------------------------------------------------------------------------+ //| Redesenha a linha da tabela especificada de acordo com o modo especificado | //+----------------------------------------------------------------------------+ void CCanvasTable::RedrawRow(const bool is_selected_row=false) { /--- Os índices das linhas atual e anterior int item_index =WRONG_VALUE; int prev_item_index =WRONG_VALUE; //--- Inicialização dos índices da linha em relação ao modo especificado if(is_selected_row) { item_index =m_selected_item; prev_item_index =m_prev_selected_item; } else { item_index =m_item_index_focus; prev_item_index =m_prev_item_index_focus; } //--- Sai, se os índices não estão definidos if(prev_item_index==WRONG_VALUE && item_index==WRONG_VALUE) return; //--- O número de linhas e colunas para desenhar int rows_total =(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE)? 2 : 1; int columns_total =m_columns_total-1; //--- Coordenadas int x1=1,x2=m_table_x_size; int y1[2]={0},y2[2]={0}; //--- Array para os valores de uma determinada sequência int indexes[2]; //--- Se (1) o cursor do mouse se mover para baixo ou, se (2) entrou pela primeira vez if(item_index>m_prev_item_index_focus || item_index==WRONG_VALUE) { indexes[0]=(item_index==WRONG_VALUE || prev_item_index!=WRONG_VALUE)? prev_item_index : item_index; indexes[1]=item_index; } //--- Se o cursor do mouse foi movido para cima else { indexes[0]=item_index; indexes[1]=prev_item_index; } //--- Desenha o fundo das linhas for(int r=0; r<rows_total; r++) { //--- Calcula as coordenadas da borda superior e inferior da linha y1[r]=m_rows[indexes[r]].m_y+1; y2[r]=m_rows[indexes[r]].m_y2-1; //--- Determina o foco da linha em relação ao modo de realce bool is_item_focus=false; if(!m_lights_hover) is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE); else is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index); //--- Desenha o fundo da linha m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],is_item_focus)); } //--- Cor da grade uint clr=::ColorToARGB(m_grid_color); //--- Desenha as bordas for(int r=0; r<rows_total; r++) { for(int c=0; c<columns_total; c++) m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],clr); } //--- Desenha os ícones for(int r=0; r<rows_total; r++) { for(int c=0; c<m_columns_total; c++) { //--- Desenha o ícone, se (1) ele está presente nesta célula e (2) o texto desta coluna está alinhado à esquerda if(ImagesTotal(c,r)>0 && m_columns[c].m_text_align==ALIGN_LEFT) DrawImage(c,indexes[r]); } } //--- Para calcular as coordenadas int x=0,y=0; //--- Modo de alinhamento do texto uint text_align=0; //--- Desenha o texto for(int c=0; c<m_columns_total; c++) { //--- Obtém (1) a coordenada X do texto e (2) o modo de alinhamento do texto x =TextX(c); text_align =TextAlign(c,TA_TOP); //--- for(int r=0; r<rows_total; r++) { //--- (1) Calcula a coordenada e (2) desenha o texto y=m_rows[indexes[r]].m_y+m_text_y_offset; m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align); } } }
Isso resulta no seguinte:
Fig. 2. Demonstração do realce das linhas da tabela quando o mouse estiver em cima.
Métodos para redesenhar rapidamente as células da tabela
Foram consideradas os métodos para redesenhar rapidamente as linhas da tabela. Os métodos para redesenho rápido das células serão demonstrados agora. Por exemplo, se é necessário alterar o texto, sua cor ou ícone em qualquer célula da tabela, é conveniente redesenhar somente a célula, e não toda a tabela. O método privado CCanvasTable::RedrawCell() é utilizado para esta finalidade. Apenas o conteúdo da célula será redesenhado, enquanto que a sua estrutura não será atualizada. A cor de fundo é determinada levando em consideração o modo de realce, se ativado. Depois de determinar os valores e inicializar as variáveis locais, o fundo, o ícone (se atribuído e se o texto é alinhado à esquerda) e o texto são desenhados na célula.
class CCanvasTable : public CElement { private: //--- Redesenha a célula especificada da tabela void RedrawCell(const int column_index,const int row_index); }; //+------------------------------------------------------------------+ //| Redesenha a célula especificada da tabela | //+------------------------------------------------------------------+ void CCanvasTable::RedrawCell(const int column_index,const int row_index) { //--- Coordenadas int x1=m_columns[column_index].m_x+1; int x2=m_columns[column_index].m_x2-1; int y1=m_rows[row_index].m_y+1; int y2=m_rows[row_index].m_y2-1; //--- Para calcular as coordenadas int x=0,y=0; //--- Para verificar o foco bool is_row_focus=false; //--- Se o modo de realce da linha está habilitado if(m_lights_hover) { //--- (1) Obtém a coordenada Y relativo ao cursor do mouse e (2) o foco sobre a linha da tabela especificada y=m_mouse.RelativeY(m_table); is_row_focus=(y>m_rows[row_index].m_y && y<=m_rows[row_index].m_y2); } //--- Desenha o fundo da célula m_table.FillRectangle(x1,y1,x2,y2,RowColorCurrent(row_index,is_row_focus)); //--- Desenha o ícone, se (1) ele está presente nesta célula e (2) o texto desta coluna está alinhado à esquerda if(ImagesTotal(column_index,row_index)>0 && m_columns[column_index].m_text_align==ALIGN_LEFT) DrawImage(column_index,row_index); //--- Obtém o modo de alinhamento do texto uint text_align=TextAlign(column_index,TA_TOP); //--- Desenha o texto for(int c=0; c<m_columns_total; c++) { //--- Obtém a coordenada X do texto x=TextX(c); //--- Encerra o ciclo if(c==column_index) break; } //--- (1) Calcula a coordenada Y, e (2) extrai o texto y=y1+m_text_y_offset-1; m_table.TextOut(x,y,m_columns[column_index].m_rows[row_index].m_short_text,TextColor(column_index,row_index),text_align); }
Agora, vamos considerar os métodos, que permitem mudar o texto, a cor do texto e o ícone (seleção daquelas que foram atribuídas) na célula. Os métodos públicos CCanvasTable::SetValue() e CCanvasTable::TextColor() devem ser usados para definir o texto e a sua cor. Estes métodos são passados para os índices da célula (coluna e linha) e o valor a ser definido. Para o método CCanvasTable::SetValue(), é um valor de string a ser exibido na célula. Aqui, a string passada, em sua versão completa e reduzida (se a string completa não couber no espaço da célula) são armazenados para os campos correspondentes da estrutura da tabela (CTCell). A cor do texto deve ser passada para o método CCanvasTable::TextColor(). Como o quarto parâmetro está em ambos os métodos, você pode especificar se for necessário redesenhar a célula imediatamente ou se ela será feita mais tarde chamando o método CCanvasTable::UpdateTable().
class CCanvasTable : public CElement { private: //--- Define o valor para a célula da tabela especificada void SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false); //--- Define a cor do texto para a célula da tabela especificada void TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false); }; //+------------------------------------------------------------------+ //| Preenche o array nos os índices específicos | //+------------------------------------------------------------------+ void CCanvasTable::SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false) { //--- Verifica se o tamanho do array não excedeu if(!CheckOutOfRange(column_index,row_index)) return; //--- Armazena o valor no array m_columns[column_index].m_rows[row_index].m_full_text=value; //--- Ajusta e armazena o texto, se ele não se encaixar na célula m_columns[column_index].m_rows[row_index].m_short_text=CorrectingText(column_index,row_index); //--- Redesenha a célula, se especificada if(redraw) RedrawCell(column_index,row_index); } //+------------------------------------------------------------------+ //| Preencher o array de cor do texto | //+------------------------------------------------------------------+ void CCanvasTable::TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false) { //--- Verifica se o tamanho do array não excedeu if(!CheckOutOfRange(column_index,row_index)) return; //--- Armazena a cor do texto no array comum m_columns[column_index].m_rows[row_index].m_text_color=clr; //--- Redesenha a célula, se especificada if(redraw) RedrawCell(column_index,row_index); }
O ícone na célula pode ser alterado pelo método CCanvasTable::ChangeImage(). O índice do ícone para troca deve ser especificado como o terceiro parâmetro aqui. Como nos métodos descritos anteriormente para modificar as propriedades das células, é possível especificar se a célula é para ser redesenhada imediatamente ou mais tarde.
class CCanvasTable : public CElement { private: //--- Altera o ícone na célula especificada void ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false); }; //+------------------------------------------------------------------+ //| Altera o ícone na célula especificada | //+------------------------------------------------------------------+ void CCanvasTable::ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false) { //--- Verifica se o tamanho do array não excedeu if(!CheckOutOfRange(column_index,row_index)) return; //--- Obtém o número de ícones da célula int images_total=ImagesTotal(column_index,row_index); //--- Sai, se (1) não há ícones ou (2) fora do alcance if(images_total==WRONG_VALUE || image_index>=(uint)images_total) return; //--- Sai, se o ícone especificado corresponde ao selecionado if(image_index==m_columns[column_index].m_rows[row_index].m_selected_image) return; //--- Armazena o índice do ícone selecionado da célula m_columns[column_index].m_rows[row_index].m_selected_image=(int)image_index; //--- Redesenha a célula, se especificada if(redraw) RedrawCell(column_index,row_index); }
Outro método público será necessário para redesenha a tabela inteira — CCanvasTable::UpdateTable(). Ele pode ser chamado em dois modos:
- Quando é necessário ele atualizar a tabela para exibir as mudanças recentes feitas pelos métodos descritos acima.
- Quando é necessário redesenhar completamente a tabela, caso as mudanças foram feitas.
Por padrão, o único argumento do método está definido para false, o que indica a atualização sem redesenho.
class CCanvasTable : public CElement { private: //--- Atualizando a tabela void UpdateTable(const bool redraw=false); }; //+------------------------------------------------------------------+ //| Atualizando a tabela | //+------------------------------------------------------------------+ void CCanvasTable::UpdateTable(const bool redraw=false) { //--- Redesenha a tabela, se especificada if(redraw) DrawTable(); //--- Atualiza a tabela m_table.Update(); }
Abaixo está o resultado do trabalho realizado:
Fig. 3. Demonstração das novas características da tabela renderizada.
O Expert Advisor com a demonstração deste resultado pode ser baixado nos arquivos anexados deste artigo. Durante a execução do programa, os ícones em todas as células da tabela (5 colunas e 30 linhas) mudarão com uma frequência de 100 milissegundos. A imagem abaixo mostra a carga da CPU, sem a interação do usuário com a interface gráfica da aplicação MQL. A carga da CPU com a frequência de atualização de 100 milissegundos não excede 3%.
Fig. 4. A carga da CPU durante a execução do aplicativo de teste MQL.
Aplicação para testar os controles
A versão atual da tabela renderizada já é "inteligente" o suficiente para criar as mesmas tabelas como na janela do Observador do Mercado (Market Watch), por exemplo. Vamos tentar demonstrar isso. Para o exemplo, crie uma tabela de 5 colunas e 25 linhas. Esses serão os 25 símbolos disponíveis no servidor MetaQuotes-Demo. Os dados da tabela serão o seguinte:
- Symbol – Instrumentos financeiros (pares de moedas).
- Bid – Oferta de compra.
- Ask – Oferta de venda.
- Spread (!) – diferença dentre a oferta de compra (Bid) e venda (Ask).
- Time – o tempo da última cotação.
Vamos preparar os mesmos ícones para denotar as últimas mudanças no preço como na tabela da janela do Observador do Mercado. A primeira inicialização das células da tabela será feito imediatamente no método de criação do controle e que será realizado através da chamada do método auxiliar CProgram::InitializingTable() da classe personalizada.
//+------------------------------------------------------------------+ //| Classe para a criação de uma aplicação | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Inicializa a tabela void InitializingTable(void); }; //+------------------------------------------------------------------+ //| Inicializa a tabela | //+------------------------------------------------------------------+ void CProgram::InitializingTable(void) { //--- Array dos títulos do cabeçalho string text_headers[COLUMNS1_TOTAL]={"Symbol","Bid","Ask","!","Time"}; //--- Array de símbolos string text_array[25]= { "AUDUSD","GBPUSD","EURUSD","USDCAD","USDCHF","USDJPY","NZDUSD","USDSEK","USDHKD","USDMXN", "USDZAR","USDTRY","GBPAUD","AUDCAD","CADCHF","EURAUD","GBPCHF","GBPJPY","NZDJPY","AUDJPY", "EURJPY","EURCHF","EURGBP","AUDCHF","CHFJPY" }; //--- Array de ícones string image_array[3]= { "::Images\\EasyAndFastGUI\\Icons\\bmp16\\circle_gray.bmp", "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp", "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp" }; //--- for(int c=0; c<COLUMNS1_TOTAL; c++) { //--- Define os títulos do cabeçalho m_canvas_table.SetHeaderText(c,text_headers[c]); //--- for(int r=0; r<ROWS1_TOTAL; r++) { //--- Define os ícones m_canvas_table.SetImages(c,r,image_array); //--- Define os nomes do símbolo if(c<1) m_canvas_table.SetValue(c,r,text_array[r]); //--- Valor padrão para todas as células else m_canvas_table.SetValue(c,r,"-"); } } }
Os valores dessas células da tabela serão atualizados a cada 16ms durante a execução do programa. Outro método auxiliar CProgram::UpdateTable() foi criado para este fim. Aqui, o programa deixa o método se for um dia do fim de semana (sábado ou domingo). Em seguida, um loop duplo itera sobre todas as colunas e linhas da tabela. Este loop duplo obtém os últimos dois ticks para cada símbolo e depois que as modificações nos preços forem analisadas, ele define os valores correspondentes.
class CProgram : public CWndEvents { private: //--- Inicializa a tabela void InitializingTable(void); }; //+------------------------------------------------------------------+ //| Atualiza os valores da tabela | //+------------------------------------------------------------------+ void CProgram::UpdateTable(void) { MqlDateTime check_time; ::TimeToStruct(::TimeTradeServer(),check_time); //--- Sai, se for um sábado ou domingo if(check_time.day_of_week==0 || check_time.day_of_week==6) return; //--- for(int c=0; c<m_canvas_table.ColumnsTotal(); c++) { for(int r=0; r<m_canvas_table.RowsTotal(); r++) { //--- O símbolo para obter os dados para string symbol=m_canvas_table.GetValue(0,r); //--- Obtém os dados dos últimos dois ticks MqlTick ticks[]; if(::CopyTicks(symbol,ticks,COPY_TICKS_ALL,0,2)<2) continue; //--- Define o array como séries temporais ::ArraySetAsSeries(ticks,true); //--- Coluna de símbolos - Symbol. Determina a direção dos preços. if(c==0) { int index=0; //--- Se os preços não mudaram if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid) index=0; //--- Se o preço Bid mudou para cima else if(ticks[0].bid>ticks[1].bid) index=1; //--- Se o preço Bid mudou para baixo else if(ticks[0].bid<ticks[1].bid) index=2; //--- Ajusta o ícone correspondente m_canvas_table.ChangeImage(c,r,index,true); } else { //--- Coluna da diferença de preço - Spread (!) if(c==3) { //--- Obtém e define o tamanho do spread em pontos int spread=(int)::SymbolInfoInteger(symbol,SYMBOL_SPREAD); m_canvas_table.SetValue(c,r,string(spread),true); continue; } //--- Obtém o número de casas decimais int digit=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); //--- Coluna das ofertas de compra (Bid) if(c==1) { m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].bid,digit)); //--- Se o preço mudou, define a cor correspondente a direção if(ticks[0].bid!=ticks[1].bid) m_canvas_table.TextColor(c,r,(ticks[0].bid<ticks[1].bid)? clrRed : clrBlue,true); //--- continue; } //--- Coluna das ofertas de venda (Ask) if(c==2) { m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].ask,digit)); //--- Se o preço mudou, define a cor correspondente a direção if(ticks[0].ask!=ticks[1].ask) m_canvas_table.TextColor(c,r,(ticks[0].ask<ticks[1].ask)? clrRed : clrBlue,true); //--- continue; } //--- Coluna do última hora de chegada dos preços do símbolo if(c==4) { long time =::SymbolInfoInteger(symbol,SYMBOL_TIME); string time_msc =::IntegerToString(ticks[0].time_msc); int length =::StringLen(time_msc); string msc =::StringSubstr(time_msc,length-3,3); string str =::TimeToString(time,TIME_MINUTES|TIME_SECONDS)+"."+msc; //--- color clr=clrBlack; //--- Se os preços não mudaram if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid) clr=clrBlack; //--- Se o preço Bid mudou para cima else if(ticks[0].bid>ticks[1].bid) clr=clrBlue; //--- Se o preço Bid mudou para baixo else if(ticks[0].bid<ticks[1].bid) clr=clrRed; //--- Define o valor e a cor do texto m_canvas_table.SetValue(c,r,str); m_canvas_table.TextColor(c,r,clr,true); continue; } } } } //--- Atualiza a tabela m_canvas_table.UpdateTable(); }
O seguinte resultado é obtido:
Fig. 5. A comparação dos dados na janela Observador do Mercado e seu análogo personalizado.
O aplicativo de teste apresentado no artigo pode ser baixado usando o link abaixo para estudá-lo ainda mais.
Conclusão
A biblioteca para a criação de interfaces gráficas no atual estágio de desenvolvimento se parece com o esquema abaixo.
Fig. 6. Estrutura da biblioteca no atual estágio de desenvolvimento.
Abaixo, você pode baixar a versão mais recente da biblioteca e seus arquivos de teste.
Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/3042
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso