Apresentando Dados com TListView por Luiz Fernando Severnini [email protected] Programador Autônomo Sumário Introdução.................................................................................................................. 3 O Componente........................................................................................................... 3 Por que usar TListView............................................................................................. 5 Algumas Propriedades............................................................................................... 6 Manipulando Items.................................................................................................... 8 Inserção de Items....................................................................................................... 8 Ordenação de Items ................................................................................................... 10 Pesquisa incremental de Itens.................................................................................... 11 Localizando registros ................................................................................................ 12 Construindo uma aplicação de exemplo.................................................................... 12 Conclusão .................................................................................................................. 17 Introdução Na verdade, o Delphi possui muitos componentes que muitas das vezes não imaginamos o que estes componentes fazem, ou o quanto podem facilitar nosso trabalho, ou simplesmente, dar um toque mais profissional às nossas aplicações. Mesmo a configuração de uma simples propriedade pode fazer a diferença. O objetivo deste artigo é apresentar uma visão geral sobre o componente TListView, sendo este uma opção para a exibição de dados. O Componente Em nossas aplicações de Banco de Dados, é comum a todo desenvolvedor o uso de DBGrid´s para apresentar consultas ao Banco de Dados, bem como a manipulação de dados. Quando digo manipulação de dados, entende-se por inserir, alterar, apagar ou até mesmo copiar os dados. O componente TListView localiza-se na paleta Win32 do Delphi. Usar este componente para exibir dados representa uma alternativa aos componentes DBGrid´s. O importante disso tudo é que a aplicação irá manter um certo padrão de interface comum do Windows oferecendo diversas características visuais e muitas funcionalidades comuns aos usuários. O componente TListView é um componente visual, que implementa controles list view do Windows, sendo bastante flexível para exibir uma lista de itens. Para exemplificar, quando estamos visualizando pastas e arquivos no Windows Explorer (figura 1), temos aí uma aplicação muito comum de controles list view. Caixas de diálogo (figura 2) para abrir/salvar/fechar arquivos também usam controles list view. Também outras interfaces que utilizam controles list view são os programas que acompanham os Sistemas Gerenciadores de Banco de Dados, ou, Servidores de Banco de Dados (ex.: Interbase, MySQL, Oracle, SQL Server), para exibição de informações sobre tabelas, colunas, índices, views, etc. Figura 1 – Controle ListView é usado na parte direita da janela do Windows Explorer Figura 2 – Controle ListView sendo usado em caixa de diálogos O controle list view pode exibir seus itens de quatro formas possíveis: ícones grandes, ícones pequenos, lista ou detalhes/relatório, como no Windows Explorer. O modo de exibição detalhes/relatório (daqui em diante, vamos chamar somente de relatório) é como uma grid que permite somente a leitura, sendo possível então sua utilização para mostrar os dados de um DataSet. Logo abaixo estará descrita algumas propriedades do componente Delphi, mas já vou adiantar que o modo de visualização é definido pela propriedade ViewStyle do componente TListView. Esta propriedade pode ter quatro valores distintos: vsIcon, vsSmallIcon, vsList ou vsReport. Sendo que o último é o que nos interessa, pois é atribuindo este valor (vsReport) à propriedade ViewStyle que teremos um list view em forma de relatório. Apesar de ser um componente largamente utilizado em aplicações de sistema, o uso desse componente não é muito difundido entre os desenvolvedores Delphi, talvez seja pelo motivo do trabalhoso método de alimentação de dados. O TListView é um controle que é nativo do sistema operacional, e sendo assim a interface e operações são comuns aos usuários. Essa á a grande vantagem do TListView sobre o TDBGrid. Portanto, para que possamos fazer um bom uso, devemos implementar um bom mecanismo de alimentação virtual de dados. Por que usar TListView Temos muitos motivos para usar componentes TListView como parte da interface gráfica de exibição de dados retornados por DataSets. Dentre os quais podemos citar: Possui um mecanismo de busca incremental de itens, ou seja, quando o usuário vai digitando as iniciais de um item ele se encarrega de ir localizando; Possibilidade de exibir ícones no início da linha e nos títulos das colunas; Por ser um controle padrão do Windows significa facilidade de uso e padronização de interfaces junto aos usuários. E o que há de melhor, se forem feitos melhoramentos em novas versões do Windows serão incorporados automaticamente nas aplicações que possuem estes componentes; Quando o conteúdo de uma célula ultrapassa o tamanho da largura da coluna, automaticamente são incluídas reticências (...), indicando que o texto tem continuidade; Possibilidade de auto-dimensionamento das colunas pelo usuário, bastando o mesmo dar um duplo clique na borda direita do título da coluna, modificando a largura da coluna para melhor ajuste na visualização dos dados; Barras de rolagem em estilo “flat”; Rolagem vertical e horizontal, de forma progressiva e harmoniosa; Os hints são exibidos com o conteúdo completo da primeira coluna, quando a largura da mesma não é suficiente para exibir todo o texto; Seleção automática de itens conforme o usuário movimenta o mouse sobre o componente, recurso este chamado de hot tracking; Os títulos das colunas podem funcionar como botões, que quando clicados geram eventos. Todos estes motivos que levam à facilidade de uso e maior interação do usuário com o sistema, acredito que todos já viram ao usar o Windows Explorer ou qualquer outro programa que faça uso destes controles. Algumas Propriedades ColumnClick: Boolean Descrição: Esta propriedade pode ser usada para definir se quando o usuário clicar no título das colunas, se será gerado um evento. Quando estiver True, ao clicar no título da coluna será disparado um evento, se estiver False, quando o título da coluna for clicado, não será disparado nenhum evento. Esta propriedade trabalha concomitantemente com a propriedade ShowColumnHeaders, devendo esta estar setada para True, e também com a propriedade ViewStyle setada para vsReport. Columns: TListColumns Descrição: Esta propriedade é usada para adicionar ou remover colunas do componente TListView. Em tempo de projeto temos um editor para adicionar/remover colunas. Em tempo de execução, devemos usar as propriedades Caption, Alignment, e Width do objeto TListColumn acessado pela propriedade Columns para que possa ser alterada a aparência do título da coluna. Ctl3D: Boolean Descrição: Propriedade que determina se o componente terá uma aparência tridimensional. Se o valor for setado para False, terá um visual mais clean, ou como se fosse estilo “flat”. FlatScrollBars: Boolean Descrição Especifica se as barras de rolagem terão aparência no estilo “flat”, logo estarão sem o efeito tridimensional, o que dá uma aparência bastante elegante ao controle. GridLines: Boolean Decrição: Determina se serão desenhadas linhas separando os itens da lista. Vale ressaltar que este efeito só será válido se a propriedade ViewStyle for vsReport. HotTrack: Boolean Descrição: Especifica se será dado um destaque aos itens da lista, quando passamos o mouse sobre eles. Quando o valor for True, a propriedade HoverTime irá controlar quanto tempo o usuário deverá ficar com o mouse sobre o item, para que ele seja automaticamente selecionado. Quando o valor for False, o usuário terá que clicar no item para seleciona-lo, pois simplesmente parar o mouse sobre o item não irá seleciona-lo. Configure também a propriedade HotTrackStyles para alterar o comportamento do cursor do mouse e outros. HoverTime: Integer Descrição: Especifica o tempo em milisegundos que o usuário deverá manter o mouse sobre o item para que ele seja selecionado. Se estiver setado para –1 o valor padrão será usado. Esta propriedade só terá efeito se a propriedade HotTrack for True. MultiSelect: Boolean Decrição: Na verdade esta propriedade é auto-explicativa. É usada para determinar se o usuário poderá selecionar mais que um item ao mesmo tempo. OwnerData: Boolean Decrição: Esta propriedade especifica se o controle list view será alimentado virtualmente. Pode ter um imenso número de itens. Porém precisamos gerenciar estes itens usando os eventos OnData, OnDataFind, OnDataHint e OnDataStateChange, além de termos que setar a propriedade Count para os itens da lista virtual. RowSelect: Boolean Descrição: Esta propriedade permite que a linha toda possa ser selecionada, se estiver como False, somente a primeira coluna, ou seja, o Caption da linha será selecionado. Mas lembrese de que a propriedade ViewStyle deve ser vsReport. ViewStyle: TviewStyle Decrição: Esta propriedade determina como os itens da lista serão exibidos. Temos quatro opções: vsIcon : Ícone vsSmallIcon : Ícone pequeno vsList : Lista vsReport : Detalhes/Relatório Manipulando Itens Como já citado anteriormente, o uso do TListView torna o desenvolvimento um pouco trabalhoso, pois para inserir os dados dos itens, devemos criar e adicionar um por um, objetos do tipo TListItem na coleção Items do componente. A seguir, um código exemplificando como é inserido um item via código: var NovoItem : TListItem; begin NovoItem := ListViewClientes.Items.Add; NovoItem.Caption := ‘10’; NovoItem.SubItems.Add(‘José da Silva’); NovoItem.SubItems.Add(‘Rua da Abolição, 10’); NovoItem.SubItems.Add(‘Jd. das Amoreiras’); end; . O método Items.Add do TListView, retorna um TListItem associado ao TListView, cujas propriedades devem ser completadas/preenchida com valores próprios. A propriedade central do item é a Caption, pois é ela que dá o nome ao item. O objeto TListItem possui uma propriedade (SubItems) descendente de TStrings, cuja funcionalidade é informar, em seqüência, os textos das colunas complementares. Todas as colunas são complementares, exceto a primeira, o que para obtermos o seu valor devemos acesssar a propriedade Caption. As colunas complementares são exibidas quando o TListView está em modo de relatório. O código acima mosta como inserir apenas um item na lista, e para cada item da lista teremos que executar o código acima, num loop codificado manualmente, antes que a janela do form seja exibida, ou simplesmente utilizando botões para que sejam inseridos os itens no momento desejado. Na verdade este processo é um pouco difícil, mas veremos adiante como facilitar um pouco a inserção de dados no TListView. Parece ser bem mais fácil só configurar a propriedade DataSource do DBGrid, não é? Inserção de itens Na verdade podemos inserir os itens de duas formas: - Adicionando-os todos de uma vez; ou - Adicionando conforme cada linha é desenhada. Vamos começar pela primeira opção: procedure InsereItens; begin ClientDataSetClientes.First; with ClientDataSetClientes do with not Eof do begin with ListViewClientes.Items.Add do begin Caption := FieldByName('Codigo').AsString; SubItems.Add(FieldByName('Nome').AsString); SubItems.Add(FieldByName('Telefone').AsString); end; Next; end; end; Este código tende a demorar conforme o número de registros na tabela, além de consumir muita memória e recursos do sistema, além de ser difícil manter os dados atualizados, caso ocorra alguma alteração nos dados da tabela. Já imaginou uma tabela com 100 mil registros, todos eles sendo incluídos no TListView? Pela segunda opção, e acredito que é a melhor, para DataSet com grande volume de dados, o melhor caminho é fazer a alimentação de dados virtualmente, bastando para isso setar a propriedade OnData para True, assim nenhum registro é transferido para o controle. Cada vez que uma linha vai ser desenhada é disparado o evento OnData, onde devemos escrever um código para retornar os valores daquele item que está para ser desenhado. Desta forma o controle dos dados fica com a aplicação. Embora a alimentação virtual de dados também demande um pouco de trabalho na codificação, este 'trabalho' é pequeno. É necessário a codificação de apenas dois eventos para que o TListView mostre os dados do DataSet. Primeiramente iremos tratar o evento OnShow do formulário, definindo a propriedade Items.Count do TListView com o total de registros do DataSet. E o outro evento que teremos que manipular é o evento OnData do TListView, de forma que os valores do registro solicitado sejam retornados ao TListView. Para isso devemos usar componentes descendentes de TDataSet que retornem o valor True para o método IsSequenced, significando que o componente assegura a ordem sequencial dos seus registros, de forma que retornem valores válidos para a propriedade RecordCount e RecNo. É preferível usar ClientDataSet, pois pode armazenar todos os seus registros em memória, permitindo que sejam acessados aleatoriamente, o que é necessário para o desenho dos itens na tela, além de tornar mais rápida a leitura e localização de dados. Se for usar ClientDataSet, certifique-se que a propriedade PacketRecords tenha o valor -1 (padrão), pois com este valor, após abrir o ClientDataSet, todos os registros serão trazidos na memória. Assim, no evento OnShow do formulário iremos colocar o seguinte código: procedure TFormClientes.FormShow(Sender: TObject); begin ClientDataSetClientes.Open; ListViewClientes.Items.Count := ClientDataSetClientes.RecordCount; end; Se executarmos aplicação antes de codificar o evento OnData do TListView, será mostrada uma lista de itens vazios, sem nenhum texto. Como definimos a propriedade OwnerData como sendo True, devemos escrever o código necessário à obtenção dos dados a serem exibidos no TListView. procedure TFormClientes.ListViewClientesData(Sender: TObject; TListItem); begin with ClientDataSetClientes do begin RecNo := (Item.Index _ 1); Item.Caption := FieldByName('Codigo').DisplayText; Item: SubItems.Add(FieldByName('Nome').DisplayText); SubItems.Add(FieldByName('Telefone').DisplayText); end; Bom, vamos entender o código ilustrado acima. Temos um o objeto Item que é passado como parâmetro na procedure do evento, e este objeto possui uma propriedade Index, que indica o número da linha onde os dados estão sendo solicitados. Devemos atribuir este número à propriedade RecNo do DataSet, o que faz o DataSet posicionar-se no registro apropriado. Sempre devemos adicionar 1 ao valor do Item.Index pois a numeração dos items de um TListView começa com 0, sendo que no DataSet a numeração começa com 1. Depois definimos a propriedade Caption do Item e adicionamos duas colunas de detalhes, ou seja, uma coluna com o Nome e a outra com o Telefone, retirando seus valores do respectivo registro no DataSet. Isso é tudo o que precisamos fazer para alimentar nosso componente TListView com dados. E também já é um bom momento para alterarmos algumas de suas propriedades, para que possamos visualizar seus efeitos na tela, como por exemplo, o recurso de Hot Tracking. Dica: Devemos inserir as colunas que irão aparecer no TListView, senão não será mostrado nada. Para inseri-las clique com o botão direito sobre o componente TListView e escolha a opção 'Columns Editor...' do menu pop-up que aparece. Adicione as colunas que irão aparecer no controle TListView. E observe, que no evento OnData, devemos alimentálas manualmente, ou seja, se temos três colunas, uma será a Caption da linha e as outras duas serão as Details, ou melhor, coluna de Detalhes. Ordenação de itens Se definirmos a propriedade ColumnClick com o valor True, os títulos das colunas terão a aparência de botões, e quando clicadas irão acionar o evento OnColumnClick. Assim podemos usar este recurso para ordenar o conteúdo do DataSet pelo campo correspondente. E fica melhor ainda se estivermos usando o ClientDataSet, pois ele permite que possamos criar índices em memória. Quando o usuário clicar na coluna, se ainda não existir o índice nós iremos criá-lo e defini-lo como sendo o índice corrente. procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn); const Colunas : array[0...3] of String = ('Codigo', 'Nome', 'Telefone'); var NomeCampo: String; begin NomeCampo := Colunas[Column.Index]; try ClientDataSetClientes.FindIndexForField(NomeCampo); except on EDataBaseError do ClientDataSetClientes.AddIndex('Idx'+NomeCampo, NomeCampo, []); end; ClientDataSetClientes.IndexFieldNames := NomeCampo; ListViewClientes.Invalidade; end; Como podemos perceber, o parâmetro Column da procedure do evento OnColumnClick é um objeto TListColumn, o qual possui uma propriedade Index, a qual possibilita encontrar a posição da coluna clicada, e através deste índice, localizamos o nome do campo correspondente em Colunas. Para que fosse possível a localização do nome do campo correspondente, definimos os nomes dos campos em um arrray do tipo String, assim é só pesquisar o índice da propriedade Column.Index no respectivo índice do array que teremos o nome do campo. Através do método FindIndexForFields da propriedade TClientDataSet.IndexDefs, tentamos localizar se existe algum índice para a variável NomeCampo. Se não for encontrado, será gerada uma exceção, e assim iremos usá-la para adicionar um novo índice para aquele campo. Nas próximas vezes que o usuário clicar na coluna, o índice não mais precisará ser criado. Logo após, nós ativamos o índice recém-criado ou já existente, atribuindo NomeCampo à propriedade IndexFieldsNames do ClientDataSet. E para que a mudança seja refletida no controle TListView, chamamos o método TListView.Invalidate. Pesquisa incremental de itens O TListView já possui um mecanismo de busca incremental de itens do tipo texto, porém quando a aplicação detém o controle dos dados, precisamos codificar o evento OnDataFind para encontrar o registro. O código necessário para a busca fica assim: procedure TfrmPrincipal.ListViewClientesDataFind(Sender: TObject; Find: TItemFind; const FindString: String; const FindPosition: TPoint; FindData: Pointer; StartIndex: Integer; Direction: TSearchDirection; Wrap: Boolean; var Index: Integer); begin ClientDataSetClientes.FindNearest([FindString]); Index := (ClientDataSetClientes.RecNo - 1); end; O que é necessário fazer é fazer uma chamada ao método FindNearest do DataSet parassando como parâmetro a constante FindString, que já é fornecida pelo evento OnDataFind, e com isso obter a posição do registro. Porém desta vez temos que subtrair 1 do valor da posição do registro, para igualar a diferença de numeração do TListView e do DataSet. Vale ressaltar que a busca será feita no índice atual da tabela. Localizando registros Dependendo do uso que estamos fazendo com o TListView, como por exemplo, ter no form botões de edição, como 'Excluir', 'Alterar', etc., para que seja possível editar os dados do DataSet. Contudo, se faz necessário saber localizar o registro que estiver selecionado no TListView com o seu correspondente no DataSet. Para isso podemos usar a propriedade Selected, a qual pertence a um objeto TListItem, sendo que por sua vez, podemos usar a propriedade Index do objeto TListItem e atribuir o seu valor à propriedade RecNo do DataSet. Não se sequeça de que temos que adicionar 1 ao valor da propriedade Index. procedure ButtonClick(Sender: TObject); begin ClientDataSetClientes.RecNo := (ListView.Selected.Index + 1); //Digitar aqui o código para o registro selecionado, por exemplo ClientDataSetClientes.Delete; //ou //ClientDataSetClientes.Edit; ShowMessage('Exclusão efetuada com sucesso!'); end; Construindo uma aplicação de exemplo Bom primeiramente, vamos criar um novo projeto no Delphi. Para isso clique em File > New > Application. Configure as propriedade do Form assim: Propriedade Name Valor frmPrincipal Salve a unit como UfrmPrincipal.pas e o projeto como TListView.dpr. Insira no frmPrincipal um componente: TListView da paleta Win32; Button da paleta Standard; ClientDataSet da paleta DataAccess. Configure suas propriedades conforme a tabela: TListView Propriedade FlatScrollBars GridLines HotTrack HotTrackStyles>htHandPoint HoverTime Name OwnerData ReadOnly RowSelect ViewStyle Valor True True True True 1000 ListViewClientes True True True vsReport Dê um duplo clique no objeto TlistView, e aparecerá o ColumnsEditor. Adicione quatro colunas e configure suas propriedades como abaixo: Coluna 0 1 2 3 Propriedade Caption Caption Caption Caption Valor Código Empresa Contato Telefone Para todas as colunas configura a propriedade AutoSize para True. Figura 3 – Columns Editor do TListView Configure a propriedade Caption do Button, ‘Excluir’. Agora configure a propriedade Name do componente ClientDataSet para ‘cdsClientes’. Clique com o botão direito sobre o mesmo e escolha a opção ‘Load from MyBase table...’ (Figura 4), e localize o arquivo customer.xml, que normalmente fica no path ‘C:\Arquivos de programas\Arquivos comuns\Borland Shared\Data\customer.xml’, ou coloque o path do arquivo na propriedade FileName do componente cdsClientes. Figura 4 – Carregando uma tabela no formato .xml em tempo de projeto Dê um duplo clique no componente cdsClientes para fazer surgir o Fields Editor. Clique com o botão direito sobre a parte em branco do Field Editor e escolha a opção ‘Add Fields’ ou pressione Ctrl+A. Será exibida uma lista com todos os campos da tabela. Escolha os campos: CustNo, Company, Phone e Contact e clique em OK (Figura 5). Figura 5 – Adicionando campos persistentes Muito bem, agora nossa aplicação terá a seguinte aparência em tempo de projeto: Figura 6 – Aplicação em tempo de projeto Agora escreva as seguintes rotinas nos respectivos eventos de cada componente: frmPrincipal procedure TfrmPrincipal.FormShow(Sender: TObject); begin cdsClientes.Open; ListViewClientes.Items.Count := cdsClientes.RecordCount; end; procedure TfrmPrincipal.FormClose(Sender: TObject; var Action: TCloseAction); begin cdsClientes.Close; end; ListViewClientes procedure TfrmPrincipal.ListViewClientesData(Sender: TObject; Item: TListItem); begin cdsClientes.RecNo := (Item.Index + 1); Item.Caption := cdsClientesCustNo.DisplayText; Item.SubItems.Add(cdsClientesCompany.DisplayText); Item.SubItems.Add(cdsClientesContact.DisplayText); Item.SubItems.Add(cdsClientesPhone.DisplayText); end; procedure TfrmPrincipal.ListViewClientesColumnClick(Sender: TObject; Column: TListColumn); const Colunas: array[0..3] of string = ('CustNo', 'Company', 'Contact', 'Phone'); var NomeCampo: string; begin NomeCampo := Colunas[Column.Index]; try cdsClientes.IndexDefs.FindIndexForFields(NomeCampo); except on EDataBaseError do cdsClientes.AddIndex('Idx' + NomeCampo, NomeCampo, []); end; cdsClientes.IndexFieldNames := NomeCampo; ListViewClientes.Invalidate; end; procedure TfrmPrincipal.ListViewClientesDataFind(Sender: TObject; Find: TItemFind; const FindString: String; const FindPosition: TPoint; FindData: Pointer; StartIndex: Integer; Direction: TSearchDirection; Wrap: Boolean; var Index: Integer); begin cdsClientes.FindNearest([FindString]); Index := (cdsClientes.RecNo - 1); end; Button1 procedure TfrmPrincipal.Button2Click(Sender: TObject); begin cdsClientes.RecNo := (ListViewClientes.Selected.Index + 1); if MessageDlg('Tem certeza', mtConfirmation, [mbYes,mbNo], 0) = mrYes then begin cdsClientes.Delete; ListViewClientes.Items.Count := cdsClientes.RecordCount; ListViewClientes.Invalidate; end; end; Nesta aplicação usamos como Banco de Dados a tabela customers.xml, distribuída como Banco de Dados de exemplo, mas o conteúdo do DataSet pode vir de outros tipos de arquivos como o .cds e mesmo de resultados de queries. Veja outros comentários das rotinas no arquivo de exemplo. A aplicação de exemplo em tempo de execução: Figura 7 – Aplicação em execução Conclusão Este artigo teve como objetivo, apresentar uma visão geral do componente TListView, e como manipular dados usando este componente, de forma a deixar a nossa aplicação com uma interface comum e intuitiva aos usuários. Foram apresentadas duas formas de alimentação de dados, sendo que a mais eficiente é a forma de alimentação virtual de dados, ou seja, passando o controle dos dados para a aplicação através da propriedade OwnerData e do evento OnData do componente TListView. Também foi apresentado soluções para localizar um registro no DataSet através de parâmetros retirados do TListView, o que permite o uso do TListView não só para visualizar dados, mas também a possibilidade de manipular dados de forma intuitiva e fácil ao usuário. Obs.: Foi utilizado o Delphi 6 para apresentação deste artigo.