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.
Download

Apresentando Dados com TListView