ADO , ListView e mais...
Neste artigo vou abordar uma pequena aplicação que acessa uma base de dados mdb via ADO e exibe os dados de mais de uma tabela em um controle ListView. Meu objetivo é tentar esclarecer algumas dúvidas mais frequentes sobre conexão ADO e outras cositas mas...
Introdução
Vou usar o banco de dados Northwind.mdb (ele vem junto com o Access) e exibir informações das tabelas Produtos , Categorias e Fornecedores em um controle ListView.
Estrutura da tabela Produtos | Estrutura da tabela Fornecedores | Estrutura tabela Categorias |
O relacionamento entre as tabelas é mostrado a seguir:
As informações que eu quero extrair
destas tabelas são:
Obs: No destaque , em azul , o nome da tabela a qual o campo pertence. |
Uma das primeiras dúvidas que eu gostaria de esclarecer é aquela famosa pergunta de quem esta começando a trabalhar com banco de dados relacionais:
Como faço para exibir informações que estão distribuidas em mais de uma tabela ??
Elementar meu caro Watson !!! Agrupe as informações e as exiba.
A maneira mais fácil de fazer isto é usar uma instrução SQL com a claúsula SELECT. Quando você têm um vínculo entre as tabelas poderá usar a palavra chave INNER JOIN na cláusula FROM de uma instrução SELECT para criar um conjunto de registros com campos das tabelas. Para selecionar campos de várias tabelas , você deve informar basicamente o seguinte:
A sintaxe básica para o JOIN é : | ||||||||||||||
|
||||||||||||||
|
Para o nosso caso temos que as informações serão extraidas de 3 tabelas diferentes. Teremos algo parecido com :
SELECT
Produtos.CódigoDoProduto, Produtos.NomeDoProduto,
Categorias.Descrição, Fornecedores.NomeDaEmpresa,
Fornecedores.NomeDoContato, Fornecedores.Telefone FROM Fornecedores INNER JOIN (Categorias INNER JOIN Produtos ON Categorias.CódigoDaCategoria = Produtos.CódigoDaCategoria) ON Fornecedores.CódigoDoFornecedor = Produtos.CódigoDoFornecedor; |
Vamos montar a nossa instrução SQL usando um variável string. Definindo esta variável como strSQL teremos:
strSQL = SELECT Produtos.CódigoDoProduto,
Produtos.NomeDoProduto, Categorias.Descrição,
Fornecedores.NomeDaEmpresa, Fornecedores.NomeDoContato,
Fornecedores.Telefone strSQL = strSQL & " FROM Fornecedores" strSQL = strSQL & " INNER JOIN (Categorias INNER JOIN Produtos ON Categorias.CódigoDaCategoria = Produtos.CódigoDaCategoria) ON Fornecedores.CódigoDoFornecedor = Produtos.CódigoDoFornecedor;" |
Assim nossa instrução estará contida na variável string sql. A próxima etapa é gerar o recordset com os campos definidos na instrução SQL. Fazemos isto usando o método Execute da ADO.
Supondo que estamos usando uma conexão ADO - conProdutos - criada anteriormente , e um recordset ADO - rsProdutos - através do qual iremos gerenciar as informações. O comando para é:
rsProdutos.Open strSQL , conProdutos , adOpenForwardOnly , adLockReadOnly , adCmdText
Chegamos agora a outra dúvida muito frequente :
Como eu estabeleço uma conexão com o banco de dados ? Abro ao iniciar o programa e fecho ao sair ou abro e fecho a conexão com o banco de dados ao carregar e descarregar o formulário ?
Podemos adotar duas estratégias distintas :
Ambas as opções têm seus prós e contras e a escolha da melhor opção vai depender do tipo da aplicação. Se a aplicação não abrir mais de um formulário de dados ao mesmo tempo não haveria problemas em adotar a segunda opção. A primeira opção seria a mais comoda , e , embora manter uma conexão aberta com o banco de dados tem um custo elevado , creio que para um ambiente multiusuário (não Cliente/Servidor) os ganhos seriam maiores que abrir e fechar a conexão para cada formulário de dados.
Então eu vou usar a primeira opção no projeto exemplo. Para isto eu vou abrir a conexão em um módulo quando a aplicação for iniciada e fechar quando o usuário encerrar a aplicação. O código para abrir e fechar a conexão com o banco de dados Northwind.mdb é o seguinte:
Public conProdutos as ADODB.Connection Public Sub Conecta_BD() Set conProdutos = New ADODB.Connection With conProdutos .Provider = "Microsoft.Jet.OLEDB.4.0" .ConnectionString = "Data Source=" & app.path & "\Northwind.mdb" .Open End With End sub Public Sub Desconecta_BD() conProdutos.close set conProdutos = Nothing End Sub |
Vou chamar a função Conecta_BD quando a aplicação for iniciada , então vou definir que o primeiro objeto a ser executado é o Sub Main() na opção Project -> Project1 Properties , opção Startup Object
O código que irei colocar em Sub Main deverá chamar a função Conecta_BD e chamar a tela de apresentação da aplicação. Algo assim:
Private Sub Main() Conecta_BD frmapresentacao.Show End Sub |
Para criar o formulário de apresentação selecione a opção Project | Add Form e escolha Splash Screen . No formulário padrão inserido faça as alterações conforme o layout abaixo:
Na opção Project Properties na aba Make defina os valores para a versão em Version Number o título da aplicação em Application e as informações sobre a versão em Version Information |
O formuláro de apresentação será exibido por dois segundos e em seguida descarregado. Fazemos isto inserindo um controle Timer no formulário e definindo a propriedade Interval como igual a 2000 ( 1000 igual a 1 segundo) e incluindo o seguinte código no evento Timer: |
Private Sub Timer1_Timer() Unload Me frmmenu.Show End Sub |
Aqui estamos descarregando o formulário frmapresentação e exibindo o formulário frmmenu do sistema. |
Vamos agora preparar o formulário - frmmenu - que apresentará o menu da aplicação. Eu vou criar um menu usando os controles ToolBar e ImageList. Para ver os detalhes sobre como implementar o menu leia o artigo : Visual Basic - Criando Menus Profissionais. Além disto eu vou a interface MDI , ou seja, o formulário principal - frmmenu - será uma formulário MDI e teremos formulário MDIChild contidos neste formulário.
No menu Project selecione Add MDI Form , o resto do procedimento é igual ap descrito no artigo. Ao final deveremos ter a seguinte aparência do formulário - frmenu :
Obs: Você ja deve saber que a interface MDI - Multiple-Document Interface - permite criar uma aplicação que mantém multiplos formulários dentro de um container - chamado - MDIForm. O Word e o Excel usam esta interface. Nela você pode exibir vários formulários ao mesmo tempo. Os demais formulários são chamados de MDIChild. Um formulário MDIChild é obtido definindo a propriedade MDIChild como True. (Para isto é necessário que exista um MDIForm). O ícone do formulário MDIForm é |
Defina propriedade WindowState do MDIForm para - 2- Maximized.
Agora vamos criar o formulário filho - MDIChild - que irá conter o controle ListView para exibir os dados das tabelas. Insira um novo formulário - frmlistview - e altere sua propriedade MDIChild para True ; em seguida inclua o controle ListView no formulário. O ícone de um formulário MDIChild é .
Para exibir o formulário - frmlistview - centralizado vamos ter que criar uma rotina , pois um formulário MDIChild não permite a utilização da propriedade StartUpPosition. Inclua o seguinte código no módulo do projeto:
Public Sub Centraliza_MDIChild(Formulario As Form) Formulario.Top = (Screen.Height) / 3 - Formulario.Height / 3 Formulario.Left = (Screen.Width) / 2 - Formulario.Width / 2 End Sub |
Nossa próxima etapa será criar a rotina que irá preencher o controle ListView com os dados da tabela . Vamos lá...
- Quando o formulário frmlistview for carregado no evento Load iremos fazer a chamada das procedures para centralizar o formulário , definir o tamanho e para preencher o controle ListView :
Private Sub Form_Load() Centraliza_MDIChild Me Me.Width = 10350 Me.Height = 5175 Preencher_Listview End Sub |
O código do procedimento Preencher_ListView é o seguinte:
Private Sub
Preencher_Listview() Dim rsProdutos As ADODB.Recordset Set rsProdutos = New ADODB.Recordset Dim strSQL As String strSQL = "SELECT Produtos.CódigoDoProduto, Produtos.NomeDoProduto, Categorias.Descrição, Fornecedores.NomeDaEmpresa, Fornecedores.NomeDoContato, Fornecedores.Telefone" strSQL = strSQL & " FROM Fornecedores" strSQL = strSQL & " INNER JOIN (Categorias INNER JOIN Produtos ON Categorias.CódigoDaCategoria = Produtos.CódigoDaCategoria) ON Fornecedores.CódigoDoFornecedor = Produtos.CódigoDoFornecedor;" rsProdutos.Open strSQL, conProdutos, adOpenForwardOnly, adLockReadOnly, adCmdText 'define o item da lista Dim ItemLst As ListItem 'limpa a lista ListView1.ListItems.Clear 'cabecalho do listview listview_cabecalho While Not rsProdutos.EOF 'insere o item do arquivo de dados Set ItemLst = ListView1.ListItems.Add(, , rsProdutos!CódigoDoProduto) 'cada item precisa de um subitem para exibir na lista ItemLst.SubItems(1) = "" & rsProdutos!nomedoproduto ItemLst.SubItems(2) = "" & rsProdutos!Descrição ItemLst.SubItems(3) = "" & rsProdutos!NomeDaEmpresa ' ItemLst.SubItems(4) = "" & rsProdutos!NomeDoContato ItemLst.SubItems(5) = "" & rsProdutos!Telefone 'vai para o proximo registro rsProdutos.MoveNext Wend rsProdutos.Close ListView1.Refresh End Sub |
Perceba que usamos a procedure Listview_cabecalho para montar o cabecalho do controle.Na rotina definimos o nome de cada coluna e sua largura com base na largura do controle.
Definimos tambem o modo de exibição que o ListView usará para exibir os dados.(lvwReport). A presenca do objeto ColumnHeaders permite a criação de subitems.(Sem eles não conseguimos usar subitems).O código é:
Private Sub listview_cabecalho() ListView1.ColumnHeaders. _ Add , , "Cod.", ListView1.Width / 18 ListView1.ColumnHeaders. _ Add , , "Produto", ListView1.Width / 5 ListView1.ColumnHeaders. _ Add , , "Descricao", ListView1.Width / 4 ListView1.ColumnHeaders. _ Add , , "Empresa", ListView1.Width / 6 ListView1.ColumnHeaders. _ Add , , "Contato", ListView1.Width / 6 ListView1.ColumnHeaders. _ Add , , "Telefone", ListView1.Width / 8 'Define a forma de exibição do controle listview para relatorio ListView1.View = lvwReport End Sub |
O resultado será os seguinte:
Obs: Definimos as propriedades do controle ListView: FullRowSelect , GridLines e CheckBoxes para True , assim exibimos as linhas de grade , permitimos a seleção da linha inteira do controle e exibimos na primeira coluna caixas de seleção. Poderiamos fazer isto via código assim :
ListView1.Checkboxes = True ListView1.GridLines = True ListView1.FullRowSelect = True |
Para manter a seleção após perder o foco você pode fazer : HideSelection = False
Excluindo registros
Para excluir registros vamos permitir que o usuário selecione a linha da grade referente ao registro que deseja excluir e pressione a tecla DEL. O código é dado a seguir:
Private Sub ListView1_KeyUp(KeyCode As Integer, Shift As Integer) Dim strsql As String On Error GoTo trata_erro If KeyCode = vbKeyDelete Then strsql = "DELETE FROM Produtos WHERE CódigoDoProduto = " & CLng(Me.Tag) If MsgBox("confirma a exclusão do registro referente ao produto => " _ & ListView1.SelectedItem.SubItems(1), vbYesNo, "Excluir") = vbYes Then conProdutos.Execute strsqlEnd If End If endif Exit Sub trata_erro: MsgBox Err.Description End Sub |
- Após o usuário selecionar a linha , verificamos se a tecla DEL foi pressionada :- If KeyCode = vbKeyDelete Then
-A seguir montamos a instrução SQL para excluir os produtos cujos código sejam iguais a Me.Tag. Onde Me refere-se ao formulário e Tag a sua propriedade Tag. Nesta propriedade eu armazeno o valor do código do produto (Item.Text) quando o usuário clica no ListView. O código é :
Private Sub ListView1_ItemClick(ByVal Item As MSComctlLib.ListItem) Me.Tag = Item.Text End Sub |
Se você tentar excluir um registro irá receber a seguinte mensagem:
Sabe porque ? Bem , Como a tabela - Detalhes do Pedido - possui registros relacionados ao produto que você esta tentando excluir , a exclusão , se realizada , iria deixar registros na tabela - Detalhes do Pedido - sem o correspondente Produto na tabela Produtos .
Isto ocorre devido ao relacionamento entre os campos CódigoDoProduto da tabela Produtos e da tabela Detalhes do Pedido e não há jeito de evitar sem alterar os relacionamentos entre as tabelas. Então por que eu estou falando sobre isto ? Para que você fique esperto e preveja estas situações !!!
Você pode prever este tipo de erro e interceptá-lo gerando uma mensagem mais amigável ao usuário. Chamamos isto de tratamento de erros.(Uma boa aplicação sempre possui um tratamento de erros).Vamos melhorar o tratamento de erro usado no evento ListView1_KeyUp , o novo código para o tratamento de erros ficará assim :
trata_erro: Select Case Err.Number Case -2147467259 MsgBox "O registro não pode ser excluido pois existem registros " & vbCrLf & _ " relacionados na tabela - Detalhes do Pedido - Verifique !!", vbCritical, " ERRO " Case Else MsgBox Err.Number & vbCrLf & " - " & Err.Description, vbCritical, "ERRO" End Select Exit Sub |
Obs: Para uma relação dos erros relacionados com os provedores OLEDB leia o artigo : Relação de Erros ADO / OLE-DB
Ordenando os registros
Para terminar este artigo vou mostrar como podemos ordernar os dados exibidos no controle ListView. Vamos implantar a seguinte funcionalidade : quando o usuário clicar em uma coluna qualquer os dados serão ordenados de form ascendentes conforme aquela coluna.
A primeira coisa a fazer é inserir o código que realiza a ordenação no módulo do projeto . O código é :
Public Sub OrdenaListView(ByVal lvw As MSComctlLib.ListView, ByVal Coluna_Cabecalho As MSComctlLib.ColumnHeader) lvw.SortKey = Coluna_Cabecalho.Index - 1 lvw.Sorted = True lvw.SortOrder = lvwAscending End Sub |
Iremos chamar esta rotina quando o usuário clicar na coluna do controle ListView , para usaremos o evento - ListView1_ColumnClick. O código é o seguinte:
Private Sub ListView1_ColumnClick(ByVal ColumnHeader As MSComctlLib.ColumnHeader) OrdenaListView ListView1, ColumnHeader End Sub |
Estamos chamando a rotina passando como parâmetros o controle ListView e a coluna clicada. Se você testar vai ver que a coisa realmente funciona.
Não esqueça de de fechar a conexão ao sair. Para isto use o evento QueryUnload do formulário MDI ; é só chamar a função criada no módulo para fechar a conexão. Lembra ?
Obs : O evento QueryUnload ocorre antes de um formulário ou aplicação fechar. Quando um objeto do tipo MDIForm fecha , o evento QueryUnload ocorre antes para o formulário MDIForm (MDI Pai) e depois para todos os os MDIChild ( MDI filhos).
Private Sub MDIForm_QueryUnload(Cancel As Integer, UnloadMode As Integer) Desconecta_BD End Sub |
Faça o download do projeto clicando aqui = ADOListView.zip ( 14,0 KB )
Pronto !!! Acabei. Até o próximo artigo.....
José Carlos Macoratti