6. BANCOS DE DADOS O suporte a bancos de dados é um dos recursos fundamentais do Delphi e o desenvolvimento deste produto frente a outros tem se baseado enormemente nesta facilidade. É comum que programadores percam muito tempo operacionalizando tarefas que o Delphi já oferece prontas. Por esta razão, o Delphi é conhecido como uma "ferramenta de desenvolvimento rápido", capaz de gerenciar bancos de dados com um mínimo de código de programação. Este capítulo apresenta uma visão geral do extenso suporte a bancos de dados do Delphi 6.1. VISÃO GERAL Um aplicativo de banco de dados no Delphi não tem acesso direto às fontes de dados. A interface é feita por meio do Borland Database Engine (BDE), que tem acesso a várias fontes, incluindo o dBase, Paradox, ASCII, FoxPro e Access, sendo que estes dois últimos são novidade da versão 3 do Delphi (são bancos de dados da Microsoft...). Isso significa que, para rodar um aplicativo Delphi em uma máquina que não tenha o Delphi instalado, será necessário instalar o BDE junto com o aplicativo. Para tornar esta tarefa mais fácil, a Borland disponibiliza uma versão mais simples do BDE, que pode ser incluída nos discos de instalação. O Delphi também permite acessar servidores SQL locais e remotos e vem acompanhado do Local Interbase for Windows, da Borland. O BDE também permite a interface com drivers ODBC, mas este método geralmente é menos eficiente. A figura 6.1 ilustra a estrutura geral de acesso a dados no Delphi. 6.2. TABELAS Uma tabela é basicamente uma coleção de registros dispostos de maneira ordenada, sendo que cada registro é composto por um ou mais campos. Desta forma, tabelas são semelhantes a arquivos do tipo record em Pascal. Alguns conceitos básicos em uma tabela são: o registro atual (o registro com o qual o usuário está trabalhando) e o campo atual (o campo selecionado do registro atual). Geralmente, cada tabela será um banco de dados individual. Isto ocorre nas estruturas do dBase e Paradox, mas não no Microsoft Access. Neste último, um arquivo tipo .mdb, que é o banco de dados em si, é uma estrutura mais complexa, formada por tabelas, índices e relações. 6.3. COMPONENTES DE ACESSO A DADOS Os componentes de bancos de dados do Delphi estão espalhados por duas páginas da paleta de componentes: a página Data Access e a página Data Controls. Os componentes fundamentais são o DataSource (Fonte de Dados), que providencia a conexão das tabelas com o BDE (Borland DataBase Engine), a Table (Tabela) e a Query (Consulta), que acessam os registros das tabelas selecionadas. Estes três componentes são encontrados na página Data Access, conforme mostrado na figura 6.3. A seguir, apresentamos uma descrição das funções destes quatro componentes fundamentais. Data Source. Literalmente, "Fonte de Dados", é o componente que faz a ligação entre o BDE e os componentes ligados a dados. Table. Este componente é simplesmente uma tabela referente a um banco de dados. Para usar a Table é necessário especificar a propriedade Databasename, que deve conter o nome do banco de dados utilizado. É possível inserir um alias (apelido) ou o próprio nome do banco de dados. No Object Inspector aparecerá uma lista dos nomes disponíveis, que dependem dos alias especificados no BDE. Após especificar a propriedade Databasename é necessário especificar a propriedade TableName, com o nome de uma tabela válida. Novamente, o Object Inspector listará as tabelas válidas do arquivo ou alias selecionado em Databasename. Query. Literalmente, "Consulta", é um componente um pouco mais complexo do que a Table, mas com a capacidade de se especificar uma consulta em linguagem SQL. A Query também tem a propriedade Databasename, mas não a propriedade Tablename. Em vez disso, a tabela será especificada dentro de um string de consulta armazenado na propriedade SQL. Este string é um comando capaz de selecionar a tabela inteira ou partes dela, bem como de ordená-la de várias maneira possíveis. Voltaremos à Query quando estudarmos comandos SQL. Stored Procedure. Este componente, "Procedimento Armazenado", é usado somente em servidores SQL e não será tratado aqui, mas ele é capaz de armazenar os resultados de procedimentos locais em forma de uma tabela de banco de dados. Além destes componentes, o Delphi fornece vários outros conhecidos como Data Aware Components (Componentes Ligados a Dados), que são componentes capazes de se conectar automaticamente a tabelas e acessar os respectivos conteúdos. Muitos deste componentes são similares aos controles padrão do Delphi. Por exemplo, o componente DBEdit tem exatamente a mesma aparência do Edit comum, mas apresentam a capacidade adicional de se ligar a dados provenientes de tabelas. Os componentes ligados a dados do Delphi, descritos a seguir, podem ser encontrados na página Data Controls da paleta de componentes. DBNavigator. É um conjunto de botões utilizado para navegar por uma tabela e executar ações no banco de dados correspondente. Quando inserido em um formulário, o DBNavigator apresenta o seguinte aspecto: Os botões do DBNavigator podem ser personalizados, sendo possível escolher quais deles exibir. Isto pode ser feito por meio da propriedade VisibleButtons, sendo que os botões têm os seguintes valores: Tabela 6.1 – Valores da Propriedade VisibleButtons do DBNavigator Valor do botão Ação nbFirst Vai para o primeiro registro nbPrior Vai para o registro anterior nbNext Vai para o próximo registro nbLast Vai para o último registro nbInsert Insere um novo registro na posição atual nbDelete Exclui o registro atual nbEdit Permite a edição do registro atual nbPost Armazena as mudanças da edição nbCancel Cancela as mudanças da edição A propriedade VisibleButtons pode ser configurada em tempo de projeto ou em tempo de execução. Por exemplo, para configurarmos o DBNavigator de modo a exibir apenas os botões de navegação, poderíamos escrever os seguinte trecho no evento OnCreate do formulário: procedure TForm1.FormCreate(Sender: TObject); begin DBNavigator1.VisibleButtons:=[nbFirst, nbPrior, nbNext, nbLast]; end; DBGrid. É uma grade capaz de exibir toda uma tabela de uma só vez, permitindo rolagem, navegação pelos registros e edição. Trata-se de uma extensão do componente Grid convencional. DBText. É um componente label (rótulo) com acesso a dados, permitindo a exibição dos dados de um campo sem permitir que o usuário os edite. DBEdit. É um componente Edit (caixa de edição) com acesso a dados, permitindo que o usuário visualize e edite o conteúdo. DBMemo. É um componente Memo com acesso a dados, permitindo que o usuário edite e visualize os dados de um campo grande de textos, geralmente armazenado em um campo Memo ou BLOB ("Binary Large Object", ou "Grande Objeto Binário"). A aparência do DBMemo é a mesma do componente Memo. DBImage. Este componente mostra uma figura armazenada em uma campo BLOB, sendo uma extensão ligada a dados do componente Image. Use DBImage quando quiser exibir figuras ou fotografias vinculadas a campos de bancos de dados. DBListBox. Permite que o usuário escolha um único valor a partir de uma lista, mas não permite a edição dos conteúdos. DBComboBox. Permite que o usuário escolha um único valor a partir de uma lista, permitindo também a edição dos conteúdos. DBCheckBox. Pode ser utilizado para marcar ou desmarcar uma opção correspondente a um campo booleano de uma banco de dados, permitindo uma maior interatividade entre o banco e a interface visual. DBRadioGroup. Pode ser utilizado para marcar ou desmarcar opções mutuamente exclusivas correspondentes a um campo booleano de uma banco de dados. DBRichEdit. Este componente, que é uma RichEdit com acesso a dados, foi incluído no Delphi 3.0 e faz uso do controle RichEdit do Windows 95 e 98. Use-o quando em substituição à DBEdit quando quiser apresentar textos com formatações diferentes (negrito, itálico, etc.). DBChart. É uma extensão ligada a dados do componente Chart, incluído a partir do Delphi 3.0. 6.4. CONFIGURAÇÃO DO BDE Como dissemos anteriormente, o Delphi usa o Borland Database Engine (BDE) para acessar bancos de dados. Não é necessário que você conheça o BDE para desenvolver aplicativos de bancos de dados em Delphi e nem é necessário que os usuários de seus aplicativos tenham esse conhecimento. O BDE é instalado automaticamente junto com o Delphi e é sempre executado a partir de então. Os bancos de dados em Delphi utilizam o conceito de "alias" (apelido). O alias é utilizado como uma abreviatura de um caminho ou diretório que pode conter várias tabelas, consultas e índices. Assim, em vez de você especificar, por exemplo, o caminho C:\Arquivos de Programas\Borland\Delphi 3\, basta que você crie um alias apontando para este caminho e o utilize a partir de então. O BDE permite a criação e edição de alias. Para tanto, inicie o BDE Administrator no menu Iniciar. Você verá a janela mostrada na figura 6.4 a seguir. O alias DBDEMOS, que aponta para o caminho C:\Arquivos de Programas\Borland\Delphi 3\Demos\Data, é configurado na instalação do Delphi e contém vários bancos de dados de demonstração. Para incluir o seu próprio alias, escolha a opção New no menu Object, ou então tecle Ctrl+N. Aparecerá a janela mostrada na figura 6.5. Escolha o driver "STANDARD", que é dBase/Paradox (o Delphi não reconhece diferença entre os dois). Pressionando o botão OK você será convidado a escolher um nome para o seu alias (por exemplo, MEU_ALIAS, MEU_ALIAS_QUERIDINHO, MEU_ALIAS_QUERIDINHO_E_FOFINHO, etc). Na janela da esquerda você pode definir um caminho para os seus bancos de dados, mesmo que estes não existam ainda. Uma vez escolhido o nome do alias e o caminho, tecle CTRL+A para aplicar as configurações do alias. Uma vez encerrada a configuração do alias, a propriedade Databasename de qualquer componente de banco de dados que a tenha (Table, Query, etc) pode ser configurada com o nome do alias. Caso contrário, deveríamos especificar tal propriedade com o caminho do banco de dados, mas somente em tempo de execução. Usando alias, podemos abrir um banco de dados e exibir seu conteúdo mesmo em tempo de projeto. 6.5. O DATABASE DESKTOP O Database Desktop (DBD) é um aplicativo instalado junto com o Delphi e é capaz de criar, consultar, reestruturar, indexar, modificar e copiar tabelas de bancos de dados do tipo Paradox, dBase, Access, MSSQL, Sybase, Oracle e outras. Em linhas gerais, trata-se de um verdadeiro editor de bancos de dados. Para criar uma tabela com o Database Desktop, faça o seguinte: a.Inicie o Database Desktop no meu Iniciar do Windows (não confunda com Database Explorer, que é outro aplicativo); b.No menu File do DBD escolha a opção New, sub-opção Table; c.No quadro Table Type escolha a tabela tipo Paradox 7.0 ou dBase IV; d.Nossa tarefa, agora, é criar os campos da tabela. Iremos criar uma tabela de Agenda Telefônica, cujos campo são: Nome: tipo character (dBase) ou alpha (Paradox); Endereço: tipo character (dBase) ou alpha (Paradox); Telefone: tipo character (dBase) ou alpha (Paradox); Fax: tipo character (dBase) ou alpha (Paradox); E-mail: tipo character (dBase) ou alpha (Paradox); Home Page: tipo character (dBase) ou alpha (Paradox); Comentários: tipo memo; Fotografia: tipo binary (dBase) ou graphic (Paradox). Pressione ENTER para passar para o campo subsequente; Para salvar a tabela, clique no botão Save As. Os campos de tabelas dBase IV são do tipo: Character (texto), Float (número de ponto flutuante), Number, Date, Logical (booleano), Memo (texto longo). Os campos de tabelas Paradox 7.0 são do tipo: Alpha (alfanumérico), Number, $ (moeda), Short (inteiro curto), Long Integer (inteiro longo), BCD (Binário codificado em decimal), Date, Time, Memo, Formatted Memo, Graphic, Logical, Binary, Bytes e outros menos usados. 6.6. O PRIMEIRO APLICATIVO DE BANCOS DE DADOS: AGENDA.DPR Nosso primeiro banco de dados consistirá de um formulário simples que mostrará o conteúdo de uma agenda. Para tanto, usaremos a tabela criada no item anterior, contendo os campos Nome, Endereço, Telefone, Fax, E-mail, Home Page Comentários e Fotografia. É interessante que você tenha adicionado alguns registros nesta tabela, usando o DataBase Desktop. Use a imaginação! Para começar, inicie um novo projeto e adicione componentes DBEdit para os campos da tabela que são do tipo character. Adicione também um componente DBMemo para o campo comentário e um componente DBImage para o campo fotografia. O acesso aos dados será feito com os componentes DataSource e Table, que você deve adicionar ao formulário, posicionando-os em qualquer lugar, pois serão invisíveis em tempo de execução. Não se esqueça dos rótulos para identificar cada um dos campos da agenda, que podem ser do tipo Label (página Standard). Finalmente, adicione o componente DBNavigator. O resultado deve ficar semelhante à figura 6.7. (Ops.: talvez você se pergunte o que Deanna Troy está fazendo em um curso de Delphi. Bem, sabe como é, trabalho sem diversão ... De qualquer forma, para que a Paramount Pictures, detentora dos direitos de cópia de Star Trek, não fique braba comigo, aqui vai o meu "disclaimer": Star Trek é marca registrada da Paramount Pictures. Bem, de volta ao trabalho.) Agora só falta configurar as propriedades dos componentes para acessar os dados da tabela Agenda. Isso não é muito difícil. Basta definir as propriedades dos componentes da tabela 6.2. a seguir: Tabela 6.2 – Propriedades dos componentes da Agenda COMPONENTE PROPRIEDADE DEFINIÇÃO DBEdit Name txtNome DataSource DataSource1 DataFields Nome DBEdit Name txtEndereco DataSource DataSource1 DataFields Endereco DBEdit Name txtTelefone DataSource DataSource1 DataFields Telefone DBEdit Name txtFax DataSource DataSource1 DataFields Fax DBEdit Name txtEmail DataSource DataSource1 DataFields Email DBEdit Name txtHomePage DataSource DataSource1 DataFields HomePage DBEdit Name txtComentarios DataSource DataSource1 DataFileds Comentarios Table Name Table1 DataBaseName CURSO_DELPHI TableName Agenda.db DataSource Name DataSource1 DataSet Table1 DBNavigator DataSource DadaSource1 Note que a propriedade DatabaseName do componente Table foi definido com o alias que criamos anteriormente no BDE. Agenda.db é a tabela criada com o DataBase Desktop. Também é possível usar o DataBase Desktop para criar um alias, caso você ainda não tenha feito isso com o BDE Administrator. Basta escolher a opção Alias Manager no menu Tools do DBD e pressionar o botão New. Uma vez definidas as propriedades da Tabela 6.2, mude a propriedade Active de Table1 para true e execute o programa. Divirta-se adicionando registros e navegando pela sua tabela. Para adicionar uma imagem em DBImage basta copiá-la de algum lugar, clicar sobre este componente (em tempo de execução) e pressionar Ctrl+V (colar). Depois, salve o registro, movendo-se pela tabela ou pressionando o botão [ (post edit). 6.7. MÉTODOS DE BANCOS DE DADOS Como vimos, o Delphi fornece uma grande quantidade de componentes de acesso a dados, mas esta não é a única vantagem desta linguagem. Por exemplo, ao contrário de outros ambientes de desenvolvimento, como o Visual Basic (sorry Bill), o Delphi não exige comandos de definição de bancos de dados como Dim ou Set e, além disso, o Delphi oferece uma maior uniformidade entre suas versões Desktop, Developer e Client-Server, uniformidade esta que não existe entre as versões Standard, Professional e Enterprise do Visual Basic. Como todos os outros componentes, podemos definir as propriedades dos componentes de bancos de dados em tempo de execução. O manipulador a seguir, por exemplo, inicializa o conjunto de dados Table1 e abre a tabela durante o evento OnCreate do formulário. procedure TForm1.FormCreate (Sender: TObject); begin Table1.active:=false; Table1.DatabaseName:= ‘CURSO_DELPHI’; Table1.TableName:= ‘Agenda.db’; Table1.active:=true; end; Note que, primeiro, desativamos a tabela, pois não seria possível modificar as propriedades DatabaseName e TableName caso a tabela estivesse previamente ativa em tempo de projeto. Outro uso dos métodos de bancos de dados é substituir o DBNavigator. Este componente é muito útil e inclui todos os comandos para movimentação por uma tabela. Entretanto, em várias situações não desejamos usá-lo. Assim, o Delphi dispõe de vários comandos alternativos para a movimentação (ou "navegação") ao longo dos registros de uma tabela. Por exemplo, para ir para o primeiro registro, faça: Table1.First; A tabela 6.3 ilustra todos os métodos de movimentação em bancos de dados. Tabela 6.3 – Métodos para movimentação em um banco de dados Método Descrição First Move para o primeiro registro Last Move para o último registro Prior Move para o registro anterior ao atual Next Move para o registro seguinte ao atual MoveBy Move para frente ou para trás, o número de registros especificado O método MoveBy é interessante e não está incluído no DBNavigator, permitindo a movimentação em blocos de registros. Por exemplo, para se mover cinco registros para frente, faça: Table1.MoveBy (5); Da mesma forma, para se mover três registros para trás, faça: Table1.MoveBy (-3); Se você está acostumado a programar para bancos de dados em Visual Basic, deve se lembrar que a movimentação através dos registros pode colocar o ponteiro em uma posição inválida (sorry, Bill, de novo, mas eu tenho que falar disso). Para evitar erros em tempo de execução, a posição inválida deveria ser previamente testada com auxílio das propriedades EOF e BOF da tabela e de rotinas if-then. Em Delphi isto não é necessário, pois as rotinas de movimentação já trazem o teste incluído internamente. Isso não chega a ser exatamente fantástico, mas é uma melhoria em relação ao VB. Mesmo assim, as tabelas em Delphi dispõem das propriedades EOF e BOF, que funcionam da seguinte maneira: EOF (End of File, ou Fim de Arquivo) será verdadeira se o cursor (ou ponteiro) da tabela estiver no último registro, o que acontecerá quando: (a) uma tabela vazia for aberta; (b) o método Last for empregado; (c) o método Next for empregado e falhar, pois o ponteiro já estava no último registro. Em todos os outros casos EOF será falsa; BOF (Begin Of File, ou Início de Arquivo) será verdadeira se o cursor da tabela estiver no primeiro registro, o que acontecerá quando: (a) uma tabela for aberta; (b) o método First for empregado; (c) o método Prior for empregado e falhar, pois o ponteiro já estava no primeiro registro. Em todos os outros casos BOF será falsa. O Delphi também fornece métodos para inserção e deleção de registros. Por exemplo, o método Insert permite adicionar registros a uma tabela e ter certeza de que os parâmetros inseridos corresponderão aos campos corretos. Para usar Insert, escreva o seguinte trecho de código: Application.ProcessMessages; Table1.Insert; Table1 [‘Campo 1’] := valor 1; Table1 [‘Campo 2’] := valor 2; Table1 [‘Campo 3’] := valor 3; ... Table1 [‘Campo n’] := valor n; Table1.Post; O método Post torna as alterações permanentes. Para editar registros existentes, é necessário colocar o aplicativo no modo de edição, fazer as modificações nos campos, e usar o método Post para atualizar as mudanças. Table1.Edit; Table1.FieldValues [‘Nome_Do_Campo’] := Novo Valor; Table1.Post; Para eliminar registros de uma tabela devemos usar o método Delete. Entretanto, este método deleta os registros sem pedir confirmação prévia. Se necessário, você deve fornecer o código para a confirmação. Por exemplo, a linha a seguir deletará o registro corrente da tabela Table1: Table1.Delete; Bookmarks, ou marcadores, são indicadores de posição que permitem que você retorne a um registro rapidamente. Para usar um marcador, você precisa declarar uma variável como sendo do tipo TBookmark (geralmente na seção var de uma unidade) e usar os seguintes métodos do componente Table: GetBookMark: define um marcador; GoToBookMark : retorna para o registro marcado; FreeBookMark : desaloca a variável da memória. Como exemplo, podemos incluir um botão que define um marcador e outro que move o ponteiro até o registro previamente marcado, liberando o marcador em seguida. var UltimaPosicao: TBookmark; procedure.TForm1.cmdRegistraMarcadorClick (Sender: TObject); begin UltimaPosicao:= Table1.GetBookmark; end; procedure.TForm1.cmdVaiParaMarcadorClick (Sender: TObject); begin Table1.GoToBookmark(UltimaPosicao); Table1.FreeBookmark(UltimaPosicao); end; Em relação a pesquisa de dados, o Delphi fornece os métodos GotoKey e GotoNearest, que podem ser utilizados em conjunto com o método SetKey para efetuar buscas em tabelas indexadas. Entretanto, busca e pesquisa de dados são feitas de maneira bem mais versátil utilizando-se SQL (Structured Query Language), que será objeto do próximo capítulo. 6.8. ADICIONANDO MELHORIAS EM AGENDA.DPR Finalmente, podemos usar os métodos da seção anterior para implementar nosso próprio navegador de bancos de dados no projeto Agenda. Antes de mais nada, remova o DBNavigator do formulário, ou torne-o invisível em tempo de execução (DBNavigator1.Visible := false). Depois, acrescente os doze botões mostrados na figura 6.8 a seguir. Note que foram usados botões comuns da página Standard. Opcionalmente você poderia usar botões BitBtn e desenhar seus próprios ícones de navegação. Inicialmente, o código para os botões de navegação registro-a-registro é o seguinte: procedure TfrmAgenda.cmdInicioClick(Sender: TObject); begin Table1.First; txtNome.setfocus; end; procedure TfrmAgenda.cmdUltimoClick(Sender: TObject); begin Table1.Last; txtNome.setfocus; end; procedure TfrmAgenda.cmdAnteriorClick(Sender: TObject); begin Table1.Prior; txtNome.setfocus; end; procedure TfrmAgenda.cmdProximoClick(Sender: TObject); begin Table1.Next; txtNome.setfocus; end; Em todos os manipuladores foi incluído o comando txtNome.setfocus, para assegurarmos que o foco retorna para a primeira caixa de texto após a execução do comando. Caso contrário, o foco ficaria no botão e o usuário teria que clicar na caixa de edição. O código para os botões de navegação em bloco também não oferece dificuldades: procedure TfrmAgenda.cmdAnt5Click(Sender: TObject); begin Table1.MoveBy(-5); txtNome.setfocus; end; procedure TfrmAgenda.cmdProx5Click(Sender: TObject); begin Table1.MoveBy(5); txtNome.setfocus; end; O código para os botões "Marcar" e "Ir Para Marcador" é: procedure TfrmAgenda.cmdMarkClick(Sender: TObject); begin UltimaPosicao:=Table1.GetBookmark; end; procedure TfrmAgenda.cmdIrParaMarkClick(Sender: TObject); begin try Table1.GoToBookmark(UltimaPosicao); Table1.FreeBookmark(UltimaPosicao); txtNome.setfocus; except beep; showmessage('Não há marcador definido'); txtNome.setfocus; end; end; Note que estamos usando uma rotina try except para o botão "Ir Para Marcador", pois é possível que o usuário pressione tal botão sem que haja um marcador definido. Caso não houvesse o tratamento de erro, seria gerado um erro em tempo de execução, abortando o programa. Para editar um registro tudo o que temos a fazer é preparar o registro atual para edição: procedure TfrmAgenda.cmdEditClick(Sender: TObject); begin Table1.Edit; txtNome.setfocus; end; Antes de deletar um registro devemos perguntar se é isso mesmo que o usuário deseja fazer. Lembre-se que usuários são pessoas muito curiosas e vivem apertando todos os botões com a intenção de "testar" o seu programa, ou seja, eles querem realmente acabar com você. Para evitar isso, usamos a função MesssageBox para fazer uma pergunta e, só depois disso e caso a resposta seja afirmativa, fazemos a exclusão. procedure TfrmAgenda.cmdDeleteClick(Sender: TObject); begin beep; if Application.MessageBox('Tem certeza que deseja excluir o registro atual?','Confirmação', mb_OKCancel + mb_iconquestion)=IDOK then begin Table1.delete; beep; ShowMessage('Registro excluído!'); end; txtNome.setfocus; end; Para salvar um registro e adicionar um novo registro, escrevemos, respectivamente: procedure TfrmAgenda.cmdSalvarClick(Sender: TObject); begin Table1.Post; txtNome.setfocus; end; procedure TfrmAgenda.cmdNovoRegistroClick(Sender: TObject); begin Application.ProcessMessages; Table1.Insert; txtNome.setfocus; end; Você já deve ter notado que o comando Salvar não é estritamente necessário, pois os registros são automaticamente salvos cada vez que nos movemos ao longo da tabela. Entretanto, isso não é evidente à primeira vista e é interessante ter um botão de salvar para evitarmos perguntas de usuários. Além disso, sem o botão salvar iria ficar sobrando um espaço no formulário ...