Top Banner
Delphi 4.0 Cliente / Servidor com Oracle Esta apostila é melhor visualizada com resolução de 800x600 e no mínimo 256 cores. © Direitos Autorais 1997, 1998 para Griaule (razão social: I.A. Pesquisa & Desenvolvimento de Sistemas Ltda.) Nenhuma parte desta publicação poderá ser reproduzida ou transmitida, no todo ou em parte, em qualquer forma ou meio, seja eletrônica ou mecânica, seja qual for o propósito, sem a expressa autorização por escrito da Griaule. Delphi, Borland, ReportSmith, dBASE e InterBase são marcas registradas da Borland International, Inc. Microsoft, MS, Windows, Windows 95, Microsoft SQL Server são marcas comerciais ou marcas registradas da Microsoft Corporation. Paradox é uma marca registrada da Ansa Software, uma empresa da Borland. TrueType é uma marca registrada da Apple Corporation. Oracle é uma marca registrada da Oracle Corporation. Todas as outras marcas e nomes de produtos são de propriedade de seus respectivos portadores. Conteúdo: Márcio Pontes Marla C. Ávila Conversão para mídia eletrônica: Renato de A. Martins Atualização para utilização do Oracle: Salim Michel Esber
142

Delphi 4 Com Oracle

Oct 26, 2015

Download

Documents

alexvrd
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Delphi 4 Com Oracle

Delphi 4.0 Cliente / Servidor com OracleEsta apostila é melhor visualizada com resolução de 800x600 e no mínimo 256 cores.

© Direitos Autorais 1997, 1998 para Griaule(razão social: I.A. Pesquisa & Desenvolvimento de Sistemas Ltda.) Nenhuma parte desta publicação poderá ser reproduzida ou transmitida, no todo ou em parte, em qualquer forma ou meio, seja eletrônica ou mecânica, seja qual for o propósito, sem a expressa autorização por escrito da Griaule. Delphi, Borland, ReportSmith, dBASE e InterBase são marcas registradas da Borland International, Inc.Microsoft, MS, Windows, Windows 95, Microsoft SQL Server são marcas comerciais ou marcas registradas da Microsoft Corporation. Paradox é uma marca registrada da Ansa Software, uma empresa da Borland.TrueType é uma marca registrada da Apple Corporation.Oracle é uma marca registrada da Oracle Corporation.Todas as outras marcas e nomes de produtos são de propriedade de seus respectivos portadores. Conteúdo: Márcio PontesMarla C. ÁvilaConversão para mídia eletrônica: Renato de A. Martins Atualização para utilização do Oracle: Salim Michel Esber

Objetos e classes: revisando O Delphi utiliza objetos e classes para quase tudo. Componentes e formulários são objetos, bem como listas de strings, objetos de desenho (Canvas) e outros objetos não visuais. Um objeto é composto de campos de dados, que podem conter dados de qualquer tipo básico (integer, double, string, boolean etc.) e métodos, procedimentos e funções que manipulam esses dados. Um objeto também pode conter propriedades, que à primeira vista parecem com um campo de dados normal, mas que geralmente provocam ações quando modificadas. Por exemplo: editNumero.Enabled := True; {propriedade boolean} editNumero.SetFocus; {método procedimento} if Clipboard.HasFormat(cf_text) then

Page 2: Delphi 4 Com Oracle

{método função boolean: HasFormat} s := Clipboard.AsText; {propriedade string}

Classes Todo objeto pertence a uma classe. A classe define a estrutura e funcionamento de vários objetos semelhantes. Por exemplo, um componente de edição chamado 'editNumero' é um objeto da classe 'TEdit'. Cada objeto é chamado também de uma instância da classe. Uma classe também é um tipo de dados. Se C é o nome de uma classe, uma variável declarada do tipo C é uma variável de objeto, que pode conter referências a objetos da classe C. Por exemplo, abaixo estamos definindo uma classe chamada TPonto e declarando variáveis do tipo TPonto: type TPonto = class x, y: integer; end; var pt1, pt2: TPonto; Cada objeto específico (como 'pt1' e 'pt2') é chamado também de uma instância da classe, criada de acordo com a definição da classe. No exemplo acima, tanto 'pt1' quanto 'pt2' têm a mesma estrutura, cada um com os campos x e y. Todos os objetos de uma mesma classe têm as mesmas características (propriedades), mas cada um tem seus próprios valores para essas características. Quando você cria um objeto, ele começa como uma cópia idêntica do modelo definido pela classe. Depois você pode alterar essas propriedades, diferenciando um objeto do outro. Resumindo, existem três formas de se pensar em uma classe: • Um modelo (ou "fôrma") usado para criar objetos • Um tipo de dados que pode ser usado para declarar variáveis • Um conjunto de objetos (instâncias) que têm a mesma estrutura

Variáveis de Objeto Não basta declarar uma variável de objeto para ter um objeto. A variável não contém propriamente os dados do objeto, mas sim uma referência indireta ao objeto (internamente é o endereço de memória do objeto, ou um ponteiro para o objeto). O nome da variável não é o nome do objeto, mas sim o nome que denota essa referência. Uma variável de objeto pode estar em três situações distintas, dependendo do seu escopo: • Indefinida: uma variável local não inicializada contém "lixo", ou seja, dados indefinidos que não referenciam um objeto válido. Você deve inicializar essa variável antes de usar, com nil ou com uma referência a um objeto válido. • Com valor nil: o valor nil (nada) é um valor especial que pode ser atribuído a uma variável de objeto (ou a um ponteiro). Ele indica que a variável não aponta para um objeto válido. Se uma variável = nil, você não deve usar os campos do objeto. Variáveis de objeto globais da unidade são inicializadas com nil. • Referenciando objeto: a variável contém uma referência que aponta para um objeto válido, que foi criado anteriormente. Nesse caso você pode utilizá-la para trabalhar com o objeto. Você pode inicializar a variável, criando um objeto. Para isso, use o método Create (o construtor da classe), com o nome da classe, por exemplo: pt1 := TPonto.Create; {depois de construir, usar o objeto} pt1.x := 10; pt1.y := 20; O construtor aloca um espaço de memória para conter os dados do objeto e retorna o endereço dessa memória. Dependendo da classe, o construtor pode ter parâmetros usados para inicializar o objeto. Algumas classes têm construtores com outros nomes ao invés de Create. Para liberar a memória usada pelo objeto, use o método Free. Se você não liberar, o espaço de memória do objeto continua alocado, mesmo que não existam mais variáveis para acessá-lo. Por exemplo: pt1.Free; Quando você atribui uma variável de objeto a outra, não é feita uma cópia física do objeto. A única coisa copiada é a referência ao objeto. Depois da atribuição, ambas as variáveis referenciam o mesmo objeto, por exemplo: pt2 := pt1; Depois do comando acima, os nomes 'pt1' e 'pt2' são sinônimos, pois lêem ou alteram o mesmo objeto. Se você fizer 'pt2.x := 10', então 'pt1.x' também terá o valor 10.

Page 3: Delphi 4 Com Oracle

Herança Quando você cria uma nova classe, ela é baseada em uma classe que já existe. Com isso, a classe herda (recebe por herança) todos os dados e métodos da outra classe, mas pode acrescentar ou redefinir alguns de acordo com o que é necessário. O mecanismo de herança [inheritance] permite que você programe apenas o que é diferente entre a sua classe e a outra (programação por exceção). A classe da qual são herdadas as características é chamada de classe base ou ancestral [ancestor] ou ainda superclasse e a classe criada é chamada de classe derivada ou descendente [descendent] ou ainda subclasse. Herança não é uma cópia. Qualquer alteração feita em uma classe ancestral automaticamente repercute nas subclasses. Por exemplo, podemos derivar uma nova classe de TPonto: type TPontoColorido = class(TPonto) cor: integer; end; A classe 'TPontoColorido' tem todos os campos de 'TPonto' (x e y), mesmo não sendo especificado, e tem mais um novo campo 'cor'. Note que a superclasse é especificada entre parênteses. Herança tem vários níveis. Uma subclasse pode ter subclasses e assim por diante. Se você criar uma classe sem especificar a superclasse, o Delphi assume implicitamente a classe 'TObject'. Essa classe é ancestral direta ou indireta de todas as outras. Herança é usada em várias situações: • Componentes: um controle de edição é um tipo de controle (a classe TEdit é derivada de TControl), controles são um subtipo de componentes (TControl é subclasse de TComponent), componentes são um tipo de objetos (TComponent é descendente de TObject), etc. • Exceções: a classe Exception é derivada de TObject e ancestral de todas as classes de exceções. Cada classe de exceção pode ter subclasses etc. • Formulários: a classe TForm é a base para todos as classes de formulário. Todo formulário que você cria tem uma classe TNomeFormulário, derivada de TForm, que herda sua estrutura, mas acrescenta novos componentes. Como veremos, você pode criar uma classe de formulário derivada de uma já existente.

AQUI.

Hierarquia de classes Quando a herança é usada sucessivamente, com uma classe derivada também tendo classes derivadas, é criada uma hierarquia [hierarchy] de classes, que pode ser representada com uma estrutura em árvore. Na raiz da árvore está a classe TObject e abaixo dela as classes derivadas. Você pode ver a hierarquia das classes do Delphi e do seu programa com o Browser (logo após compilar o programa, clique em View|Browser).

Classes de formulário Formulários também são objetos. Quando você cria um novo formulário, o Delphi cria uma classe de formulário e um objeto de formulário dessa classe. O nome da classe de formulário sempre inicia com um T, seguido do nome do objeto de formulário (que você define na propriedade Name do formulário). A vantagem de ter uma classe de formulário é que isso torna possível criar dinamicamente outros objetos de formulário, além do inicial, e alterar suas propriedades. Crie um novo projeto (File|New Application). Você verá inicialmente o formulário principal. Altere o nome para 'FormBase'. Coloque um botão com o Caption= 'Fechar' e o nome 'btnFechar'. Clique duas vezes no botão e complete chamando o método Close: procedure TFormPrincipal.btnFecharClick(Sender:TObject); begin Close; end; Na seção de interface da unidade, logo após a cláusula uses, você verá a declaração da classe de formulário: type TFormBase = class(TForm) btnFechar: TButton; procedure btnFecharClick(Sender:TObject);

private { Private declarations }

Page 4: Delphi 4 Com Oracle

public { Public declarations } end;

var FormBase: TFormBase; A primeira linha: TFormBase = class(TForm) quer dizer que a classe do formulário é derivada da classe 'TForm'. Dentro da definição da classe (que vai até a palavra end), existe uma declaração para cada componente do formulário (no caso, apenas o botão 'btnFechar'). Se você adicionar, remover, ou renomear qualquer componente, o Delphi altera automaticamente essa seção. No nosso caso, temos: btnFechar: TButton onde 'btnFechar' é o nome do componente e 'TButton' é a classe do componente. Depois das declarações de componentes, começam as declarações para os procedimentos de evento. Para cada procedimento de evento, o Delphi adiciona uma declaração dentro da classe, como: procedure btnFecharClick(Sender: TObject); O corpo do procedimento, que fica na seção de implementação, contém os comandos que implementam realmente o procedimento. O código até a palavra private é mantido automaticamente pelo Delphi e você não deve alterá-lo. Você pode adicionar campos de dados ou métodos nas seções private ou public. O que fica na seção private é acessível apenas na unidade atual, mas o que for declarado na seção public é visível externamente em outras unidades. O Delphi acrescenta também uma declaração de variável para o formulário (FormPrincipal: TFormPrincipal). Essa variável, durante a execução, irá conter uma referência a um objeto de formulário. Se o formulário estiver na lista "Auto-create forms" (em Project|Options..., página 'Forms') então esse objeto será criado automaticamente no início da execução. Senão, ele deve ser criado explicitamente pelo programa, por exemplo: FormBase := TFormBase.Create(nil);

Herança Visual de Formulários Se você prever que terá vários formulários semelhantes no programa, pode fazer todos eles derivados a partir de uma única classe base (usando o mecanismo de herança). Isso permite: - Padronizar a aparência e comportamento dos formulários - Criar as funções padrão do programa apenas uma vez (na classe base) em vez de copiá-las quando necessário - Reutilização melhor do código em outros projetos. Por exemplo, numa aplicação de banco de dados, muitos formulários trabalham com apenas uma tabela e são bem semelhantes entre si. Vejamos como podemos usar as características comuns entre eles. Salve o projeto e dê o nome de BASE.PAS para a unidade e ALMOXARIFADO.DPR para o projeto.

Criando uma classe de formulário derivada Lembre-se que quando você criou o FormBase, foi criada uma classe de formulário chamada 'TFormBase'. Vamos criar agora uma classe derivada desta. Para fazer isso, clique em File|New.... Você verá o repositório do Delphi, um conjunto de itens disponíveis na criação de um novo projeto. No repositório, sempre estará disponível uma página com o nome do Projeto atual, no caso "Almoxarifado". Clique nesta página e selecione o formulário "FormBase", como na figura abaixo:

Clique no botão 'Ok' . Agora você verá um formulário 'FormBase1', que inicialmente tem as mesmas propriedades do 'FormBase' (exceto Name e Caption). Ele recebe como herança o botão 'btnFechar' e seu funcionamento.

Alterando propriedades Coloque os dois formulários lado a lado na tela e faça algumas experiências. Primeiro no 'formbase' altere a propriedade Caption do botão "Fechar" para "Fechar agora". Note que isso vai alterar simultaneamente o botão "Fechar" no formulário derivado. Mova o botão "Fechar agora" no formulário base e o botão também se move no formulário derivado. O que acontece é que todas as propriedades do botão "Fechar" são herdadas pelo formulário derivado. Qualquer alteração na classe base é automaticamente herdada. Mas se você alterar propriedades no formulário derivado, isso não acontece. Clique no formulário derivado e altere a propriedade Caption do botão "Fechar agora" para "Fechar". Isso não vai afetar o original. E agora, se

Page 5: Delphi 4 Com Oracle

você voltar ao original e alterá-lo, isso não afeta o derivado. Também se você mover o botão no formulário derivado, a sua posição (as propriedades Left e Top) não estará mais vinculada ao original. Para cada propriedade de cada componente no formulário derivado, se você alterá-la, ela perde o vínculo com o formulário original. Se você quiser vincular a propriedade novamente, clique sobre ela (no caso, Caption do botão "Fechar") com o botão direito e clique em Revert to inherited [reverter ao herdado]. Se você clicar sobre o componente e escolher Revert to inherited, então todas as propriedades serão revertidas (inclusive Left e Top, que determinam a posição e Width e Height, que determinam o tamanho). Note também que: - Se você acrescentar, excluir ou renomear um componente ao FormBase, ele será acrescentado, excluído ou renomeado no formulário derivado. - No formulário derivado, você não pode excluir ou renomear um componente que foi herdado do FormBase, só os que você tenha acrescentado na própria classe.

Criando bancos de dados e tabelas Para o nosso exemplo, vamos criar um alias de banco de dados e algumas tabelas nesse banco de dados. Usaremos o formato Paradox. Clique em Database|Explore e crie um novo alias de banco de dados, usando o drive 'STANDARD' , com o nome 'Curso', e no seu parâmetro PATH= coloque o caminho dos arquivos do curso. Usando o Database Desktop, crie duas tabelas com os seguintes nomes e estrutura: tabela Produto.db Campo Tipo Tamanho Required FieldCodProduto S(short) XNome A(alpha) 50QuantEstoque S(short)QuantMinima S(short)CodFornecedor S(short)tabela Fornecedor.db Campo Tipo Tamanho Required FieldCodFornecedor S(short) XNome A(alpha) 50

Usando um formulário padrão de banco de dados Acrescente os seguintes componentes no FormBase: - um componente Table, que acessa um alias de banco de dados - um componente DataSource, conectado ao Table - um componente DBNavigator, conectado ao DataSource Disponha os componentes como na figura abaixo:

Altere os nomes dos componentes para: tblGenerico (), dsGenerico () e dbnGenerico (o DBNavigator). No componente Table, 'tblGenerico', em DatabaseName, coloque o nome do alias, 'Curso'. Em 'dsGenerico', faça a propriedade DataSet = 'tblGenerico'. Em 'dbnGenerico', faça a propriedade DataSource= 'dsGenerico'. Agora vamos criar um procedimento de evento no formulário. Selecione o evento OnShow e clique duas vezes para criar o procedimento. Complete com o seguinte: procedure TFormBase.FormShow(Sender:TObject); begin tblGenerico.Open; end; A função desse procedimento será abrir a tabela sempre que o formulário for mostrado na tela.

Alterando as classes derivadas Retorne ao formulário 'FormBase1'. Vamos fazê-lo acessar a tabela de produtos. Altere o nome do formulário para 'FormProduto', o Caption para "Produtos" e salve a unidade como DPRODUTO.PAS (D de derivado). Clique em 'tblGenerico' nesse formulário. Altere a propriedade TableName, selecionando 'PRODUTO.DB'. Depois clique duas vezes sobre o componente para abrir o editor de campos. Clique com o botão direito, selecione Add Fields..., mantenha todos os campos e clique Ok. No editor de campos, mantenha todos os campos selecionados (CodProduto, Nome, ..., QuantEstoque) e arraste-os com o mouse, soltando-os sobre o formulário de produtos. Organize os controles de edição da forma que achar melhor.

Page 6: Delphi 4 Com Oracle

Agora para criar o formulário de fornecedores, faça um processo semelhante. Primeiro, para criar o formulário, clique em File|New..., clique na página "Almoxarifado", escolha "FormBase" e clique Ok. Com isso você tem uma nova classe derivada de 'TFormBase'. No formulário novo, altere o nome 'FormFornecedor', o Caption para "Fornecedores" e salve a unidade como DFORNECEDOR.PAS. Clique no componente Table, altere a propriedade TableName para 'FORNECEDOR.DB'. Depois clique duas vezes sobre o componente para abrir o editor de campos, adicione todos os campos e arraste-os para o formulário. Para chamar esses formulários, vamos criar um menu principal . Crie um novo formulário, altere o nome para 'formPrincipal', a propriedade Caption 'Menu Principal'. Coloque um componente MainMenu() no formulário e clique duas vezes nele para criar um menu. Como iremos utilizar este Menu para chamar outros exemplos que serão realizados durante o curso, iremos acrescentar outros items , para implementação posterior.Crie a seguinte estrutura de menus:Caption Name Shortcut

&Cadastro mnuCadastro

&Pessoas mnuCadPessoa Ctrl+ P

- N1(Separador)

P&rodutos mnuCadProduto Ctrl+ R

&Fornecedores mnuCadFornecedor

&Sair mnuCadSair

&Consulta mnuConsulta

&Produto mnuConsProdutoReduza o tamanho do formulário na vertical, ou a propriedade Height, de forma que ele não ocupe muito espaço na tela, deixando apenas a barra de menu invisível.Agora clique no menu em Cadastro|Sair . Vamos chamar o método Close, para fechar o formulário e salve o formulário como MENU.PAS.Para chamar o formulário 'formProduto no Menu, no procedimento de evento OnClick , do menu Cadastro|Produtos , acrescente o seguinte comando :procedure TformPrincipal.mnuCadProdutosClick(Sender: TObject);begin formProduto.showend;Para o Fornecedor, clique no menu Cadastro|Fornecedores e, no procedimento de evento, coloque o comando a seguir:procedure TformPrincipal.mnuCadFornecedorClick(Sender: TObject);begin formFornecedor.showend;Para isso funcionar , você precisa adicionar uma cláusula uses, no início da seção de implementação do formulário. Adicione o seguinte:Uses DProduto, DFornecedor;Como o 'formBase' é uma classe que será utilizada para criar outros formulários, não precisamos que durante a execução do projeto seja criado um objeto para esta classe , portanto, iremos retirá-la da lista de 'auto-create' do delphi. Para isto clique no menu Projetc|options, irá aparecer uma janela como a figura abaixo:

Para retirar o 'formBase' da lista de 'auto-create' , clique neste formulário na opção 'Auto-create forms' , em seguinte clique no botão (), e este formulário irá ficar em 'Available forms', com isso , não será o objeto para o formulário durante a execução do programa.Mude também a opção 'Main Form' para 'formPrincipal', deste forma ele será o primeiro formulário a ser mostrado no projeto.Cadastre alguns produtos e fornecedores.

Repositório O repositório do Delphi é um local para armazenamento de objetos, como formulários e projetos, que facilita o compartilhamento desses objetos por vários projetos. Quando você clica em File|New... para criar um novo objeto, você pode escolher um dos itens do repositório.

Page 7: Delphi 4 Com Oracle

Acrescentando um formulário ao repositório Abra o projeto ALMOXARIFADO.DPR, e dentro dele o formulário "FormBase" (base.pas). Esse formulário está sendo compartilhado dentro do mesmo projeto, mas se você quiser reutilizá-lo em outros projetos, pode acrescentá-lo ao repositório. Para isso, clique com o botão direito e em Add to Repository. Você deve informar o título do item, uma descrição e qual a página onde ele será inserido (se você digitar um nome de página que não existe, uma nova será criada), além do seu nome para indicar qual o autor desse item. Opcionalmente você pode escolher um ícone que será usado para representar o item. Para o exemplo digite o seguinte: Title: Formulário para uma tabelaDescription: Formulário de banco de dados para uma tabelaPage: Forms (default)Author: seu nome O botão "Browse" permite você procurar um ícone , para representar sua classe de formulário, caso não informe ele irá mostrar o seguinte ícone . Clique Ok. O novo item será adicionado ao repositório. Agora crie um novo projeto com File|New Application e veremos como o item pode ser reutilizado.

Formas de criar novos objetos Se você clicar em File|New... e na página "Forms", você verá que o novo item ("Formulário para uma tabela") está dentro do repositório. Agora ele está disponível em qualquer projeto, permitindo criar novos formulários a partir dele. Existem três opções para criar novos objetos a partir do repositório: copiar [copy] o item, herdar [inherit] do item (criar uma nova classe derivada) ou usar [use] o item diretamente. Se você marcar a opção "Copy" e clicar Ok, o Delphi cria uma cópia separada do objeto original que está no repositório. Qualquer alteração no original posteriormente não vai afetar essa cópia e qualquer alteração na cópia é independente do que está no repositório. Note que a unidade do formulário não foi salva e está em memória como "Unit2". Isso permite você salvar com um nome qualquer. A opção "Inherit" (herdar) faz diferente: faz uma referência ao original (ou seja, acrescenta o formulário original dentro do projeto) e cria uma nova classe derivada da original, TFormBase. O novo formulário será chamado de 'FormBase1', com a classe 'TFormBase1'. A unidade do formulário não foi salva ainda. Nesse caso, qualquer alteração no original, que está no repositório, será herdada pela classe derivada. A opção "Use" não copia o objeto original, mas compartilha com o projeto atual. Nesse caso, alterações feitas no item dentro do projeto afetam o item no repositório e vice-versa. Se vários projetos usarem o mesmo item, todos eles compartilham o mesmo item.

Adicionando um projeto ao repositório Para reutilizar um projeto inteiro, você pode acrescentá-lo ao repositório com o menu Project|Add to Repository... e informar a descrição do item como antes.

Gerenciando o repositório Para gerenciar as páginas do repositório e os itens de cada página, você pode clicar em Tools|Repository. Na caixa de diálogo você pode criar, renomear ou excluir uma página do repositório (você só pode excluir se ela estiver vazia). É possível também mudar a ordem das páginas. Você pode mover os itens entre páginas arrastando o item da lista da esquerda e soltando sobre a página desejada. Também é possível renomear ou alterar características de um item ou excluir o item.

Compartilhando o repositório numa equipe Quando uma equipe de desenvolvedores trabalha em conjunto, é importante que eles possam compartilhar o repositório, de forma que novos itens adicionados a ele estejam disponíveis para toda a equipe. Para compartilhar o repositório, você deve usar um diretório na rede que seja acessível a todos os desenvolvedores. Por exemplo, se G: é uma letra de drive que aponta para a rede, você pode usar G:\REPOS. Você deve também copiar os arquivos do repositório do Delphi para esse diretório. Os arquivos do repositório do Delphi são armazenados num subdiretório OBJREPOS, abaixo do diretório onde o Delphi foi instalado (geralmente C:\Arquivos de Programas\Borland\Delphi 4). Além desses arquivos, o Delphi usa um arquivo de texto chamado DELPHI32.DRO, localizado no subdiretório BIN do Delphi. Para compartilhar o repositório na rede, faça o seguinte: • Crie um diretório compartilhado (e.g. G:\REPOS). Verifique que todos os desenvolvedores têm acesso a ele e usam a mesma letra de drive;

Page 8: Delphi 4 Com Oracle

• Copie o repositório de um computador para o diretório compartilhado (G:\REPOS), ou seja, todos os arquivos do subdiretório OBJREPOS do Delphi, mais DELPHI32.DRO, do subdiretório BIN; • Em cada um dos computadores, no Delphi, clique em Tools|Environment Options. Na página Preferences Em "Shared Repository", digite o caminho do diretório compartilhado. Agora lembre-se que qualquer alteração ou acréscimo feito por um desenvolvedor afeta todos os outros. É importante também notar que quando você quiser compartilhar um objeto, ele deve ser salvo num diretório compartilhado também, acessível a todos (por exemplo, G:\BIBLIOTECAS).

Criando componentes dinamicamente Componentes são objetos e qualquer objeto pode ser criado dinamicamente durante a execução do programa. Um componente deve ser criado em um formulário (ou módulo de dados, para componentes não visuais). Para vermos como criar componentes dinamicamente, crie um novo projeto.

Exemplo: criando componentes Nesse projeto, quando for clicado o mouse no formulário, vamos criar um componente TButton na posição do cursor. No formulário, altere o nome para FormCriaComp e o título para "Criação de componentes". Coloque um controle Label com Caption="Texto" e um controle Edit com o nome de 'editTexto'. No evento OnMouseDown do formulário, vamos criar um componente na posição onde foi clicado o mouse. Crie um procedimento nesse evento e faça o seguinte: procedure TFormCriaComp.FormMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var botao:TButton; begin botao := TButton.Create(Self); with botao do begin Parent := Self; Caption := editTexto.Text; Left := X; Top := Y; Visible := True; end; end; Quando você cria um componente, usa o construtor Create da classe TComponent. Esse construtor exige como parâmetro o formulário onde o componente será criado. No caso, passamos 'Self' (o formulário atual), que é o mesmo que 'FormCriaComp' nesse caso. O formulário 'Self' será o dono [owner] do componente. Mas no caso de um controle (componente visual) é preciso dizer também quem será o pai [parent] desse controle. Se for o próprio formulário, o controle fica sobre o formulário. Mas se houver outro controle que é capaz de conter controles, o pai pode ser esse outro controle. A propriedade 'Parent' de um componente determina qual o pai dele. Depois o Caption é tirado do texto digitado em 'editTexto', e a posição do novo componente (propriedades Left e Top) é definida como o ponto onde foi clicado o mouse no formulário. A propriedade Visible faz com que o componente apareça no formulário. Salve o projeto como CRIACOMP.PAS e CRIACOMPP.DPR . Execute o programa. Para testar, digite um texto, que será o Caption do rótulo. Depois clique no formulário e um botão será criado.

Detalhes de tratamento de eventos Um evento é uma forma que um componente tem de notificar o formulário de algum acontecimento. Se você clica com o mouse em um botão, por exemplo, ele deve notificar o formulário desse clique, para que o código do formulário possa fazer alguma coisa em resposta. Quando o componente aciona um evento, ele executa um procedimento de evento, um procedimento que faz parte da classe de formulário. Quando você cria um procedimento na página de eventos, você está associando um evento de um componente com um procedimento de evento do formulário.

Page 9: Delphi 4 Com Oracle

Eliminando um Procedimento de Evento Se você quiser eliminar um procedimento de evento, não apague o corpo dele no código. Apague apenas os comandos entre o begin e o end, e declarações de variáveis (var), se houver. Quando você compila o programa, ou quando salva o projeto, o Delphi nota que o procedimento está vazio, e o remove automaticamente do programa.

Nomes de procedimentos O nome de um procedimento de evento é usado para declarar esse procedimento na classe do formulário. Pode ser qualquer nome, desde que siga as regras de identificadores em Delphi. Quando você cria um procedimento de evento, clicando duas vezes no componente ou em um evento qualquer, o Delphi atribui um nome padrão, da forma NomeComponenteNomeEvento. Se você tem um botão chamado btnFechar no formulário, e clicar duas vezes, o Delphi cria um procedimento chamado 'btnFecharClick' (note que o On é omitido): procedure TForm1.btnFecharClick(Sender:TObject)' begin

end; Para mudar o nome do procedimento, clique na página de eventos do Object Inspector, no evento desejado e digite o novo nome. Mas você não deve mudar o nome diretamente no texto da unidade. Se fizer isso, o Delphi não conseguirá localizar mais o procedimento e mostrará várias mensagens de erro. O nome é declarado em vários lugares: na declaração da classe de formulário, na página de eventos e na unidade. Se você alterar em um dos lugares, tem que alterar todos.

Associando um Procedimento Existente Se depois de criar um procedimento, você criar um novo componente que vai fazer a mesma coisa, você pode ligá-lo a um procedimento já existente. Por exemplo, um botão e um item de menu que estejam associados à mesma ação. Para fazer isso, selecione o componente que você quer associar, clique no evento e no botão de seta . Aparece na lista os nomes dos procedimentos compatíveis com aquele evento. Um procedimento é compatível se ele tem exatamente o mesmo número de parâmetros e o mesmo tipo de dados e passagem para cada parâmetro correspondente. Para desvincular um procedimento do componente, clique no nome do evento e tecle [Delete]. O componente não vai mais chamar aquele procedimento.

Identificando qual controle acionou o evento No Delphi você pode associar um mesmo procedimento de evento com dois ou mais componentes. Se você selecionar vários componentes e criar um procedimento de evento, ele estará associado dessa forma. O problema é saber qual dos componentes acionou o evento. O Delphi fornece essa informação para o procedimento através do parâmetro 'Sender'. Essa variável é uma referência ao componente que acionou o evento, como se fosse um nome diferente para o componente dentro do procedimento, e através dele podemos acessar as propriedades e métodos do componente. 'Sender' é declarado como 'TObject', o que quer dizer que ele é um objeto genérico do Delphi, que pode ser um componente qualquer. Se você quiser acessar uma propriedade específica de um componente, por exemplo, Color, precisa dizer ao Delphi que temos certeza que 'Sender' é um componente Edit. Por exemplo: (Sender as TEdit).Color := clYellow; A expressão Sender as TEdit força o Delphi a tratar 'Sender' como um objeto da classe TEdit, ou seja, um componente Edit. Usando essa expressão, podemos acessar propriedades do componente. Outra sintaxe poderia ser usada: with Sender as TEdit do Color := clYellow;

Associando um Procedimento em Tempo de Execução A associação entre procedimentos e eventos pode ser feita também durante a execução do programa. Para isso, basta usar o nome do evento como uma propriedade e atribuir um valor que é o nome do procedimento de evento. Por exemplo, para associar o evento OnEnter com o procedimento "EditRecebeuFoco", basta usar o seguinte comando: editOperando1.OnEnter := EditRecebeuFoco;

Page 10: Delphi 4 Com Oracle

Tratando Eventos com Componentes Dinâmicos Um componente criado dinamicamente não tem nenhum tratamento de eventos definido, mas como eventos podem ser alterados como propriedades, você pode criar um procedimento de evento manualmente e durante a execução, associar esse procedimento ao componente. Para isso abra o projeto CRIACOMPP, caso ele não esteja aberto.Dentro da definição da classe do formulário, na parte private (ou public, não importa no caso), acrescente uma declaração de método: procedure TrataClick(Sender:TObject); Agora, na seção de implementação, crie o corpo do método 'TrataClick', da seguinte forma: procedure TFormCriaComp.TrataClick(Sender:TObject); begin ShowMessage('Você clicou no botão '+ (Sender as TButton).Caption); end; Esse método assume que 'Sender' é um componente da classe TButton, e mostra uma mensagem com a propriedade Caption do Sender. A cada vez que criarmos um componente, vamos associar esse método ao evento OnClick do componente. Para isso, volte ao procedimento 'FormMouseDown' e acrescente a linha abaixo depois de Visible := True: OnClick := TrataClick; Quando você usa o nome do evento como uma propriedade, o valor atribuído deve ser o nome de um método do formulário. Agora, quando você clicar em um dos botões criados, o procedimento 'TrataClick' será executado e vai mostrar o Caption desse botão numa mensagem. Nota: a variável OnClick é do tipo procedure, um tipo de dados que permite fazer referência a procedimentos. Veremos isso em detalhe posteriormente.

Criando formulários dinamicamente Um formulário, sendo um objeto, também pode ser criado dinamicamente. Na verdade, para cada formulário que você desenha na tela, o Delphi cria uma classe de formulário, como já vimos. Se o nome do formulário é FormExemplo, o nome da classe é TFormExemplo. Você pode criar objetos de formulário em tempo de execução com a sintaxe: form := TFormExemplo.Create(Application); O parâmetro do construtor Create pode ser nil, pode ser o objeto Application do Delphi, ou pode ser um outro formulário, dependendo da situação. Você pode criar várias instâncias da mesma classe de formulário, que serão objetos distintos durante a execução do programa.

Auto-criação de formulários Para cada classe de formulário do projeto, esse formulário pode ser auto-criado [auto-created] no início da execução ou não. Se o formulário 'FormExemplo' for auto-criado, significa que a variável de formulário FormExemplo já é inicializada com um objeto da classe TFormExemplo. Isso é feito no arquivo do projeto (.DPR), com comandos do tipo: Application.CreateForm(TFormExemplo, FormExemplo); É mais fácil fazer um programa com todos os formulários auto-criados, porque todos os formulários estarão disponíveis para serem chamados a qualquer momento. Em compensação todos os formulários estarão ocupando memória no início da execução, mesmo aqueles que estão invisíveis e não estão sendo usados. Se você desativar auto-criação para uma classe de formulário, significa que antes de chamar o formulário você deve criar um objeto de formulário e referenciá-lo com alguma variável (provavelmente a própria variável FormExemplo). Mas a vantagem de fazer isso é que o início da execução do programa leva bem menos tempo. Nas opções do projeto você pode determinar para cada formulário se ele será auto-criado ou não. O formulário principal do projeto é o primeiro formulário da lista "Auto-create forms". Logo deve haver pelo menos um formulário nessa lista ou o projeto não tem formulário principal.

Alterando a opção "auto-create" Vejamos o que deve ser alterado num projeto para não usar auto-create. Abra o projeto ALMOXARIFADO.DPR criado anteriormente. Clique em Project|Options, na página 'Forms'. Na lista da esquerda, "Auto-create forms", clique no 'FormProduto', segure a tecla [Ctrl] enquanto clica em 'FormFornecedor' e clique no botão [>] para mover os formulários para a direita. Se 'FormBase' estiver do lado esquerdo, faça o mesmo com ele.

Page 11: Delphi 4 Com Oracle

Agora o único formulário auto-criado é o formulário principal, 'FormPrincipal'. Clique Ok e abra esse formulário. Abra o procedimento de evento do item Cadastro|Produtos do menu. Antes de chamar o 'FormProdutos', vamos acrescentar um comando para criar o formulário: procedure TformPrincipal.mnuCadProdutosClick(Sender: TObject);begin formProduto := TFormProduto.Create(Application); formProduto.showend;Faça o mesmo para o item Cadastro|Fornecedores com relação ao 'FormFornecedor': procedure TformPrincipal.mnuCadFornecedorClick(Sender: TObject);begin formFornecedor := TFormFornecedor.Create(Application); formFornecedor.showend;Cada vez que um desses procedimentos é chamado, o formulário correspondente é criado e mostrado. Execute o programa e verifique que agora ele inicia muito mais rapidamente. Quando você clicar em um dos menus, ele pode levar um pouco mais de tempo para abrir cada formulário. Mas existe ainda um problema. Quando você clica no botão novamente, ele abre uma nova cópia do formulário e mantém o antigo. Vamos resolver isso no próximo passo. Outro problema, que não é visível, acontece quando você fecha o formulário. O Delphi não destrói o objeto de formulário. Ele é mantido em memória, mas invisível ao usuário. Com isso, se forem criados vários formulários, eles nunca são liberados, o que provoca vazamento de memória na aplicação.

Destruindo o formulário ao fechar Para evitar o segundo problema acima, precisamos indicar para o Delphi que o formulário será destruído ao ser fechado. Para isso, acrescente um procedimento de evento no FormBase, que será herdado pelos outros. No evento OnClose, faça: procedure TFormBase.FormClose(Sender:TObject; var Action:TCloseAction); begin Action := caFree; end; O parâmetro 'Action' do procedimento indica o que será feito quando o usuário fechar o formulário. Ele é passado por referência para que você possa modificá-lo. Você pode colocar um dos seguintes valores: 'caFree' para liberar o objeto da memória, 'caMinimize' minimiza o formulário, 'caHide' torna o formulário invisível (o default) ou 'caNone', que não faz nada (ignora a ação de fechar). Nós colocamos Action := caFree para forçar a destruição do objeto. Agora o formulário principal tem que saber se um objeto de formulário foi destruído ou não. Para isso, crie a seguinte função na unidade principal, logo após a implementation: //retorna True se 'F' é um formulário aberto function FormularioAberto(F:TForm):boolean;var k:integer; begin result := False; if F = nil then exit; //com certeza ñ está aberto for k := 0 to Application.ComponentCount -1 do if Application.Components[k] = F then begin result := True; exit; end; end; A função procura o formulário dentro da propriedade Components do objeto 'Application'. Essa propriedade é um vetor contendo os componentes (formulários) adicionados à aplicação. Note que isso funciona porque os formulários são criados com .Create(Application), o que faz com que o formulário seja adicionado a Application.Components. Se a função acha o formulário, retorna True (ele já está aberto), senão False. Agora altere os procedimentos anteriores, que chamam os formulários. Antes de criar um objeto formulário, usamos a função FormularioAberto para saber se ele já foi aberto: procedure TformPrincipal.mnuCadProdutosClick(Sender: TObject);begin if not FormularioAberto(FormProduto) then

Page 12: Delphi 4 Com Oracle

FormProduto:=TFormProduto.Create(Application); FormProduto.Show;end;

procedure TformPrincipal.mnuCadFornecedorClick(Sender: TObject);begin if not FormularioAberto(FormFornecedor) then FormFornecedor:=TFormFornecedor.Create(Application); FormFornecedor.Show;end;Note que o Show é incondicional - o formulário é mostrado nas duas situações, quando já existia e quando é criado um novo.

Componentes de banco de dados O Delphi possui vários componentes, visuais e não-visuais, relacionados com as tarefas de acesso a bancos de dados. Os componentes não-visuais, da página Data Acess, acessam os dados e dão suporte aos outros componentes, visuais. Os controles de dados [Data Controls] se conectam a um componente não-visual (geralmente um DataSource) para mostrar seus dados na tela e permitir edição. Na página Data Access, existem dois componentes principais: O componente Database [banco de dados] mantém toda a informação necessária para acessar um banco de dados, inclusive o formato de banco de dados usado (DriverName), os parâmetros de conexão (Params). Permite criar um alias local dentro do programa, o que evita a necessidade de um alias externo. Ele mantém aberta uma conexão com o banco de dados, que é usada por todos os outros componentes. Nem sempre é necessário usar um componente Database, mas ele é muito útil em qualquer situação O componente DataSource [fonte de dados] serve como intermediário entre um dataset (ver abaixo) e vários controles visuais e transfere os dados de um para o outro.

DataSets Um dataset [conjunto de dados] é um componente que permite manipular um conjunto de linhas (registros) e colunas (campos). Esse conjunto pode estar ou não em um banco de dados. Ele pode ser o conteúdo exato de uma tabela ou pode ser baseado em duas ou mais tabelas, ou ainda pode ser gerado dinamicamente no servidor, por um procedimento armazenado. A classe TDataSet é a base para todas as classes de datasets. Ela contém propriedades, como Active, State, EOF e métodos para abrir e fechar o dataset (Open, Close), para movimentação de linhas (First, Next, Prior, Last) e para editar o conteúdo do dataset, alterando, incluindo ou excluindo registros (Edit, Insert, Post, Cancel, Delete). Possui também eventos que permitem interceptar uma ação antes que [before] ela seja efetuada (BeforeOpen, BeforePost, BeforeDelete, etc.) e fazer algum tratamento depois que [after] ela seja efetuada (AfterPost, AfterDelete, etc.), além de outros eventos usados em situações específicas (OnNewRecord, OnCalcFields, OnPostError, ...). As subclasses de TDataSet existentes no Delphi aumentam essas capacidades para finalidades específicas: Table Acessa uma tabela física do banco de dados. Query Executa um comando SQL de consulta (SELECT) e retorna o resultado como um conjunto de registros, em alguns casos permitindo modificação. Ou executa um comando de manipulação do SQL, que não retorna resultado algum. StoredProc Executa um procedimento armazenado [stored procedure] em um SGBD cliente/servidor. Pode retornar resultados ou não. ClientDataSet Conecta-se remotamente com um componente Provider executando em outra aplicação, possivelmente em outro computador. Veremos mais tarde como utilizá-lo.

Objetos TField Cada campo de um DataSet é representado por um objeto de campo, da classe TField. Algumas propriedades desse objeto são tiradas dos campos da tabela. Outras podem ser modificadas pelo programa para determinar como o campo será formatado, como ele será mostrado num DBGrid etc. Você pode adicionar objetos de campo com o editor de campos [fields editor], clicando duas vezes no DataSet. Se o editor de campos estiver vazio, objetos de campo são criados automaticamente para cada campo, mas sem

Page 13: Delphi 4 Com Oracle

nome. De qualquer forma você pode ter acesso a eles usando a propriedade Fields (acessa por posição) ou o método FieldByName (acessa por nome): tblProduto.Fields[0].Value := 3; tblProduto.FieldByName('CodProduto').Value := 3; A propriedade Name do objeto de campo é definida pelo Delphi a partir do nome do componente Table e do nome do campo da tabela. Por exemplo, se o componente se chama 'tblProduto' e o campo se chama 'QuantEstoque', o nome do objeto de campo é 'tblProdutoQuantEstoque'. Mas não há problema em mudar esse nome. Quando o nome do campo contém espaços, eles são ignorados. Quando ele contém caracteres ilegais em Object Pascal, como Ç ou letras acentuadas, ele é retirado do nome. O campo Preço, por exemplo, terá um objeto 'tblProdutoPreo' (sem o C). Para cada tipo de dados, existe uma classe derivada de TField. Se o campo 'QuantEstoque' é do tipo "Short" (Paradox), é criado um objeto da classe TSmallintField. Se o campo 'Nome' é do tipo "Alpha" (Paradox) ou "char", "varchar" (InterBase, SQL Server, Oracle), é criado um objeto da classe TStringField. A propriedade Value permite consultar/modificar o valor de um campo. Ela retorna um valor do tipo variant, um tipo "mutante" em Delphi, que pode conter dados de vários tipos de dados diferentes. Para converter os dados do campo em outro tipo, pode-se usar uma das propriedades de conversão (AsTipo). Por exemplo, a propriedade AsString permite trabalhar com qualquer campo como se ele fosse uma string. Outras propriedades de conversão são: AsBoolean, AsCurrency, AsDateTime, AsFloat, AsInteger e AsVariant.

Outras propriedades Outras propriedades de um objeto TField são: Alignment Determina o alinhamento do texto dentro do campo (quando ele não está sendo editado). DataSize Para consulta apenas. Quantidade de bytes de memória ocupados pelo valor do campo, exceto no caso de BLOBs, onde o valor é sempre zero. DataType Para consulta apenas. O tipo do campo. DisplayLabel Nome mostrado em um grid. DisplayText Texto que é mostrado no campo (valor convertido para string) quando ele não está sendo editado. Já formatado. DisplayWidth Largura usada em uma coluna de um grid. EditMask Máscara de edição. FieldName Nome do campo físico. IsNull Para consulta apenas. Verdadeiro se o campo tem o valor NULL (não informado). ReadOnly Verdadeiro se o campo não pode ser modificado. Required Se verdadeiro, o campo é obrigatório. Deve ser informado. Size Tamanho do campo, quando ele é do tipo string. Text Texto que é mostrado no campo quando ele está sendo editado. ValidChars Conjunto de caracteres que determina quais caracteres são válidos para digitação dentro de um campo. Visible Se falso, o campo não aparece em um grid.

Controles de dados Os controles de dados mostram informação de um campo de uma determinada tabela. Os mais usados são o DBEdit, para editar um campo diretamente e o DBGrid, para mostrar vários campos e registros. Outros componentes úteis são: DBText - mostra um campo, mas não permite editar (como um Label). DBMemo - edita um texto de várias linhas. DBRichEdit - edita um texto de várias linhas, com formatação de fontes e tamanhos (como o RichEdit). DBCheckBox - mostra um campo lógico (verdadeiro ou falso) como um valor marcado ou desmarcado. DBRadioGroup - mostra um campo que tem algumas escolhas possíveis como um grupo de "botões de rádio". A propriedade Items determina o texto que aparece nos itens e a propriedade Values determina o valor gravado no campo para cada item. Semelhante ao RadioGroup da página Standard. DBComboBox e DBListBox - mostram uma lista de valores possíveis de um campo. DBLookupComboBox e DBLookupListBox - mostram uma lista de valores possíveis de um campo, consultando uma outra tabela.

Page 14: Delphi 4 Com Oracle

Módulos de dados Numa aplicação que utiliza bancos de dados, existem vários componentes de banco de dados como componentes Table, Query, DataSource, Database(citados anteriormente) e esses componentes ficam geralmente espalhados pela aplicação. Um módulo de dados [data module] é um local central para guardar os componentes de dados usados pela aplicação. Ele é semelhante a um formulário, mas só pode conter componentes não-visuais; controles não podem ser colocados em um módulo de dados. Além dos componentes da página Data Access, você pode ter outros componentes como Timer, OpenDialog etc. Geralmente um programa tem apenas um módulo de dados, que contém, além dos componentes de banco de dados, todo o processamento de eventos desses componentes, como por exemplo validação de campos e de registros. Além disso, você pode criar rotinas (métodos públicos) no módulo de dados para ajudar o restante do programa. A principal vantagem de um módulo de dados é que se houver alteração nas tabelas do banco de dados, essa alteração pode ser feita em um único lugar. Outra vantagem é que, se vários projetos diferentes usam o mesmo banco de dados, eles podem compartilhar o mesmo módulo de dados. Vamos alterar o projeto ALMOXARIFADO.DPR, para utilizar módulo de dados que irá manter as tabelas. Esse módulo inicialmente irá usar as tabelas PRODUTO.DB e FORNECEDOR.DB criadas anteriormente.

Criando um módulo de dados Para criar um módulo de dados, clique em File|New na página 'New' clique no ícone Data Module() . O módulo de dados é semelhante a um formulário com um fundo branco, mas não aparece para o usuário. Ele tem menos propriedades também. Altere a propriedade Name e coloque 'DMAlmoxarifado'. Salve o projeto, informando os nomes: Para 'Unit1' (do formulário): MAlmoxarifado.PAS

Ligando um formulário ao módulo No formulário 'formProduto', o componente DataSource precisa fazer referência ao módulo de dados 'DMAlmoxarifado'. Para que isso seja possível, você deve acrescentar uma cláusula uses ao formulário, fazendo referência à unidade 'MAlmoxarifado'. Você pode fazer isso com File|Use unit.

Convertendo um programa já ExistenteAgora vamos colocar os componentes de banco de dados no módulo. Comece com um componente Database. Altere o nome para 'dbAlmoxarifado'. Clique duas vezes sobre ele: digite em "Name", o nome "DBAlmoxarifado", em "Driver Name", selecione STANDARD e clique em Defaults. Embaixo, no parâmetro PATH=, coloque o diretório onde estão as tabelas (C:\CursoDelphi). Depois clique Ok. Coloque no módulo dois componentes Table, um para cada tabela . Nos dois Table, altere DatabaseName para "DBAlmoxarifado", conectando o componente com o Database. Defina os nomes dos componentes como tblProduto, tblFornecedor. Clique duas vezes em 'tblProduto', adicione todos os campos ([Ctrl+A], depois Ok) , faça o mesmo processo para o componente tblFornecedor.Como iremos utilizar os componentes table no Data Module não faz mas sentido ter o componente tblGenerico no formulário 'FormBase', pois, o cadastro de produtos irá utilizar o componente tblProduto e o cadastro de fornecedores irá utilizar o componente tblFornecedor. Então delete o componente tblGenerico no formulário 'formBase' e retire o comando que abre o componente , no evento OnShow. O comando a ser excluído será:tblGenerico.open;Abra o formulário 'formProduto'. Irá aparecer uma janela perguntado se você deseja excluir as referências desse componente tblGenerico ou se deseja criar este componente na classe TformProduto, isso ocorre porque a classe TFormProduto foi herdada da classe TFormBase, e esta classe utilizava o componente tblGenerico.Deixe a opção para excluir a referencia marcada e clique no botão 'Ok'. A mensagem é mostrada na figura abaixo:

Faça o mesmo para o formulário 'formFornecedor'.Abra o formulário 'formProduto'. Como todos os componentes estão associados ao componente DataSource existente neste formulário , podemos então alterar somente a propridade DataSet do componente DataSource para 'DMAlmoxarifado.tblproduto', com isso estamos utilizando o componente tlbProduto do módulo 'DMAlmoxarifado'. No formulário 'formFornecedor', iremos alterar a propriedade DataSet do componente DataSource para 'DMAlmoxarifado.tblfabricante', com esta alteração o formulário está utilizando o componente tblFornecedor do módulo 'DMAlmoxarifado'.

Page 15: Delphi 4 Com Oracle

Se você executar o programa observe que não é mostrado nenhum registro das tabelas e nos cadastros o DBNavigator esta desabilitado, isto esta ocorrendo porque os componente tables ainda não foram abertos, mas iremos abri-los mais na frente.

Usando DBCtrlGrid O componente DBCtrlGrid é semelhante ao DBGrid: ele mostra uma célula para cada registro. Mas em vez de ter um layout fixo, com linhas e colunas, ele permite que você projete o layout desejado. Nesse controle você projeta um painel modelo, colocando vários controles e ele replica esse painel várias vezes. Abra o projeto ALMOXARIFADO.DPR. Crie um novo formulário, com o nome 'FormListaProduto' e Caption= "Listar Produtos". Clique em File|Use unit e selecione a unidade 'MAlmoxarifado'. No módulo 'DMAlmoxarifado' acrescente um componente DataSource , altere seu nome para 'dsProduto' e na propriedade DataSet coloque 'tblProduto'. No Cadastro de Produto e Almoxarifado deixamos o compomente DataSource fazendo parte do formulário, mas, isto também poderia ser alterado, ou seja, além do componente Table , colocar também os componentes DataSource no módulo de dados..Voltando ao formulário 'formListaproduto', coloque um componente DBCtrlGrid no formulário. Altere Align para alClient. Em DataSource, selecione 'DMAlmoxarifado.dsProduto'. Agora vamos colocar sobre esse componente controles de dados. Coloque dois componentes Label e dois DBEdit no formulário, como indicado na figura:

Os componentes aparecem apenas no primeiro painel, mas serão replicados em tempo de execução. A propriedade DataSource de cada um já é definida automaticamente, mas você precisa definir o DataField de cada um. Coloque, respectivamente "Nome" e "QuantEstoque" na propriedade DataField. Salve a unit como LISTAPRODUTO.PAS.No formulário principal, iremos chamar este formulário, precisamos acrescentar uma referência deste formulário , utilize o item de menu File|Unit . Como nos formulários anteriores utilizamos a opção para criar os formulários dinamicamente o mesmo será feito para este formulário. Primeiro precisamos retirar este formulário da lista "Auto-Create", em seguida no evento OnClick do item de menu Consulta|Produto . Faça o seguinte: if not FormularioAberto(FormListaProduto) then FormListaProduto :=TFormListaProduto.Create(Application); FormListaProduto.Show;Como o formulário 'formListaProduto', está sendo criado dinâmicamente , iremos destruir o formulário quando o usuário fechá-lo.Então no evento Onclose deste formulário acrescente o código a seguir: action := caFree;Outras propriedades úteis desse componente são: RowCount e ColCount (definem o número de linhas e colunas que aparecem no controle) e Orientation (que define se o grid ordena as linhas horizontalmente ou verticalmente. Para que este exemplo funcione é necessário abrir o componente 'tblProduto', no item abaixo iremos discutir sobre isto.

Abrir os componentes Table do móduloUm problema existente com a utilização de módulos, é aonde devemos abrir e fechar o componente Table, pois, podemos ter vários formulários utlizando o mesmo componente . Neste exemplo estamos utilizando o mesmo componente tblProduto para os formulários 'formProduto' e 'formListaproduto', se passamos a abrir o componente tblProduto no envento OnShow de cada formulário, o que pode ocorrer é que o componente table pode deixar uma determinada tabela aberta sem que ela esteja sendo utilizada no programa, isso ocorre quando os dois formulários que a utiliza estejam fechados. Mas se passarmos a fechar todas as vezes que o formulário for fechado, pode ocorrer que o usuário pode estar utilizando o 'formProduto' antes de fechar este formulário ele resolve visualizar o 'formListaProduto' ', se neste momente o formulário 'formListaProduto' for fechado , o componente tblproduto também será fechado, como isso o formulário 'formProduto' não irá mostrar nenhum produto, porque o tblproduto foi fechado para ele também.Quando vários formulários utilizam o mesmo compomente Table, qualquer modificação feita em um formulário será afetado no outro também, por exemplo, se movimentar a tabela para o próximo registro.Para resolvermos este problema iremos criar um procedimento para abrir tabelas e um procedimento para fechar tabelas. No procedimento AbrirTabelas iremos contar a quantidade de formulários que utiliza este componente s, esse quantidade será armazenada na propriedade tag de cada componente Dataset. A propriedade tag se acrescentar algum valor para ela não tem efeito nenhum, pois o Delphi deixa o programador utilizá-ls para a finalidade que desejar, neste caso iremos utilizar para ter um controle da quantidade de formulários que utiliza este componente.

Page 16: Delphi 4 Com Oracle

No procedimento FecharTable iremos verificar se a propriedade Tag é 1 , pois se for este é o único formulário que utiliza o componente . Independente se for ou não o formulário único iremos decrementar a propriedade Tag.Como podemos criar procedimentos ou funções nos módulos de dados, os procedimentos AbrirTabela e FecharTabela serão criados no módulo 'DMAlmoxarifado'. Abra este módulo e acrescente os código a seguir: procedure AbrirTabela(tabela :TDataSet);begin tabela.tag := tabela.tag + 1; tabela.open;end;

procedure FecharTabela(tabela :TDataSet);begin tabela.tag := tabela.tag - 1; if tabela.tag = 0 then tabela.close;end;Nos procedimentos foram colocado um parâmetro do tipo TDataSet , porque esses procedimentos podem ser usados para abrir ou fechar qualquer componente da classe TDataSet, não somente o compomente Table. O procedimento AbrirTabela será colocado no evento OnCreate , quando os formulários forem criados as tabelas utilizadas por ele serão abertas. E no evento OnClose iremos colocar o procedimento FecharTabela .Abra o formulário 'formProduto' , crie um procedimento para o evento OnCreate e acrescente o código abaixo para abrir as tabelas utilizadas por este formulário:procedure TformProduto.FormCreate(Sender: TObject);begin inherited; AbrirTabela(DMAlmoxarifado.tblProduto);end;No evento OnClose , para fechar os componentes utilizados por este formulário acrecente o código abaixo:procedure TformProduto.FormClose(Sender: TObject;var Action:TCloseAction);begin inherited; FecharTabela(DMAlmoxarifado.tblProduto);end;Iremos repetir a mesma operação para os formulários 'formFornecedor' e 'formListaProduto'.Abra o formulário 'formListaProduto' , no evento OnCreate coloque: procedure TformListaProduto.FormCreate(Sender: TObject);begin AbrirTabela(DMAlmoxarifado.tblProduto);end;No evento OnClose , faça: procedure TformListaProduto.FormClose(Sender: TObject;var Action:TCloseAction);beginaction := cafree;FecharTabela(DMAlmoxarifado.tblProduto);end;Abra o formulário 'formFornecedor' , no evento OnCreate e acrescente o código abaixo:procedure TformFornecedor.FormCreate(Sender: TObject);begin inherited; AbrirTabela(DMAlmoxarifado.tblFornecedor);end;No evento OnClose , para fechar os componentes utilizados por este formulário acrecente o código abaixo:procedure TformFornecedor.FormClose(Sender: TObject;var Action:TCloseAction);begin inherited; FecharTabela(DMAlmoxarifado.tblFornecedor);end;

Page 17: Delphi 4 Com Oracle

Salve o programa e execute. Para testar abra o formulário 'Lista de Produtos' , note que ao mostrar o formulário, os componentes serão duplicados para cada registro. Não fecha esse formulário, e abra o formulário de 'Cadastro de Produtos', no 'Lista de Produtos', caso tenha mais de um registro cadastrado , clique por exemplo no segundo registro , observe que o 'Cadastro de Produto' também foi modificado, isso ocorre porque os dois formulários estão utilizando o mesmo compomente Table , e qualquer modificação feito em um afeta no outro, caso esteja aberto.

4 - Cliente / Servidor

Diferenças na programação cliente / servidor

A linguagem SQL

Usando o Oracle e o SQL Server

Criando uma tela de login

Monitorando comandos SQL

Diferenças na programação cliente/servidor Um sistema cliente/servidor é particionado em duas partes: o cliente é aplicação que executa numa estação de rede e implementa a interface com o usuário. Essa aplicação fica responsável por validar as entradas do usuário, e iniciar pesquisas de acordo com um pedido do usuário. Ela se comunica remotamente com o servidor, que é um sistema gerenciador de banco de dados (SGBD) executando em um computador central, chamado servidor de banco de dados. O servidor é responsável pela manutenção das estruturas de dados necessárias em arquivos, pelos detalhes internos do acesso aos dados, pelo controle de acesso (usuários autorizados e suas senhas) e pela manutenção da integridade dos dados. A comunicação entre cliente e servidor é feita através da linguagem SQL, tanto para operações de definição de dados, quanto para consulta ou atualização dos dados. Quando o SGBD recebe um pedido para selecionar alguns dados, ele acessa localmente os dados no servidor e retorna apenas o resultado pedido. No caso de uma atualização, ele apenas informa que a atualização foi feita (e quantas linhas foram atualizadas). No Delphi você pode começar a desenvolver sua aplicação usando um banco de dados desktop e depois migrar para cliente/servidor, com poucas modificações no programa, pois a forma de acesso através do programa é praticamente a mesma. Neste capítulo veremos o SGBD Oracle, que é atualmente popular pela sua eficiência. O objetivo não é vermos todas as características do Banco de Dados, mas principalmente como trabalhar com o Delphi acessando o Oracle. Em notas, traçaremos um paralelo da conexão com o Oracle e com o SQL Server, da Microsoft.

Evitando acesso seqüencial à tabela A principal coisa para ter em mente é tentar usar o ambiente cliente/servidor para o que ele foi projetado. Programas que lêem toda a tabela seqüencialmente, como um arquivo enorme, para fazer totalizações ou alterações são ineficientes nesse ambiente. Por exemplo: o trecho de programa abaixo pode funcionar bem para tabelas Paradox, mas não é recomendável num ambiente cliente/servidor: while not tblProduto.EOF do begin if tblProdutoCodFornecedor.value = codFornecedor then begin tblProduto.Edit; tblProdutoPreco.Value := (1 + porcent/100) *tblProdutoPreco.Value; tblProduto.Post; end; tblProduto.Next; end;

Page 18: Delphi 4 Com Oracle

Em vez de fazer esse processamento seqüencial, que lê todos os registros mesmo os que não são necessários, é melhor usar um comando SQL que faz a mesma coisa que esse tipo de programa: update Produto set Preco = Preco * :fatorAumento where CodFornecedor = :codFornecedor

Nomes de campos e tipos de dados Cada tipo de banco de dados tem as suas próprias regras para os nomes de campos que podem ser usados e os tipos de dados que podem haver para cada campo. Só a título de exemplo, o Paradox aceita nomes de campo com até 25 caracteres, que podem conter espaços ou caracteres acentuados, por exemplo "Preço Venda" ou "Situação". O Oracle (bem como o SQL Server) não aceita espaços em nomes, mas permite caracteres acentuados. Os campos teriam que ser renomeados para "Preço_Venda" ou "Situação". Já o InterBase não aceita espaços nem caracteres acentuados e os campos teriam que ser "Preco_Venda" e "Situacao". Resumindo, para que uma aplicação seja fácil de converter de um formato para outro, é recomendável usar nomes que seguem todos os padrões, ou seja, sem acentos nem espaços. Outra questão são os tipos de dados. Cada banco de dados tem seus próprios tipos. Embora com nomes diferentes, esses tipos podem ser compatíveis entre si. A tabela abaixo resume algumas diferenças entre tipos. Essa tabela não é completa, mas você pode saber quais os mapeamentos de tipos ao usar o Data Migration Wizard do Delphi: Paradox Oracle SQL Server A20 (alpha, tam.20) varchar2(20) ou char(20) varchar(20) ou char(20)S (short) number smallint I (long integer) integer int M (memo) long textG (graphic) long raw image D (date) date datetimeT (time) date datetimeN (number) number float$ (money) number money+ (autoincrement) não existe definido como IDENTITY Em bancos de dados cliente/servidor, o tipo varchar(n) representa uma informação de tamanho variável, com no máximo n caracteres e o tipo char(n) representa um campo de tamanho fixo, que é sempre completado com espaços. O SQL Server não têm tipos separados para data e hora, mas datetime guarda as duas informações juntas no mesmo campo. No caso de valores numéricos, SQL Server oferecem os tipos numeric(n,p) e decimal(n,p), que são mais exatos que o "number" do Paradox. Ou double precision, que tem uma precisão maior. Esses tipos guardam valores com menos exatidão do que numeric, decimal e money.

Bloqueio de registros Quando você trabalha com tabelas Paradox ou dBase, o Delphi trata o bloqueio de registros automaticamente. Ele usa o que é chamado de bloqueio pessimista, porque ele assume que é muito provável outro usuário tentar editar o mesmo registro. Nesse modo de trabalho, o registro é bloqueado logo que começa a ser modificado (com Edit) e permanece bloqueado até que ele tenha sido salvo (com Post) ou sua edição seja cancelada (com Cancel). Num ambiente cliente/servidor, o bloqueio pessimista não é adequado. Se vários usuários trabalham com a mesma tabela, quando um usuário A bloqueia um registro, um outro usuário B fica bloqueado pelo SGBD. O programa aparece "travado" para B até que A resolva liberar o registro. Num SGBD cliente/servidor, o Delphi e o SGBD usam um bloqueio otimista, onde o registro é bloqueado apenas por um curto período de tempo, durante a operação de alteração (Post). Mas não é bloqueado pelo simples fato de estar sendo modificado em memória. Como veremos, uma forma melhor de controlar o bloqueio e de garantir a integridade do banco de dados são as transações.

A linguagem SQL SQL (Structured Query Language - Linguagem estruturada de consulta) é uma linguagem usada para comunicação com bancos de dados, que permite especificar comandos de consulta e manipulação de dados. Ela não é uma linguagem de programação completa, mas é sempre chamada a partir de outra linguagem.

Page 19: Delphi 4 Com Oracle

A maioria dos bancos de dados seguem o padrão ANSI SQL-92, que define um conjunto de comandos básicos. Mas cada banco de dados tem algumas diferenças na forma de implementação do SQL e tem algumas características a mais que não fazem parte do padrão. Os comandos do SQL podem ser divididos nas seguintes categorias (note que isso não abrange todos os comandos, só os comandos ANSI que serão vistos nesse curso): DDL: Data Definition Language - comandos usados para definir a estrutura dos dados, como CREATE DATABASE, DROP DATABASE, CREATE TABLE, DROP TABLE, ALTER DATABASE, ALTER TABLE, e mais alguns outros. DML: Data Manipulation Language - comandos que operam sobre os dados. Podem ser divididos ainda em: Comando de consulta: o comando SELECT consulta dados armazenados no banco de dados. Comandos de alteração: os comandos INSERT, UPDATE e DELETE e outros manipulam dados, alterando o conteúdo de uma tabela do banco de dados.

SQL Local No Delphi, é possível usar SQL para operações com tabelas Paradox ou dBase. Nesse caso, usa-se uma variação do padrão SQL chamada SQL Local. O SQL local tem algumas limitações em relação ao SQL padrão, especialmente nos comandos DDL. Ele também tem algumas diferenças de sintaxe, por exemplo: como tabelas e campos podem ter espaços no meio do nome, ele permite colocar o nome da tabela ou do campo com aspas, incluindo a extensão de arquivo, como: select Nome, Produto."Preço Venda" from "Produto.db" as Produto Note que a maioria dos bancos de dados não suporta espaços nos nomes de campo, como o Paradox suporta. Muitos também não suportam nomes acentuados, ou com Ç. Pensando nisso, se você quiser portar as tabelas mais facilmente para outro formato, é melhor usar nomes de campos sem espaço ou acentos, como Preco_Venda em vez de "Preço Venda". Note também que, no SQL Local, é possível omitir a extensão do arquivo: select * from Produto Com isso, o Delphi procura "PRODUTO.DB ", "PRODUTO.DBF", nessa ordem, até encontrar. Isso também ajuda a portar a aplicação para outros formatos de banco de dados.

Testando comandos SQL Para executar um comando SQL e ver seus resultados imediatamente, você pode usar o SQL Explorer do Delphi. Clique em Database|Explore. Do lado esquerdo, abaixo de "Databases" selecione o alias de banco de dados onde você vai executar a consulta. No nosso caso, crie um novo alias, chamado "Curso", se não existir. Clique em "Databases" com o botão direito e em New e escolha o drive 'STANDARD' . Digite o nome "Curso" para o alias e tecle [Enter]. Do lado direito, na página "Definition", clique no parâmetro PATH e digite o caminho do diretório onde estão os arquivos do curso (p.ex.: C:\CursoDelphi). No SQL Explorer, clique no botão Apply para salvar as alterações. Agora clique no botão [+] ao lado do nome "Curso" para conectar-se ao alias. O ícone do alias ficará com uma borda em destaque, indicando que ele está aberto. Clique na página "Enter SQL" e digite o seguinte comando: select * from Produto Esse comando vai mostrar todos os dados da tabela "Produto.db". Clique no botão para executar a consulta (ou tecle [Ctrl+E]).

Usando o Oracle e o SQL ServerOracle é um SGBD cliente/servidor que roda em Windows NT e Linux, e permite ter estações-cliente em vários tipos de plataforma, como Windows 95, Windows NT e o próprio Linux (no SQL Server, as estações Clientes podem ser Windows 3.11, Windows 95, Windows NT, DOS e Macintosh). Iremos utilizá-lo como exemplo de banco de dados Client/Server. O que iremos mostrar deste Banco de Dados será somente o suficiente para utilizarmos o Delphi com o ambiente Client/Server.

Configurando o servidor A instalação dos servidores de banco de dados não será vista em detalhes, só suas linha gerais. Um servidor Oracle pode rodar em duas plataformas, Linux e NT. Ambas exigem 48Mb de memória RAM para serem instaladas, e em torno de 200Mb livres para serem instalados.

Page 20: Delphi 4 Com Oracle

Se a versão do SQL Server for 6.5 exige o Windows NT , mas se a versão for 7.0 o servidor pode ser instalado no Windows NT, 95 ou 98. Ele exige um computador com 16 Mb de memória, embora mais memória seja recomendável. Do ponto de vista do desenvolvimento, é importante chamar a atenção para duas opções de instalação no servidor: o character set [conjunto de caracteres] e sort order [ordem de classificação]. O character set determina quais são os códigos que o servidor usa para representar caracteres. No Oracle, a opção character set padrão é a "USASCII", caracterizando os caracteres padrões mundias ASCII.No SQL Server, é recomendável usar o character set default durante a instalação (ISO 8859-1) que vai usar o mesmo conjunto de caracteres do Windows. Já a ordem de classificação é recomendável mudar para "Dictionary order, case-insensitive, accent-insensitive" que vai permitir pesquisar no banco de dados ignorando acentos. Essas opções só podem ser mudadas reinstalando o SQL Server.

Instalando o software cliente Após instalar o servidor, é necessário instalar o software cliente nas estações. Isto é feito com o mesmo programa que instala o servidor (SETUP.EXE). Esse instalador vai colocar no seu computador todas as biblicotecas usadas para a comunicação com o servidor de banco de dados (tanto Oracle quanto SQL Server). São instaladas também ferramentas que vão permitir o gerenciamento do banco de dados remotamente.Observação: Caso esteja utilizando o SQL Server, você deve mudar uma opção para evitar problemas com campos do tipo data (datetime) no Delphi. Clique em "SQL Client Configuration Utility" no menu de programas "Microsoft SQL Server Utilities". Desmarque a opção "Use international settings" e clique Ok. Se o Delphi ou qualquer programa dele estiver rodando, feche-os e inicie novamente. Para testar a conexão com o banco de dados, você pode usar o utilitário Oracle Schema Manager. Este é um dos utilitários possíveis em que você pode testar a conexão com o servidor Oracle. Clique em Schema Manager que está localizado em Oracle Enterprise Manager no menu de Programas. Aparecerá uma tela de login. Informe o Nome do usuário, a senha, e em Service coloque o nome do servidor de banco de dados Oracle na rede. Uma vez conectado, você verá todos os objetos (tabelas, visões) que este usuário conectado tem direito de acesso.Observação: Se você estiver usando o SQL Server, execute o "SQL Enterprise Manager", no menu de programas "Microsoft SQL Server Utilities". Da primeira vez que ele for executado, ele pedirá para registrar o servidor, que é simplesmente guardar algumas informações sobre o servidor em seu computador. Para registrar, digite o nome do computador servidor (em todos os exemplos, citaremos esse nome como CURSO). Mantenha Use Standard Security marcado, e digite 'sa' em User Name. Deixe a senha, Password, vazia, a não ser que tenha sido alterada em seu servidor. Você deve ver uma árvore com o nome do servidor no topo. Clique no ícone [+] ao lado desse nome. Os itens que estão aqui dentro ainda serão vistos com mais detalhes.

Criando um banco de dados Vamos criar um banco de dados e depois migrar os dados das tabelas Paradox para ele. No momento de instalação do servidor Oracle, você define um nome para a instância do banco de dados que irá ser criado pelo Oracle. Tal instância é que identificará o banco de dados no Oracle. O Oracle já cria um banco de dados no momento de sua instalação, podendo ser usado pelos seus usuários. Foge ao escopo deste curso a criação e manutenção do bancos de dados, mas sim como o Delphi os utiliza. Pergunte a quem instalou o servidor Oracle ou ao próprio DBA do servidor o nome da instância. Caso você esteja usando o SQL Server, proceda da seguinte forma: Clique no servidor de dados (dentre os já registrados) que deseja utilizar em seguida clique em "Databases" com o botão direito no Enterprise Manager e clique em New Database. O nome do banco de dados será "Curso". Altere a guia "Location" se quiser especificar em que unidade de seu computador o banco de dados deverá ser criado. O padrão é /Data de dentro da raiz do diretório onde o SQL Server foi instalado. Em "Size", coloque 5Mb, que será o suficiente para nossos exemplos. Repare que na guia "Transaction Logs", o SQL Server colocará um tamanho de 1Mb. Clique em Ok, e o banco de dados será criado.Agora vamos criar um alias para esse banco de dados no Database Explorer do Delphi.Clique em "Databases" com o botão direito, em New escolha 'ORACLE' e digite o nome "OracleCurso". Você precisa agora configurar os parâmetros de conexão. Eis os mais importantes:

SERVER NAMEA "string de conexão" ou alias do SQL*Net ou Net8. Esse é o mesmo nome usado, por exemplo, para se conectar usando o SQL*Plus do Oracle.

USER NAMENome de usuário no Oracle. Isso é apenas um default, que geralmente é alterado para um nome diferente a cada conexão.

Page 21: Delphi 4 Com Oracle

LIST SYNONYMSLIST SYNONYMS = {NONE | PRIVATE | ALL}

Um sinônimo do Oracle é um outro nome criado para uma tabela já existente, de forma a facilitar o acesso por outros usuários. Um sinônimo pode ser definido com um owner [proprietário] específico, ou como sinônimo público, acessível a todos usuários.O Delphi pode sempre acessar um sinônimo como se fosse uma tabela real. A opção LIST SYNONYMS define quais os sinônimos que ficam disponíveis para visualizar em uma lista de tabelas do banco de dados (por exemplo, que aparece na lista da propriedade TableName):NONE: nenhum sinônimo aparece em listas de tabelas. Só aparecem tabelas "reais" e visões [views].PRIVATE: aparecem os sinônimos cujo proprietário é o usuário conectado, mas não aparecem os sinônimos públicos.ALL: aparecem todos os sinônimos, incluindo os públicos.

NET PROTOCOLNET PROTOCOL = {TNS | NETBIOS | TCP/IP | NAMED PIPES | APPC | ASYNC | 3270 | IPX/SPX }

Use sempre TNS, que é o protocolo suportado pelas versões mais recentes do Oracle, usando SQL*Net (7.x) ou Net8 (8.x). Se você usar outro valor, consulte a documentação do Oracle para definir o SERVER NAME.

OBJECT MODEOBJECT MODE = {TRUE | FALSE}

Use FALSE para o Oracle 7.x e versões do Oracle8 que não tenham instalado a Objects Option.Use TRUE para o Oracle8 com Objects Option. Com essa opção, o Delphi passa a suportar a programação baseada em objetos do Oracle8 e os novos tipos de dados do Oracle8, como OBJECT, VARRAY e nested table.

ENABLE INTEGERSENABLE INTEGERS = {FALSE | TRUE}

Quando FALSE, qualquer campo numérico do Oracle (NUMBER) é interpretado pelo BDE como sendo um campo de ponto flutuante (tipo "Float"). Se você mudar para TRUE, os campos NUMBER(x), com x < 5 são interpretados pelo Delphi como do tipo "Smallint" (inteiro pequeno, de 16 bits) e com 5 <= x < 10 são interpretados como do tipo "Integer" (inteiro grande, de 32 bits).

ENABLE BCDENABLE BCD = {FALSE | TRUE}

Quando FALSE, campos NUMBER são tratados como do tipo "Float" (Double), o que pode provocar erros de arredondamento. Quando TRUE, campos NUMBER são tratados como do tipo Currency no Delphi/BDE, permitindo operações mais exatas.

ROWSET SIZEROWSET SIZE = n (um número inteiro)

O Delphi busca n linhas de cada vez do servidor para o cliente. Aumentar esse número pode melhorar o desempenho.

No nosso caso, vamos preencher os campos Server Name, com o nome do servidor Oracle na Rede, e User Name, com o nome do usuário cadastrado no servidor Oracle, mantendo os outros valores default. Isto fará com que o alias criado se comunique com a instância padrão criada no momento da instalação.Observação: Caso você esteja usando o SQL Server, proceda da seguinte forma: Clique em "Databases" com o botão direito, em New escolha o driver 'MSSQL' e digite o nome "SQLCurso". Agora, do lado direito, preencha os seguintes parâmetros: em SERVER NAME coloque "INSTRUTOR" (ou o nome de computador do seu servidor SQL), em DATABASE NAME coloque "Curso" (o nome do banco de dados criado). Para facilitar a conexão, em USER NAME digite 'sa'. Para migrar os dados que estão na tabela Paradox(Produtos e Fornecedores) para o Oracle (bem como para o SQL Server), execute o " Data Pump" do menu de programas ("Iniciar|Programas|Borland Delphi 4"). Esse é um programa que permite migrar dados de um formato de banco de dados para outro. Na tela inicial, selecione "Select by alias name" se já existe um alias referenciando utilize "Curso". Caso contrário, escolha "Select by directory" e selecione "C:\CursoDelphi" (diretório do Banco de Dados Paradox). Clique em Next.

Page 22: Delphi 4 Com Oracle

Como alias de destino, escolha 'OracleCurso' (ou 'SQLCurso', caso seja um servidor SQL Server), que nós criamos anteriormente. Clique em 'Next'. Você terá que informar a senha do usuário. Na próxima tela, selecione quais tabelas você quer migrar ou clique em [>>] para incluir todas. Clique em Next.O Data Pump vai mostrar para cada tabela o status "Unchanged" (sem mudança) se não houve necessidade de alterar nomes de campos. Vai mostrar o status "Modified names" caso algum nome de campo tenha sido alterado (lembre-se de que o Oracle e nem o SQL Server aceitam espaços em nomes). Caso ele mostre "Modified names", clique duas vezes sobre a tabela afetada. Você verá do lado esquerdo a lista de nomes de campos. Se algum deles tiver espaços, o Data Pump troca o espaço por '_'. Nem sempre é isso que você quer, mas você pode clicar no campo e digitar o novo nome em "Field Name", abaixo de "Target". Você pode também ver ou modificar o tipo de dados de destino. Após conferir e corrigir essas informações, clique em "Next". Agora, para fazer o processo de cópia, clique no botão UpSize. O Data Pump começa a copiar as tabelas. Após terminar, ele vai mostrar um relatório de todas as operações feitas, que você pode guardar numa tabela Paradox (Write a copy of this report to a file) se quiser conferir depois. Clique em "Done" para finalizá-lo.

Acessando o banco de dados O BDE pode acessar o Oracle (bem com o o SQL Server) de duas formas: • ODBC: através de um driver ODBC, fornecido juntamente com o Oracle. Nesse caso existe uma camada de software a mais (o driver), o que pode reduzir o desempenho significativamente. Disponível no Delphi Developer. • SQL Links: um driver SQL Link para o Oracle é fornecido juntamente com o Delphi Client/Server. Esse driver se comunica nativamente com o BDE, portanto com velocidade bem maior. A informação necessária para acessar uma tabela no SQL Server é: • Nome do servidor: nome do computador na rede; • Login de usuário: login válido para o SQL Server (exceto se for usada segurança integrada com o NT); • Senha do usuário: senha correspondente ao login informado.

A informação necessária para acessar uma tabela no SQL Server é a mesma, com apenas um detalhe a mais: • Nome do banco de dados: qual banco de dados será usado no servidor; Todas essas informações, exceto a senha de usuário, podem ser guardadas em um alias, que pode ser uma alias global do BDE ou um alias local criado com um componente Database. A senha deve ser informada no momento da execução (tipicamente no início do programa) e obviamente também o nome de usuário (a não ser que você esteja desenvolvendo uma aplicação de teste). A opção LANGDRIVER, em um alias do Delphi, determina qual o "language driver" que será usado para converter caracteres de/para o Oracle (e também SQL Server) . Se esta opção estiver configurada incorretamente, caracteres acentuados não serão mostrados ou gravados corretamente, porque diferentes "language drivers" têm diferentes codificações para os caracteres. Essa opção também define a classificação dos caracteres. Para o SQL Server, se o servidor foi instalado com o character set default, ISO 8859-1, use o language driver chamado "'ascii' ANSI",e para o Oracle, escolha a mesma opção caso tenha deixado o conjunto de caracteres padrão no momento da instalação. Esse driver tem o nome interno de "DBWINUS0". Se por acaso o servidor estiver com o conjunto de caracteres "code page 850", o driver a ser usado deve ser algum compatível com o código de página 850. Essa opção LANGDRIVER pode ser alterada em vários lugares: - No Database Explorer, ou BDE Administrator, quando você está alterando parâmetros de um alias: nesse caso aparece uma combobox, que permite selecionar um dos drivers disponíveis. Nesse caso, escolha "'ascii' ANSI". - No componente Database, propriedade Params. Nesse caso, altere a linha do LANGDRIVER= para conter: LANGDRIVER=DBWINUS0. Se você não especificar um language driver, o Delphi assume o conjunto de caracteres do DOS, e a ordenação baseada nos códigos desses caracteres. Isso não funcionará bem com o SQL Server usando o character set default.

Configurando o Componente Database Abra o projeto ALMOXARIFADO.DPR, que atualmente utiliza Paradox.No módulo de dados, clique duas vezes no componente Database. Em "Driver Name", selecione ORACLE (ou MSSQL, caso desejado). Clique no botão "Defaults" e altere os parâmetros: SERVER NAME=nome do seu servidor OracleUSER NAME=system (ou outro login válido)LANGDRIVER=DBWINUS0

Page 23: Delphi 4 Com Oracle

No SQL Server, você precisará configurar mais o seguinte parâmetro:DATABASE NAME=nome do banco de dados criado no servidorOpcionalmente, num programa de teste, você pode colocar a senha de acesso ao SGBD diretamente no componente Database. Para isso, modifique o parâmetro PASSWORD = , o último da lista, e digite a senha do servidor. Também desmarque "Login prompt". Se a propriedade LoginPrompt é falsa, significa que não vai mais aparecer a tela de login quando você conectar-se ao banco de dados.Outra propriedade , KeepConnection, aparece nessa tela como "Keep inactive connection". Se ela for verdadeira, o componente Database fecha a conexão com o banco de dados quando não há nenhuma tabela aberta.Deixe os outros parâmetros com os valores default e clique em OK. Altere a propriedade Connected do Database para True para testar se a conexão está funcionando. Depois tente ativar (altere Active=True) para cada uma das tabelas. Verifique se ao especificar os nomes das tabelas na propriedade TableName de cada componente foi mantida a extensão .db, e em caso afirmativo é necessário retirá-la. Verifique também se algum campo tem nome diferente ou tipo de dados incompatível. Se houver, você tem que: - abrir o editor de campos da tabela- remover o campo com problemas - adicionar esse campo novamente- no formulário que usa a tabela, alterar a propriedade DataField dos controles necessários. Depois execute o programa e teste o seu funcionamento.

Criando usuários e definindo permissões Todo banco de dados cliente/servidor tem um controle de segurança, que não permite o acesso ao banco de dados se o usuário não for identificado corretamente. O servidor mantém um cadastro de usuários, cada um com sua senha de acesso. No Oracle, o usuário 'system' tem poderes absolutos (geralmente identificamos estes usuário como sysdba ou até mesmo, dba.). Ele pode tirar (revogar) e dar (conceder) permissões a todos os usuários normais do sistema.Para criar novos usuários no Oracle, abra o Oracle Security Manager, informe o nome de usuário, senha, e em service coloque o nome do servidor Oracle de sua Rede. É necessário efetuar a conexão utilizando-se um usuário com poderes de administração sobre o banco de dados. Clique com o botão direito em 'Users', e selecione 'New User'. Aparecerá uma tela para você configurar o novo usuário. Digite o nome do usuário, e em Authentication, escolha 'Password'. Isto fará com que o usuário tenha sua conta e senha própria para se conectar ao Oracle. Em TableSpaces, escolha 'Tools' para a opção default e 'Temp' para o opção 'Temporary'. Na guia 'Roles/Privileges', haverá uma lista com os privilégios padrões do sistema. Procure pelo privilégio 'RESOURCE' e adicione-o clicando na seta para baixo. Este privilégio dá direito à criação de tabelas, triggers, etc.Observação : Caso você esteja utilizando o SQL Server, abra o SQL Enterprise Manager, selecione o servidor, e dentro dele clique em Logins com o botão direito e em New login. Digite o nome 'usuario'. Você deve marcar quais os bancos de dados que esse usuário pode acessar, na coluna "permit", no nosso caso é o banco de dados 'Curso'. Dentro de cada banco de dados, ele terá as permissões que você indicar. Digite e confirme a senha do usuário e clique no botão Add para adicionar esse novo "login". Depois de criar o usuário, você deve dar permissão a este usuário de acessar os objetos e recursos já existentes no Oracle. Para isto, entre no 'Oracle Security Manager', escolha o usuário que você acabou de criar, e em 'Object Privileges Granted', clique com o botão direito do mouse e escolha 'Add Privileges to Users'. Aparecerá uma tela de configuração de permissões. Escolha quais são os usuários que receberão os privilégios necessários clicando no nome dos mesmos. Em 'Privilege Type', escolha 'Object Privileges'. Aparecerá no canto inferior os esquemas contidos naquele banco de dados. Escolha o esquema do usuário criador do objeto e clique nos itens relacionados àqueles usuário (tables, views, etc). Expanda o item desejado e clique no objeto. Aparecerá em um quadro à direita a lista de possíveis permissões a serem concedidas para aquele objeto. Escolha as permissões que achar necessário e clique em 'OK'.Você pode também atribuir permissões usando o comando 'GRANT'. Por exemplo, para se dar permissão de SELECT na tabela 'Alan.Clientes' ao usuário 'Rogerio', você deverá executar o seguinte comando: "Grant select on Alan.Clientes to Rogerio".Observação: Caso você esteja trabalhando no SQL Server, proceda da seguinte forma: expanda o item "Databases" e selecione o banco de dados "Curso" que foi criado antes. Clique em [+] para expandir esse item, dentro dele expanda "Groups/Users", dentro dele o nome "public" e dentro de public selecione o usuário "usuario". Clique nesse nome com o botão direito e em Permissions. Para cada tabela, o usuário pode ter permissões de SELECT (consultar), UPDATE, INSERT e/ou DELETE individuais. Para simplificar e conceder todas as permissões a este usuário, clique em Grant All. Para salvar as permissões concedidas, clique em Set, depois clique em Close para fechar a caixa de diálogo.

Page 24: Delphi 4 Com Oracle

Criando uma tela de login Agora retorne ao projeto. Se você deixar a propriedade LoginPrompt do componente Database = True, ele mostra uma tela de login automática que lê o nome de usuário e senha para conexão. Mas você pode também criar uma tela de login personalizada, que é o que faremos. Crie um novo formulário. Altere as seguintes propriedades: Name: FormLoginCaption: ConexãoBorderStyle: bsDialogBorderIcons, sub-item biSystemMenu: False Salve a unidade do formulário como LOGIN.PAS. Coloque dois componentes Label, dois Edit e dois botões, como na figura:

Coloque os nomes dos componentes de 'editUsuario', 'editSenha', 'btnOk' e 'btnCancelar'. Para o 'editSenha', altere a propriedade PasswordChar, colocando um * (asterisco). No botão 'btnOk', altere Default=True e em 'btnCancelar', coloque Cancel=True. Clique em File|Use unit e escolha 'MAlmoxarifado', porque vamos precisar usar o componente Database aqui dentro. Crie um procedimento para o botão de Ok: procedure TFormLogin.btnOkClick(Sender: TObject); begin with DMAlmoxarifado.DBAlmoxarifado do begin Connected := False; Params[1] := 'USER NAME=' + editUsuario.Text; Params[20] := 'PASSWORD=' + editSenha.Text; try Connected := True; Self.Close; except onEDatabaseError do ShowMessage('Usuário ou senha inválida!'); end; end;end; A propriedade Params do Database é uma lista de strings. Caso você esteja utilizando o Oracle, o parâmetro no índice 1 é o USER NAME e o parâmetro 20 é a senha. Caso seja o SQL Server, os índices são 2 e 23, respectivamente. (O ideal seria uma rotina que percorre Params e descobre onde estão os nomes USER NAME e PASSWORD). O programa altera esses parâmetros antes de conectar o banco de dados. Note o tratamento de exceções: quando ocorre alguma exceção durante a conexão, o programa simplesmente mostra uma mensagem dizendo que a senha é inválida. Agora crie um procedimento para o botão Cancelar: ele simplesmente termina a aplicação caso o usuário recuse a se conectar: procedure TFormLogin.btnCancelarClick(Sender: TObject); begin Application.Terminate; end; Vamos chamar esse formulário a partir do formulário principal, no evento OnShow. Crie um procedimento nesse evento, contendo apenas o seguinte: procedure TFormPrincipal.FormShow(Sender: TObject); begin FormLogin.ShowModal; end; Clique em File|Unit e clique em 'Login' , pois utilizamos este formulário no fomulário principal. Como foi utilizado a classe de Exceção EDataBaseError é necessário acrescentar um uses para a unit 'db'. Teste o programa , mas primeiro com o usuário sa e senha vazia, depois com o usuário que você criou e a senha dele.

Page 25: Delphi 4 Com Oracle

Monitorando comandos SQL Uma ferramenta muito útil do Delphi é o SQL Monitor. Ele permite monitorar o que acontece internamente: quais os comandos SQL executados pelo Delphi durante a execução do programa. Abra o projeto anterior e abra também o SQL Monitor no menu [ Iniciar] |Programas|Borland Delphi 4|SQL Monitor. Execute o programa e note quais as mensagens que aparecem no SQL Monitor. Ele vai mostrar os comandos que o programa está executando, entre outras coisas. Ao iniciar o programa, ele deve mostrar uma mensagem "Log started for "Almoxarifado". Durante a execução, ele vai mostrar várias linhas de log relacionadas a outras ações, como por exemplo na figura:

Alguns tipos de mensagens que você vai ver na janela do SQL Monitor são: - SQL Prepare: preparação de uma consulta. Fase em que ele monta um comando SQL e envia uma consulta preparada para o servidor, mas ainda sem substituir os parâmetros dessa consulta. - SQL Stmt: operação sobre um comando [statement] SQL. Pode conter uma outra indicação como: Fetch, que indica busca de registros de resultado de uma consulta. - SQL Data In: geralmente aparece antes de executar um comando SQL. Indica que o valor de um parâmetro está sendo passado para a consulta. - SQL Execute: indica a execução de um comando SQL. O comando nem sempre aparece completo. Às vezes você verá parâmetros dentro do comando, indicados com ? ou :1, :2,... Nesse caso, verifique as linhas SQL Data In que aparecem imediatamente antes, que definem os valores dos parâmetros passados. Você pode filtrar as informações que o SQL Monitor mostra. Para isso, clique em Options|Trace Options. Para ver estritamente os comandos SQL executados e seus parâmetros, deixe marcadas apenas as três primeiras opções: "Prepared Query Statements", "Executed Query Statements" e "Input Parameters".

5 - Linguagem SQL

Modelo de Dados

Comandos DDL

Comandos DML

Exemplo: Consultando Várias Tabelas

Comandos de atualização

Modelo de DadosDurante o curso, iremos desenvolver um mini-sistema para Almoxarifado, onde iremos fazer o controle de saídas de um determinado produto. Isso envolve algumas tabelas adicionais no nosso banco de dados.O modelo abaixo mostra como são relacionadas as tabelas que serão utilizadas no exemplo:

As tabelas deste modelo serão criadas utilizando a Linguagem SQL.

Comandos DDLOs comandos DDL (Data Definition Language) são usados para definir a estrutura das tabelas e outros objetos do banco de dados.

Tipos de DadosCada coluna de uma tabela tem um tipo de dados [datatype], que determina que tipo de informação (caracteres, números, datas/horas) pode ser colocada na coluna e quais as características desses dados. O tipo geralmente é determinado quando a tabela é criada. O padrão ANSI SQL define alguns tipos para uso em vários bancos de dados: Os tipos de dados existentes são: Para dados Tipo

Page 26: Delphi 4 Com Oracle

Caractere char(n), varchar2(n)Numérico exato decimal(p,e) ou numeric(p,e)Numérico aproximado double precision, real Numérico inteiro int, smallint Data e hora datetimePara dados contendo caracteres, char(n) armazena um número fixo de caracteres. Por exemplo, uma coluna do tipo char(30) tem sempre 30 caracteres. Se forem informados menos, o restante é completado com espaços. Já o tipo varchar(n) armazena uma quantidade variável de caracteres, até o máximo informado e não completa o texto com espaços. Os tipos "numéricos exatos", decimal e numeric, permitem armazenar dados exatos, sem perdas devidas a arredondamento. Ao usar esses tipos, você pode especificar uma precisão, que indica quantos dígitos podem ser usados no total e uma escala, que indica quantos dígitos podem ser usados à direita do ponto. Por exemplo, decimal(9,2) permite guardar 7 dígitos antes do ponto decimal e 2 após, num total de 9, assim o maior valor possível é 9999999,99. Os tipos "numéricos inexatos", double precision (ou float) e real, armazenam dados numéricos, mas nem sempre mantém a precisão suficiente para armazenar corretamente números de vários dígitos. Dos tipos inteiros, int usa 32 bits (4 bytes), permitindo armazenar até +/-2,147,483,647 e smallint usa 16 bits (2 bytes) permitindo +/-32767. O tipo datetime armazena informações de data e hora juntas, com precisão de 1/300 de segundo, entre 1 de janeiro de 1753 e 31 de Dezembro de 9999 . Os comandos da linguagem SQL que iremos utilizar , podem ser executados no Explorer do Delphi.Vamos utilizar o alias OracleCurso

Criando TabelasPara criar uma tabela, usa-se o comando CREATE TABLE. Por exemplo, para criar a tabela 'Pessoa', a sintaxe é: CREATE TABLE Pessoa ( CodPessoa Number NOT NULL , Nome varchar2 (50) NOT NULL , Sexo char (1) , Rua varchar2 (50) NULL , Bairro varchar2 (30) NULL , Cidade varchar2 (30) default ('Goiânia') NULL, Estado char (2) NULL , CEP char (8) NULL , CPF char (11) NULL , DataCadastro date default (sysdate) NULL,)As colunas para as quais a opção "NULL" não foi especificada permitirão o valor NULL, ou seja, não é brigatório informar um valor para elas (no caso sexo) . Já as colunas marcadas com NOT NULL não permitem valores nulos, ou seja, não podem ser deixados sem preencher ao inserir dados (no caso CodPessoa, Nome). No Oracle, NULL é a opção padrão, sendo que no SQL Server NOT NULL é a opção default. Para declarar que uma coluna pode ter valores nulos, é preciso especificar a opção NULL. O tipo do campo DataCadastro é 'date', (datetime no SQL Server) e para retornar a data atual, caso o usuário não informe, colocamos o valor default sendo a função sysdate (o mesmo que getdate() no SQL Server).Note que definimos algumas colunas com o tipo char, como CPF e Estado, porque elas geralmente têm tamanho fixo. Já outras como Nome, Cidade e Bairro, geralmente têm tamanho variável, por isso, para economizar espaço no banco de dados, usamos varchar2. A opção DEFAULT é uma restrição numa coluna, como veremos mais tarde. Ela especifica um valor default que é inserido caso nada tenha sido informado. No caso da data de cadastro, usamos o literal 'sysdate', que é interpretado como a data atual. No caso da cidade, o default é a string "Goiânia" caso nada seja informado.

Excluindo uma tabela Para excluir uma tabela, pode-se usar o comando DROP TABLE, por exemplo: DROP TABLE Pessoa

Page 27: Delphi 4 Com Oracle

Alterando uma Tabela O comando ALTER TABLE altera a estrutura de uma tabela. Ele pode ser usado para acrescentar , remover uma coluna e alterar o tipo ou opções de uma coluna. NO SQL Server 6.5 ou inferior, ALTER TABLE só possibilitava a inserção ou remoção de colunas. No SQL Server 7.0, já é possível realizar todas as operações.Para acrescentar uma nova coluna, usa-se o comando ALTER TABLE com a opção ADD. Por exemplo, vamos acrescentar uma coluna Fone e Fax à tabela Pessoa: ALTER TABLE Pessoa ADD (Fone char (7) NULL , Fax char (7) NULL) O tipo de dados da coluna, é especificado como no CREATE TABLE. Para alterar a estrutura da tabela acrescentando colunas, você terá duas opções: Se a tabela estiver vazia, você poderá adicionar tanto colunas NULL quanto NOT NULL. Caso a tabela já conter dados, você só poderá adicionar colunas NOT NULL. No SQL Server, você só pode adicionar campos que sejam NULL, ou seja, que aceitem valores nulos em seus dados.No SQL Server, como o comando ALTER TABLE não permite modificar uma coluna, é necessário excluir a tabela (DROP TABLE) e recriá-la novamente com a estrutura modificada. Se a tabela contém dados, esses dados serão perdidos. O que se deve fazer é renomear a tabela, criar uma nova com o nome desejado, e copiar os dados de uma para outra (veremos o processo para copiar dados mais tarde). Por todas essas razões, é bom planejar bem as suas tabelas e as colunas de cada uma de acordo com os requisitos do sistema, antes de começar a implementar o seu banco de dados.

Definindo e Usando Restrições [Constraints] Uma restrição [constraint] é uma propriedade de uma coluna usada para reforçar a integridade de dados. Geralmente restrições são definidas quando a tabela é criada (com CREATE TABLE), mas podem também ser definidas ou retiradas quando a tabela já contém dados (com o comando ALTER TABLE). As restrições são verificadas durante a execução de um comando de alteração (INSERT ou UPDATE). Se o comando não satisfaz uma das restrições, o comando é cancelado. Toda restrição tem um nome, que você pode informar nos comandos CREATE TABLE e ALTER TABLE. Se você não informar um nome, o banco de dados gera um nome automaticamente, geralmente aleatório. De forma geral, a sintaxe para uma restrição é: CONSTRAINT nome_da_restrição definição Onde a definição inicia com as palavras PRIMARY KEY, UNIQUE, CHECK, FOREIGN KEY ou DEFAULT. A palavra CONSTRAINT e o nome_da_restrição podem ser omitidos. Nesse caso, o nome será gerado automaticamente.

Chave Primária [PRIMARY KEY] A chave primária [primary key] de uma tabela é uma coluna ou seqüência de colunas que identificam unicamente uma linha dentro da tabela, ou seja, seu valor não pode ser repetido para outras linhas. Ao definir uma chave primária, automaticamente é criado um índice na tabela. Só pode haver uma chave primária na tabela. Na criação da tabela, essa restrição pode ser definida da seguinte forma (quando a chave primária é composta de uma só coluna), para mostrar iremos criar a tabela "COPIAFORNECEDOR" , onde a coluna 'CodFornecedor' será definida como chave primária da tabela: CREATE TABLE CopiaFornecedor ( CodFornecedor number NOT NULL primary key, Nome varchar (50) NOT NULL )Note que 'CodFornecedor' deve ter a opção NOT NULL. Não é possível criar uma chave primária com colunas que podem ser NULL. Opcionalmente, poderia ser informado um nome para a restrição, por exemplo 'ChaveCopiaFornecedor'. Nesse caso, a segunda linha acima seria: CodFornecedor number not null constraint ChaveCopiaFornecedor primary key, Agora, na tabela Fornecedor, não pode haver duas linhas com o mesmo valor de 'CodFornecedor'. Quando a chave é composta de duas ou mais colunas, nesse caso ela tem que ser especificada com a lista de colunas entre parênteses, por exemplo: create table ProdutoFornecedor ( CodProduto int NOT NULL, CodFornecedor int NOT NULL, primary key (CodProduto, CodFornecedor)

Page 28: Delphi 4 Com Oracle

) Uma chave primária pode ser acrescentada à tabela depois que ela já foi criada, com o comando ALTER TABLE. Por exemplo, vamos acrescentar chaves primárias à tabela Pessoa : alter table Pessoa add primary key (CodPessoa) Em qualquer um dos casos pode-se especificar ou não o nome da restrição, na forma CONSTRAINT nome, por exemplo: alter table Produto add constraintPkProduto primary key (CodProduto)

alter table Fornecedor add constraintPkFornecedor primary key (CodFornecedor) Para retirar uma restrição, use o comando ALTER TABLE...DROP CONSTRAINT..., por exemplo: alter table Produto drop constraint PkProduto

Unicidade [UNIQUE] Uma restrição UNIQUE em uma coluna ou grupo de colunas determina que o seu valor deve ser único na tabela. Esse tipo de restrição é usado para chaves alternadas, ou seja, valores que se repetem na tabela além da chave primária. Pode haver várias restrições UNIQUE na tabela e as colunas de uma restrição UNIQUE permitem valores nulos. Esse tipo de restrição pode ser criada com exatamente a mesma sintaxe do PRIMARY KEY, por exemplo : alter table Fornecedor add constraint UqNomeFornecedor unique (nome) Também é criado um índice automaticamente, que não permite valores duplicados.

Chave estrangeira [FOREIGN KEY] Uma forma importante de integridade no banco de dados é a integridade referencial, que é a verificação de integridade feita entre duas tabelas. Por exemplo, a tabela 'Produto' será usada para relacionar dados dos fornecedores de um determinado produto com a tabela 'Fornecedor'. Ela contém a coluna CodFornecedor . A coluna 'CodFornecedor' da tabela 'Produto' deve conter um código válido que exista na coluna 'CodFornecedor' da tabela 'Fornecedor'. A outra deve ter um código válido existente na tabela 'Fornecedor'. Como validar essa verificação? Uma chave estrangeira [foreign key] é uma restrição de integridade referencial. Ela consiste de uma coluna ou grupo de colunas cujo valor deve coincidir com valores de outra tabela. No nosso caso, vamos adicionar uma chave estrangeira na tabela 'Produto' para a coluna 'CodFornecedor', ela irá fazer referência a tabela poduto. Como a tabela produto já foi criada iremos alterar a estrutura desta tabela, portanto, iremos usar o comando Alter Table:alter table Produto add foreign key (CodFornecedor) references Fornecedor(CodFornecedor)Note que a chave primária de Fornecedor é (CodFornecedor), como foi definido antes. Para poder criar uma chave estrangeira, é preciso haver antes uma chave primária ou uma restrição UNIQUE, na tabela referenciada. Outra forma de criar restrições é durante a criação da tabela , ou seja, com o comando CREATE, então vamos criar a tabela MovimentacaoProduto, onde a coluna 'CodProduto' esta relacionada com a tabela 'Produto':CREATE TABLE MovimentacaoProduto ( CodMovimentacao int NOT NULL constraint pkMovimentacaoProduto primary key, CodProduto smallint NOT NULL constraint fkCodProduto foreign key references Produto, DataMov datetime NULL default (getdate()), TipoMov char (1) NOT NULL , Quantidade int NOT NULL )Uma chave estrangeira não cria um índice automáticamente no SQL Server. Mesmo assim é recomendável criar um índice para maior desempenho. Pode-se especificar o nome da restrição opcionalmente, na forma CONSTRAINT nome, logo antes das palavras "FOREIGN KEY", como foi mostrado em : CodProduto smallint NOT NULL constraint fk_CodProduto foreign Key references Produto .

Page 29: Delphi 4 Com Oracle

Default Um default especifica um valor que será fornecido para aquele campo. Na definição da tabela, como já vimos, é possível fazer isso: create table Pessoa ( ... DataCadastro datetime default sysdate, ... Cidade varchar(30) default ('Goiânia')) O valor de um default pode ser uma constante ou uma chamada de função, como sysdate (ou getdate(), no SQL Server)Pode-se também especificar ou não um nome para o default: Cidade varchar(30) constraint DefPais default 'Brasil' Você pode acrescentar ou retirar um default no Oracle. Se o default for acrescentado com o comando ALTER TABLE, é preciso usar o comando especificar para qual coluna ele vai ser ativado, por exemplo: ALTER TABLE Cliente MODIFY Estado char(2) default 'Go'Caso você esteja fazendo no SQL Server, a sintaxe seria:ALTER TABLE Cliente ADD default 'GO' for Estado

Verificação [CHECK] Uma restrição CHECK é muito semelhante a uma regra, que verifica os valores que estão sendo inseridos. A vantagem é que ele pode fazer referência a uma ou mais colunas da tabela. Por exemplo, vamos verificar, na tabela 'MovimentacaoProduto', se o 'TipoMov' informado é 'E' ou 'S' e se a coluna Sexo na tabela 'Pessoa' é 'F' ou 'M'.ALTER TABLE MovimentacaoProduto ADD CHECK (TipoMov in ('E', 'S'))

ALTER TABLE Pessoa ADD CHECK (Sexo in ('F', 'M'))Para testar, tente inserir uma linha com Sexo sendo 'G' . Esta linha não será incluída enquanto não for informado o sexo sendo 'F' ou 'M'.Note que a expressão do CHECK deve estar sempre entre parênteses. Pode-se especificar ou não o nome da restrição, na forma CONSTRAINT nome logo antes da palavra "CHECK".

Comandos DMLOs comandos DML (Data Manipulation Language) são utilizados para inserir novas linhas numa tabela, alterar colunas das linhas existentes, excluir linhas e consultar dados. Para realizar consultas nas tabelas , é necessário ter algumas informações nestas tabelas , para isto acrescente alguns registros nestas tabelas, podemos utilizar o utilitário 'SQL Explorer do Delphi'.

Comando SELECT - básico O comando SELECT consulta dados de uma ou mais tabelas. A sua sintaxe mais simples pode ser resumida da forma: SELECT lista_de_colunasFROM lista_de_tabelasWHERE condiçõesA lista_de_colunas especifica quais colunas serão retornadas como resultado, separadas por vírgulas ou um asterisco (*) que indica todas as colunas da tabela. A cláusula FROM, com uma lista_de_tabelas, especifica quais tabelas serão consultadas. E a cláusula WHERE especifica condições que devem ser satisfeitas pelas linhas das tabelas.

Exemplo: Consultando Todas as Colunas Para nossos exemplos, vamos usar o banco de dados curso , que é o nosso banco de exemplo. Mas antes é necessário cadastrar informações nas tabelas deste banco de dados. Pode ser feito utilizando a pasta 'Data' de cada tabela.Execute o comando abaixo no SQL Explorer (página Enter SQL) do Alias 'CursoOracle':select * from pessoa

Page 30: Delphi 4 Com Oracle

O resultado irá mostrar todas as colunas e todas as linhas da tabela 'pessoa' (ou seja, todo o seu conteúdo). Vale lembrar que para poder consultar a tabela pessoa, você deverá ter direito de select sobre a mesma. No Oracle os nomes de tabela devem ser especificados da mesma forma com que foram criados e o nome do autor deve ser incluído, caso não seja o mesmo que se esteja logado. Por exemplo: Se você estiver usando o Alias 'CursoOracle', feche o mesmo, e reabra-o usando um usuário diferente. Você verá que este usuário agora não tem acesso às mesmas tabelas que o usuário anterior tinha. Suponha agora a existência de duas tabelas 'Pessoa', uma criada por João e outra por Maria. Para João consultar sua tabela ele teria que fazer:select * from PessoaAgora, para consultar a tabela Pessoa criada por Maria, ele deverá executar: select * from Maria.PessoaO '*' no comando acima especifica que todas as colunas são retornadas, mas você pode listar só as que são desejadas. Continue na página 'Enter SQL' , e altere o comando anterior para o seguinte: select CodPessoa, Nome, Sexo from PessoaExecute o comando. Agora apenas as colunas 'codpessoa', 'nome' e 'sexo' são retornadas, nessa ordem. Note que a ordem das colunas não precisa ser a mesma ordem presente na definição da tabela. De fato, na maioria das aplicações, a ordem das colunas na tabela não tem importância. Você também pode mudar o cabeçalho das colunas retornadas, criando um alias de coluna. Execute o seguinte comando: select CodPessoa Código, Nome, Sexo from PessoaO resultado será o mesmo do comando anterior, mas a coluna 'codpessoa' aparece como Código.

Usando Condições Os comandos que já usamos não têm a cláusula WHERE. Nesse caso, todas as linhas da tabela são retornadas. Se o WHERE estiver presente, ele especifica uma condição que seleciona as linhas e apenas as que satisfazem essa condição serão mostradas. Por exemplo, se quisermos as pessoas que moram em 'Goiânia', podemos consultar as linhas cuja coluna 'cidade' tem o valor 'Goiânia': select Nome from Pessoawhere Cidade = 'Goiânia'Note que o resultado irá mostrar somente as pessoa que moram em 'Goiânia'. As linhas que aparecem são apenas as que satisfazem a consulta. Existem vários tipos de condições de pesquisa, como veremos.

Manipulando Expressões Um comando SELECT pode retornar nas colunas de resultado uma coluna da tabela, ou um valor calculado. Por exemplo, a tabela Produto contém os produtos cadastrados na empresa, com sua quantidate em estoque. Se quisermos ver como fica a quantidade em estoque após um acrescimo de 10% do que já existe, pode ser feito o seguinte: select codproduto, quantEstoque, (quantEstoque * 1.1) from produtoOu seja, a terceira coluna mostra o resultado de (quantEstoque * 1.1) para cada linha. Você pode também usar vários operadores em expressões com colunas numéricas: adição (+), subtração (-), multiplicação (*) e divisão (/). A idéia de mostrar o comando select é somente para você ter um conhecimento básico com este comando, mas podem ser realizados várias consultas complexas com ele.

Visões [views] Uma visão [view] é uma forma alternativa de olhar os dados contidos em uma ou mais tabelas. Para definir uma visão, usa-se um comando SELECT que faz uma consulta sobre as tabelas. A visão aparece depois como se fosse uma tabela. Visões tem as seguintes vantagens: • Uma visão pode restringir quais as colunas da tabela que podem ser acessadas (para leitura ou para modificação), o que é útil no caso de controle de acesso, como veremos mais tarde. • Uma consulta SELECT que é usada muito freqüentemente pode ser criada como visão. Com isso, a cada vez que ela é necessária, basta selecionar dados da visão. • Visões podem conter valores calculados ou valores de resumo, o que simplifica a operação. • Uma visão pode ser usada para exportar dados para outras aplicações.

Page 31: Delphi 4 Com Oracle

Vamos criar uma visão no nosso banco de dados, onde iremos mostrar quais os vendas dos produtos em um determinado período . Como vimos, as visões são criadas através do comando SELECT. Mas para realizar esta consulta iremos precisar utilizar as tabelas MovimentacaoProduto e Produto. Para iso, podemos usar um comando select do SQL envolvendo várias tabelas, chamado de junção de tabelas.

Exemplo: Consultando Várias TabelasUm comando SELECT também pode fazer uma consulta que traz dados de duas ou mais tabelas . Esse é um processo chamado de junção[join]. As tabelas têm uma coluna em comum que é usado para fazer as junções.Para realizar a consulta onde iremos mostrar os produtos vendidos e qual a sua quantidade disponível em estoque, temos que executar o comando SELECT abaixo:select distinct prod.CodProduto, Nome, Quantidade QuantVendida,DataMov Datafrom MovimentacaoProduto mov , Produto prodwhere mov.CodProduto = prod.CodProdutoNote que na cláusula FROM , os nomes das tabelas estão seguidos de nomes mais curtos, que são os apelidos [alias] utilizados para as tabelas dentro do SQL. Esses apelidos afetam apenas o comando atual. A lista de campos do SELECT seleciona a coluna 'codProduto' da tabela 'Produto', neste caso, foi necessário informar a tabela e a coluna, porque a coluna codproduto pode ser encontrada na tabela 'MovimentacaoProduto' e 'Produto'.Com relação a junção de tabelas, acrescentamos todas as tabelas que queremos utilizar na cláusula FROM , temos também que colocar uma condição na cláusula WHERE que irá fazer a junção das duas tabelas. A condição que utilizamos foi 'mov.codproduto = prod.codproduto'. Ela diz qual a ligação entre elas. Se não for especificada , a consulta vai funcionar, mas vai retornar o produto cartesiano das duas tabelas, ou seja, todas as combinações possíveis com o registro de uma e o registro da outra tabela.Se não utilizar o comando distinct a resposta terá o nome dos produtos repetidos várias vezes,este comando mostra somente as linhas que são diferentes.Esta consulta irá mostrar o resultado independente da data. Porque o importante é o usuário escolher qual o período que ele deseja visualizar, e isso será feito pelo programa.

Criando uma visão A visão que iremos criar será gerada através do comando SELECT feito no exemplo anterior. E iremos mostrar como chamar esta visão a partir do Delphi. Para criar um visão temos que utilizar o comando CREATE VIEW, execute o comando abaixo:Create View VendaProduto as select distinct prod.CodProduto, Nome,Quantidade QuantVendida, DataMov Data from MovimentacaoProduto mov , Produto prod where mov.CodProduto = prod.CodProdutoComo desejamos obter os dados por período e pretendemos colocar este parâmetro no Delphi , por isso acrescentamos a coluna 'Data' no comando SELECT. Podemos testar o resultado desta visão no SQL Explorer, basta executar o comando abaixo:select * from VendaProdutoO resultado terá as colunas 'CodProduto', 'Nome', 'QuantVendida' e 'Data' , mostrando os dados relacionados entre as tabelas.Para excluir uma visão , pode ser usado o comando DROP VIEW:drop view VendaProduto

Chamando uma Visão no DelphiAbra o projeto 'ALMOXARIFADO' e crie um novo formulário. Altere o nome do formulário para 'formVendaProduto' , altere a propriedade Caption para 'Produtos Vendidos'.O componente utilizado para visualizar o resultado de uma visão é o componente Query (), pois neste componente podemos obter dados baseados em um comando SQL. Abra o DataModule 'DMAlmoxarifado' e acrescente um componente Query . Altere a propriedade Name para qryProdutoVenda e a propriedade DatabaseName para DBAlmoxarifado. Como podemos realizar consultas utilizando visões (sendo mais utilizadas para este caso) , e a propriedade SQL é utilizada para colocar o comando SQL que desejamos executar , vamos acrescentar o comando abaixo nesta propriedade:Select * from VendaProdutowhere Data =:ParamData

Page 32: Delphi 4 Com Oracle

Order by NomeNa cláusula WHERE criamos o parâmetro chamado 'ParamData'. O que define o parâmetro são os dois-pontos antes do nome. O parâmetro foi criado para o usuário informar o período que deseja realizar a consulta. A coluna 'Data' é uma das colunas que a visão VendaProduto retorna. Temos que usar este nome porque a coluna 'DataMov' na visão foi alterada para 'Data'. As consultas em visões podem ser feitas seguindo o mesmo critério para tabelas, desde que utilize somente as colunas de retorno. Foi acrescentada o cláusula ORDER BY para que os valores de retorno fiquem ordenados pelo nome do produto.Selecione a propriedade Params do componente QryVendaProduto e abra seu editor. O editor de parâmetros é o local que define o tipo de dado do parâmetro , primeiro deve-se escolher o parâmetro como mostra a figura abaixo:

Clique no parâmetro 'ParamData' em seguida posicione no object inspector e altere a propriedade Value em Type escolha "Date", que será o tipo de dados do campo, ao confirmar o Object Inspector irá ficar como a figura abaixo:

Observação: Se você está usando o SQL Server, a propriedade DataType a ser informada será 'ftDateTime'.Vamos retornar ao formulário 'formVendaProduto' e acrescentar os componentes abaixo :(DataSource) :Name: dsProdutoVendaDataSet: DMAlmoxarifado.qryVendaProduto(DBGrid)

DataSource: dsProdutoVenda(Panel)

Caption: ''

Name: mskDataEditMask: !99/99/0000;1;_(Button)

Name : btnProcurarCaption: Procurar (Label)Caption: Data:Note que na propriedade DataSet do componente dsProdutoVenda , o seu conteúdo é DMAlmoxarifado.qryVendaProduto, porque este componente foi criado no Data Module 'DMAlmoxarifado'.Crie um procedimento para o evento OnClick do btnCalcular , nele iremos executar a consulta, acrescente o código abaixo:try with DMAlmoxarifado.qryVendaProduto do begin close; Params[0].asdate := strtodate(mskData.text); Open; end;except On EConvertError do begin showmessage('Data Inválida!'); mskData.setFocus; end;end;Se o usuário informar uma data errada ou se não informar nenhum valor para data e clicar no botão 'Calcular' , irá aparecer uma mensagem de erro em inglês dizendo que a data é inválida, para mostrar nossa própria mensagem de erro , fizemos o tratamento de erro com o comando Except e a classe testada é EConvertError.A propriedade Params do componente Query é um vetor de objetos do tipo TParam, com índices começando de zero. Cada um dos objetos tem propriedades com AsString, AsInteger, ... que permitem alterar o valor do parâmetro.

Page 33: Delphi 4 Com Oracle

Para testar este exemplo falta acrescentar no menu principal a chamada deste formulário. Como todos os nossos formulários estão sendo criados dinamicamente iremos fazer o mesmo para este formulário. Primeiro precisamos retirar este formulário da lista "Auto-Create", em seguida no evento OnClick do item de menu Consulta|Venda por Produto . Faça o seguinte: if not FormularioAberto(FormVendaProduto) then FormVendaProduto:=TFormVendaProduto.Create(Application); FormVendaProduto.Show;Retorne ao formulário 'formVendaProduto' e crie um procedimento para o evento OnClose. Acrescente o código abaixo:procedure TformVendaProduto.FormClose(Sender: TObject;var Action:TCloseAction);begin Action := caFreeend;Execute e teste o formulário , informe uma data que foi efetuado alguma venda e pressione o botão 'Procurar'.Notas: Uma visão também pode ser acessada com um componente Table, como se fosse uma tabela. Os nomes das visões no banco de dados aparecem na lista de nomes da propriedade TableName, como se fossem tabelas.

Comandos de atualização O comando SELECT é usado para consultar uma ou mais tabelas ou visões, produzindo como resultado um conjunto de linhas e colunas. Mas para modificar os dados no banco de dados, existem os comandos INSERT, UPDATE e DELETE.

Inserindo Linhas O comando INSERT insere linhas em uma tabela ou visão, mas o comando SELECT que gerou a visão não pode ter mais de uma tabela na claúsula FROM. A forma mais simples do comando INSERT insere uma linha . Para inserir uma linha na tabela Fornecedor, execute o seguinte:insert into Fornecedorvalues (7, 'Fornecedor 7 ')Nesse caso, são informados os valores de todas as colunas da tabela, na ordem em que elas foram definidas na tabela. Mas é possível também inserir dados parciais de apenas algumas colunas. Para testar, iremos incluir uma linha na tabela pessoa, execute os comando abaixo:insert into Pessoa (CodPessoa, Sexo,Nome) values (8, 'F', 'Oitava Pessoa') Nesse caso, os nomes das colunas que serão inseridas são especificados entre parênteses após o nome da tabela. A ordem não precisa ser a mesma das colunas na tabela. Mas a ordem dos valores em VALUES corresponde à ordem dos nomes de colunas informados. Quando uma coluna é omitida da lista, o que acontece é: • Se a coluna tem um valor default (como Cidade e DataCadastro), o valor default é inserido • Caso contrário, se a coluna permite valores NULL, será inserido um NULL. • Caso a coluna não tenha default e tenha sido criada como NOT NULL, a execução do comando será cancelada com uma mensagem de erro. Digite agora SELECT * from Pessoa. Você verá que a pessoa 8 está com a cidade "Goiânia", já que o valor não foi informado para a coluna Cidade e o default é "Goiânia".

Atualizando Linhas Para atualizar linhas de uma tabela ou visão baseados em condições, use o comando UPDATE. Na alteração de visões caso a clausula FROM do comando SELECT que gerou a visão possui mais de uma tabela, a atualização pode ser feita somente nas colunas específicas de uma das tabelas a cada comando utilizado. No nosso caso, vamos assumir que quando a cidade for 'Goiânia' iremos colocar o Estado sendo 'Go'. No banco de dados Curso, execute: update Pessoaset Estado = 'Go'where Cidade = 'Goiânia'As condições que podem aparecem no WHERE são idênticas às condições do comando SELECT. Ao usar o comando UPDATE, caso alguma restrição de integridade tenha sido violada, a atualização inteira é cancelada, não apenas a linha que provocou o erro.

Page 34: Delphi 4 Com Oracle

Um comando de atualização também pode usar uma sub-consulta (um SELECT dentro de parênteses) para buscar dados em outras tabelas antes fazer a atualização.

Excluindo Linhas O comando DELETE exclui permanentemente uma ou mais linhas de uma tabela ou visão, baseado em alguma condição.Na exclusão de linhas de uma visão o comando SELECT que a gerou não pode ter mais de uma tabela na claúsula FROM. Por exemplo, para excluir a Pessoa nº 8 (CodPessoa =8), execute o seguinte comando no banco de dados Curso do SQL Server: delete from Pessoawhere CodPessoa = 8Note que a exclusão não pode ser desfeita.

6 - Programação Cliente / Servidor

Usando os componentes do Delphi

Usando transações

Salvando movimentações de produtos

Usando o componente Query

Usando os componentes do Delphi Os mesmos componentes do Delphi que podem ser usados em ambiente desktop, Database, Table, Query e DataSource, podem ser usados também num ambiente Client/Server. Mas nesse caso existem alguns detalhes que é importante analisar.

O componente Table Todas as operações num banco de dados cliente/servidor, como inclusões, alterações e exclusões de registros, devem ser feitas através de comandos SQL. Mas o componente Table esconde esse fato do programador. Você pode usar os métodos normais do Delphi para navegar pela tabela (First, Last, Prior, Next e outros), pesquisar valores (FindKey e FindNearest), e modificar os dados (Insert, Delete, Edit, Post e Cancel). Ele automaticamente gera e executa os comandos SQL necessários. Por exemplo, suponha uma tabela chamada 'Professor' com os campos: CodProfessor, Nome, Telefone, onde a chave primária é 'CodProfessor'. Imagine as seguintes situações: 1) O programa abre a tabela. O Delphi gera um comando SELECT para buscar os registros, como: select CodProfessor, Nome, Telefone from Professor 2) O usuário clica no botão para iniciar a inclusão de um registro (o programa chama o método Insert). Ele informa o código 12 para o professor, o nome "João da Silva" e o telefone "222-3333". Depois ele clica no botão do DBNavigator (que chama o método Post). O SQL gerado é: insert into Professor (CodProfessor, Nome, Telefone) values (12, 'João da Silva', '222-3333') 3) O usuário começa a modificar esse mesmo registro que já foi salvo (o programa chama o método Edit). Ele altera o telefone para 222-4444 e salva o registro (o programa chama o método Post). O SQL gerado é: update Professor set Telefone = '222-4444' where CodProfessor = 12 4) O usuário exclui esse mesmo registro anterior (o programa chama o método Delete). O SQL executado é: delete from Professor where CodProfessor = 12 O componente Table busca toda a informação do esquema da tabela (ou seja, a estrutura da tabela, com os tipos de dados de cada campo) quando ele é ativado. Isso permite que o acesso aos dados seja mais rápido.

Page 35: Delphi 4 Com Oracle

Filtrando Linhas Você pode também (e geralmente deve) filtrar registros da tabela, em vez de retornar todos os dados. Isso é feito com a propriedade Filter do componente. Essa propriedade gera uma cláusula WHERE no comando SELECT de consulta. Ela só tem efeito se a propriedade Filtered estiver com o valor True. É altamente recomendável usar Filter para evitar uma consulta que retorna todos os registros da tabela. No caso de tabelas grandes, trazer todos os registros consume recursos desnecessários do SQL Server. Você pode restringir os registros por algum critério, dependendo da aplicação. Por exemplo, você pode mostrar as letras do alfabeto e trazer apenas os nomes que começam com a letra escolhida. No exemplo da tabela acima, se Filtered = True, e Filter contém Nome = 'A*', por exemplo, o Delphi gera um comando de consulta da forma: select CodProfessor, Nome, Telefone from Professor where Nome like 'A%' Note que a sintaxe do Filter nesse caso é diferente da sintaxe do LIKE.

Usando o componente Table Abra o projeto ALMOXARIFADO.DPR . Crie um fomulário. Nele iremos usar as duas formas de acesso a uma tabela: com o componente Table e com o componente Query. Coloque no formulário um componente PageControl. Modifique a propriedade Align para alBottom e crie duas páginas. Para criar um página clique com o botão direito do Mouse e escolha a opção 'New Page'. Na primeira página, coloque o Caption= "Table", na outra Caption= "Query". Vamos alterar algumas propriedades do fornulário. Altere o nome para 'formMovProduto' e o caption para 'Movimentação Produto'. Salve o formulário como 'DMOVPRODUTO.PAS'.No Data Module 'DMAlmoxarifado' acrescente um componente Table, com Name=tblMovProduto, DatabaseName=DBAlmoxarifado e TableName=MovimentacaoProduto (ou "dbo.MovimentacaoProduto", como aparece na lista) . É necessário relacionar a tabela PRODUTO com a tabela MOVIMENTACAOPRODUTO , pois, iremos chamar o formulário 'formMovProduto' a partir do 'formProduto', ao cadastrar uma movimentação, iremos realizar a saída ou entrada para o produto que estiver posicionado na tabela Produto'. Como a tabela MOVIMENTACAOPRODUTO é a tabela subordinada e a tabela mestre é PRODUTO , vamos associar a propriedade MasterSource do componente tblMovProduto ao componente dsGenerico do fomulário 'formProduto', seu conteúdo será MasterSource = formProduto.dsGenerico. Na propriedade MasterField iremos colocar as colunas que permitem o relacionamento, como o nosso banco de dados é Client/Server, as colunas que fazem parte do relacionamento não precisa ter índices criados, como ocorre no Paradox, mas se tiver seu relacionamento pode se tornar mais rápido. Então na propriedade MasterField associe a coluna CodProduto da tabela PRODUTO a coluna CodProduto da tabela MOVIMENTACAOPRODUTO.Retorne ao formulário 'formMovProduto' e acrescente um DataSource, com Name=dsMovProdutoTable e DataSet= DMAlmoxarifado.tblMovProduto. Acrescente no formulário um componente label altere a propriedade Caption para 'Produto' , coloque também um componente DBEdit , altere sua propriedade DataSource para 'formProduto.dsGenerico' , na propriedade DataField coloque a coluna 'Nome', altere a propriedade ReadOnly para 'True'.Falta acrescentar os controles de dados ao formulário 'formMovProduto', então vamos retornar ao formulário DMAlmoxarifado e criar objetos de campos para o componente tblMovProduto, em seguida selecione todos os objetos de campo exceto 'codproduto' , depois arraste os objetos de campo para o formulário 'formMovProduto'. Ele terá a aparência como a figura abaixo:

Como a coluna 'TipoMov' tem dois valores vamos substituir o componente 'DBEdit' pelo componente 'DBRadioGroup' . Retire o componente DBEdit que esta mostrando o 'TipoMov' e o label cujo caption é 'TipoMov'. Acrescente o componente 'DBRadioGroup' (). Na propriedade Items acrescente 'Entrada' na linha0 e 'Saída' na próxima linha. Altere também a propriedade Values para 'E' na linha0 e 'S' na próxima linha. Coloque a propriedade Caption vazia.Na propriedade DataSource altere para 'dsMovProdutoTable' e DataField especifique a coluna 'tipomov'.Na página "Table", coloque os seguintes controles: 3 componentes Buttons e 1 DBNavigator , altere as propriedades de acordo com a tabela abaixo:Button1Caption IncluirName btnIncluirButton2Caption SalvarName btnSalvar

Page 36: Delphi 4 Com Oracle

Button3Caption CancelarName btnCancelarDBNavigator1DataSource dsMovProdutoTable

VisibleButtons

nbInsert = FalsenbDelete = FalsenbEdit = FalsenbPost = FalsenbCancel = FalsenbRefresh = False

Dependendo do estado da tabela , iremos desabilitar ou habilitar os botões de inclui, salvar e cancelar. Crie um procedimento para o evento OnStateChange do componente dsMovProdutoTable e coloque o código abaixo:procedure TformMovProduto.dsMovProdutoTableStateChange(Sender:TObject);begin btnIncluir.Enabled := dsMovProdutoTable.State = dsBrowse; btnSalvar.Enabled := dsMovProdutoTable.State = dsInsert; btnCancelar.Enabled := btnSalvar.Enabled;end;No evento OnClick do botão btnIncluir acrescente o código a seguir :procedure TformMovProduto.btnIncluirClick(Sender: TObject);begin DmAlmoxarifado.tblMovproduto.insertend;No envento OnClick do botão btnCancelar coloque o código abaixo:procedure TformMovProduto.btnCancelarClick(Sender: TObject);begin DmAlmoxarifado.tblMovproduto.cancelend;

Usando TransaçõesMuitas vezes é necessário garantir a consistência do banco de dados mesmo em caso de falha. Para cada atualização individual de registro, o Delphi garante que ela será completada imediatamente, mas muitas vezes várias atualizações precisam ser feitas em conjunto para manter os dados consistentes. Se há uma falha no meio do processo, os dados estão inválidos. Uma transação é um grupo de ações que devem ser todas executadas com sucesso, ou todas são canceladas. Por exemplo, no nosso sistema é necessário controlar a quantidade disponível em estoque dos produtos. Suponhamos que foi realizada uma venda, é necessário subtrair a quantidade vendida da quantidade existente em estoque. Isso envolve atualizações em duas tabelas: primeiro salvar o registro incluído na tabela 'MOVIMENTACAOPRODUTO' e depois subtrair a quantidade vendida pela quantidade existente em estoque na tabela 'PRODUTO' . Se apenas a primeira atualização é completada iremos vender um produto e não iremos atualizar o estoque, com isso o estoque não irá bater com as quantidades vendidas deste produto, gerando inconsistência no banco de dados.Você inicia uma transação com o método StartTransaction do componente Database, e finaliza com Commit, por exemplo: dbAlmoxarifado.StartTransaction; {atualiza os registros} ... dbAlmoxarifado.Commit; Dentro da transação, qualquer alteração no banco de dados (inclusão, alteração ou exclusão de registros) será registrada num log. Se houver uma falha no meio da transação, todas as alterações serão canceladas. Quando você chama o método Commit, todas as alterações serão confirmadas e se tornam permanentes. Se em vez de confirmar, você quiser cancelar a transação no meio, pode usar o método Rollback, que cancela todas as atualizações feitas: dbAlmoxarifado.Rollback;Lembre-se que uma transação num componente Database, por exemplo, 'dbAlmoxarifado', só afeta os componentes Table ou Query que estejam ligados ao componente 'dbAlmoxarifado', ou seja , que tenham a propriedade DatabaseName igual a ' dbAlmoxarifado'.

Page 37: Delphi 4 Com Oracle

Para saber se uma transação está em curso atualmente, consulte a propriedade InTransaction do componente Database (True indica que há uma transação ocorrendo). Os métodos de transação também equivalem a comandos SQL, que podem ser usados em procedimentos: Método Comando ANSI-SQLStartTransaction BEGIN TRANSACTION Commit COMMIT TRANSACTIONRollback ROLLBACK TRANSACTION

Níveis de Isolamento Se vários usuários abrem transações simultaneamente no mesmo banco de dados, uma transação pode interferir com a outra de várias formas. Por exemplo, um usuário A inicia uma transação no tempo t1 e por alguma razão executa um rollback no tempo t3. Enquanto isso uma transação do usuário B lê os dados que foram modificados (ou novos registros incluídos) no tempo t2: A ------ t1:StartTrans., depois altera dados ----------------- t3:Rollback----- B-------------------------------------------- t2:lê dados alterados ---------------------------- Se 'A' modifica um registro, mas ele ainda não foi confirmado [commited] no banco de dados, então 'B' leu dados inválidos, que depois foram cancelados. Esse problema tem duas variantes: quando 'B' lê registros modificados por A é chamado de leitura suja [dirty read]. Quando 'B' lê um registro incluído por 'A', que depois desaparece, é chamdo de valor fantasma [phantom value]. Esses problemas acontecem porque um programa pode ler dados que ainda não foram confirmados por uma transação. Outro problema pode acontecer se uma transação precisa ler dados e modificar esses dados. No intervalo entre a leitura e a modificação, outro usuário altera esses dados, por exemplo: C ---- t1:Start.-----t2:lê registro X-------t4:altera registro Y----t5:Commit D ----------------------------------------------t3: altera X-------------------------------------- No caso, primeiro 'C' lê os dados de um registro X do banco de dados. Logo após, 'D' altera esses dados. E depois 'C' usa os dados lidos para alterar outro registro Y, mas eles não são mais válidos. Esse problema é chamado de leitura não-repetível [non-repeatable read]. Para evitar esses problemas, existem vários níveis de isolamento de transações. Você escolhe o nível mais adequado para garantir a consistência do banco de dados. Esses níveis podem ser definidos com um comando SQL ou com a propriedade TransIsolation do componente Database: Nome SQL Ação READ UNCOMMITED

(TransIsolation=tiDirtyRead). Nesse nível podem acontecer leituras sujas [dirty reads], valores fantasmas e leituras não-repetíveis.

READ COMMITED

(TransIsolation=tiReadCommited). Nesse nível um usuário só consegue ler um valor que já foi commited [confirmado] por uma transação. Mas pode acontecer o problema de leituras não-repetíveis.

REPEATABLE READ

(TransIsolation=tiRepeatableRead). Nesse nível não acontece nenhum dos problemas mencionados

SERIALIZABLE

O mesmo que REPEATABLE READ

Os níveis de isolamento são implementados pelo SGBD com bloqueios de registro. Se uma transação está em modo READ UNCOMMITED, o nível mais baixo de isolamento, não ocorre bloqueio automático de registros. Se uma transação roda no nível READ COMMITED, então qualquer registro modificado pela transação será mantido bloqueado até a transação terminar com Commit ou Rollback. Esse bloqueio evita que outros usuários leiam valores ainda não confirmados. Se o nível de isolamento for REPEATABLE READ ou SERIALIZABLE (que para o Delphi são o mesmo), então vale o que foi dito para READ COMMITED e além disso qualquer registro lido dentro da transação será bloqueado até a transação terminar. Esse bloqueio evita que os registros usados pela transação sejam modificados depois que ela leu. Usar um nível mais alto de bloqueio tem a vantagem de manter a consistência do banco de dados, mas tem o potencial de diminuir a possiblidade de acesso simultâneo aos dados.

Otimizando Transações Como uma transação pode colocar bloqueios em vários registros, dependendo do nível de isolamento, você deve criar transações que terminam no menor tempo possível. Se você deixa uma transação aberta por muito tempo, ela vai "segurar" outros usuários que dependem dos registros usados por ela.

Page 38: Delphi 4 Com Oracle

Principalmente, não faça uma transação que depende da vontade do usuário para executar o Commit ou Rollback, ou seja, não deixe a transação aberta, esperando a resposta de uma mensagem ou evento.

Oracle e TransaçõesUma transação é um mecanismo usado para manter a consistência do banco de dados. Após iniciar uma transação, todos os dados são gravados "condicionalmente", com a opção de confirmar a transação (COMMIT) ou cancelar toda a transação (ROLLBACK).Normalmente o Delphi usa AUTOCOMMIT - ele faz um COMMIT no banco de dados automaticamente após cada modificação. Para modificar isso, use o método StartTransaction do componente Database, antes de fazer as modificações. Após terminar as modificações de dados, use o método Commit, que confirma tudo o que foi gravado desde o StartTransaction. Se quiser cancelar a transação, use o método Rollback, que cancela tudo o que foi gravado desde o StartTransaction.Em alguns bancos de dados, se um usuário (A) inicia uma transação e começa a alterar ou incluir registros, outro usuário (B) pode ver essas alterações ou inclusões, mesmo que a transação não tenha sido confirmada. Esse problema é chamado de leitura suja [dirty read] ou de valor fantasma [phantom value]. Esse problema não ocorre no Oracle.Em muitos casos, se uma usuário (A) lê dados dentro de uma transação, ele precisa que esses dados sejam consistentes até o término da transação. Se os dados forem alterados depois, isso cria o problema de leitura não repetível [non-repeatable read].O Delphi suporta três níveis de isolamento de transações, definidos pela propriedade TransIsolation do componente Database. O suporte a esses níveis depende do banco de dados. Eles são: tiDirtyRead, tiReadCommited, e tiRepeatableRead.O Oracle até a versão 7.2 suporta dois tipos de transações: READ/WRITE e READ ONLY. O tipo de transação pode ser definido também com o comando SQL SET TRANSACTION do Oracle:

Uma transação READ/WRITE pode modificar os dados, mas pode haver o problema de leituras não-repetíveis. Esse nível corresponde a TransIsolation=tiReadCommited no Delphi. Se você fizer TransIsolation=tiDirtyRead num banco de dados Oracle, isso não tem nenhum efeito e o Delphi funciona como se estivesse tiReadCommited.Uma transação READ ONLY é somente leitura - ela não pode modificar os dados. Ela pode ser finalizada com Commit ou Rollback indiferentemente. Em compensação, o Oracle garante que não ocorre o problema de leituras não-repetíveis. Os dados lidos pela transação são bloqueados e não podem ser modificados por outros usuários até que ela termine. Esse nível equivale a TransIsolation=tiRepeatableRead no Delphi.

O Oracle a partir da versão 7.3 suporta um outro tipo de transação. Com o comando SET TRANSACTION ISOLATION LEVEL, você pode definir a transação como READ COMMITED (default) ou SERIALIZABLE. No modo SERIALIZABLE, uma transação (A) não pode modificar dados que foram alterados por outra transação (B), enquanto a transação A não terminar. Se ela tentar modificar esses dados, recebe um erro de execução. Esse nível ainda não é suportado pelo Delphi/BDE, mas você pode enviar um comando diretamente ao Oracle, usando um componente Query com:SET TRANSACTION ISOLATION LEVEL SERIALIZABLEouSET TRANSACTION ISOLATION LEVEL READ COMMITED

Salvando Movimentações de ProdutosPara salvar a movimentação de um determinado produto é necessário ter o cuidado de não registrar a compra ou venda de um produto sem atualizar o seu estoque. Para que isto não ocorra utilizaremos transações. Se, durante a alteração do produto, for feita alguma leitura por outro usuário podemos ter problemas , por exemplo:Vamos supor que um usuário A deseja comprar um determinado Produto , mas a quantidade em estoque deste produto é 5, e ele deseja comprar 5 unidades. Suponhamos que no momento em que a venda está sendo realizada, um usuário B deseja comprar 3 unidades do mesmo produto . Se for realizada a venda para o usuário A, este produto ficará em falta no estoque. Se o usuárioB conseguir obter a quantidade em estoque do produto antes que o usuarioA confirme as alterações, para ele este produto tem 5 Unidades , o que não é verdade. Para evitar o problema citado iremos alterar a propriedade TransIsolation para 'tiReadCommited'' antes de inicializar a transação e depois vamos retornar ao seu valor default que é 'tiDirtyRead'. No evento OnClick do botão btnSalvar coloque o código abaixo:with DmAlmoxarifado dobegin

Page 39: Delphi 4 Com Oracle

dbAlmoxarifado.TransIsolation := tiReadComitted; try dbAlmoxarifado.StartTransaction; // Inicializa Transação tblMovProduto.post // Salva o registro da tabela MovimentacaoProduto tblProduto.edit; // Colocar tabela produto em modo de edição if tblMovProdutoTipoMov.value = 'E' then

tblProdutoQuantEstoque.value := tblProdutoQuantEstoque.value + tblMovProdutoQuantidade.value else begin if tblProdutoQuantEstoque.value < tblMovProdutoQuantidade.value then begin

showmessage('Quantidade Inválida!'); dbAlmoxarifado.RollBack; // Cancela Transação abort; end;

tblProdutoQuantEstoque.value := tblProdutoQuantEstoque.value - tblMovProdutoQuantidade.value; end; tblProduto.post; dbAlmoxarifado.Commit; // Confirma a Transação Except on EDBEngineError do begin

dbAlmoxarifado.Rollback; // Cancela Transação abort;

end; end; dbAlmoxarifado.TransIsolation := tiDirtyReadend; Se o tipo da movimentação for entrada iremos acrescentar a quantidade que foi comprada ao estoque, caso seja saída iremos retirar a quantidade vendida ao estoque.Todas estas alterações serão feitas somente se o componente tblMovProduto estiver no modo de inserção. Não iremos permitir alteração de um determinado movimento . Caso tenha feito o cadastro errado é necessário excluir e incluir novamente. Para que a alteração não seja permitida altere a propriedade AutoEdit para False no componente dsMovProdutoTable do formulário 'formMovProduto'. Observação: Usamos o nível de transação tiReadCommited porque para usarmos tiRepeatAbleRead teríamos que processar uma query com o comando 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'. No SQL Server o nível de isolamenteo tiRepeatAbleRead poderia ser usado sem nenhum esforço a mais.Como estamos utilizando a classe 'EDBEngineError' , então, temos que acrescentar a unit 'dbTables' na unit 'DMovProduto'. Com o tratamento de exceções qualquer erro que ocorrer durante a transações , iremos cancelar a transação. Notas: Para excluir ou estornar uma determinada movimentação de um produto segue a mesma idéia utilizada para salvar.Notas: Quando utilizamos banco de dados client/server as operações utilizadas durante a alteração ou exclusão de uma movimentação podem ser feitas utilizando triggers(seria a melhor opção).

Chamando o Cadastro de Movimentações do Produto a partir do Cadastro de ProdutosVamos abrir o formulário 'formProduto' e acrescentar um componente Button. Altere a propriedade Name para btnMovProduto, Caption para 'Movimentações' e no evento OnClick , acrescente o código abaixo:procedure TformProduto.btnMovProdutoClick(Sender: TObject);begin inherited; if DMAlmoxarifado.tblProduto.RecordCount = 0 then begin showMessage('Não é possível chamar movimentações! É necessário cadastrar um produto antes.');

Page 40: Delphi 4 Com Oracle

exit; end;

if not FormularioAberto(FormMovProduto) then FormMovProduto:=TFormMovProduto.Create(Application);

FormMovProduto.Show;end;Como a função FormularioAberto está na unit 'Menu' , e não foi definido que outras units podem utilizá-la, vamos declará-la na secão interface desta unit, conforme o código abaixo:interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus;

type ...

end;function FormularioAberto(F:TForm):boolean;var formPrincipal: TformPrincipal;No uses da seção implementation deste formulário acrescente a Unit 'DMOVPRODUTO'.Retorne ao formulário 'formMovProduto'. Como ele será criado dinamicamente é necessário retirá-lo da lista de "Auto-Create". E no evento OnClose devemos destruí-lo, portanto, crie um procedimento para este evento e acrescente o código abaixo:procedure TformMovProduto.FormClose(Sender: TObject;var Action:TCloseAction);begin Action := caFreeend;Finalmente para testar é necessário abrir o componente tblMovProduto no formulário 'formMovProduto' e em seguida colocar a tabela em modo de insercão, então no evento OnCreate deste formulário acrescente:procedure TformMovProduto.FormCreate(Sender: TObject);begin with DMAlmoxarifado.tblMovProduto do begin Open ; Insert; end;end;Execute o programa e verifique o que acontece. No cadastro de produtos escolha um produto e clique no botão 'Movimentações'. Faça algumas movimentações e certifique se a tabela produto está sendo atualizada.

Controlando o bloqueio de registros Os datasets têm uma propriedade chamada UpdateMode que controla como eles atualizam ou excluem registros existentes na tabela. Para ver o efeito dessa propriedade, inicie o SQL Monitor. Clique em Options|Trace Options, marque as três primeiras opções e desmarque as restantes. Execute o programa e faça alterações na tabela "Produto". Verifique que quando você edita um registro, alterando o campo Nome e clica em Salvar, o Delphi executa o seguinte comando (note a última linha que tem "SQL Execute": UPDATE dbo.Produto SET Nome=:1 WHERE CodProduto=:2 AND Nome=:3 AND QuantEstoque =:4 AND QuantMinima IS NULL andCodFornecedor =:5

Page 41: Delphi 4 Com Oracle

Os parâmetros :1, :2, etc. são substituídos no momento da execução. Para saber seus valores, verifique as linhas "SQL Data In" logo acima. O importante a notar é que o Delphi usa todos os campos na condição WHERE para localizar os registros. Isso quer dizer que, se você começar a editar o registro e algum outro usuário alterar qualquer campo do registro (por exemplo, Preço) antes de você tentar gravar, a cláusula WHERE não consegue mais achar aquele registro. O Delphi vai mostrar uma mensagem de exceção "Could not update because another user changed the record" (Não pude salvar porque outro usuário alterou o registro). Nesse caso, prevalecem as alterações do primeiro usuário. Esse é o comportamento default e corresponde à propriedade UpdateMode do componente Table com o valor 'upWhereAll', que indica que Delphi vai montar uma cláusula WHERE com todos os campos. Você pode escolher outros valores que mudam esse comportamento. Se você mudar para 'upWhereKeyOnly', o Delphi usa apenas a chave primária da tabela (no caso, CodProduto) para localizar valores. Nesse caso, ele sempre consegue encontrar o registro (a não ser que a chave seja alterada, o que é improvável). Se você mudar para 'upWhereChanged', o Delphi usa a chave primária e os campos que foram alterados (por exemplo, Nome, no teste acima). Para testar, altere UpdateMode para 'upWhereKeyOnly', execute o programa e faça o mesmo teste anterior. Você verá que o comando de atualização executado é: UPDATE dbo.Produto SET Nome=:1 WHERE CodProduto=:2 Ou seja, o Delphi usou apenas 'CodProduto' para localizar o registro. Faça o mesmo teste com 'upWhereChanged'.

Usando o componente Query Vamos usar o mesmo exemplo do componente Table , mas com um componente Query. Clique na página "Query" do componente PageControl.O componente Query permite consultar dados, mas nem sempre permite alterar esses dados. Existem várias formas de poder alterar os registros retornados pela consulta. A forma mais fácil é usar a propriedade RequestLive=True, mas tem certas desvantagens, como veremos. Como iremos utilizar um component Query , vamos colocá-lo no data module . Altere Name= qryMovProduto, DatabaseName= DBAlmoxarifado e RequestLive=True, para permitir alteração. Na propriedade SQL, digite: select *from MovimentacaoProdutowhere codProduto =:codProdutoorder by DataMovAo incluir uma movimentação iremos atribuir o código do produto a coluna 'codproduto' da tabela 'MovimentacaoProduto', no evento OnNewRecord crie o procedimento abaixo:procedure TdmAlmoxarifado.qryMovprodutoNewRecord(DataSet: TDataSet);begin qryMovProduto.fieldbyname('codproduto').asinteger := tblProduto.fieldbyname('codproduto').asintegerend;Essa consulta terá um parâmetro 'codProduto', que será alterado dinamicamente. O conteúdo deste parâmetro será o valor da coluna 'CodProduto' na tabela produto. No componente Query não temos a propriedade MasterSource que faz o relacionamento, mas podemos utilizar a propriedade DataSource deste componente, altere esta propriedade para 'formProduto.DSGenerico'. O parâmetro colocado no componente Query deve ter o mesmo nome da coluna que irá definir o relacionamento com a tabela Produto. O parâmetro será substituído a cada mudança realizada na tabela produto.Retorne ao formulário 'formMovProduto' , na página 'Query', crie um componente DataSource, com Name=dsMovProdutoQuery e DataSet= DMAlmoxarifado.qryMovProduto. Copie os mesmos componentes da página "Table" (ou crie outros iguais) e altere a propriedade DataSource desses componentes para 'dsMovProdutoQuery'. Altere também a propriedade Name dos componentes Button para:Button1.Name = btnIncluirQueryButton2.Name = btnSalvarQueryButton3.Name = tnCancelarQueryRetire todos os procedimentos associados ao evento OnClick dos botões e crie novos procedimentos com os códigos a seguir:No procedimento btnIncluirQueryClick faça a modificação abaixo:procedure TformMovProduto.btnIncluirQueryClick(Sender: TObject);begin

Page 42: Delphi 4 Com Oracle

DMAlmoxarifado.qryMovProduto.insertend;

É necessário fazer modificações no procedimento btnCancelarQueryClick , coloque de acordo com o código abaixo:procedure TformMovProduto.btnCancelarQueryClick(Sender: TObject);begin DMAlmoxarifado.qryMovProduto.Cancelend;

A modificação maior será no procedimento btnSalvarClick, pois, iremos criar um procedimento que irá servir para os dois botões, então é necessário substituir o tblMovProduto , os objetos de campos tblMovProdutoQuantidade e tblMovProdutoTipoMov. Substitua o objeto de campo tblMovProdutoQuantidade.value por (Sender as TDataSet).fieldbyname('quantidade').asinteger, substitua tblMovProdutoTipoMov.value por (Sender as TDataSet).fieldbyname('TipoMov').asString e o componente tblMovProduto substitua por (sender as TDataSet). Crie o procedimento abaixo:Procedure SalvaDados(Sender: TDataSet);Begin

with DmAlmoxarifado dobegin

dbAlmoxarifado.TransIsolation := tiReadComitted; try

dbAlmoxarifado.StartTransaction; // Inicializa a transação (sender as TDataSet).post; // Salva o registro da tabela MovimentacaoProduto

tblProduto.edit; // Colocar tabela produto em modo de edição if (sender as TDataSet).fieldbyname('TipoMov').asstring = 'E' then

tblProdutoQuantEstoque.value := tblProdutoQuantEstoque.value + (sender as TDataSet).fieldbyname('Quantidade').asinteger else begin if tblProdutoQuantEstoque.value < (sender as TDataSet).fieldbyname('Quantidade').asinteger then begin

showmessage('Quantidade Inválida!'); dbAlmoxarifado.RollBack; // Cancela

Transação abort;

end; tblProdutoQuantEstoque.value :=

tblProdutoQuantEstoque.value - (sender as TDataSet).fieldbyname('Quantidade').asinteger; end; tblProduto.post; dbAlmoxarifado.Commit; // Confirma a Transação Except on EDBEngineError do begin

dbAlmoxarifado.Rollback; // Cancela Transação abort; end; end; dbAlmoxarifado.TransIsolation := tiDirtyRead

end;end;

Page 43: Delphi 4 Com Oracle

Notas: Não é recomendável usar a propriedade Filter do componente Query, porque ela filtra os dados localmente, e não no servidor, o que é mais adequado. Crie um procedimento para o evento OnClick do Button 'btnIncluirQuery' e acrescente o código abaixo:Procedure TformMovproduto.btnSalvarQueryClick(Sender: TObject);begin SalvaDados(DMALmoxarifado.qryMovProduto)end;

Altere o procedimento do evento OnClick do Button 'btnSalvar' , deixe-o de acordo com o código a seguir:Procedure TformMovproduto.btnSalvarClick(Sender: TObject);begin SalvaDados(DMALmoxarifado.tblMovProduto)end;Antes de executar o programa é necessário abrir o componente Query, no formulário altere o procedimento do evento onCreate, acrescentando os códigos abaixo:with DMAlmoxarifado.qryMovProduto dobegin Open ; Insert;end;Execute o programa, clique na página "Query" e teste. Como RequestLive=True, você também pode alterar os dados. Na prática, RequestLive não funciona em todas as situações e, mesmo quando funciona, tem alguns problemas. Se você usa RequestLive, o Delphi faz duas consultas ao banco de dados: uma com a condição de pesquisa especificada, que vai retornar um subconjunto da tabela, e outra, buscando todos os registros da tabela.

7 - Exemplo Cadastro de Pessoas

Procedimentos Armazenados

Componente UpdateSQL

Procedimentos ArmazenadosUm procedimento armazenado [stored procedure] é um conjunto de comandos SQL que são compilados e armazenados no servidor. Ele pode ser chamado a partir de um comando SQL qualquer. A vantagem de usar procedimentos armazenados é que eles podem encapsular rotinas de uso freqüente no próprio servidor, e estarão disponíveis para todas as aplicações. Parte da lógica do sistema pode ser armazenada no próprio banco de dados, em vez de ser codificada várias vezes em cada aplicação. Eles também aumentam o desempenho de várias operações pois são executados no servidor e pré-compilados, ao contrário de comandos SQL que devem ser interpretados no momento da execução.

Criando procedimentos Para criar um procedimento, use o comando CREATE PROCEDURE. Infelizmente não existe uma sintaxe padrão ANSI para procedimentos. A sintaxe para criar procedimentos, os tipos de parâmetros que eles podem receber, e os comandos especiais para uso dentro de procedimentos, são específicos de cada SGBD.Faremos um pequeno procedimento de exemplo, que retornará todas as pessoas que tenham nome parecido com um parâmetro informado. Não tentaremos ver toda a linguagem de procedimentos, por tratar-se de um assunto extenso. Para uma referência mais completa dos comandos para uso com procedimentos consulte os livros da linha Oracle Press (autorizados da Oracle) ou SQL Server Books Online, no comando CREATE PROCEDURE. Para que o Oracle possa retornar linhas em cima de um select (que o nosso procedimento terá, é necessário que um cursor seja declarado para poder conter o objeto de resposta. Tal cursor servirá como uma tabela "intermediária"de armazenamento no Oracle. Veremos que para fazer a mesma coisa no SQL Server, não há necessidade de se declarar cursores.

Page 44: Delphi 4 Com Oracle

Para podermos usarmos um cursor, é necessário declará-lo primeiro (assim como variáveis de estrutura nas linguaguens de programação). Tal declaração deverá ser feita dentro de um pacote no Oracle, que pode ser entendido como um local de armazenamento de declarações. Abra o Alias "OracleCurso" e digite o seguinte comando:CREATE Package PacoteCursor AS type TipoCursor is ref Cursor;end;Aqui, PacoteCursor é o nome que este pacote terá no servidor Oracle. 'Type' inicia a declaração de um novo tipo (ou uma nova estrutura), 'TipoCursor' é o nome da nova estrutura, sendo escolhida aleatoriamente neste caso. 'is ref Cursor' indica que este novo type é um objeto que faz referência a um cursor. Uma vez criado o pacote que contém a declaração do cursor, podemos agora realmente criar a procedure dentro do Oracle. Repare que nada disto seria necessário se você estivesse utilizando o SQL Server. Execute o seguinte comando:CREATE procedure BuscaPessoa(nomebusca in varchar2, umcursor in out PacoteCursor.Tipocursor) asbegin open umcursor for select * from Pessoa where nome like '%'||nomebusca||'%';end;Aqui executamos o comando de criação da procedure, que recebe um parâmetro de entrada(que será passado pelo Delphi), e um parâmetro de entrada e saída (in out), que caracteriza um Cursor. Note que ao declarar o segundo parâmetro, colocamos que ele é do tipo PacoteCursor.Tipocursor. Isto faz com que o Oracle automaticamente detecte que a declaração de Tipocursor está dentro do pacote PacoteCursor. No corpo da procedure, temos de início uma chamada de abertura ao cursor, o que fará com que ele possa ser executado e que possar retornar resultados. O resultado retornado é o conjunto de todas as linhas da tabela Pessoa que tenham o parâmetro nomebusca de entrada em qualquer parte do campo nome. O símbolo "||" indica concatenação de strings e "%" indica qualquer string (inclusive a string nula).Obs: Para criar a mesma procedure no SQL Server, você deverá executar o seguinte comando:CREATE procedure BuscaPessoa @nomebusca varchar(50) as select * from Pessoa where nome like '%'+@nomebusca+'%'Note que a diferença entre os SGBDs é que o SQL Server não precisa do cursor para retornar o resultado da procedure. No SQL Server, os parâmetros são especificados com uma '@' antes do nomes, indicando parâmetro local de procedure. O símbolo de concatenação é agora o comum '+', e não mais '||'.

Chamando um procedimento Abra o projeto ALMOXARIFADO.DPR e dentro dele crie um formulário. Altere a propriedade Name para 'formPessoa' e o Caption para 'Cadastro de Pessoas'.Coloque neste formulário um componente Label altere o Caption para 'Pessoas'. Acrescente também um componente Edit , um Button altere as propriedades de acordo com a tabela abaixo:EditName editPessoaText ''ButtonCaption ProcurarName btnProcurarSalve o formulário como 'DPessoa.pas'. Como iremos criar o formulário dinâmicamente vamos retirá-lo da lista de "Auto-create" e no procedimento do evento onClose coloque o comando a seguir: Action := caFree;Neste formulário iremos utilizar o procedimento armazenado 'BuscaPessoa'. Para chamar um procedimento armazenado no Delphi, usa-se um componente StoredProc. Esse componente é um dataset, o que significa que, se o procedimento retorna um conjunto de registros, ele pode ser usado para manipular esse conjunto de registros. Vamos usá-lo para executar o procedimento BuscaPessoa, que retorna um conjunto de registros como resultado. Para executar o procedimento, basta chamar o método ExecProc desse componente. Vamos abrir o data module 'DMAlmoxarifado' e em seguida acrescentar o componente StoredProc.Altere as propriedades:

Page 45: Delphi 4 Com Oracle

Name: stpBuscaPessoaDatabaseName: DBAlmoxarifadoStoredProcName: BUSCAPESSOAQuando você seleciona um nome na propriedade StoredProcName, o Delphi automaticamente busca informação sobre os parâmetros do procedimento. Você pode ver essa informação na propriedade Params. Ao abrir essa propriedade, você verá algo como:

E caso esteja usando o SQL Server, você verá:

Repare que no Oracle o parâmetro que se refere ao cursor não aparece. Você deve adicionar este parâmetro manualmente, ou o Delphi acusará número incompatível de parâmetros. Para isto, clique com o botão direito do mouse na janela dos parâmetros, e escolha a opção "Add". Na propriedade name, coloque o nome do parâmetro do cursor, que no nosso caso é "umcursor". Na propriedade "ParamType", escolha "PtInputOutPut" e na propriedade datatype escolha "ftCursor".No SQL Server, o parâmetro @nome é de entrada , então vamos alterar a propriedade ParamType para ptInput. O parâmetro Result devolve o resultado de um procedimento armazenado , caso no procedimento tenha sido utilizado o comando RESULT no procedimento.Retorne ao formulário 'Cadastro de Pessoas'. Para que você possa acessar os componentes do módulo 'DMAlmoxarifado' nesse formulário, deve acrescentar uma claúsula uses ao formulário, fazendo referência à unidade 'MAlmoxarifado'.Para executar o procedimento, foi acrescentado o componente Edit ,onde iremos informar o nome a ser procurado e no botão 'Procurar' , iremos chamar o procedimento. Para mostrar o resultado vamos acrescentar um componente DBLookupListBox() e um componente DataSource. No DataSource altere seu nome para dsPessoa e a propriedade DataSet coloque 'DMAlmoxarifado.strBuscaPessoa' . Para o DBLookupListBox vamos coloque a propriedade ListSource = 'dsPessoa' , ListField = 'Nome' (campo que será mostrado) e KeyField = 'CodPessoa' .No evento OnClick do btnprocurar faça: procedure TformPessoa.btnprocurarClick(Sender: TObject);begin with DMAlmoxarifado.strBuscaPessoa do begin close; ParamByName('NomeBusca').asstring :=editPessoa.text; open; end;end;Este procedimento retorna dados como resultado utilizamos o método Open (poderia utilizar a propriedade Active = true). Para isso associamos a um componente DataSource. Desta forma podemos mostrar o resultado em controles de dados, no nosso caso estamos utilizando o DBLooupComboBox. Caso não retorna resultado ao invés de abrir o componente, temos que executá-lo, utilizando o método ExecProc.Se você estiver usando o SQL Server, você deve apenas alterar o nome do parâmetro. Ao invés de 'NomeBusca', coloque '@nomebusca'.Iremos chamar este formulário, através do formulário principal. Abra o formulário principal e acrescente uma referência da unit DPessoa. E no evento OnClick do item de menu Cadastro|Pessoas. Faça o seguinte:if not FormularioAberto(FormPessoa) then FormPessoa :=TFormPessoa.Create(Application);FormPessoa.Show;Execute o formulário e teste. Para testar informe o sobre nome de uma pessoa que esteja cadastrada. Para visualizar os dados temos que acrescentar alguns componentes de controle, para facilitar vamos criar objetos de campos no componente strBuscaPessoa (neste caso podemos criar , porque o procedimento retorna registros como resultado) que está no data module DMAlmoxarifado. Em seguida arraste os objetos para o formulário 'formPessoa', organize o formulário como abaixo:

Acrescente um componente DBNavigator () e altere a propriedade DataSource para dsPessoa.Execute o programa . Observe que só é possível movimentar de uma pessoa para outra, não permitindo fazer alterações.

Page 46: Delphi 4 Com Oracle

Componente UpdateSQL Uma forma de ter consultas alteráveis é usar um componente chamado UpdateSQL (). Esse componente permite definir comandos SQL de alteração que serão executados para modificar os dados retornados pelo componente Query ou Stored Proc. Para usar esse componente, você deve trabalhar em modo cached updates. Primeiro vamos ver o que vem a ser o esse modo cached updates.

O modo "cached updates" Normalmente, a cada operação de alteração, inclusão, ou exclusão que você faz em um dataset, o Delphi grava imediatamente o resultado daquela operação. Nem sempre essa é a melhor opção. Você pode ter um controle maior do processo de alteração com o modo cached updates [atualizações em cache]. Nesse modo, todas as operações feitas no dataset são registradas apenas em memória, num cache de atualizações, mas não são enviadas para o servidor de banco de dados (ou para as tabelas locais). Depois, quando o programa desejar gravar os dados fisicamente, ele usa o método ApplyUpdates para salvar as alterações. Vamos ver como funciona isso com a tabela 'tblProduto' no Data Module 'DMAlmoxarifado' . Então vamos abrir este Data Module. Para ativar o modo cached updates, altere a propriedade CachedUpdates desse componente para True.Mas o formulário que utiliza o componente tblProduto para cadastro é o 'formProduto', então vamos abrir este formulário. Coloque um botão a mais no formulário, com Caption="Salvar" e chame-o de 'btnSalvar'. Crie um procedimento para o evento Onclick com o seguinte: procedure TformProduto.btnSalvarClick(Sender: TObject);begin inherited; DMAlmoxarifado.tblProduto.ApplyUpdates; DMAlmoxarifado.tblProduto.CommitUpdates;end;

O método ApplyUpdates salva todas as modificações que estão no cache, gravando fisicamente no banco de dados. O método CommitUpdates limpa o conteúdo do cache, depois que os dados foram gravados. É necessário chamar os dois em seqüência. Uma outra forma de salvar as alterações, que discutiremos mais tarde, é usar o método ApplyUpdates do componente Database: DMAlmoxarifado.dbAlmoxarifado.ApplyUpdates([tblProduto]); Execute o programa. Altere alguns produtos , incluindo ou excluindo alguns dados. Note que quando você estiver modificando a tabela, o componente não está gravando fisicamente os dados. Se você fechar o formulário sem clicar no botão "Salvar", nada será gravado e, ao executar novamente, você verá os mesmos dados que antes. Podemos também pedir confirmação ao fechar o formulário, para ter certeza de que o usuário vai salvar as alterações. Podemos verificar se existem modificações pendentes (isto é, no cache, mas não gravadas), lendo a propriedade UpdatesPending. Para isso, crie um procedimento no evento OnCloseQuery do formulário:procedure TformProduto.FormCloseQuery(Sender: TObject;var CanClose: Boolean);var resposta :integer;begin inherited; if (DMAlmoxarifado.tblProduto.Active) and(DMAlmoxarifado.tblProduto.UpdatesPending) then begin resposta := Application.MessageBox('Salvar asalterações pendentes?','Confirmação',MB_IconQuestion + MB_YesNoCancel); case resposta of idYes:DMAlmoxarifado.dbAlmoxarifado.ApplyUpdates([DMAlmoxarifado.tblProduto]); idNo:DMAlmoxarifado.tblProduto.CancelUpdates; idCancel: CanClose :=False; end; end;

Page 47: Delphi 4 Com Oracle

end;

No procedimento, o método ApplyUpdates do componente database chama indiretamente os métodos ApplyUpdates e CommitUpdates do componente 'tblProduto', salvando as modificações. O método CancelUpdates limpa o cache de modificações, cancelando tudo que foi alterado. E finalmente, 'CanClose' é um parâmetro do procedimento que, se alterado para false, faz com que ele não feche o formulário. Execute o programa, altere ou inclua algum registro, e tente fechar o formulário sem clicar em "Salvar". O procedimento acima vai mostrar a mensagem e você deve responder de acordo.

Consultas alteráveis com UpdateSQL Para que o cadastro de pessoas permita alterações , iremos utilizar um componente UpdateSQL . Como vimos ele utiliza o modo 'Cached Updates'. Mas o componente que contém os dados das pessoas é o componente strBuscaPessoa, portanto, iremos colocar este componente para utilizar 'Cached Updates'.Abra o Data Module 'DMAlmoxarifado' , clique no componente strBuscaPessoa. Altere a propridade CachedUpdate para True. Acrescente um componente UpdateSQL() e altere apenas seu nome para 'updPessoa' .Clique no componente strBuscaPessoa . Na propriedade UpdateObject selecione 'updPessoa'. Desta forma estamos dizendo que quem vai permitir alterações neste componente e o componente updPessoa. O componente UpdateSQL permite gerar um comando SQL para cada tipo de operação no dataset: inclusão (comando INSERT), alteração de dados (comando UPDATE) ou exclusão (comando DELETE). Depois de gerar os comandos, eles serão executados quando ocorrem as operações correspondentes no componente dataset associado a ele. Para gerar os comandos SQL, clique duas vezes no componente 'updPessoa'. Você verá uma tela como a seguinte:

Nas listas Key Fields e Update Fields, aparecem todos os campos da tabela usada, no caso, Pessoa. Os campos selecionados em Key Fields serão usados para localizar um registro no momento da alteração (UPDATE) ou exclusão (DELETE). Os campos selecionados em Update Fields terão seus valores modificados (UPDATE) ou incluídos (INSERT). Em TableName informe a tabela 'Pessoa'. Na primeira lista, selecione apenas o campo "CodPessoa", como indicado acima. Na segunda lista, mantenha todos os campos selecionados. Clique no botão "Generate SQL". Você verá agora a página "SQL", com os comandos SQL gerados. Se você clicar em (.) Modify, verá o comando usado para alteração:update Pessoaset CodPessoa = :CodPessoa, Nome = :Nome, Sexo = :Sexo, Rua = :Rua, Bairro = :Bairro, Cidade = :Cidade, Estado = :Estado, CEP = :CEP, CPF = :CPF, DataCadastro = :DataCadastro, Nota = :Nota, fone = :fone, fax = :faxwhere CodPessoa = :OLD_CodPessoaOnde :NomeDoCampo é um parâmetro que será substituído pelo valor digitado naquele campo (o novo valor) e :OLD_NomeDoCampo será substituído pelo valor anterior do campo (o valor antes da modificação). Durante a execução, se você selecionar o registro com CodPessoa = 1 e alterar CodPessoa para 25 e Nome para 'Pessoa25', o comando realmente executado, com parâmetros substituídos, será: update Pessoaset CodPessoa = 25, Nome = 'Pessoa25',

...

Page 48: Delphi 4 Com Oracle

where CodPessoa = 1 Note que os campos que aparecem no WHERE (apenas CodPessoa) são os mesmos selecionados na lista Key Fields e os campos que aparecem no SET são os que foram selecionados em Update Fields. Você pode também editar manualmente o SQL para modificar seu funcionamento. Por exemplo, se você não quiser permitir alterar o código do produto, remova-o da lista do SET. Se você clicar no botão (.) Insert na página SQL, verá o seguinte comando:insert into pessoa (CodPessoa, Nome, Sexo, Rua, Bairro, Cidade, Estado, CEP, CPF,DataCadastro, Nota, fone, fax)values (:CodPessoa, :Nome, :Sexo, :Rua, :Bairro, :Cidade, :Estado, :CEP, :CPF, :DataCadastro, :Nota, :fone, :fax)Nesse caso, todos os valores de campos (da lista Update Fields) são inseridos na tabela. E se você clicar em (.) Delete, verá: delete from pessoawhere CodPessoa = :OLD_CodPessoaNesse caso, os campos de Key Fields (CodPessoa) são usados para localizar o registro a ser excluído. Clique no botão 'Ok' da janela do UpdateSQL. Abra o formulário 'formPessoa' e coloque um novo botão "Salvar" , com o nome 'btnSalvar'. Nesse botão, coloque o código abaixo: procedure TformPessoa.btnSalvarClick(Sender: TObject);begin with DMAlmoxarifado do dbAlmoxarifado.ApplyUpdates([strBuscapessoa]);end;Isso permite salvar as alterações, já que a consulta está em modo cached updates e elas não são salvas automaticamente. Execute o programa e note que agora você pode alterar o resultado do procedimento.

Simulando modo "normal" em cached updates O componente UpdateSQL pode ser usado tanto com o componente StoredProc (como usamos) quanto com um componente Query,Table. Ele é mais útil no primeiro e no segundo caso. No segundo caso porque permite transformar uma consulta não-atualizável (que não usa RequestLive=True) em uma consulta alterável. Mas esse componente só funciona em modo cached updates, que nem sempre é o que se deseja. Às vezes é necessário gravar as alterações imediatamente, uma vez para cada registro, mas poder usufruir dos recursos do UpdateSQL. Para isso, podemos simular o modo "normal" (atualizações salvas instantaneamente) mesmo com o modo cached updates. A forma de fazer isso é forçar a gravação das alterações logo depois de um Post (inclusão ou alteração) e de um Delete (exclusão) na consulta. No data module 'DMAlmoxarifado' clique no componente strBuscaPessoa, na página de eventos, e no evento AfterPost. Digite um nome de procedimento: 'SalvarDados' e tecle [Enter]. Isso vai criar um procedimento com aquele nome. No procedimento faça: procedure TdmAlmoxarifado.SalvarDados(DataSet: TDataSet);begin dbAlmoxarifado.ApplyUpdates([DataSet as TDBDataSet]);end;O parâmetro 'DataSet' diz qual o dataset (Table, Query, storedProc etc.) que chamou esse evento. Isso permite tratar de forma genérica qualquer componente, não só 'strBuscaPessoa'. O procedimento chama o método ApplyUpdates e força a gravar fisicamente a(s) alteração(ões) que está(ão) em cache naquele 'DataSet'. Clique no mesmo componente, 'strBuscaPessoa'. Na página de eventos, no evento AfterDelete, selecione o mesmo nome de procedimento (SalvarDados). Assim as exclusões de registro também serão executadas imediatamente. Execute o programa e veja a mudança de funcionamento. Note que agora o botão "Salvar" não é mais necessário.

Page 49: Delphi 4 Com Oracle

8 - Utilizando o ORACLE

Conectando ao ORACLE

Opções de um alias ORACLE

Oracle e nomes do banco de dados

Oracle e Transações

Oracle e Procedimentos Armazenados

Conectando ao OraclePara estabelecer uma conexão com o banco de dados Oracle a partir do Delphi, é preciso ter o software cliente do Oracle instalado no computador. Você precisa também utilizar o SQL*Net (Oracle 7.x) ou Net8 (Oracle 8.x) para criar um database alias ou "string de conexão" com o banco de dados.O Delphi 4 já vem configurado para acessar o Oracle8, mas ele suporta também o Oracle 7.x, o Personal Oracle 7.x, ou o Oracle Lite (novo nome do Personal Oracle) versão 8.0.4 ou posterior. Dependendo da versão do software cliente que você tem instalada, você talvez precise mudar parâmetros de configuração no BDE Administrator do Delphi.Execute o BDE Administrator, clique na página "Configuration" e abra System\Drivers\Native\ORACLE.Alguns parâmetros que você define para o driver ORACLE são apenas valores default, que podem ser alterados quando você define um alias. Outros são opções globais. As mais importantes são:

DLL32DLL32 - define qual das DLLs do BDE será o driver usado:

SQLORA32.DLL: use esse valor para o Oracle 7.xSQLORA8.DLL: use esse valor para o Oracle a partir da versão 8

VENDOR INITVENDOR INIT - define o nome de um arquivo DLL que é instalado pelo software do Oracle:

ORA7x.DLL ou ORANT711.DLL: use o nome da DLL adequado dependendo da versão do Oracle. Verifique o nome da DLL no diretório de instalação do cliente Oracle, subdiretório BIN (por exemplo, em C:\ORAWIN95\BIN ou C:\ORANT\BIN). Esse diretório deve estar no PATH do computador (AUTOEXEC.BAT no Windows 9x ou Painel de Controle, Sistema, Ambiente no Windows NT).OCI.DLL: use esse valor para o Oracle8. O diretório que contém esse arquivo deve estar no PATH.MTXOCI.DLL: use para o Oracle8 apenas se aparecer disponível na lista e se esse arquivo existe no seu computador.

Assim por exemplo, se você tiver instalado o cliente do Oracle 7.3, use:DLL32 = SQLORA32.DLLVENDOR INIT = ORA73.DLL

Se você estiver usando o software cliente do Oracle8 (que também pode acessar servidores 7.x), use:DLL32 = SQLORA8.DLLVENDOR INIT = OCI.DLL

Após alterar os valores (se necessário), teste a conexão. Clique na página "Databases", crie um novo alias do tipo ORACLE e veja se você consegue conectar-se a ele. Se não funcionar, tente alterar os parâmetros do driver novamente.

Opções de um alias ‘ORACLE’Quando você cria um alias do tipo ORACLE, ou quando você usa um componente Database e seleciona o "Driver name" como ORACLE, várias opções podem ser definidas. Algumas se aplicam exclusivamente ao Oracle ou têm um funcionamento diferente no Oracle:

Page 50: Delphi 4 Com Oracle

SERVER NAMEA "string de conexão" ou alias do SQL*Net ou Net8. Esse é o mesmo nome usado, por exemplo, para se conectar usando o SQL*Plus do Oracle.

USER NAMENome de usuário no Oracle. Isso é apenas um default, que geralmente é alterado para um nome diferente a cada conexão.

LIST SYNONYMSLIST SYNONYMS = {NONE | PRIVATE | ALL}

Um sinônimo do Oracle é um outro nome criado para uma tabela já existente, de forma a facilitar o acesso por outros usuários. Um sinônimo pode ser definido com um owner [proprietário] específico, ou como sinônimo público, acessível a todos usuários.O Delphi pode sempre acessar um sinônimo como se fosse uma tabela real. A opção LIST SYNONYMS define quais os sinônimos que ficam disponíveis para visualizar em uma lista de tabelas do banco de dados (por exemplo, que aparece na lista da propriedade TableName):NONE: nenhum sinônimo aparece em listas de tabelas. Só aparecem tabelas "reais" e visões [views].PRIVATE: aparecem os sinônimos cujo proprietário é o usuário conectado, mas não aparecem os sinônimos públicos.ALL: aparecem todos os sinônimos, incluindo os públicos.

NET PROTOCOLNET PROTOCOL = {TNS | NETBIOS | TCP/IP | NAMED PIPES | APPC | ASYNC | 3270 | IPX/SPX }

Use sempre TNS, que é o protocolo suportado pelas versões mais recentes do Oracle, usando SQL*Net (7.x) ou Net8 (8.x). Se você usar outro valor, consulte a documentação do Oracle para definir o SERVER NAME.

OBJECT MODEOBJECT MODE = {TRUE | FALSE}

Use FALSE para o Oracle 7.x e versões do Oracle8 que não tenham instalado a Objects Option.Use TRUE para o Oracle8 com Objects Option. Com essa opção, o Delphi passa a suportar a programação baseada em objetos do Oracle8 e os novos tipos de dados do Oracle8, como OBJECT, VARRAY e nested table.

ENABLE INTEGERSENABLE INTEGERS = {FALSE | TRUE}

Quando FALSE, qualquer campo numérico do Oracle (NUMBER) é interpretado pelo BDE como sendo um campo de ponto flutuante (tipo "Float"). Se você mudar para TRUE, os campos NUMBER(x), com x < 5 são interpretados pelo Delphi como do tipo "Smallint" (inteiro pequeno, de 16 bits) e com 5 <= x < 10 são interpretados como do tipo "Integer" (inteiro grande, de 32 bits).

ENABLE BCDENABLE BCD = {FALSE | TRUE}

Quando FALSE, campos NUMBER são tratados como do tipo "Float" (Double), o que pode provocar erros de arredondamento. Quando TRUE, campos NUMBER são tratados como do tipo Currency no Delphi/BDE, permitindo operações mais exatas.

ROWSET SIZEROWSET SIZE = n (um número inteiro)

O Delphi busca n linhas de cada vez do servidor para o cliente. Aumentar esse número pode melhorar o desempenho.

Oracle e nomes do banco de dadosNormalmente nomes de tabelas e campos no Oracle são case-insensitive, ou seja, ignoram a diferença entre maiúsculas e minúsculas. Se você criar uma tabela no Oracle com:create table

Page 51: Delphi 4 Com Oracle

MinhaTabela (Codigo number, Nome varchar2(50)), o Oracle converte o nome da tabela para todas letras maiúsculas (MINHATABELA), assim como qualquer campo declarado. Você pode fazer consultas ou alterações na tabela sem se preocupar com isso, ou seja:select codigo, NOME from minhaTAbelaAgora, se você criar o nome da tabela ou campo entre aspas, e esse nome não estiver todo em maiúsculas, o Oracle assume que ele será case-sensitive, ou seja, qualquer consulta naquela tabela tem que informar o nome entre aspas, e exatamente como foi escrito originalmente:create table "MinhaTabela" ("Codigo" number, "Nome" varchar2(50))ao tentar usar a tabela:select codigo, NOME from minhaTAbelaErro ORA-00942: table or view does not exist.select "Codigo", "Nome" from "MinhaTabela"funcionaO utilitário Data Pump do Delphi 4 (antes chamado Data Migration Wizard), ao converter tabelas para o Oracle, cria todos os nomes de tabelas e campos entre aspas e isso pode causar o problema acima. A única forma de resolvê-lo é alterar todos os nomes de tabelas e campos (no último passo do Data Pump) para todas letras maiúsculas (MINHATABELA, CODIGO, NOME etc.).

Oracle e Procedimentos ArmazenadosO Delphi pode chamar qualquer procedimento armazenado [stored procedure] ou função armazenada [stored function] do Oracle. Por exemplo, crie no banco de dados do Oracle o seguinte procedimento armazenado:create or replace procedure Aumenta_Preco(aumento in number, tot_aumento out number) as begin select sum(PrecoVenda * (aumento-1)) into tot_aumento from Produto;

update Produto set PrecoVenda = PrecoVenda * aumento; end;O procedimento recebe um parâmetro de entrada (in), chamado ‘aumento’, do tipo number. Esse parâmetro será um multiplicador usado para alterar os preços dos produtos na tabela ‘Produto’. Por exemplo, aumento = 1.1 é usado para aumentar em 10% os preços dos produtos. O procedimento calcula qual o total de aumento na tabela Produto e coloca no parâmetro de saída (out), chamado ‘tot_aumento’. Depois ele atualiza a tabela Produto, multiplicando o campo PrecoVenda pelo aumento.Um procedimento armazenado pode ter vários (ou nenhum) parâmetros de entrada (in), aos quais você atribui valores antes de chamar o procedimento. Ele pode ter também vários (ou nenhum) parâmetros de saída (out), cujos valores você pode ler no Delphi após a execução do procedimento. Um parâmetro também pode ser in out (usado tanto para entrada quanto para saída).Para usar esse procedimento no Delphi, crie um componente StoredProc no formulário. Conecte-o a um componente Database que esteja acessando o banco de dados do Oracle. Selecione em StoredProcName o nome do procedimento. Depois, na propriedade Params, você deve ver a lista de parâmetros, que é trazida automaticamente. Os parâmetros têm ambos DataType = ftFloat, indicando o tipo de dados a ser passado. A propriedade ParamType de cada um diz a direção do parâmetro (in, out ou in out). O parâmetro de entrada ‘AUMENTO’ fica com ParamType = ftInput e o parâmetro de saída, ‘TOT_AUMENTO’, fica com ParamType = ftOutput. Não é necessário mudar nada.Antes de executar o procedimento, atribua um valor ao parâmetro ‘aumento’. Para executar o procedimento, use o método ExecProc do componente. Após executar o procedimento, leia o valor do parâmetro ‘tot_aumento’, por exemplo:with stpAumentaPreco do begin ParamByName('AUMENTO').AsFloat := aumento; ExecProc; tot_aumento := ParamByName('TOT_AUMENTO').AsFloat; end;

Page 52: Delphi 4 Com Oracle

Nota: um procedimento que faz parte de um package [pacote] do Oracle não aparece na lista de procedimentos, na propriedade StoredProcName. Isso não significa que você não possa usá-lo no Delphi. Apenas digite nome_do_proprietário.nome_do_package.nome_do_procedimento manualmente na propriedade StoredProcName e o Delphi vai ler as informações dos parâmetros.Um procedimento também pode fazer uma pesquisa em uma ou mais tabelas e retornar um cursor como resultado dessa pesquisa. Para poder usar procedimentos que retornam cursores, você deve primeiro definir um pacote [package] no Oracle contendo a definição de um tipo de dados cursor. Isso só precisa ser feito uma vez. No SQL*Plus, crie o seguinte pacote:create package Pkg_cursor -- pode ser qualquer nome em vez de pkg_cursorastype TipoCursor is ref Cursor; -- idem acimaend;Se o SQL*Plus disser 'Package created', o pacote foi criado sem erros. Se ele disser 'Package created with compilation errors', digite SHOW ERRORS PACKAGE Pkg_cursor para saber que erro ocorreu e depois tente criar o pacote de novo. Para isso, use create or replace packageAgora você pode criar um procedimento que retorna um cursor. Esse procedimento deve ter um parâmetro in out (de entrada e saída), declarado com o tipo PKG_CURSOR.TipoCursor. Crie por exemplo o procedimento abaixo:create or replace procedure Busca_Pessoa(nome_busca in varchar2, ret in out Pkg_cursor.TipoCursor) as begin open ret for select * from Cliente where Nome like ('%' || nome_busca || '%') end;(Se houver erros na criação do procedimento, digite SHOW ERRORS PROCEDURE Busca_Pessoapara saber quais foram e corrigí-los).Esse procedimento faz um SELECT na tabela ‘Cliente’, usando o parâmetro ‘nome_busca’ como uma string de pesquisa. Ele abre um cursor, ‘ret’, nesse SELECT. Como o cursor é um parâmetro de entrada/saída, o Delphi pode usar o cursor criado como se fosse uma tabela, lendo os dados retornados.Nesse caso, faça como antes, usando um componente StoredProc, e associando-o ao nome do procedimento. Na propriedade Params, selecione o parâmetro ‘ret’ e altere a sua propriedade DataType para ftCursor (sem isso, o Delphi não sabe que está lidando com um cursor).Antes de executar o procedimento, defina o valor do parâmetro ‘nome_busca’. Mas para executar o procedimento, não use ExecProc. Use o método Open do componente ou faça a propriedade Active = True. Os dados do cursor podem ser mostrados em qualquer controle de banco de dados. Coloque um componente DataSource no formulário, ligado ao StoredProc, e um componente DBGrid, ligado ao DataSource. Você deverá ver o resultado da execução do procedimento (o conteúdo do cursor ‘ret’). Você pode também executar dinamicamente como:stpBuscaPessoa.ParamByName('NOME_BUSCA').AsString := nomeBusca; stpBuscaPessoa.Open;Resumindo:procedimento sem cursor (mais comum) --- use ExecProc para executarprocedimento com cursor --- use Open (Active := True) para executarPara chamar uma stored function do Oracle, o processo é o mesmo, com um detalhe a mais: o Delphi cria um parâmetro chamado "Result", que representa o valor retornado da função. Após executar a função, você pode pegar o valor desse parâmetro.create function Total_Produto(cod_produto in number) return number as total_aux number begin select sum(p.PrecoVenda * it.Quantidade) into total_aux

Page 53: Delphi 4 Com Oracle

from Produto p, Item it where p.CodProduto = it.CodProduto and p.CodProduto = cod_produto;

return total_aux; end;A função recebe como parâmetro um código de produto, ‘cod_produto’ , pesquisa a tabela Item e retorna como resultado a soma de todos os itens vendidos para aquele produto. Você pode colocar um componente StoredProc no formulário, com Name=stpTotalProduto e colocar o nome da função na propriedade StoredProcName = nome_do_ proprietário. Total_ProdutoPara executar a função, informe o valor do parâmetro ‘COD_PRODUTO’ e chame o método ExecProc. Após executar, pegue o valor do parâmetro ‘Result’, que contém o valor retornado pela função, por exemplo:with stpTotalProduto do begin ParamByName('COD_PRODUTO').AsInteger := codProduto; ExecProc; result := ParamByName('Result').AsFloat; end;

Padrão de uso de procedimentos (recomendado)Para facilitar a utilização de um procedimento/função armazenado, siga sempre o seguinte padrão de programação:

1. coloque um componente StoredProc no Data Module; coloque seu nome como stpNomeDoProcedimento e associe ao procedimento armazenado correspondente.

2. defina um procedimento Pascal na parte public da classe do Data Module, com o mesmo NomeDoProcedimento e os mesmos parâmetros do procedimento Oracle. Se um parâmetro for de entrada (in), declare o parâmetro por valor, ou usando const. Se for de saída (out) ou de entrada/saída (in out), declare o parâmetro por referência (var antes do nome dele).

3. no corpo do procedimento Pascal, passe os parâmetros para o procedimento Oracle, usando o componente StoredProc e execute o procedimento (ExecProc ou Open). Após executar, pegue o valor dos parâmetros de saída do Oracle e atribua para os parâmetros var do seu procedimento Pascal.

A vantagem de usar esse padrão é que o procedimento Oracle pode ser chamado em qualquer lugar do programa, como se fosse um procedimento Pascal normal. Seguindo esse padrão, os três procedimentos criados aqui seriam declarados como:unit DataModule1;

{ Nesse exemplo genérico, 'MeuDataModule' é o nome do módulo de dados e 'DataModule1.pas' é o nome do arquivo de unidade. }

type TMeuDataModule = class(TDataModule) ... stpAumentaPreco: TStoredProc; stpBuscaPessoa: TStoredProc; stpTotalProduto: TStoredProc; private ... public procedure AumentaPreco(aumento: double; var tot_aumento: double); procedure BuscaPessoa(nomeBusca: string); function TotalProduto(codProduto: integer): double; ... endEm algum outro formulário/unidade, a função TotalProduto, por exemplo, pode ser chamada:unit MeuFormulario;

uses ..., DataModule1;

Page 54: Delphi 4 Com Oracle

... umaVariavelQualquer := MeuDataModule.TotalProduto(umCodigoDeProduto);

9 - Componentes MIDAS

Criando aplicações multi-tiered

Criando uma aplicação multi-tiered com banco de dados

COM / DCOM

Criando um servidor de objetos

Criando aplicações multi-tiered Uma aplicação cliente/servidor pode ser two-tier [duas camadas] ou multi-tier [mais de duas camadas]. Numa aplicação two-tier, as duas "camadas" de software são: a camada de interface com o usuário, que faz a entrada e apresentação dos dados e a camada de gerenciamento de dados, representada por um software gerenciador de bancos de dados (SGBD), que faz a consulta e modificação dos dados em seu armazenamento físico. Uma aplicação multi-tier ou distribuída é aquela onde várias partes são distribuídas para serem executadas em diferentes computadores e se comunicam entre si através da rede local ou da Internet. Geralmente as partes ou "camadas" da aplicação são três: uma parte se encarrega da interface visual do programa. Outra, apenas mantêm a consistência lógica dos dados, através das chamadas regras de negócio. Outra, ainda, trabalha diretamente com os dados, lendo e gravando registros (essa geralmente é implementada por um SGBD). Mas numa aplicação mais complexa, pode haver mais divisões. No Delphi, você pode criar uma aplicação de banco de dados distribuída onde os dados são acessados indiretamente através de um servidor de aplicações, e são acessados remotamente em outro computador com um programa cliente. O cliente (primeira camada) lê e grava dados através do servidor de aplicações (a camada do meio ou middle-tier), que por sua vez, lê e grava esses dados em tabelas locais ou se comunica com um SGBD cliente/servidor (a terceira camada). Teoricamente, você poderia criar até seu próprio SGBD, mas isso é muito mais complexo. Veremos como criar aplicações com essa arquitetura em três camadas.

Vantagens e desvantagens Nesse modelo de aplicação, o servidor de aplicações coordena os pedidos e atualizações de dados vindos de múltiplos clientes. Ele pode também conter toda a validação de dados (regras de negócios), que assim ficam guardadas num local central, em vez de serem feitas em cada cliente. Você também pode criar aplicações clientes menores, que ocupam menos memória e são mais fáceis de instalar (porque não precisam do BDE). Além disso, distribuindo a aplicação, você está aproveitando o poder de processamento de vários computadores ao mesmo tempo. Note no entanto que esse tipo de aplicação é obviamente mais difícil de criar e de testar, pois você precisa executá-la em mais de um computador. Mas, na fase de testes, você pode executar o cliente e o servidor de aplicações no mesmo computador.

Componentes usados Os componentes abaixo fazem parte da paleta de componentes MIDAS:(ClientDataSet): Componente descendente de TDataSet, que portanto suporta todas as propriedades, eventos e métodos comuns a todos os datasets. Substitui o Table na aplicação cliente.(DCOMConnection): Utilizado para estabelecer comunicação remota entre o cliente e o servidor, utilizando o DCOM.(CorbaConnection): Utiliza o CORBA (Common Object Request Broker Architeture ) para estabelecer conexão entre o servidor e o cliente. (SocketConnection): Utiliza Windows sockets para administrar a conexão com o de servidor.(OLEnterpriseConnection): Utiliza o protocolo OLEnterprise (criado pela Inprise) para estabelecer conexão entre o servidor e o cliente.(Provider): No servidor de aplicações substitui o componente DataSource.

Page 55: Delphi 4 Com Oracle

(SimpleObjectBroker): Mantem uma lista de servidores com os quais o cliente pode se conectar. Permite distribuir a carga entre os servidores.(RemoteServer): Componente que utiliza o DCOM para estabelecer conexão entre o servidor e o cliente. Como temos o componente TDCOMConnection , o RemoteServer tornou-se obsoleto.(MidasConnection): Utilizado também nas aplicações Multi Tier para administrar a conexão entre o cliente e o servidor. Também obsoleto.Na aplicação cliente, todos os controles de dados padrão do Delphi podem ser usados normalmente, como grids, DBEdits, DBLookupCombo etc. A diferença é que em vez de usar um componente Table ou Query, você deve usar o componente (ClientDataSet). A aplicação cliente também pode utilizar um objeto DCOMConnection, CorbaConnection, OLEnterpriseConnection, que serão o elo de ligação com o servidor de aplicações. Esses componentes, de certa forma, tomam o lugar do componente Database. No servidor de aplicações, um módulo de dados remoto [remote data module] faz a interface com os clientes. Ele pode conter qualquer componente que é normalmente colocado num módulo de dados, como componentes Database, datasets diversos (Table, Query, StoredProc) e normalmente tem um ou mais componentes (Provider) para cada dataset. O componente Provider: - Recebe os pedidos de leitura do cliente, lê os dados de um dataset e envia os dados através da rede para o cliente. - Recebe os dados alterados do cliente, aplica as atualizações ao banco de dados, registra alterações que não puderam ser aplicadas e envia códigos de erro ao cliente informando o que não pôde ser feito. O seguinte diagrama ilustra como são feitas as conexões:

Do lado esquerdo, os componentes usados na aplicação cliente são o RemoteServer, que faz a ligação com o servidor, os componentes ClientDataSet, onde cada um faz a ligação com um componente Provider do servidor e componentes DataSource além de controles de dados (não mostrados). No lugar do RemoteServer podemos utilizar o DCOMConnection, CORBAConnection, OLEnterpriseConnection.Do lado direito (servidor de aplicações), são usados componentes de dados como Table e Database, além de componentes Provider para cada tabela acessada.

Software necessário Se você utilizar o Componente RemoteServer ou DCOMConnection ,o software que faz a comunicação entre o cliente e o servidor entre máquinas diferentes é chamado DCOM (distributed component object model) e será visto em detalhes mais tarde. Por enquanto, note que o DCOM está disponível com o Windows NT 4.0 e Windows 98. Para usá-lo no Windows 95 é preciso instalá-lo separadamente. Por enquanto faremos testes executando tanto o servidor quanto o cliente no mesmo computador. Nesse caso, iremos utilizar o DCOM. Os programas utilizam o software DCOM da Microsoft, que é parte integrante do Windows NT. Além da leitura e gravação de dados, um objeto DCOM pode ter métodos definidos pelo programador para fazer qualquer outra coisa, como veremos.

Criando uma aplicação multi-tiered com Banco de Dados

Criando o servidor de aplicações Você deve geralmente criar o servidor de aplicações primeiro, e depois criar os clientes. Isso vai permitir testar mais facilmente a comunicação entre eles. O servidor de aplicações é um programa Delphi comum, exceto que geralmente ele não tem interface visível com o usuário (não precisa ter, mas pode ter). Crie um novo projeto no Delphi. Por enquanto ignore o formulário principal do projeto, apenas altere sua propriedade WindowState para 'wsMinimized', para que ele não apareça ao executar o programa. Salve os arquivos desse projeto como FSERVIDOR.PAS e SERVIDOR.DPR. Como veremos, é importante salvar o projeto (e dar um nome a ele), antes de executá-lo. Vamos criar um módulo de dados remoto para conter os componentes de dados. Um módulo de dados remoto funciona como um módulo de dados comum, exceto que ele é um objeto DCOM, e isso permite chamar remotamente rotinas nesse módulo e transferir remotamente os dados. Para criá-lo, clique em File|New, na página 'MultiTier' selecione "Remote Data Module" e clique Ok. O Delphi vai pedir o nome da classe do módulo. Digite "DadosProduto" e clique Ok, ignorando a opção "Instancing" e 'Threading Model' por enquanto. Salve o arquivo de unidade do módulo como MSERVIDOR.PAS.

Page 56: Delphi 4 Com Oracle

Nesse módulo de dados, vamos acessar as tabelas do Oracle. Selecione a página "Data Access" na paleta de componentes. Coloque um componente Database, altere a propriedade Name para 'dbProduto' e clique duas vezes neste componente. Em "Name" coloque 'dbProduto' e em DatabaseName, na opção "Driver Name", selecione Oracle e desmarque a opção "Login prompt". Clique no botão "Defaults" e altere os parâmetros: SERVER NAME=nome do seu servidor OracleUSER NAME=nome de um usuário válido LANGDRIVER='DBWINUS0'PASSWORD= Informar a senha do servidorColoque um componente Table no formulário e conecte-o à tabela PRODUTO, alterando as propriedades: Name: tblProdutoDatabaseName: DBProdutoTableName: PRODUTO Vamos colocar agora um componente Provider () , da página Data Access no formulário e conectá-lo a essa tabela. Altere as suas propriedades: Name: provProdutoDataSet: tblProduto Ative o componente 'tblProduto' (Active=True) para que ele possa ser usado pelos clientes. Além disso, você deve exportar o componente Provider nesse módulo de dados. Para fazer isso, clique em 'provProduto' com o botão direito e na opção Export provProduto from data module. A exportação acrescenta alguns métodos ao módulo de dados, como a função "Get_provProduto". Execute o programa. Isso vai compilar o projeto e além disso vai registrar o servidor no Windows. O servidor deve ser registrado primeiro, antes que possa ser usado pelos clientes. Você pode finalizar o programa. O servidor será executado pelo DCOM quando for necessário. Notas: No Windows 95 e 98 é necessário iniciar o programa servidor manualmente, enquanto no Windows NT é iniciado automaticamente quando necessário.

Criando a aplicação cliente Para criar a aplicação cliente, salve este grupo que estamos utilizando . Clique em View|Project Manager, e depois clique com o botão direito no grupo (Project Group1) , escolha a opção 'Save Project Group' e salve com o nome 'MultitieredDCOM'. Neste grupo iremos criar um novo aplicativo que será a aplicação cliente. Clique com o botão direito e escolha a opção 'Add New Project' e escolha o ícone 'Application ' . Vamos fazer simplesmente um formulário que acessa a tabela Produto remotamente. É sempre recomendável usar um módulo de dados, mas para uma aplicação pequena, vamos dispensá-lo.

No formulário principal, coloque um componente (DCOMConnection) da página "Data Access" e altere seu nome para 'DCOMServidor'. Ele é que faz a interface com o servidor. Como o servidor pode ser acessado por qualquer máquina, na propriedade ComputerName coloque o nome do computador . Se for executado no mesmo computador, deixe essa propriedade vazia. Clique na propriedade ServerName e no botão . Você verá o nome de todas as classes de servidor registradas no Windows. No nosso caso, selecione 'Servidor.DadosProduto'. Note que esse é o nome do programa (Servidor) seguido do nome da classe do módulo remoto. A propriedade Connected desse componente determina se ele está conectado ao servidor ou não. Quando é False, o servidor não está sendo executado. Altere seu conteúdo para True. Isso vai automaticamente iniciar o programa servidor no outro computador.

Para acessar a tabela no cliente, coloque um componente (ClientDataSet) no formulário. Altere seu nome para 'cdsProduto'. Esse componente pode ser usado em qualquer lugar que um dataset comum. Altere sua propriedade RemoteServer, selecionado 'DCOMServidor'. Você também deve escolher a qual provedor [provider] do servidor ele vai ser conectado. Clique na propriedade ProviderName e no botão . Deve aparecer uma lista de um elemento, "provProduto". Selecione esse elemento. Altere a propriedade Active para true.Agora o componente 'cdsProduto', no cliente, está indiretamente conectado ao componente 'tblProduto', no servidor:

Coloque agora um componente DataSource no formulário. Altere Name para 'dsProduto' e DataSet para 'cdsProduto'. Coloque um componente DBNavigator e altere seu DataSource para 'dsProduto' . Acrescente também um componente DBGrid colocando sua propriedade DataSource para 'dsProduto'. Agora a execução do programa é como uma aplicação de banco de dados comum, com uma diferença: o componente ClientDataSet não envia os dados imediatamente para o servidor. Ele mantém apenas um registro de quais alterações (inclusões, modificações ou exclusões) foram feitas nos dados. É preciso chamar o método

Page 57: Delphi 4 Com Oracle

ApplyUpdates desse componente para forçar a atualização dos dados. Isso é semelhante ao modo cached updates que foi discutido anteriormente.Para isso, crie um procedimento para o evento AfterPost do componente cdsProduto e faça o seguinte: cdsProduto.ApplyUpdates(0);O parâmetro de ApplyUpdates é o número máximo permitido de erros de atualização. Caso seja zero, todas as atualizações devem ser gravadas corretamente ou todas serão canceladas. Salve a unit como 'ClienteDCOM' e o projeto coloque 'ClienteDCOMP' .Agora execute o programa e verifique o seu funcionamento. Nota: se você colocar um componente Database num módulo de dados remoto, altere sua p

COM/DCOM Como vimos, você pode criar classes de objetos em Delphi. Uma classe bem-construída pode ser reutilizada facilmente em vários programas. Geralmente para criar e usar objetos de uma classe, você só precisa saber quais são os métodos dessa classe, e não como ela é implementada internamente. Você pode também criar uma biblioteca de classes, com várias classes que funcionam juntas, e usar essa biblioteca em outros programas. Mas um problema é que várias linguagens na plataforma Windows permitem também a criação e utilização de classes. Normalmente dois softwares feitos em linguagens diferentes não podem se comunicar entre si, portanto uma biblioteca feita em C++, por exemplo, não seria utilizável a partir do Delphi.

Component Object Model (COM) O Component Object Model [modelo de objetos componentes] da Microsoft, é um mecanismo criado para permitir que objetos, na forma de código compilado, possam interoperar entre si e fazer parte de um mesmo projeto de software, independentemente da linguagem em que eles foram criados. Isso significa que qualquer linguagem que suporte esse modelo pode criar objetos que cooperam com objetos em outras linguagens. Assim, um programa em Delphi pode usar as funções de um objeto COM criado em C++, ou Visual Basic, ou outras linguagens. E vice-versa: você pode criar um objeto COM em Delphi que pode ser utilizado por outras linguagens. COM é parte integrante do Windows 95 e NT. Uma classe de objeto COM possui uma ou mais interfaces. Uma interface é um conjunto de declarações de métodos (funções e procedimentos) e propriedades. Quando um programa se comunica com um objeto COM, ele precisa especificar qual a interface que vai utilizar (não qual a classe a ser utilizada). Ele pode obter uma referência a esta interface, e chamar os métodos definidos na interface. Uma classe que implementa uma ou mais interfaces é chamada CoClass (component object class). Uma biblioteca de tipos [type library] é um arquivo contendo declarações de interfaces para uma ou mais classes. Um ambiente de programação, como o Delphi, pode consultar uma biblioteca de tipo para saber quais as propriedades e métodos válidos para uma determinada interface. Sem uma biblioteca de tipo, ainda é possível usar o objeto COM, mas só é possível verificar o tipo de dados correto dos parâmetros e propriedades em tempo de execução. Por isso é recomendável que o criador do objeto crie também uma biblioteca de tipos. Um servidor de objetos é um componente de software que implementa uma ou mais interfaces COM (em uma ou mais classes) e permite a outros programas usar essas interfaces externamente. Um servidor de objetos pode ser um programa executável (.EXE) independente ou pode ser uma biblioteca (.DLL). No primeiro caso, ele pode ser uma aplicação completa, com interface gráfica, executada separadamente pelo usuário. A segunda opção (.DLL) cria um servidor intra-processo [in-process], e geralmente é mais vantajosa. A DLL servidora é carregada para a memória apenas quando os seus clientes começam a utilizar algum objeto dela e é geralmente compartilhada por todos os clientes. Além disso, a velocidade da chamada de métodos é bem maior, porque os valores não precisam ser passados entre dois processos (.EXEs) diferentes. Nota: as funções fornecidas pelo COM antes eram conhecidas pelo nome de OLE (Object Linking and Embedding - vinculação e incorporação de objetos). Hoje OLE é apenas uma parte do modelo COM, que permite objetos visuais serem inseridos de um programa em outro. Nota: não é recomendável usar o tipo Variant exceto para manipular objetos COM. Ele pode ser fonte de erros no programa difíceis de detectar, já que ele evita a verificação normal de tipos do Delphi. Além disso, acessar variáveis desse tipo é mais ineficiente.

O tipo Variant O tipo Variant em Delphi é muito usado com objetos COM. Uma variável desse tipo pode conter dados de tipos diferentes durante a execução, por exemplo: var qualquerCoisa: Variant; begin qualquerCoisa := 2.5; // um número real

Page 58: Delphi 4 Com Oracle

qualquerCoisa := 'Texto'; // uma string Quando você não tem acesso a uma biblioteca de tipos, essa é às vezes a única forma de passar parâmetros a um método de um objeto COM. Se o Delphi não sabe o tipo dos parâmetros, ele assume que são todos do tipo Variant. Por exemplo: //o tipo do parâmetro de Mostrar é string ou inteiro? //o Delphi não tem como verificar em tempo de projeto obj.Mostrar('Texto'); obj.Mostrar(23); Esse tipo de variável também pode conter referência a uma interface de um objeto COM qualquer, o que permite chamar métodos desse objeto. Veremos essa característica com mais detalhes.

Distributed COM (DCOM) O modelo COM permite a comunicação entre objetos no mesmo computador. Já o modelo de objetos componentes distribuído (DCOM) permite a comunicação entre objetos em diferentes computadores, através de uma rede. Essa comunicação, depois de feita a conexão, é totalmente transparente para os objetos envolvidos. Um objeto pode chamar os métodos de outro como se eles estivessem sendo executados no mesmo computador e receber valores de retorno de funções normalmente. O DCOM é usado pelo próprio Delphi, para implementar aplicações multi-tier, com os componentes ClientDataSet, DCOMConnection e Provider vistos anteriormente. Como o DCOM é uma tecnologia nova, não está disponível no Windows 95. O software do DCOM é incluído com o Windows NT 4.0, no Windows 98 e no Windows 2000 (futura versão do NT). Para usá-lo no Windows 95 é preciso instalá-lo separadamente. Você pode obter o DCOM para Windows 95 (DCOM95) na Internet em: http://www.microsoft.com/comClique no link "DCOM95" e siga as instruções da página para download e instalação.

ActiveX ActiveX é um nome comum para um conjunto de tecnologias de criação de componentes desenvolvidas sobre a base do COM/DCOM, algumas delas direcionadas para a Internet. Por enquanto note que a página "ActiveX" do repositório no Delphi permite criar vários tipos de componentes, sejam objetos COM ou ActiveX.

Criando um servidor de objetos Vamos criar um servidor de objetos simples, com uma classe de objeto que vai simplesmente fazer um cálculo e retornar um resultado. Para criar um .EXE servidor de objetos, simplesmente use File|New Application. No caso, vamos criar uma .DLL servidora de objetos. Clique em File|New, depois clique na página "ActiveX" do repositório, escolha "ActiveX Library" e clique Ok. Uma DLL servidora tem que ser do tipo "ActiveX Library", não pode ser uma DLL comum.O Delphi vai criar apenas o arquivo de projeto de uma DLL (com library Project1 no cabeçalho). Agora salve o projeto com o nome de SERVOBJ.DPR. Lembre-se que esse será o nome da DLL compilada (SERVOBJ.DLL). Agora vamos criar um objeto nessa biblioteca. Clique em File|New novamente, na página "ActiveX" e escolha "Automation Object". Isso vai mostrar o "Automation Object Wizard":

Em Class name, digite o nome da classe que será criada. No caso, use "Calculo". Note que esse não inclui um "T". A opção Instancing determina como podem ser criados os objetos dessa classe pelos programas clientes. Se você deixar "Multiple Instance", o default, vários objetos podem ser criados. "Single instance" determina que apenas um objeto dessa classe pode ser criado. Já "Internal" determina que clientes não podem criar, mas apenas utilizar objetos já existentes. Mantenha o default em Instancing e clique Ok. O Delphi vai fazer várias coisas no projeto: • Cria uma classe de objetos COM (ou CoClass) chamada "Calculo", que corresponde a uma classe no Delphi chamada "TCalculo". • Cria uma interface para essa classe, chamada "ICalculo". • Cria uma biblioteca de tipos, que define essa classe e interface. A biblioteca tem o nome do projeto e é guardada num arquivo chamado SERVOBJ.TLB. Essa biblioteca é mostrada imediatamente na tela. • Cria uma unidade auxiliar para trabalhar com a biblioteca de tipos, chamada "ServObj_Tlb.PAS". Essa unidade é gerada automaticamente a partir do conteúdo da biblioteca de tipo. • Cria uma unidade com a declaração da classe 'TCalculo', que aparece no editor (inicialmente com o nome provisório de 'Unit1').

Page 59: Delphi 4 Com Oracle

A unidade recém-criada ainda não foi salva. Salve o projeto inteiro e dê o nome a essa unidade de 'MCALCULO.PAS'. O editor de biblioteca de tipo [type library editor] aparece na tela com o arquivo 'Servobj.tlb' aberto. Se você fechar essa janela, pode abri-la de da mesma forma que se abre um formulário, posicione em 'Servobj_tbl' e pressione a tecla F12.Cada classe ou interface criada tem um identificador gerado para ela, chamado de GUID (globally unique identifier - identificador globalmente único), um número grande que deve ser diferente para cada classe instalada no computador. Normalmente o Delphi gera os GUIDs automaticamente, e você não precisa se preocupar com eles. Para poder acrescentar novos métodos ou propriedades ao nosso objeto, devemos acrescentá-los na interface ICalculo nesse editor, você pode utilizar a página "Text" para escrever o código diretamente, desde que saíba a sintaxe correta. Clique em "ICalculo" do lado esquerdo do editor em seguida clique com o direito do Mouse e escolha a opção "New" e "Method". Depois clique na página "Parameters" e informe os parametros para este método como a seguir:

Somar será um método que retorna um valor do tipo Integer , por isso foi necessário alterar a opção "Return Type". Crie outro método como o nome 'Converter' e os parâmetros como a figura abaixo:

Nem todos os tipos de parâmetros que podem ser declarados no Delphi podem ser usados em um método de um objeto COM. No caso 'WideString' é uma versão diferente do tipo string que deve ser usada em métodos COM. Como o parâmetro 's' será passado por referência,então é necessário que o conteúdo da opção "modifier" seja 'var'. Nota: o tipo WideString, usado em métodos COM suporta caracteres de outros alfabetos, como chinês, japonês, russo etc. Agora clique no botão "Refresh Implementation" para atualizar o código. Agora note o conteúdo da unidade 'MCALCULO.PAS' no editor de texto do Delphi. Ela contém a declaração da classe 'TCalculo', com o seguinte: type TCalculo = class(TAutoObject, ICalculo) protected function Somar(x, y: Integer): Integer; safecall; procedure Converter(var s: WideString); safecall; end; Essa classe é derivada de 'TAutoObject', a classe básica para todos os objetos de automação OLE. Na declaração também está indicado que ela implementa a interface ICalculo, ou seja, que contém todos os métodos declarados nessa interface, que está declarada na unidade SERVOBJ_TLB.PAS. Os métodos em questão são a função 'Somar' e o procedimento 'Converter'. Se você der uma olhada na parte implementation da unidade, verá que o corpo desses métodos já foi criado, embora vazio. Vamos completá-los: function TCalculo.Somar(x, y: Integer): Integer; begin result := x + y; end; procedure TCalculo.Converter(var s: WideString); begin // converte 's' para maiúsculas s := AnsiUpperCase(s); end; Nota: a função 'AnsiUpperCase' converte uma string para maiúsculas, considerando caracteres acentuados corretamente. Para usar a função AnsiUpperCase, inclua o nome 'SysUtils' na cláusula uses dessa unidade (ao lado de ComSrv). Agora compile o projeto (como é uma DLL, você não vai executá-lo) com Project|Compile. Além disso, você deve registrar esse servidor de objetos no Windows. A operação de registro vai guardar informações sobre esse servidor no Registro [registry] do Windows, para que outros programas possam saber da existência dele. Para registrar o servidor no botão "Register Type Library" do editor de biblioteca de tipo. Salve o projeto novamente.

Page 60: Delphi 4 Com Oracle

Utilizando o servidor Vamos fazer um pequeno programa exemplo que chama o servidor. Caso não esteja posicionado no projeto 'ServObj.dpr' abra-o . Salve seu grupo com o nome 'ServObj' e crie um novo aplicativo neste projeto. No formulário altere a propriedade Caption para 'Usando servidor COM', coloque os seguintes componentes:

Coloque os seguintes nomes nos componentes: botão "Criar objeto" btnCriar botão "Destruir objeto" btnDestruir primeiro Edit editNum1 segundo Edit editNum2 botão "Somar" btnSomar terceiro Edit editSoma quarto Edit editTexto botão "Converter" btnConverter O botão "Criar objeto" será responsável por criar um novo objeto, usando o servidor de objetos. Antes de criar um procedimento para ele, abra o arquivo de unidade, e declare uma variável global do tipo Variant. Também acrescente uma cláusula uses: implementation

{$R *.DFM}

uses ComObj;

var Objeto: Variant;A unidade 'ComObj' foi utilizada por causa da função CreateOleObject, que será usada para criar o objeto. A variável 'Objeto' foi declarada para fazer referência ao objeto criado. No procedimento de evento do botão btnCriar, escreva: procedure TForm1.btnCriarClick(Sender: TObject); begin Objeto := CreateOleObject('ServObj.Calculo'); end; O parâmetro de CreateOleObject é o nome qualificado da classe de objeto a ser criada, composto do nome do servidor, 'ServObj', mais o nome da CoClass, 'Calculo'. O botão "Destruir objeto" só precisa atribuir o valor especial Unassigned à variável de objeto. Isso libera o objeto da memória e também o servidor de objetos, se não houver outros objetos criados. Esse valor é o mesmo com o qual é inicializada uma variável Variant, no início da execução: procedure TForm1.btnDestruirClick(Sender: TObject); begin Objeto := Unassigned; end; Agora os botões "Somar" e "Converter" vão simplesmente chamar os métodos correspondentes, através da variável 'Objeto': procedure TForm1.btnSomarClick(Sender: TObject); begin editSoma.Text := Objeto.Somar(editNum1.Text, editNum2.Text); end;

procedure TForm1.btnConverterClick(Sender: TObject); var s: widestring; begin s := editTexto.Text; Objeto.Converter(s); editTexto.Text := s; end; Note que ao chamar o método 'Somar', não foi feita conversão de strings para inteiros. O tipo Variant faz essas conversões dinamicamente. Já no método 'Converter', que recebe um parâmetro por referência, uma variável foi declarada com exatamente o tipo esperado (WideString) pelo procedimento.

Page 61: Delphi 4 Com Oracle

Execute o programa. Clique primeiro em 'Criar objeto', antes de clicar num dos botões que chamam os métodos. Se você fizer o contrário, verá uma mensagem de erro de execução. Na verdade, não é preciso destruir o objeto sempre, ele será destruído ao terminar a execução. Salve o projeto com os nomes de USACOM.PAS e USACOMP.DPR.

Importando uma biblioteca de tipo O que acontece se você digita um nome errado de método, por exemplo: Objeto.Coverter(s); //falta o 'n' Note que o Delphi não consegue verificar o erro durante a compilação, mas só ao tentar executar o programa. Isso é porque ele não está usando a biblioteca de tipos. Outros servidores de objeto poderiam ser chamados a partir do Delphi. Por exemplo, como o Microsoft Word é um servidor de objetos, você poderia usar a funcionalidade do MS Word dentro do seu programa. Por exemplo, o código abaixo executa o Word, abre um arquivo chamado "C:\teste.doc", imprime esse arquivo e fecha o Word: var app, doc: Variant; begin app := CreateOleObject('Word.Application'); doc := app.Documents.Open('c:\teste.doc'); doc.Insert(Cabecalho); app.PrintOut; app.Quit(False); end; Todos os comandos do Visual Basic for Applications (VBA) do Word, podem ser chamados dessa forma. Nesse caso, para que o Delphi possa saber os métodos e propriedades dos objetos indicados, você pode importar a biblioteca de tipos do programa. Isso cria uma unidade que declara as interfaces e classes necessárias. Para fazer isso, use o menu Project|Import Type Library e escolha qual a biblioteca que quer importar, por exemplo "Microsoft Office 8.0 Type Library".

10 - Páginas Web

Conceitos e protocolos da Internet

Aplicações cliente

Criando aplicações de servidor

Páginas Web dinâmicas

Incluindo produtos dinamicamente

Conceitos e protocolos da Internet A Internet é um meio de comunicação que permite transmitir dados entre computadores do mundo inteiro. Ela usa tecnologias abertas (padrões) que permitem diferentes sistemas interoperarem entre si. Os protocolos da Internet definem como é implementado o transporte de dados entre computadores. Os computadores conectados à Internet (sejam estações ou servidores) são chamados geralmente de hosts. A pilha de protocolos TCP/IP é o software básico para comunicação via Internet e abrange alguns protocolos principais: IP Internet Protocol: protocolo que fornece a base para todos os outros. Para se comunicar via IP, cada computador tem um endereço IP único, por exemplo 200.201.202.203. Muitas vezes um computador também tem um nome de host [hostname], como ftp.borland.com, que é traduzido dinamicamente para um endereço. TCP Transmission Control Protocol: permite transmitir pacotes de dados entre computadores, e garante a entrega desses pacotes apesar de erros que possam ocorrer no caminho. Automaticamente retransmite os pacotes se necessário. UDP User Datagram Protocol: transmite os dados, mas não garante a entrega dos pacotes. A aplicação decide se vai retransmitir os dados ou não. Muitos computadores têm hostnames amigáveis, que são compostos da forma: nomeComputador.nomeDomínio, onde o nomeDomínio é registrado para uma empresa em particular. Por exemplo, ftp.borland.com e

Page 62: Delphi 4 Com Oracle

www.borland.com são dois computadores (ftp e www) no mesmo domínio (borland.com). Esses nomes são traduzidos dinamicamente para seus endereços IP correspondentes, através de um banco de dados distribuído chamado DNS (Domain Name System). Um programa que usa os protocolos TCP ou UPD para se comunicar com outro computador deve usar uma porta no computador de destino. Uma porta é apenas um número lógico que é alocado para cada programa. Um programa pode escutar numa porta, ou seja, ele pode ficar esperando conexões de clientes que se comunicam com aquela porta. Nesse caso, o programa é chamado de serviço ou programa servidor. Por exemplo, um browser (programa que mostra páginas Web) se conecta a um host, que tem um programa chamado serviço HTTP (ou servidor Web) sendo executado. O browser usa o protocolo TCP e se conecta à porta 80 do host (número padrão).

Protocolos de alto nível Outros protocolos trabalham em um nível mais alto do que TCP/IP e UDP/IP e são usados para várias aplicações úteis (obs. P.=Protocol): FTP File Transfer P.: usado para transferência de arquivos. SMTP Simple Mail Transfer P.: usado para transferência de e-mail entre diferentes servidores. Permite enviar e-mail a qualquer local, e roteia o correio até chegar ao local de destino. POP Post Office P.: usado por um cliente (geralmente um PC) para se conectar a um servidor de correio e consultar uma caixa postal, trazendo as mensagens atuais. HTTP Hypertext Transfer P.: usado para transferir páginas Web (arquivos HTML) de um servidor para um browser (programa de acesso à Web), como o Netscape Navigator ou o Internet Explorer. A rede de servidores HTTP é chamada de World Wide Web. NNTP Network News Transfer P.: usado para manter e atualizar grupos de discussão ou grupos de notícia [newsgroups], uma espécie de fórum aberto onde as pessoas dão suas opiniões e recebem respostas, ou anunciam novidades sobre um determinado assunto. A rede de servidores NNTP é chamada de Usenet. Um serviço é um programa que executa num computador, atendendo a conexões feitas para um desses protocolos. Por exemplo, um serviço HTTP (ou software servidor Web) é um programa que escuta na porta 80 e, ao receber uma conexão, com um pedido de busca de arquivo, lê o arquivo e o manda de volta. A biblioteca Windows Sockets ou Winsock foi criada para facilitar o desenvolvimento de aplicações Windows com acesso à Internet. Essa biblioteca (DLL) trabalha com o conceito de socket [soquete], que é uma conexão estabelecida entre dois programas para transmitir dados.

URLs Num browser, o usuário pode digitar diretamente um endereço que corresponde a um item de informação. Esse endereço é chamado URL (Uniform Resource Locator). Alguns exemplos de URLs são: file://D|/Doc/Web/teste.htmlhttp://www.borland.com/delphi/support/index.htmlftp://ncsa.uiuc.edu/mosaic/windows/mw32.zipnews:comp.lang.delphi Uma URL se divide em várias partes, algumas opcionais: - O protocolo usado para acessar o arquivo: file, http, ftp, news, etc. O nome de protocolo é sempre seguido de ":" (dois-pontos). Esse nome é obrigatório, embora os browsers assumam http como o protocolo default. - O nome de host (para http ou ftp): nos exemplos: www.borland.com ou ncsa.uiuc.edu. - O diretório ou caminho para localizar o arquivo, como "/delphi/support/", "/mosaic/windows", "D:/Doc/Web/"; - O nome de arquivo, como "teste.html", "mw32.zip", "default.htm" ou "comp.lang.delphi". Nem sempre corresponde a um arquivo físico realmente.

Intranets Uma intranet é uma rede local ou uma rede em pequena escala que usa os protocolos da Internet e na qual são executadas aplicações que utilizam esses protocolos. Numa intranet, os computadores podem ser acessados também com as funções de rede comuns. Por exemplo, se um servidor HTTP está sendo executado numa Intranet (no Windows NT ou 95, por exemplo), você pode "publicar" páginas Web nele simplesmente copiando arquivos. Uma URL para este servidor pode ser montada usando apenas o nome do computador na rede, por exemplo: http://meuservidor/teste.htm. Uma intranet pode estar ou não conectada à Internet. Se não estiver, algumas tarefas de configuração e utilização são mais simples. Por exemplo, não é preciso ter a resolução de nomes do DNS nem registrar um nome de domínio ou faixa de enderereços IP.

Page 63: Delphi 4 Com Oracle

Aplicações cliente Uma aplicação cliente Internet usa algum dos protocolos da Internet para se conectar a um serviço em um computador remoto. Alguns tipos de aplicação cliente são: - Clientes de e-mail: software usado para ler e enviar correio eletrônico (e-mail) via Internet. Usa os protocolos POP e SMTP. - Cliente FTP: software que transfere arquivos de/para um servidor FTP. - Leitor de notícias [newsreader]: software que acessa um servidor de newsgroups e permite ler ou enviar mensagens para os newsgroups desejados. - Browser Web: (também chamado navegador Web): busca páginas Web da Internet, compostas de arquivos HTML, imagens etc. e mostra o conteúdo dessas páginas numa interface gráfica de usuário. Usa o protocolo HTTP para transferir os arquivos do servidor. Os browsers mais usados hoje, Netscape Navigator e Microsoft Internet Explorer, também funcionam como cliente de FTP e têm programas associados de e-mail e leitores de notícias.

Componentes para aplicações cliente Você pode criar esses tipos de aplicações em Delphi, usando os componentes da página "Internet". Todos os componentes dessa página são não-visuais, exceto o HTML. Os componentes usados em aplicações clientes são: ClientSocket e TCP: permite fazer uma conexão genérica com qualquer servidor através de um socket, usando o protocolo TCP para se conectar a um servidor remoto, permitindo ler e enviar dados através da conexão. Veremos como usar o ClientSocket mais tarde. Use um ou outro desses componentes. Eles oferecem a mesma funcionalidade, mas a forma de usar é diferente. UDP: permite se conectar com um servidor, usando o protocolo UDP, que não garante a entrega dos dados. Permite ler e enviar dados através da conexão.

Criando aplicações de servidor Existem vários tipos de aplicações de servidor (ou serviços): - Servidor Web: serviço HTTP, que atende a pedidos e retorna ao cliente páginas Web. Para obter essas páginas, ele lê arquivos HTML armazenados em disco, ou chama um programa CGI (ver abaixo) que gera o texto da página dinamicamente. - Serviço SMTP: recebe uma mensagem de e-mail e verifica se ele próprio é o destinatário da mensagem. Se for, guarda a mensagem em disco. Se não, envia-a para outro servidor SMTP até que ela chegue ao destino. - Serviço POP: lê as mensagens que foram guardadas em disco pelo serviço SMTP e retorna essas mensagens ao cliente quando ele as requisitar. Nota: para um exemplo mais completo de Web browser, veja o programa exemplo WEBBROWS.DPR no diretório DEMOS\COOLSTUF do Delphi.Você pode criar uma aplicação que se comunica em baixo nível, usando o protocolo TCP (ver na próxima seçãp), mas isso é um pouco complexo. Ou você pode criar um serviço como um dos acima, mas geralmente não vale a pena.

Páginas Web dinâmicas A maioria das aplicações de servidor hoje estão relacionadas à Web e funcionam em conjunto com o software servidor Web. Quando um usuário consulta uma página Web, ela pode ser estática (um arquivo HTML criado anteriormente e guardado em disco) ou pode ser uma página dinâmica (gerada por um programa executável). O programa deve gerar dados em formato/linguagem HTML. No primeiro caso, o próprio servidor Web lê os arquivos da página e os repassa para o cliente. No segundo caso, ele executa outro programa para gerar dinamicamente o conteúdo da página. Por exemplo, O AltaVista (http://www.altavista.digital.com) usa um programa para pesquisar dinamicamente no seu banco de dados de documentos. Existem várias formas de criar uma aplicação que gera páginas dinâmicas: - CGI: (Common Gateway Interface - interface de gateway comum): permite que o servidor Web execute um programa. Na plataforma Windows 32 bits, esse deve ser um programa .EXE de modo texto (ou aplicação de console) que lê dados a partir da entrada padrão e grava dados na saída padrão. Os dados "escritos" na saída padrão serão mostrados como conteúdo da página Web dinâmica. CGI é suportado por todos os softwares de servidor Web existentes.

Page 64: Delphi 4 Com Oracle

- Win-CGI: variação do CGI criada especificamente para programas Windows e suportada por alguns servidores Web. Nesse caso, o programa não precisa ser uma aplicação de console, mas pode ser um programa gráfico normal. - ISAPI/NSAPI (Internet Server API/Netscape Server API): interfaces criadas pela Microsoft e Netscape, respectivamente, para seus softwares de servidor Web. Nesse caso, em vez de um programa executável, as páginas dinâmicas são geradas por uma DLL, que é carregada dinamicamente pelo servidor. Isso tem várias vantagens em relação a CGI e Win-CGI: menos ocupação de memória e maior velocidade. Como a DLL pode ficar na memória, mesmo depois que atendeu a um pedido de página, da próxima vez que ela for chamada, a consulta será mais rápida. No Delphi, você pode criar os três tipos de aplicações. Uma aplicação CGI é um programa de console, o que significa que ele não pode ter formulários (mas pode ter data modules). Já uma aplicação Win-CGI pode ter formulários, embora isso não seja realmente útil, pois ela é executada no servidor e não no computador cliente. Você deve escolher o tipo de aplicação ao criar um novo projeto. Uma DLL ISAPI/NSAPI criada em Delphi pode ser usada com os dois tipos de API: tanto pode ser usada no Microsoft Internet Information Server quanto nos servidores Web da Netscape.

URLs e aplicações de servidor Uma aplicação de servidor é chamada através de uma URL, um endereço que pode ser informado de vários modos: • Pode ser digitada pelo usuário diretamente no browser. • Pode estar associada a um "link" no qual o usuário clicou. No código HTML, seria algo como <A HREF=url ....>Clique aqui</A> • Pode ser chamada por um "formulário" HTML, com a sintaxe:<FORM ACTION=url... > Em qualquer caso, essa URL se compõe de várias partes. Por exemplo, considere a seguinte URL e suas divisões: http://www.servidor.com/progs/pesquisa.exe?nome=fulano&escopo=3 http é o protocolo usado (sempre HTTP) www.servidor.com é o nome do servidor (hostname) onde está executando o servidor Web. /progs/ é o diretório onde o programa está localizado pesquisa.exe é o nome do programa CGI que será executado nome=fulano é o primeiro parâmetro passado para o programa. O nome do parâmetro é 'nome' e o valor é 'fulano' escopo=3 é o segundo parâmetro, com nome 'escopo' e valor '3'. Note que os parâmetros são separados do nome do programa com '?' e são separados entre si pelo caractere '&'. O nome do parâmetro pode ser digitado diretamente ou, no caso de chamar a URL a partir de um formulário HTML, corresponde aos nomes dos controles no formulário HTML. Outro exemplo de URL, para uma aplicação ISAPI/NSAPI é: http://teste/scripts/produtos.dll/consultar?Codigo=3 teste é o nome do servidor Web. Se o servidor está numa Intranet, não é preciso especificar o domínio (.com) /scripts/ é o subdiretório no servidor. produtos.dll é o nome da DLL servidora, que gera páginas Web dinâmicas /consultar é a informação de caminho que será passada como um parâmetro a mais para a DLL Codigo=3 é um parâmetro adicional. A informação de caminho [path info] é usada para selecionar diferentes ações dentro do mesmo executável ou DLL. Apesar de parecer um subdiretório na URL, ela é tratada apenas como um parâmetro a mais. Uma aplicação pode tomar ações diferentes, dependendo da informação de caminho que foi passada. Num servidor Web, geralmente um diretório específico (chamado de raiz da Web) é criado para armazenar páginas Web e aplicações que geram páginas dinâmicas e os subdiretórios especificados numa URL são sempre relativos a esse diretório. Por exemplo, suponhamos que o servidor Web com o nome TESTE exista na sua Intranet e está configurado para usar o diretório C:\PROJETOS\HTML como raiz da Web. Quando o usuário digita a URL acima, contendo o caminho /scripts/produtos.dll, o servidor na verdade vai procurar o arquivo: C:\PROJETOS\HTML\SCRIPTS\PRODUTOS.DLL. Componentes para aplicações de servidor Os seguintes componentes não-visuais são usados para criar aplicações de servidor: ServerSocket e TCP: permite criar um serviço, que escuta em uma determinada porta, e permite aos clientes se conectarem através de sockets, usando o protocolo TCP. Permite aos clientes lerem e escreverem dados através da conexão. Veremos como usar o ServerSocket mais tarde. Use um ou outro desses componentes. Eles oferecem a mesma funcionalidade, mas a forma de usar é diferente.

Page 65: Delphi 4 Com Oracle

WebDispatcher: usado em aplicações CGI, Win-CGI ou ISAPI/NSAPI. Geralmente não é necessário usá-lo. Em vez dele, a aplicação pode usar um WebModule, como veremos. Só poderia ser útil em aplicações que usam um DataModule normal. PageProducer: componente que produz páginas Web dinâmicas a partir de um arquivo HTML modelo. Esse arquivo-modelo contém marcadores [tags] que devem ser substituídos dinamicamente pela aplicação. QueryTableProducer: recebe parâmetros passados pelo browser e passa esses parâmetros a um componente Query, produzindo como resultado uma "tabela" HTML. Esse componente formata cada campo da consulta em uma coluna da tabela. DataSetTableProducer: produz uma "tabela" HTML, lendo os dados de um componente DataSet qualquer. WebModule: não é um componente da paleta, mas é um tipo especial de módulo de dados (DataModule) que está presente em todas as aplicações de servidor. Um WebModule é criado automaticamente quando você cria uma aplicação de servidor.

Passos para criação da aplicação Os princípios para criar uma aplicação de servidor são os mesmos para qualquer um dos três tipos de aplicação. Basicamente você deve seguir os seguintes passos: 1) Criar uma nova aplicação de servidor no Delphi e escolher o tipo desejado: CGI, Win-CGI, ou ISAPI/NSAPI. O Delphi cria um WebModule, no qual você pode colocar componentes não-visuais. 2) Coloque nesse módulo os componentes que serão usados, como componentes para acessar bancos de dados e tabelas e/ou componentes que ajudam a gerar páginas, como PageProducer, QueryTableProducer , DataSetTableProducer etc.3) No módulo, defina uma ou mais ações. Uma ação será executada quando a aplicação for chamada por uma URL. Para cada ação, você deve definir a informação de caminho (path info) que vai diferenciá-la das outras. 4) Para cada ação, crie um procedimento para o evento OnAction e escreva o código que gera o conteúdo da página. 5) Salve e compile o projeto (não execute). 6) Copie o arquivo .EXE ou .DLL compilado para um diretório no seu servidor Web. 7) Para testar a aplicação, abra um browser e digite uma URL que vai executar a aplicação. Faremos os exemplos usando aplicações CGI, que podem ser executadas em qualquer servidor Web. No entanto, se você tiver acesso a um dos servidores da Microsoft ou Netscape, é recomendável usar um deles para o teste. Nos exemplos, vamos assumir os seguintes nomes: INSTRUTOR Será o nome de computador e hostname do servidor Web de teste. O mesmo nome é usado para acessá-lo com as funções de rede do Windows e também em URLs, como http://curso. Troque pelo nome do seu servidor para fazer os testes em sua intranet/Internet. C:\Web Será o nome do diretório onde são colocadas as páginas Web dentro do servidor INSTRUTOR. Todos os caminhos especificados em URLs são relativos a esse diretório. Para testar na sua Intranet, troque pelo diretório apropriado (por exemplo, C:\INETPUB\WWWROOT no servidor Microsoft IIS).

Consultando tabelas dinamicamente As aplicações Web mais úteis são aquelas que fazem consultas a bancos de dados, e retornam páginas com o resultado de uma pesquisa nesses bancos de dados. Vamos criar como exemplo uma aplicação que lê e mostra a tabela de produtos. Clique em File|New, escolha "Web server application" e clique Ok. Você verá uma caixa de diálogo com três opções:

Nas opções de tipo de aplicação, escolha "CGI" e clique Ok. Ao fazer isso, você verá um novo WebModule chamado WebModule1, que tem a mesma aparência de um módulo de dados. Como essa é uma aplicação de console, ela não tem e não pode ter formulários. Se você olhar no arquivo de projeto, com Project|View Source, verá uma linha contendo:{$APPTYPE CONSOLE}Essa linha foi criada para definir que o Delphi vai gerar uma aplicação de console. Antes de mais nada, salve o projeto. Dê os nomes de MWPROD.PAS para a unidade do WebModule e WPROD.DPR para o projeto. Lembre-se que o nome do projeto também será o nome da aplicação compilada (MWPROD.EXE).Nota: alguns servidores Web exigem que programas executáveis sejam colocados num diretório específico, como "/cgi-bin". Nesse módulo, coloque um componente Database para connectar com o Oracle. Clique duas vezes no componente Database. Em "Name" coloque dbProduto, na opção "Driver Name", selecione Oracle. Clique no botão "Defaults" e altere os parâmetros:

Page 66: Delphi 4 Com Oracle

SERVER NAME=nome do seu servidor OracleUSER NAME=um login válidoLANGDRIVER=DBWINUS0PASSWORD=senha do usuárioDesmarque a opção 'Login Prompt' e altere a propriedade Name para 'DbProduto'.Acrescente também um componente Table para acessar a tabela de produtos. Altere o nome para 'tblProduto', DatabaseName para ,'dbProduto' e TableName para o nome da tabela, 'PRODUTO.DB'. Ative a tabela. Coloque agora no módulo um componente DataSetTableProducer, da página Internet. Esse componente produz dados tabulares, em formato HTML, a partir de uma tabela do banco de dados (ou um dataset qualquer). Altere o seu nome para 'dstpProduto'. Na propriedade DataSet, selecione 'tblProduto'. Na propriedade Header, você pode editar um conteúdo HTML que aparece antes da tabela propriamente dita. Essa propriedade é uma lista de strings. Abra o editor para essa propriedade e digite o seguinte: <H1>Lista de Produtos</H1>Depois clique duas vezes sobre o componente para ver o resultado. Você verá uma janela como a seguinte:

A lista de colunas na parte superior direita (com "Field Name", "Field Type") deve estar inicialmente mostrar todos as colunas existente na tabela 'Produto' . Caso deseja remover alguma coluna clique no botão "" Delete na parte superior direita. Isso vai remover a coluna que esta posicionada. O botão "" Add , permite adicionar uma coluna . Se adicionar , clique no item "THTMLColumn1" e altere a propriedade FieldName, selecionando o campo que deseja da tabela.Para cada coluna, você pode também alterar a propriedade Align para determinar o alinhamento dessa coluna, ou Title.Align para alterar o alinhamento do cabeçalho de coluna. Feche essa janela. Agora o que precisamos é definir uma ação que vai mostrar o conteúdo da tabela. Para isso, clique com o botão direito no módulo de dados e escolha 'Action Editor'. Crie uma nova ação com o botão Add. Você verá um novo item na lista, chamado 'WebActionitem1'. Clique nesse item. No Object Inspector , altere a propriedade Name para 'wacLista' e a propriedade Default para true.O que distingue uma ação da outra é a propriedade PathInfo. No caso vamos deixá-la vazia, porque só teremos uma ação. Se houver mais de uma, o módulo decide qual será executada baseado na informação de caminho da URL. Por exemplo, uma URL da forma http://curso/wprod.exe/testar executaria a ação cujo PathInfo = '/testar'.Ainda no editor de ações, com a ação 'wacDefault' selecionada, clique na página de eventos do Object Inspector. No evento OnAction, crie um procedimento de evento. Ele será chamado quando a ação está sendo executada. Digite o seguinte:procedure TWebModule1.WebModule1wacListaAction (Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin Response.Content := dstpProduto.Content; end;Essa ação simplesmente coloca na propriedade Content de 'Response' o resultado da função-método Content do componente 'dstpProduto'. A função Content gera o código HTML listando os campos da tabela. Crie um procedimento para o evento OnCreate do DataModule , nele iremos abrir o componente tblProduto :procedure TWebModule1.WebModule1Create(Sender: TObject);begin tblproduto.openend;Compile essa aplicação e copie para o diretório apropriado no seu servidor Web. Para executá-la, digite a URL: http:// instrutor /wprod.exe Você deverá ver a lista de produtos.

Outros componentes Outro componente que não foi usado é o QueryTableProducer, que permite facilmente passar os parâmetros que foram informados na URL para um componente Query, que executa essa consulta. Para usá-lo em sua aplicação, coloque um componente Query no Web module. A propriedade Query do componente QueryTableProducer deve referenciar esse componente Query. Os parâmetros do comando SQL devem corresponder exatamente aos parâmetros que serão passados na URL, por exemplo: URL: http:// instrutor /pesquisarNome.exe? nomeBusca =abc& tipo =2 SQL: select Nome, PrecoVenda

Page 67: Delphi 4 Com Oracle

from Produto where Nome like :nomeBusca and Tipo = :tipo Numa das ações do seu módulo, faça algo como: Response.Content := QueryTableProducer1.Content;

Incluindo produto dinamicamentePara incluir um produto na tabela 'Produto' utilizando browser , é necessário criar uma aplicação cliente que será uma página em HTML esta irá permitir informar o código e o nome do produto. A intenção deste exemplo não é explicar a linguagem HTML , mas, como passar os parâmetros utilizados numa página HTML para o servidor de aplicação, e este irá incluir esta informação no banco de dados.

Criando uma aplicação clienteNa aplicação cliente iremos criar uma página em HTML , utilizando o bloco de notas. Então vamos abrir o bloco de notas e acrescentar os comandos abaixo:<html>

<head> <title>Cadastro de Produtos </title> </head>

<body> <form method="GET"action="//instrutor/Wprod.exe/Incluir?Nome=Nome&codigo=codigo"> <p>Código:<input type="text" name="Codigo" size="5"></p> <p>Nome:<inputtype="text" name="Nome" size="40"></p> <p><inputtype="submit" value="OK" name="BtnOk"></p> </form> </body>

</html>O código <html> indica que os comandos abaixo fazem parte da linguagem HTML. Nesta página que será gerada iremos colocar dois componentes para entrada de dados que serão mostrados através do comando: <p>Código:<input type="text" name="Codigo" size="5"></p>

<p>Nome:<inputtype="text" name="Nome" size="40"></p>Em 'input type' quando o seu conteúdo é 'text' indica que você esta criando um componente parecido com o 'Edit' do Delphi. Em 'name' foi informado o nome deste componente , e este nome será usado no Delphi para obter o nome e o código do produto, em 'size' você especificar o tamanho deste componente. Abaixo destes códigos é criado um botão 'Submit' , que desta forma estamos dizendo que quando for precionada a tecla <Enter> esse botão irá executar o método 'GET'. No comando abaixo será mostrado como este método foi criado. <form method="GET"action="//instrutor/Wprod.exe/Incluir?Nome=Nome&codigo=codigo">E em 'action' definimos a ação deste método que é chamar o servidor de aplicação WProd.exe e a ação que será executada é a Incluir(que será criada no próximo item).Salve o arquivo como 'IncluirProduto.htm'.

Criando uma aplicação servidorVamos acrescentar uma ação para incluir produtos no servidor de aplicação que criamos anteriormente , o 'WProd'. Abra o projeto 'WProd.dpr'.

Page 68: Delphi 4 Com Oracle

No módulo 'MWProd' iremos criar uma ação 'Incluir'. Para isso clique com o botão direito neste módulo e escolha 'Action Editor' . Crie um nova acão com o botão Add New (). Altere seu nome para 'WacInluir' e na propriedade PathInfo informe 'Incluir'. Agora crie um procedimento para o evento OnAction:procedure TWebModule1.WebModule1wacIncluirAction(Sender:TObject;Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);var nome : string; codigo :integer;begin nome := Request.QueryFields.Values['Nome']; codigo := strtoint(Request.QueryFields.values['codigo']); tblProduto.insert; tblproduto.fieldbyname('nome').asstring := nome; tblproduto.fiedlbyname('codProduto').asinteger := codigo; tblproduto.post; response.content := '<HTML> Produto Cadastrado </HTML>'end;Esse procedimento de evento recebe alguns parâmetros: 'Request' é um objeto da classe 'TWebRequest', que contém várias informações sobre a requisição da página, incluindo os parâmetros fornecidos. O parâmetro 'Response' é um objeto da classe 'TWebResponse', através do qual o programa retorna os dados.A propriedade QueryFields de 'Request' é uma lista de strings contendo os parâmetros enviados, na forma nome=valor. Na aplicação cliente chamamos a URL como ' <form method="GET" action="//instrutor/Wprod.exe/Incluir?Nome=Nome&codigo=codigo">', essa propriedade vai conter o valor do componente Nome e Codigo. A propriedade Values de uma lista de strings procura um nome ( no caso 'Nome' ou 'Codigo') e retorna o valor correspondente, que está depois do '='. Isso permite saber os parâmetros passados. Na página iremos mostrar que o produto foi cadastrado, caso ocorra algum erro ele retorna a mensagem de erro. Para gravar os dados na tabela produto iremos usar os métodos do componente Table que permite essa inclusão, mas poderia ter utilizado um componente 'Query'.Salve o programa e compile, em seguido copie o arquivo Wprod para o diretório apropriado no seu servidor Web. Para testar no explorer abra o arquivo 'IncluirProduto.htm'.

11 - Classes e Objetos

Criando uma classe simples

Listas de objetos

Exemplo: conta bancária

Resumo

Criando uma classe simples Uma classe, como vimos, define a estrutura de vários objetos semelhantes. Cada objeto é uma instância da classe, que tem essa mesma estrutura. Por exemplo, se quisermos representar um ponto na tela, podemos criar uma classe contendo as coordenadas x e y: type TPonto = class x, y: integer; end; Vamos usar essa classe dentro de um projeto para manter uma lista de pontos e desenhar (ligar esses pontos) quando for necessário.

Exemplo: desenho de pontos Crie um novo projeto e coloque dois botões no formulário, com as propriedades: 1º ponto: Caption: "Ligar pontos" e Name: btnLigar2º ponto: Caption: "Limpar lista" e Name: btnLimpar

Page 69: Delphi 4 Com Oracle

Na unidade do formulário, declare a classe 'TPonto', logo após a implementation, da forma como foi colocado acima. Acrescente também as seguintes declarações de variáveis: var Lista: array of TPonto; N: integer; A variável 'Lista' é um vetor de pontos, que seu tamanho será criado em tempo de execução. Cada elemento desse vetor é uma referência a um objeto da classe TPonto. A variável 'N' será usada para controlar quantos elementos estão sendo usados em 'Lista'. Como iremos criar o tamanho do vetor em tempo de execução , então no evento OnCreate do fomulário acrescente o código abaixo:procedure TForm1.FormCreate(Sender: TObject);begin setlength(lista, 30)end;Com o comando setlength definimos que o tamanho do vetor será de 30 posições.No evento OnMouseDown do formulário, vamos pegar as coordenadas do ponto que foi clicado e criar um objeto TPonto para guardá-las: procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var p: TPonto; begin p := TPonto.Create; //cria o objeto p.x := X; p.y := Y; Lista[N] := p; //guarda na lista N := N + 1; Canvas.Pixels[X,Y] := clBlue;//pinta um ponto azul end; Ao clicar no botão "Ligar pontos", vamos percorrer a lista de pontos e desenhar linhas entre eles: procedure TForm1.btnLigarClick(Sender: TObject); var k: integer; p: TPonto; begin if N = 0 then exit; // nada para ligar //definir a pos. de desenho no primeiro ponto Canvas.MoveTo(Lista[0].x, Lista[0].y); for k := 1 to N-1 do begin p := Lista[k]; Canvas.LineTo(p.x, p.y);//liga o anterior com p end; end; A última linha antes do end poderia ser escrita também: Canvas.LineTo(Lista[k].x, Lista[k].y);

Liberar memória Agora falta limpar a memória usada pelos pontos quando o usuário clica no botão "Limpar lista". Para isso, vamos percorrer o vetor de pontos e chamar o método Free em cada um deles: procedure TForm1.btnLimparClick(Sender: TObject); var k: integer; begin for k := 0 to N-1 do begin Lista[k].Free; //destrói o objeto Lista[k] := nil; //indica que não tem mais ref. end; N := 0; end;

Page 70: Delphi 4 Com Oracle

Note que quando você fizer 'Lista[k].Free', o elemento na posição 'k' do vetor passa a conter uma referência inválida a um objeto que já foi liberado. Por segurança, colocamos o valor nil nesse elemento. Salve os arquivos desse projeto como PONTOS.PAS e PONTOSP.DPR.

Listas de objetos No exemplo anterior (PONTOSP.DPR), usamos um vetor para armazenar os objetos, de acordo com o comando SetLength definimos que seu tamanho será 30 , mas este tamanho pode ser aumentado de acordo com a necessidade.Mas é necessário usar a variável N para saber quantos pontos foram adicionados. Em vez de usar vetor, iremos utilizar a classe TList, para ter uma idéia do seu funcionamento. Um objeto TList funciona como um vetor dinâmico. Quando você acrescenta um objeto a ele, ele aumenta de tamanho. Você pode também retirar objetos dinamicamente. A propriedade Count diz a quantidade de objetos existentes dentro do TList. Inicialmente ela será 0 (lista vazia). Vamos alterar o projeto anterior (PONTOSP.DPR) para usar um TList em vez de um vetor.

Exemplo: usando um objeto TList Na unidade principal do projeto (PONTOS.PAS), logo após a palavra implementation, existem algumas declarações de variáveis. Apague as declarações das variáveis 'Lista' e 'N'. Troque pelo seguinte: var Lista: TList; O objeto TList já tem a propriedade Count, por isso não é preciso uma variável separada só para guardar a contagem de elementos. Essa declaração de variável não cria um objeto TList. Ela só diz que a variável 'Lista' está apta a referenciar um objeto TList. Altere o procedimento de evento OnCreate do formulário, para inicializar essa variável, criando um objeto TList: procedure TForm1.FormCreate(Sender: TObject); begin Lista := TList.Create; end; No evento OnMouseDown do formulário, faça as seguintes alterações: begin p := TPonto.Create; //cria o objeto p.x := X; p.y := Y; Lista.Add(p); //guarda na lista Canvas.Pixels[X,Y] := clBlue;//pinta um ponto azul end; O método Add adiciona um objeto à lista e aumenta o valor de Lista.Count. Agora você deve alterar também o procedimento do 'btnLigar': begin if Lista.Count = 0 then exit; p := TPonto(lista.items[0]); Canvas.MoveTo(p.x, p.y); for k := 1 to Lista.Count - 1 do begin p := TPonto(Lista.Items[k]); Canvas.LineTo(p.x, p.y); end; end; Note que 'Lista.Count' agora faz o papel de N. A propriedade Items é um vetor que retorna um item guardado na lista, dado a posição. Mas essa propriedade retorna um item do tipo pointer (ponteiro), que pode ser várias coisas em Object Pascal: uma referência de objeto, um PChar, ou outros tipos de ponteiro. Resumindo, 'Lista', um objeto TList, não sabe o tipo de objeto que foi guardado nele e nem se importa com isso. Mas quando você vai pegar um objeto de volta, deve informar o seu tipo, o que é feito com a sintaxe TPonto(Lista.Items[k]) ou, em geral, NomeDaClasse(valor). Com isso, você está interpretando valor como sendo uma referência de objeto daquela classe.

Page 71: Delphi 4 Com Oracle

Agora falta liberar a memória dos objetos TPonto e, além disso, retirá-los do TList. Note que são duas ações separadas: se você executa Free para um objeto, você vai destruir esse objeto, mas pode ser que na lista ainda haja uma referência para ele. Abra o procedimento de evento 'btnLimparClick': var k: integer; p: TPonto; begin for k := 0 to Lista.Count - 1 do begin p := TPonto(Lista[k]); p.Free; //destrói o objeto end; // limpar a lista Lista.Clear; end; Para limpar toda a lista, usamos o método Clear. Se usássemos apenas o Clear, o objeto TList ficaria vazio, mas os objetos que ele referenciava continuariam existindo e ocupando memória. Salve o projeto novamente. Nota: a propriedade Items pode ser omitida, por exemplo:p := TPonto(Lista[k]);

Outros métodos do TList Além dos métodos e propriedade que utilizamos, a classe TList tem alguns outros que podem ser úteis, como: Insert(pos,novoItem) - Insere um item no meio, na posição 'pos' e desloca os outros para frente. (Diferente de Add, que acrescenta ao final). Delete(pos) - Remove um item, dada a posição Remove(item) - Remove um item, dada uma referência a ele. IndexOf(item) - Função que Procura um item na lista e retorna a posição onde ele está, ou -1 se não encontrou Exchange(posPrimeiro, posSegundo) - Troca os dois itens de lugar: o que está em 'posPrimeiro' e o que está em 'posSegundo' Sort(NomeFuncao) - Ordena a lista. O parâmetro é o nome de uma função de comparação que será chamada durante a ordenação. Essa função deve ser declarada como: function NomeFuncao(Item1,Item2: Pointer): Integer; A função deve retornar um valor negativo se Item1 < Item2, um valor positivo se Item1 > Item2 e zero se Item1 = Item2.

Exemplo: conta bancária Uma das utilizações de classes é criar objetos nos programas que se comportam como objetos do mundo real. Cada objeto tem campos que correspondem aos atributos ou propriedades dos objetos reais e tem métodos que correspondem às operações ou ao comportamento do objeto real. Por exemplo, se um programa faz o controle de contas de um banco, uma classe TConta pode representar contas bancárias. Cada instância dessa classe representa uma conta bancária, com seu saldo individual. As operações que podem ser feitas com uma conta envolvem depositar um certo valor em dinheiro ou retirar um valor. Vamos criar um programa que define e utiliza essa classe.

Definindo a classe Crie um novo projeto. Esse projeto automaticamente tem um formulário, que será ignorado por enquanto. Salve os arquivos do projeto, ainda sem alterações, como TESTECONTA.PAS e TESTECONTA.DPR. Vamos criar uma nova unidade separada para definir a classe TConta, porque uma unidade separada facilita reutilizar essa classe em outros projetos. Clique em File|New, escolha "Unit" e clique Ok. Salve a nova unidade como CLASSECONTA.PAS. Na parte interface da unidade, declare a classe como abaixo: unit ClasseConta;

interface

Page 72: Delphi 4 Com Oracle

type TConta = class Saldo: double;

procedure Depositar(valor: double);

procedure Retirar(valor: double);

end; Cada objeto dessa classe tem um campo 'Saldo', que guarda o saldo da conta e métodos 'Depositar' e 'Retirar' para as operações de movimentação. Esses métodos devem ser implementados na parte implementation da unidade: implementation uses SysUtils; //por causa de 'Exception'

procedure TConta.Depositar(valor: double); begin //depositar um valor Saldo := Saldo + valor; end; Note que o procedimento deve ser declarado novamente com o nome da classe (TConta.Depositar). Isso é porque na mesma unidade poderiam haver várias classes com procedimentos que têm o mesmo nome. Note também que dentro do procedimento, que é um método da classe, o campo 'Saldo' é acessado sem nome de objeto. Quando esse método for chamado, será para um objeto específico, como: conta3.Depositar(200) e nesse caso o campo acessado, 'Saldo' equivale a conta3.Saldo. De forma geral, um método da classe "sabe" qual o objeto no qual ele está executando. Se precisar de um nome para esse objeto, pode usar a variável Self. Agora no procedimento Retirar, vamos retirar um valor do saldo, mas antes, verificaremos se existe saldo disponível. Caso contrário, vamos gerar uma exceção. Uma exceção na verdade é um objeto da classe Exception ou de alguma classe derivada. procedure TConta.Retirar(valor: double); begin //retirar um valor //mas antes verificar se o valor excede o saldo if valor > Saldo then raise Exception.Create('Saldo insuficiente'); Saldo := Saldo - valor; end; Quem chamar esse método é responsável por tratar as exceções. Se o chamador não tratar, então o programa irá mostrar a mensagem de exceção 'Saldo insuficiente'.

Usando a classe Agora volte ao formulário principal. Vamos colocar vários controles no formulário, que vão permitir o gerenciamento de uma lista de nomes de pessoas, com um objeto TConta para cada pessoa. Altere o Caption do formulário para "Testando Contas". Coloque controles como na figura abaixo. O componente maior é um ListBox. Os outros são labels, botões ou Edits.

Coloque os seguintes nomes e propriedades para todos os componentes, exceto os Label: primeiro Edit: editNomelist box: lstContas, Sorted: Truebotão "Criar conta": btnCriarConta, Default: Truebotão "Exc. conta": btnExcluirContasegundo Edit: editValorbotão "Depositar": btnDepositarbotão "Retirar": btnRetirarterceiro Edit: editSaldo, ReadOnly: True, ParentColor: True Na unidade do formulário, no início da seção implementation, declare o seguinte: implementation

{$R *.DFM}

uses ClasseConta;

Page 73: Delphi 4 Com Oracle

var ContaAtual: TConta; Agora crie um procedimento para o botão "Criar conta". Nesse procedimento, vamos acrescentar o nome digitado à lista, criar um objeto TConta e associá-lo a esse nome. Para manter uma lista de objetos associados, poderíamos usar um objeto TList. Mas nesse caso existe uma forma mais fácil: um componente TListBox tem a propriedade Items, que é um objeto TStrings para guardar os nomes adicionados à lista. Esse objeto tem uma propriedade Objects, que permite guardar um objeto qualquer para cada nome da lista. No procedimento do btnCriar, faça o seguinte: procedure TForm1.btnCriarContaClick(Sender: TObject); begin ContaAtual := TConta.Create; lstContas.Items.AddObject(editNome.Text,contaAtual); lstContas.ItemIndex := lstContas.Items.Count - 1; editnome.clear;end; O método AddObject de TStrings adiciona um nome à lista e também um objeto associado. Nesse caso, usamos 'ContaAtual' para guardar o objeto criado. Quando o usuário clicar no listbox, (no evento OnClick), vamos buscar a conta associada ao item selecionado e guardar em 'ContaAtual'. Vamos também mostrar o saldo dessa conta: procedure TForm1.lstContasClick(Sender: TObject); begin ContaAtual := lstContas.Items.Objects[lstContas.ItemIndex] as TConta; MostrarSaldo; end; Para pegar de volta o objeto que foi guardado, usamos Items.Objects[posição]. Note a sintaxe "as TConta", porque a propriedade Objects retorna uma referência do tipo TObject. É preciso converter essa referência para interpretá-la como TConta. O procedimento 'MostrarSaldo' será um método da classe de formulário, que será usado em outros lugares também. Declare-o primeiro, dentro da classe de formulário: TForm1 = class(TForm)

....

private

{ Private declarations }

procedure MostrarSaldo;

public

... Agora implemente o procedimento com os seguintes comandos: procedure TForm1.MostrarSaldo; begin editSaldo.Text := FormatFloat('0.0',ContaAtual.Saldo); end; O botão "Excluir conta" deve destruir o objeto 'ContaAtual' e remover da lista o nome atual. Faça da seguinte forma: procedure TForm1.btnExcluirContaClick(Sender:TObject); begin ContaAtual.Free; ContaAtual := nil; with lstContas do Items.Delete(ItemIndex); end; Usando os métodos Depositar e Retirar Agora os botões "Depositar" e "Retirar" vão chamar os métodos correspondentes da classe TConta, no objeto 'ContaAtual'. Implemente os procedimentos de evento como abaixo: procedure TForm1.btnDepositarClick(Sender: TObject); begin ContaAtual.Depositar(StrToFloat(editValor.Text));

Page 74: Delphi 4 Com Oracle

MostrarSaldo; end;

procedure TForm1.btnRetirarClick(Sender: TObject); begin ContaAtual.Retirar(StrToFloat(editValor.Text)); MostrarSaldo; end; Execute o programa e verifique o que você pode fazer com as contas. Existe uma falha no programa, que vamos ignorar por enquanto: quando não existe conta atual selecionada (no início do programa ou quando é excluída uma conta), a variável ContaAtual = nil, e isso provoca erros de acesso à memória. A maneira correta de evitar isso é desabilitar os botões btnExcluirConta, btnDepositar e btnRetirar sempre que não existir uma conta atual e habilitá-los novamente quando for criada.

Escondendo campos O método Depositar faz uma validação do valor a ser retirado, para evitar saldo negativo. Mas nada impede um programa de alterar o campo 'Saldo' diretamente, que viola a lógica da classe: conta.Saldo := -100; Para evitar esse tipo de problema, o Delphi permite esconder campos ou métodos de uma classe com vários níveis de proteção. O ocultamento de campos ou métodos, também chamado encapsulamento, evita que sejam criados programas que possam violar a lógica interna do objeto. Vamos ocultar o campo Saldo de forma que ele não possa ser modificado. Altere a declaração da classe TConta em CLASSECONTA.PAS: type TConta = class

public procedure Depositar(valor: double);

procedure Retirar(valor: double);

function Saldo: double;

private

FSaldo: double;

end; O nome do campo foi mudado para 'FSaldo' (F de field - campo) e o seu nível de proteção foi mudado para private. Foi criada uma nova função 'Saldo', que permitirá o acesso somente de leitura a esse campo. Existem três níveis de proteção principais: public Campos e métodos públicos. Podem ser usados pela própria classe e por qualquer parte do programa. protected [Protegido]: Campos e métodos podem ser usados pela própria classe e por classes derivadas desta, que recebem o campo ou método por herança. private [Privativo]: Campos e métodos são completamente escondidos: eles só podem ser usados pela própria classe onde eles foram definidos. Se o nível de proteção não é especificado, o default é public, como antes. Vamos implementar a função 'Saldo', que simplesmente retorna o valor de 'FSaldo': function TConta.Saldo: double; begin Result := FSaldo; end; A função 'Saldo' pode ser chamada para ler o saldo, mas não pode ser usada para alterá-lo. O comando abaixo geraria erro de sintaxe: conta.Saldo := -100; Vamos substituir a variável Saldo para FSaldo nos métodos depositar e retirar.Execute o programa novamente. Note que o formulário não precisa de nenhuma alteração. Outra vantagem de usar encapsulamento também evita a criação de programas que dependem da estrutura interna do objeto. Com isso, a forma de guardar o saldo poderia mudar, sem que os clientes dessa classe (programas que usam a classe) soubessem, desde que a função Saldo continue funcionando. Por exemplo, a

Page 75: Delphi 4 Com Oracle

classe poderia guardar o saldo inicial e calcular dinamicamente o saldo atual (na função Saldo) a partir da lista de movimentações. Salve o projeto novamente.

Usando Herança A classe TConta tem um certo comportamento bem definido. Ela permite fazer depósitos ou retiradas e guarda o saldo. Mas suponhamos que seja necessário para um programa específico tirar um extrato da conta. Nesse caso, o objeto conta poderia guardar as movimentações da conta internamente e ter um método a mais para mostrar o extrato dessa conta. Em vez de alterar a classe original, o que poderia causar problemas em programas que já estão utilizando essa classe, nós vamos usar herança para criar uma nova classe derivada. Nessa classe, vamos modificar o funcionamento dos métodos Depositar e Retirar. Para isso, vamos definir métodos com os mesmos nomes, que vão substituir os métodos herdados. Vamos também criar um novo método, MostrarExtrato, que recebe um objeto TStrings (por exemplo, a propriedade Lines de um componente Memo) e mostra o extrato nessa lista de strings. Crie uma nova unidade independente (File|New, escolha "Unit" e Ok). Salve a unidade como CLASSECONTAEX.PAS. Dentro da unidade coloque o seguinte: unit ClasseContaEx;

interface // deve usar a classe já na interface, por isso o // uses será colocado aqui

uses ClasseConta, Classes; //contém TList e TStrings do Delphi

type TContaEx = class(TConta)

public

procedure Depositar(valor: double);

procedure Retirar(valor: double);

procedure MostrarExtrato(lista: TStrings);

private

Movimentos: TList;

end; Note que a classe tem um campo a mais: Movimentos, que é um objeto da classe TList. Esse objeto será usado para guardar as movimentações (depósitos e retiradas) feitas na conta. Agora, na seção de implementação, vamos criar uma classe auxiliar para guardar cada movimentação. Declare como abaixo: implementation uses SysUtils; //contém TDateTime type TMovimento = class Valor: double; Data: TDateTime; end; A classe TMovimento tem dois campos. O campo 'Valor' diz qual o valor movimentado. Se negativo, foi uma retirada. Se positivo, um depósito. O campo 'Data' registra a data e hora da movimentação.

Redefinindo Métodos Agora vamos implementar o novo método Depositar. Ele vai chamar o método herdado da classe TConta, que altera o saldo, e depois registra a movimentação em 'Movimentos': procedure TContaEx.Depositar(valor:double); var mov: TMovimento; begin

Page 76: Delphi 4 Com Oracle

inherited; mov := TMovimento.Create; mov.Valor := valor; mov.Data := Now; //função que retorna data+hora Movimentos.Add(mov); end; Repare que a primeira coisa dentro do método é o comando inherited [herdado]. Esse comando chama o método Depositar que foi herdado (TConta.Depositar) e passa o valor como parâmetro. Isso garante que nós estamos acrescentando funcionalidade à conta, e não substituindo o que já funcionava. O método herdado nesse caso tem que ser chamado para continuar a atualizar o saldo. O método Retirar é bem semelhante, só que ele deve inverter o sinal do valor no objeto 'mov': procedure TContaEx.Retirar(valor:double); var mov: TMovimento; begin inherited; mov := TMovimento.Create; mov.Valor := -valor; //note o sinal "-" (menos) mov.Data := Now; Movimentos.Add(mov); end; Agora, o método MostrarExtrato deve percorrer a lista de movimentações e gerar descrições na lista de strings. Ele vai gerar uma lista de strings da forma: 20/10/95 10:10:13 - Depósito: 1234,00 20/10/95 12:22:53 - Retirada: 500,00 22/10/95 09:07:23 - Depósito: 2100,00 Saldo atual: 2834,00 Implemente esse método como abaixo: procedure TContaEx.MostrarExtrato(lista:TStrings); var k: integer; mov: TMovimento; s: string;

begin lista.Clear; for k := 0 to Movimentos.Count - 1 do begin mov := TMovimento(Movimentos[k]); s := FormatDateTime('dd/mm/yyyy hh:mm:ss', mov.Data) + ' - '; if mov.Valor >= 0 then s := s + 'Depósito: ' else s := s + 'Retirada: '; s := s + FormatFloat('0.00',mov.Valor); lista.Add(s); end; end;

Construtores e destrutores A classe ainda não vai funcionar do jeito que foi definida. Da primeira vez que o método Depositar ou Retirar for chamado, a variável 'Movimentos:TList' ainda não foi inicializada. Ela deve ser inicializada antes de qualquer operação, criando um objeto 'TList' O ideal é inicializar essa variável quando é criado o objeto TContaEx e destruir o objeto TList quando for destruído o objeto TContaEx. Para isso, existem rotinas especiais chamadas de construtores e destrutores. Um construtor é um método executado para criar e inicializar o objeto. Não é possível criar um objeto sem construtor. A classe TObject tem um construtor chamado Create, que já usamos para criar vários tipos de objetos. No nosso caso, vamos redefinir o construtor Create para acrescentar ações adicionais.

Page 77: Delphi 4 Com Oracle

Um destrutor é um método executado para destruir o objeto. Um destrutor que já usamos é o método Free, mas, internamente, Free chama um outro método, Destroy para destruir realmente o objeto. Vamos redefinir o Destroy. Na classe TContaEx, acrescente: type TContaEx = class(TConta)

public

constructor Create; destructor Destroy; override;

... A diretiva override será vista em detalhes mais tarde. Na implementação da unidade, acrescente: constructor TContaEx.Create; begin inherited; Movimentos := TList.Create; end; Note que o construtor herdado geralmente deve ser chamado. Para isso, usa-se o comando inherited. Agora acrescente o destrutor: destructor TContaEx.Destroy; begin Movimentos.Free; inherited; end; Note que no destrutor, primeiro a classe libera os objetos que criou (Movimentos, no caso), depois é que chama o destrutor herdado. Se essa ordem for invertida, quando o inherited retorna, já não existe mais um objeto para destruir e o campo 'Movimentos' pode não ser mais válido.

Usando a classe herdada Para testar a classe herdada, retorne ao formulário principal. Acrescente na cláusula uses o nome da unidade ClasseContaEx: uses ClasseConta, ClasseContaEx; Agora troque todas as referências a TConta por TContaEx, por exemplo: var ContaAtual: TContaEx;

procedure TForm1.btnCriarContaClick(...); ... ContaAtual := TContaEx.Create;

procedure TForm1.lstContasClick(...); ... ContaAtual := lstContas.Items.Objects[k] as TContaEx; Vamos também acrescentar mais componentes ao formulário, para mostrar o extrato da conta. Acrescente do lado direito um botão, com Caption = "Mostrar extrato" e nome 'btnExtrato'. Também coloque um componente Memo, com o nome de 'memoExtrato'. Quando o usuário clica em "Mostrar extrato", o programa mostra em Dica: você pode usar [Ctrl+R] para substituir texto. Digite 'TConta' no texto de origem, 'TContaEx' no destino, desmarque "Prompt on Replace" e clique em "Replace All". 'memoExtrato' o extrato da conta atual: procedure TForm1.btnExtratoClick(Sender: TObject); begin ContaAtual.MostrarExtrato(memoExtrato.Lines); end;

Page 78: Delphi 4 Com Oracle

Agora salve o projeto como ele está.

Métodos virtuais Quando você chama um método em uma variável de objeto, como por exemplo: contaAtual.Depositar(valorDigitado), o Delphi deve decidir qual método "Depositar" será chamado. No programa anterior, existem dois métodos com o mesmo nome: Depositar da classe TConta e Depositar da classe TContaEx. Uma variável de objeto, como contaAtual, tem um tipo declarado, mas ela pode conter um objeto de outra classe, por exemplo: var conta: TConta; begin conta := TContaEx.Create; conta.Depositar(valorDigitado); end; No código acima, a variável 'conta' é declarada do tipo-classe TConta, mas ela contém um objeto da classe TContaEx. Qual o método "Depositar", de qual classe, será chamado? Isso depende da declaração do método. A maioria dos métodos do Delphi são métodos estáticos. Quando um método é estático, o compilador decide estaticamente, em tempo de compilação, qual método chamar, baseado no tipo da variável declarada. No caso acima, será chamado o método 'Depositar' da classe 'TConta'. Um método também pode ser virtual ou dinâmico. Ambos têm o mesmo efeito: quando o Delphi vai chamar o método, ele verifica dinamicamente, em tempo de execução, qual a classe do objeto, e chama o método adequado daquela classe. A desvantagem dos métodos virtuais ou dinâmicos é que a chamada do método leva um pouco de tempo a mais, mas a vantagem é que um programa pode usar uma variável de objeto, e chamar seus métodos, sem saber o tipo específico (classe específica) do objeto em tempo de execução. Classes derivadas podem redefinir [override] o método e ter certeza de que ele será chamado no lugar do método herdado de mesmo nome. Como veremos mais tarde, a VCL do Delphi usa bastante métodos virtuais.

Declarando um método virtual Um método deve ser declarado como virtual na classe base. As classes derivadas, quando redefinem esse método, devem declará-lo novamente com o mesmo nome e com o mesmo número e tipo de parâmetros. Além disso, a diretiva override deve ser usada na classe derivada para indicar que um método está sendo substituído. Abra o projeto anterior, TESTECONTAP.DPR. Faça um teste para ver o que ocorre com métodos estáticos. No procedimento do botão "Depositar", modifique o seguinte: procedure TForm1.btnDepositarClick(Sender: TObject);

var

conta: TConta;

begin

conta := ContaAtual;

conta.Depositar(StrToFloat(editValor.Text));

MostrarSaldo;

end; Execute o programa, crie uma conta, deposite e retire valores e mostre o extrato da conta. Os depósitos não aparecem no extrato, porque o que está sendo chamado é o método estático Depositar da classe TConta, que não registra o depósito. Abra a unidade 'ClasseConta'. Dentro da declaração de classe, acrescente a diretiva virtual aos métodos Depositar, Retirar e Saldo: type TConta = class

public

Page 79: Delphi 4 Com Oracle

procedure Depositar(valor: double); virtual;

procedure Retirar(valor: double); virtual;

function Saldo: double; virtual;

private

FSaldo: double;

end; Agora abra a unidade ClasseContaEx. Na declaração da classe, acrescente a diretiva override: type TContaEx = class(TConta) public ...

procedure Depositar(valor: double); override;

procedure Retirar(valor: double); override; ...

end; Agora execute o programa novamente. Ele vai mostrar os extratos, porque o Delphi vai determinar dinamicamente qual a classe do objeto, para saber qual método será chamado. Em geral, se você cria um método dentro de uma classe, e imagina que ele será redefinido [overriden], deve declará-lo como virtual ou dynamic e as classes derivadas devem redeclará-lo com override. A única diferença entre métodos virtual e dynamic é que os primeiros são mais rápidos, mas gastam ligeiramente mais memória para cada classe. Nota: É possível ter dois métodos com o mesmo nome, mas diferentes número/tipos de parâmetros. O nome + parâmetro indica a assinatura de um método. Ex :procedure Depositar(valor :Double);Overload;procedure Depositar(conta :TConta);Overload;

Resumo A programação orientada a objeto (POO) é uma forma de programação que tenta construir os programas como coleções de classes e objetos, relacionados entre si. Uma classe é uma estrutura de dados, definida em tempo de projeto, que contém também os procedimentos que manipulam esses dados. Um objeto é uma instância de uma classe, criada em tempo de execução, de acordo com a estrutura da classe e que pode ser manipulada com os métodos da classe. A idéia da POO é criar o mínimo possível de dependências de uma parte do programa em relação à outra. (As "partes" do programa são as classes e seus objetos). Com isso, uma parte pode ser alterada sem afetar as outras. Num programa orientado a objeto bem construído, o efeito de uma alteração no código é menor e mais previsível. Em outros tipos de programação, uma alteração no código pode causar problemas, bugs, inconsistências etc. em várias partes do programa. Além disso, a programação orientada a objeto facilita bastante a reutilização de código. Sem POO, é possível reutilizar código, por exemplo, procedimentos e funções isolados, mas isso tem suas limitações. Na POO, vários conceitos são importantes: encapsulamento, herança e polimorfismo. Encapsulamento permite esconder membros de uma classe para evitar que outras classes alterem a estrutura interna do objeto. Por exemplo, um "cliente" da classe TConta não precisa saber como ela guarda as movimentações da conta e não tem acesso para modificar essas informações (e falsificar um extrato, por exemplo). Com o encapsulamento, você garante que o objeto pode alterar sua representação interna sem afetar nenhuma outra classe. Ele só não pode alterar a sua interface, isto é, o conjunto de métodos e dados públicos (por exemplo, Depositar(valor:double) e Retirar(valor:double)). O encapsulamento então força uma separação entre interface e implementação, onde a interface geralmente é fixa ou muda pouco enquanto a implementação de uma classe tem total liberdade para mudar.

Page 80: Delphi 4 Com Oracle

Herança permite criar uma classe derivada a partir de uma classe base. A classe derivada herda a implementação da base, reutilizando o código que já existe, que já foi testado e aprovado. A classe derivada pode modificar ou estender o funcionamento de métodos já existentes. Uma classe derivada herda também a mesma interface da classe base. Isso quer dizer que em qualquer lugar que possa ser usado um objeto da classe TBase, ele pode ser substituído por um objeto da classe TDerivada (porque ele suporta os mesmos métodos). Essa possibilidade, de substituir um objeto por um de outra classe derivada, é chamado polimorfismo. Com o polimorfismo, um "cliente" de uma classe (procedimento que usa objetos da classe) pode ser escrito sem se preocupar com a subclasse específica que ele vai manipular. Futuramente, se novas subclasses são criadas, com capacidades adicionais, o cliente não precisa nem mesmo saber que elas existem, mas pode usá-las como objetos da classe TBase. É claro que ainda é possível criar programas mal-estruturados, confusos, e instáveis com POO. Mas se você entender corretamente os conceitos e aplicar da forma correta os recursos da linguagem, corre um risco menor de acontecer isso.

12 - Criando Componentes

Pacotes da VCL

Hierarquia da VCL

Componentes e pacotes

Desenhando o controle

Criando um controle de edição

Pacotes da VCL Um pacote [package] é um tipo de DLL que contém várias unidades, e classes de componentes. Quando você compila um programa, o conteúdo do pacote pode ser incorporado ao executável, ou pode continuar separado dentro do arquivo de pacote. Toda a VCL do Delphi é dividida em pacotes. Em modo de projeto [design-time], os pacotes são carregados para a memória, e os seus componentes ficam disponíveis na paleta de componentes. Durante a execução do programa, o executável pode carregar pacotes de tempo-de-execução [runtime packages] ou pode ser compilado incluindo todo o conteúdo dos pacotes.

Pacotes design-time Um pacote de tempo de projeto [design-time] é usado pelo ambiente do Delphi durante o projeto do programa. Ao iniciar, o Delphi carrega vários pacotes design-time, e mostra na paleta de componentes o conteúdo desses pacotes. Cada componente está contido em um pacote e cada pacote contém um ou mais componentes. Se houver alguns componentes que você não está usando, você pode remover alguns pacotes da memória. Para fazer isso, clique em Component|Install Packages. Você verá uma lista dos pacotes instalados. Cada um tem uma descrição, como "Delphi Database Components" e um nome de arquivo, que você pode ver ao clicar na descrição.

Pacotes de run-time Ao compilar um projeto em Delphi, normalmente todas as unidades do Delphi utilizadas são incluídas no programa executável. Com isso, um programa fica com um tamanho mínimo de 150 Kb, mas chega até 800 Kb ou mais quando usa os componentes de banco de dados. Desse código, só uma pequena parte (cerca de 60 Kb ou pouco mais) corresponde ao que foi escrito pelo programador. Um pacote de run-time é uma DLL que contém unidades do Delphi e seus componentes, que pode ser mantida separadamente do executável principal. Quando você compila com a opção de pacotes de run-time, o programa executável fica bem menor, mas em compensação ele depende dos arquivos de pacote externos para funcionar (como VCL40.BPL, por exemplo).

Page 81: Delphi 4 Com Oracle

Vale a pena usar pacotes de run-time principalmente se você tiver um sistema composto de vários arquivos EXE separados. Quando todos eles estiverem carregados em memória, vão compartilhar o código dos pacotes. Como um pacote é uma DLL, o Windows garante que não haverá duas cópias do mesmo pacote em memória. Outra vantagem é que, com um executável menor, para fazer "upgrades" no programa, basta copiar um arquivo de menos de 100 Kb, em vez de levar um executável com todas as unidades embutidas. Para usar pacotes de run-time, clique em Project|Options e na página "Packages". Marque a opção "Build with runtime packages" e recompile o programa. Note a diferença no tamanho do arquivo EXE com e sem essa opção. Se você usar essa opção, precisa saber quais os pacotes que devem ser distribuídos com o seu arquivo EXE. Todos os arquivos de pacote estão no diretório de sistema do Windows: C:\WINDOWS\SYSTEM (95) ou C:\WINNT\SYSTEM32 (NT). Você deve instalá-los (com o InstallShield por exemplo) no mesmo diretório no computador de destino. O pacote VCL40.dcp contém os componentes e funções mais usados, exceto os componentes de bancos de dados. Os outros pacotes contém os componentes como indicado na tabela abaixo: VCL40 A maioria dos componentes e unidades do Delphi. VCLDB40 Unidades e componentes de banco de dados (páginas Data Access e Data Controls), exceto DBChart (v. TEEDB40) VCLX40 Componente da página Win32: CheckListBox; da página Samples: ColorGrid; da página System: DdeClientConv, DdeServerConv, DdeClientItem, DdeServerItem, MediaPlayer; da página Win3.1 (obsoletos): Outline, TabbedNotebook, FileListBox, DirectoryListBox, DriveComboBox, FilterComboBox. VCLDBX40 Componentes de banco de dados obsoletos: da página Win3.1: DBLookupCombo, DBLookupL DblookupList; componente Report (do ReportSmith), que normalmente não aparece em nenhuma página. DSS40 Componentes da página Decision Cube. INET40 Componentes da página Internet: todos exceto QueryTableProducer e DataSetTableProducer (v. abaixo). INETDB40 Componentes da página Internet: QueryTableProducer e DataSetTableProducer. QRPT40 Componentes do QuickReport, da página QReport. TEE40 Componente da página Additional: Chart. TEEDB40 Componente da página Data Controls: DBChart. IBEVNT40 Componente da página Samples: IBEventCtrl.

Estrutura de arquivos de pacote Para criar um pacote, você cria um arquivo fonte de pacote (.DPK). Vamos criar alguns pacotes mais tarde, mas por enquanto vamos abrir um arquivo já existente para ver como é sua estrutura. Clique no botão na janela do Delphi. Em Listar arquivos do tipo, selecione "Delphi package source (*.dpk)". Selecione o diretório de instalação do Delphi e, dentro dele, o diretório LIB. Abra o arquivo DCLUSR40.dpk. Um arquivo de pacote aparece com duas páginas: "Contains", que lista quais as unidades incluídas nele e "Requires", que lista quais os pacotes dos quais ele depende. Outra forma de ver o conteúdo do arquivo é no editor: clique na janela do pacote com o botão direito e em View Source. Você verá algo como: package dclusr40;

{$R *.RES}

{$ALIGN ON}

...

{$DESCRIPTION 'Borland User Components'}

{$DESIGNONLY}

{$IMPLICITBUILD ON}

contains

...

requires

Page 82: Delphi 4 Com Oracle

vcl40;

end. No início do arquivo, após o cabeçalho"package dclusr40;" está a representação em texto das opções de compilação do pacote. A opção {$DESCRIPTION...} é a descrição do pacote, um texto que aparece quando ele é mostrado no Delphi. E {$DESIGNONLY} indica que esse pacote é usado só em modo de projeto [design-time]. Normalmente não é preciso editar essas opções diretamente, apenas através da caixa de diálogo "Options", chamada a partir da tela do pacote, no botão Options. A cláusula contains só está presente se houver alguma unidade dentro do pacote (nós vamos acrescentar algumas mais tarde). Ela lista as unidades que serão incluídas. A cláusula requires declara quais são os pacotes dos quais este pacote depende, no caso, apenas VCL40. Esses pacotes devem estar carregados em memória para poder usar DCLUSR40. Para compilar o pacote, basta usar o botão Compile na janela do pacote. Isso vai gerar o arquivo DCLUSR40.bpl. Para instalar um pacote na paleta de componentes, mostrando todos os componentes que fazem parte dele, você pode clicar no botão Install.

Hierarquia da VCL Você pode criar seu próprio tipo de componente e acrescentá-lo à paleta de componentes do Delphi para usar em qualquer outro projeto. Essa é uma forma de reutilização de código bem maior, mas é mais complicado do que outros recursos do Delphi, como modelos de componentes. Para criar um componente, você deve criar uma nova classe de componente. Essa classe deve ser baseada no mínimo na classe TComponent do Delphi, que é ancestral de todos os componentes. Mas pode ser baseada em outras subclasses. É importante entender antes a hierarquia da VCL e quais são as classes envolvidas:

A classe TComponent Componentes não visuais, como TTable, TTimer e TMainMenu, derivam da classe TComponent. Componentes visuais (controles) são derivados de TComponent também, mas indiretamente através de TControl. Essa classe define propriedades e métodos que permitem manipular os sub-componentes de um componente, por exemplo: property Name: string; - o nome do componente property Tag: integer; - um número identificador As propriedades ComponentCount e Components permitem percorrer a lista de componentes possuídos pelo componente (por exemplo, a lista de componentes de um formulário). Por exemplo, para chamar um procedimento qualquer para todos os componentes possuídos pelo componente 'c': for k := 0 to c.ComponentCount -1 do UmProcedimentoQualquer(c.Components[k]); Um componente, digamos 'c', que possui outro componente, digamos 'd', é o dono de d. A propriedade Owner de d faz referência a 'c'. Ou seja, se d = c.Components[k] para algum elemento 'k', então d.Owner = c. O método FindComponent permite procurar um componente por nome, por exemplo: comp := c.FindComponent('Nome');

A classe TControl A classe TControl é derivada de TComponent. Todos os componentes visuais são derivados de TControl. Eles têm as propriedades Left, Top, Width e Height que dizem as coordenadas do retângulo que o componente ocupa na tela, em pixels. Essas coordenadas (Left e Top) são sempre relativas ao pai [parent] do controle, que é um outro controle que o contém visualmente. Um formulário é um controle que não tem pai. Nesse caso, as coordenadas são relativas à tela. A propriedade Parent de um controle faz referência ao pai dele. A classe TControl também define várias propriedades que são herdadas por todos os controles. Algumas delas são públicas, como Align, Enabled, Visible, Hint. Mas algumas delas estão encapsuladas (com nível protected), de forma que não aparecem em qualquer controle. Se uma classe derivada quiser, ela pode simplesmente publicar essas propriedades. São elas: Text e Caption (que fazerm exatamente a mesma coisa), ClientHeight, ClientWidth, Color, Cursor, DragCursor, DragMode, Enabled, Font, ParentColor, ParentFont, ParentColor, ParentShowHint, PopupMenu e ShowHint. Existem várias outras propriedades, principalmente para uso interno da classe TControl. Existem dois tipos de controles, que correspondem a subclasses de TControl. Controles de janela são controles que internamente possuem uma janela do Windows, que tem uma superfície de desenho própria. E um controle

Page 83: Delphi 4 Com Oracle

de janela geralmente pode conter outros controles. Já os controles gráficos não são controlados pelo Windows, e usam a janela de seu controle-pai para desenhar o conteúdo.

A classe TWinControl Controles de janela são identificados como janelas internamente pelo Windows. Uma janela é uma área retangular da tela que possui uma superfície de desenho independente e pode conter outras janelas. Todos os controles de janela são derivados de TWinControl. Um controle de janela pode: - Receber o foco de teclado enquanto a aplicação está executando. Por exemplo, TEdit, TDBEdit, TMemo, TButton são controles de janela, porque eles podem receber o foco e processar teclas. - Pode (geralmente) conter outros controles, ou seja, pode ser o pai de outro controle qualquer [parent]. Por exemplo, um controle TPanel ou TTabControl pode conter outros colocados sobre ele. - Tem uma superfície de desenho, ou Canvas, independente de outros controles. Isso permite que ele fique "por cima" de qualquer outro controle. - Tem uma alça de janela [window handle], um número que identifica internamente a janela do controle. A propriedade Handle permite consultar esse identificador. Para usar algumas funções do Windows, é possível passar nomeControle.Handle quando é esperando uma alça de janela (parâmetro do tipo HWND).

A classe TGraphicControl Um controle gráfico é um controle que não tem uma janela do Windows. Ele desenha a si mesmo em cima da janela do controle pai. Um controle gráfico não pode receber o foco de teclado, nem conter outros controles. Os controles TLabel e TImage são controles gráficos. Você pode ver que num formulário, os controles gráficos, comoTLabel ficam sempre por baixo de controles de janela. Controles gráficos são mais "leves" em termos de utilização de memória. Controles gráficos são aqueles que não são derivados de TWinControl. Um controle gráfico poderia ser derivado diretamente de TControl, mas existe uma classe intermediária mais usada: TGraphicControl, que tem algumas facilidades a mais para desenho sobre a janela. A propriedade Canvas de TGraphicControl contém um objeto de desenho, da classe TCanvas, que permite desenhar qualquer coisa sobre a área do controle. O método Paint, que é virtual, deve ser redefinido nas classes derivadas para desenhar o conteúdo do controle.

Resumo da Hierarquia TComponent (todos os componentes)+--- TControl (todos os controles) | +--- TGraphicControl (controles gráficos) | +--- TWinControl (controles de janela)+--- outros (componentes não-visuais) Quando você vai criar uma nova classe de componente, deve escolher qual a classe da qual ela será derivada, dependendo do tipo de componente a ser criado.

As classes TCustomXXX Se você usar o Object Browser (View|Browser) com um projeto qualquer, verá que para cada classe de componente VCL (p.ex. TEdit), existe uma classse ancestral logo acima com a palavra "Custom" (p.ex. TCustomEdit). O objetivo dessas classes intermediárias é facilitar a criação de componentes semelhantes, mas que não usam todas as propriedades. Por exemplo, se você quer criar um novo componente de edição, ele pode ser derivado de TEdit diretamente ou de TCustomEdit. No primeiro caso, ele herda todas as propriedades, métodos e eventos de TEdit. Pode ser que algumas dessas características não se apliquem ao seu tipo de componente. Nesse caso, você ainda pode ocultar propriedades, eventos ou métodos herdados. Já se você derivar uma classe de TCustomEdit, ela herda as propriedades, eventos e métodos, mas a maioria deles já está oculta. Nesse caso, você pode mostrar (tornar públicas) apenas as propriedades, eventos e métodos que você deseja que sejam usadas.

Componentes e pacotes Cada componente da VCL é colocado dentro de um pacote de tempo de projeto [design-time package]. Como vimos antes, você pode escolher quais pacotes são carregados no ambiente do Delphi em determinado momento, o que vai determinar quais são os componentes disponíveis.

Page 84: Delphi 4 Com Oracle

Quando você vai criar e instalar um novo componente no Delphi, deve escolher em qual pacote ele será colocado ou criar um novo. O Delphi vem com um pacote default chamado DCLUSR40.DPK, com a descrição "Borland Users". Você pode colocar seus componentes nesse pacote, ou criar um outro para facilitar a separação deles. Se você criar um outro pacote, fica mais fácil distribuir seus componentes para outros usuários do Delphi.

Criando um controle gráfico Vamos criar um controle gráfico, que apenas desenha uma elipse no formulário. Vamos chamá-lo de 'Elipse' (o nome da classe será 'TElipse'). Um controle gráfico é as vezes mais simples, porque ele não pode receber o foco de teclado (e não tem eventos de teclado). Esse controle terá uma propriedade 'LarguraBorda', que permite alterar a largura da borda da elipse e terá também a propriedade 'Color' padrão para alterar a cor da elipse.

Usando o New Component Wizard A forma mais rápida de criar uma classe componente é usar o New Component Wizard do Delphi. Para isso, clique em Component|New Component. Preencha as opções como na figura abaixo:

Em "Ancestor Type", você escolhe qual a classe da VCL que será a ancestral (classe base) da sua classe. Como vamos criar um controle gráfico, escolha "TGraphicControl". Em "Class Name", digite "TElipse". Em "Palette Page", você pode escolher qual a página onde o novo componente será instalado ou digitar um nome para criar uma nova página. Em "Unit file name", você vai determinar onde a unidade do componente será criada. O nome de arquivo default é ELIPSE.PAS. Mude o diretório para "D:\CursoDelphi". Você deve também acrescentar esse diretório embaixo ao "Search path" [caminho de busca] do Delphi: acrescente um ponto e vírgula e mais o diretório. Quando o pacote do componente for compilado, o Delphi vai procurar as unidades nesse diretório. Clique em "OK" e a nova unidade ELIPSE.PAS aparecerá no editor. Salve a unit 'Elipse.pas'. Note que não é preciso criar um projeto para criar uma classe de componente, basta uma unidade.

A classe de componente O Delphi já declara na unidade Elipse uma classe de componente chamada TElipse. Nada impede que você inclua mais de uma classe de componente na mesma unidade, e o nome da unidade é independente do nome das classes. A classe foi declarada pelo Delphi com: type TElipse = class(TGraphicControl)

private { Private declarations }

protected { Protected declarations }

public { Public declarations }

published { Published declarations }

end; As quatro divisões da classe determinam os níveis de proteção dos campos: private, protected, public e published. As três primeiras, como já vimos, permitem acesso à propria classe apenas (private), às classes derivadas apenas (protected) e a todas as classes (public). O nível published funciona como public, só que propriedades e eventos declarados nesse nível aparecem também no Object Inspector em tempo de projeto, enquanto os do nível public só podem ser usados em tempo de execução.

Desenhando o controle Como estamos criando um controle gráfico, devemos desenhar alguma coisa na tela para que o controle tenha alguma aparência. A classe TGraphicControl declara um método virtual chamado Paint. Esse método não faz

Page 85: Delphi 4 Com Oracle

nada, mas é chamado internamente sempre que for necessário redesenhar o conteúdo do componente. Ele existe apenas para "segurar o lugar", para que você defina um método com o mesmo nome que irá realmente desenhar o conteúdo do componente. Declare o método Paint na parte public da classe: public { Public declarations }

procedure Paint; override; A diretiva override é necessária porque o método é virtual na classe ancestral, TGraphicControl. Agora implemente esse método na parte implementation da unidade: procedure TElipse.Paint; begin { 'Canvas' é uma propriedade que dá acesso a uma área de desenho gráfica } Canvas.Ellipse(0,0,Width,Height); end; Por enquanto iremos fazer somente isto. Vamos compilar e testar esse componente.

Instalando o componente Para instalar um componente na paleta, você deve escolher o pacote onde ele será instalado ou criar um novo. No caso, vamos escolher o pacote "Borland User Component" (DCLUSR40.DPK). Clique em Component|Install Component:

Em Unit file name, aparece automaticamente o nome do arquivo de unidade. Em Search Path, já existe o diretório que foi colocado. Em Package file name, aparece o nome do pacote DCLUSR40.DPK, que já é o default, mas se deseja criar um pacote escolha a página "New into page" . Clique em Ok. Agora vai aparecer uma mensagem avisando que o pacote será compilado (built) e depois instalado. Clique em "Yes". Depois uma mensagem vai indicar que o pacote foi recompilado e o componente TElipse foi acrescentado à paleta. Agora vai aparecer uma janela com o conteúdo do pacote:

Deixe-a aberta porque será usada quando você quiser recompilar o componente. Clique na página Samples e você verá um novo ícone de componente genérico, para o componente Elipse. Clique nesse ícone e coloque um componente no formulário. Ele deve desenhar uma elipse na posição onde foi criado. Você pode também criar várias cópias desse componente.

Acrescentando propriedades Vamos permitir ao "usuário" desse componente (ou seja, o programador que usa esse componente em um formulário, que pode ser você mesmo) que possa alterar a espessura da linha de contorno da elipse com uma nova propriedade chamada LarguraBorda. Uma propriedade, do ponto de vista do usuário do componente, é um campo de dados, que ao ser modificado, altera alguma característica visual do componente. Do ponto de vista do criador do componente, uma propriedade apenas simula um campo de dados. O criador pode definir o que acontece quando a propriedade é lida ou modificada. Quando o usuário do componente modifica uma propriedade, pode ser que internamente, o Delphi chame um método (procedimento) para alterar essa propriedade. Ou quando a propriedade é lida, o Delphi pode chamar um método (função) para ler (ou calcular) o seu valor. Vamos criar uma propriedade para tornar isso mais claro. Isso envolve declarar várias coisas na classe: na parte private, um novo campo de dados, um novo procedimento e na parte published a própria propriedade: type TElipse = class(TGraphicControl)

private { Private declarations } FLarguraBorda: integer; //campo usado pela prop.

procedure SetLarguraBorda(larg:integer); //procedimento usado pela propriedade ...

Page 86: Delphi 4 Com Oracle

published { Published declarations }

property LarguraBorda:integer read FLarguraBorda write SetLarguraBorda;

end; A declaração da propriedade diz o tipo de dados (integer) e o que será feito quando ela é lida (read FLarguraBorda) e gravada (write SetLarguraBorda). Nesse caso, ao ler a propriedade, o campo FLarguraBorda será lido. Ao modificar a propriedade, o procedimento SetLarguraBorda será executado, por exemplo: Elipse1.LarguraBorda := 2; Esse comando na verdade será executado internamente como: Elipse1.SetLarguraBorda(2); Você deve implementar o procedimento SetLarguraBorda, na parte implementation: procedure TElipse.SetLarguraBorda(larg:integer); begin if larg <= 0 then raise Exception.Create( 'Largura deve ser positiva'); FLarguraBorda := larg; Repaint; //redesenha o conteúdo end; O procedimento verifica se o valor que foi passado ('larg') é positivo. Caso contrário, ele gera uma exceção com o comando raise. Isso impede que essa propriedade possa ter valores inválidos. Caso o valor esteja correto, ele guarda o valor de 'larg' no campo FLarguraBorda e chama o método Repaint que força o redesenho do componente, chamando o método Paint indiretamente. Agora devemos usar o valor que foi guardado em FLarguraBorda para pintar a elipse. Vamos usar também a propriedade Color para definir a cor da elipse. Altere o procedimento Paint: procedure TElipse.Paint; begin Canvas.Pen.Width := FLarguraBorda; Canvas.Brush.Color := Color; Canvas.Ellipse(0,0,Width,Height); end; Antes de testar vamos criar também um construtor para inicializar essa propriedade.

Redeclarando uma propriedade O seu componente não vai mostrar as propriedades e eventos herdados de TGraphicControl, porque a maioria deles são declarados como private na classe TGraphicControl. Para que elas apareçam, você pode redeclará-las como published. Na classe de formulário, faça as seguintes alterações: published { Published declarations }

property LarguraBorda:integer

read FLarguraBorda write SetLarguraBorda;

property Align;

property Color;

property Enabled;

property Visible;

property OnClick;

property OnDblClick;

property OnDragDrop;

Page 87: Delphi 4 Com Oracle

property OnDragOver;

property OnEndDrag;

property OnMouseDown;

property OnMouseMove;

property OnMouseUp;

property OnStartDrag;

end; Note que os eventos são declarados com property. Veremos o porquê mais tarde.

Criando um construtor Um dos problemas do componente é que o tamanho dele ainda não está correto, porque as propriedades Width e Height são inicializadas com zero. Para poder inicializar estas e outras propriedades (inclusive LarguraBorda), vamos criar um construtor na classe de componente. Dentro da classe, na parte public, declare: ...

public { Public declarations }

constructor Create(aOwner:TComponent); override;

... O construtor deve seguir o mesmo padrão da classe TComponent, por isso deve ter um parâmetro do tipo TComponent, que será o dono desse componente. O dono geralmente é um formulário e controla a alocação de memória do componente: se o dono sair da memória, o componente também sai. Vamos implementar o construtor (na parte implementation), inicializando as propriedades necessárias: constructor TElipse.Create(aOwner:TComponent); begin inherited; FLarguraBorda := 1; Width := 100; Height := 100;end;

Recompilando o componente Para recompilar o componente, você deve clicar na janela do pacote DCLUSR40.DPK. Se ela estiver fechada, abra novamente o arquivo de pacote no diretório LIB debaixo do diretório do Delphi. Na janela de pacote, clique no botão "Compile". O componente deverá ser atualizado. Clique no ícone dele e coloque em um formulário para testar o seu funcionamento. Note o que acontece quando você altera a propriedade LarguraBorda. Se você tentar entrar com um valor negativo, o componente mostra a mensagem de erro. Quando você aumenta, a linha de contorno fica mais espessa. Quando você altera a propriedade Color, a cor do componente será alterada. Para salvar o que foi feito, salve a unidade, ELIPSE.PAS e o arquivo de pacote, DCLUSR40.DPK. Não precisa salvar o projeto de teste.

Criando um controle de edição Agora vamos criar um outro tipo de componente, um controle de edição baseado na classe TMemo, que permite apenas edição de valores numéricos, alinhados à direita.

Criando a classe de componente Para alinhar números à direita, precisamos criar o componente baseado em TMemo e não em TEdit, porque só o TMemo tem capacidade de alinhar à direita.

Page 88: Delphi 4 Com Oracle

Vamos usar o New Component Wizard, como antes. Em "Ancestor Type", escolha "TCustomMemo". Em "Class Name", digite "TEditNum". Em "Palette Page", deixe o default, "Samples" e em Unit file name, altere o o diretório para "C:\CursoDelphi", deixando o nome de arquivo EDITNUM.PAS. Clique no botão 'Install' para instalar o componente. Em seguida irá aparecer uma janela onde é necessário especificar se vai utilizar o arquivo de pacote existente ou se iremos criar outro arquivo de pacote. Clique na página "Into new package" e coloque as opções de acordo com a figura abaixo:

Clique no botão "OK" , em seguida pergunta se você deseja isntalar o componente , confirme clicando "OK". Desta forma ele cria a unit 'EditNum.pas', instala o componente e adiciona-o ao arquivo de pacote criado. Não feche a janela que aparece o arquivo de pacote , pois, iremos utilizá-la para compilar a unit do componente.

Acrescentando propriedades Vamos criar uma propriedade numérica chamada 'Valor', que permite ler ou alterar o valor do componente, sem precisar de uma conversão de numérico para string ou vice-versa. Para isso, declare alguns itens dentro da classe 'TEditNum': type TEditNum = class(TCustomMemo)

private { Private declarations }

property Text;

procedure SetValor(v: double);

function GetValor: double;

...

published { Published declarations }

property Valor: double

read GetValor write SetValor;

end;

A declaração property Text; dentro da parte private da classe tem por objetivo esconder a propriedade 'Text' herdada, de forma que não seja possível alterar diretamente o texto, mas apenas com a propriedade 'Valor'. A propriedade Valor foi declarada com read GetValor write SetValor, ou seja, ao ser lida, ela invoca a função GetValor e ao ser modificada, o procedimento SetValor será chamado. Vamos implementar esses dois métodos: procedure TEditNum.SetValor(v: Double); begin Text := FloatToStr(v); end;

function TEditNum.GetValor: double; begin try // por segurança Result := StrToFloat(Text); except on EConvertError do Result := 0; end; end; Simplesmente esses métodos convertem de/para string o valor numérico e gravam/lêem o valor da propriedade Text, que foi herdada de TCustomMemo.

Page 89: Delphi 4 Com Oracle

Para testar, você pode instalar o componente no pacote DCLUSR30.DPK e compilar o pacote. (Clique em Component|Install Component, selecione o nome do pacote na lista em "Package file name", clique Ok e confirme a recompilação do pacote). Ao colocar um componente EditNum em um formulário, você pode alterar a propriedade Valor e ver o resultado dessa alteração. Note que o Delphi não permite entrar com valores não-numéricos. Mas o componente ainda não valida a digitação de teclas.

Inicializando o componente Vamos criar um construtor nessa classe para inicializar os campos do componente. Declare o construtor como público: public { Public declarations }

constructor Create(aOwner: TComponent); override; Agora implemente o construtor na parte implementation. Ele vai inicializar o texto, o tamanho do componente e algumas outras propriedades, como Alignment para alinhar o texto à esquerda: constructor TEditNum.Create(aOwner: TComponent); begin inherited; // importante! Text := '0'; Alignment := taRightJustify; //alinhar à direita Width := 100; // um tamanho default... Height := 25; // ...mais razoável WordWrap := False; //só mostra uma linha de texto WantReturns := False; // não aceita [Enter] end;

Tratando as teclas Quando o usuário digita uma tecla dentro de um componente, o Windows avisa esse componente, que pode fazer alguma coisa com a tecla, ou simplesmente aceitar a digitação da tecla. Normalmente o componente aceita a tecla e chama um dos eventos de teclado associados a ele (OnKeyDown, OnKeyUp ou OnKeyPress). Mas o que acontece internamente no componente é um pouco diferente. A classe ancestral (TCustomMemo) chama o método KeyPress internamente (um método virtual) para processar as teclas. Esse método é que faz o processamento default. Nós vamos redefinir esse método (com override) para tratar as teclas antes mesmo de elas chegarem ao procedimento do evento OnKeyPress. Declare o novo método na parte protected da classe (acessível a esta classe e classes derivadas): protected { Protected declarations }

procedure KeyPress(var Tecla: Char); override;

Esse método só vai permitir valores numéricos, a tecla [Backspace] ou uma vírgula. Implemente o método da seguinte forma: procedure TEditNum.KeyPress(var tecla: Char); begin if not (tecla in ['0'..'9',#8,',']) then tecla := #0; if (tecla = ',') and (pos(',',Text) > 0) then tecla := #0; inherited; end; No primeiro if, as teclas que não estiverem no conjunto ['0'..'9', #8 (backspace), ',' (vírgula)] são eliminadas. No segundo if, é feita uma verificação para evitar digitar uma segunda vírgula. Compile novamente o pacote e teste o componente em tempo de execução. Agora ele edita os valores alinhados à direita e não permite entrada de valores não-numéricos.

O tipo procedure e variáveis de evento Um evento é como um método "ao contrário": no caso de um método, um formulário chama o componente. No caso de um evento, é o componente que chama um método do formulário (procedimento de evento) através do evento.

Page 90: Delphi 4 Com Oracle

Eventos são baseados em variáveis do tipo procedure. Uma variável do tipo procedure (ou function) em Delphi é uma variável que contém uma referência a um procedimento e permite chamá-lo indiretamente. Por exemplo, no código a seguir: var Proc: procedure (x:integer); A variável 'Proc' pode referenciar um procedimento qualquer, desde que este aceite um parâmetro do tipo integer. Por exemplo: procedure ABC(a:integer); begin ... end;

procedure DEF(y:integer); begin ... end;Em determinado ponto do programa, é valido atribuir um valor à variável 'Proc', que seja o nome de um dos procedimentos acima. E depois o procedimento vai ser chamado a partir dessa variável, por exemplo: if x = 1 then Proc := ABC else Proc := DEF; ... ...

Proc(N); //vai chamar ABC ou DEF, dependendo do // valor de 'Proc'Uma variável de evento é uma variável dentro de um componente usada exatamente para essa finalidade. Ela contém uma referência a um procedimento do formulário e é usada para chamar esse procedimento indiretamente. O componente não precisa saber o nome exato do procedimento, mas apenas qual o nome da sua própria variável. Um evento na verdade é uma propriedade, cujo valor é do tipo procedure. Quando você liga um procedimento a um evento, está atribuindo um valor àquela propriedade.

Criando um evento Vamos criar um evento chamado 'OnErroTecla', que será executando sempre que o usuário digitar uma tecla não-numérica. O prefixo 'on' [ao] em nomes de evento é opcional: ele é usado só como um padrão do Delphi: (ao acontecer isso, execute isso). Primeiro você deve declarar o tipo de evento, para facilitar. O tipo de evento é um tipo procedure, que declara quais os parâmetros do evento. Acrescente a declaração antes da classe do componente: type TErroTecla = procedure(Sender: TObject; Tecla: char) of object; TEditNum = class(TCustomMemo) private ... A declaração of object declara que variáveis desse tipo (TErroTecla) podem fazer referência a um método de uma classe, juntamente com o objeto associado. Para um evento, isso é obrigatório: a variável de evento referencia tanto o formulário que trata o evento quanto o método do formulário que será chamado. Agora, dentro da classe, acrescente uma declarações na parte private, para a variável de evento e outra na parte published, para a propriedade de evento: TEditNum = class(TCustomMemo)

private { Private declarations }

FOnErroTecla: TErroTecla; ...

Page 91: Delphi 4 Com Oracle

published

...

property OnErroTecla: TErroTecla read FOnErroTecla write FOnErroTecla; Note que a propriedade lê e grava diretamente no campo 'FOnErroTecla'. Dentro da classe de componente vamos usar esse campo para acionar o evento. Retorne ao procedimento KeyPress criado anteriormente e acrescente algumas linhas: begin if not (tecla in ['0'..'9',#8,',']) then begin if assigned(FOnErroTecla) then FOnErroTecla(Self,tecla); tecla := #0; end; ... Note que o procedimento passa dois argumentos: 'Self' é o próprio objeto de componente e será passado para o argumento 'Sender'. Isso segue o padrão do Delphi, que permite ao receptor do procedimento verificar qual foi o componente que provocou o evento. O outro parâmetro é a tecla inválida. Compile novamente o pacote. Quando você coloca esse componente no formulário e clica na página de eventos, verá um evento 'OnErroTecla'. Para testá-lo, crie um procedimento de evento com o seguinte: procedure TForm1.EditNum1ErroTecla(Sender: TObject; Tecla: Char);

begin ShowMessage('Tecla inválida:' + tecla); end;

Apêndice A - DLLs

Criando e utilizando uma DLL

Usando strings de tamnho variável com DLLs

Criando uma unidade de importação

A API do Windows

Procura de DLLs

Criando e utilizando uma DLL No Windows, uma DLL é um arquivo executável, contendo uma biblioteca de procedimentos e funções compilados, mas separada fisicamente do executável principal. Ela nunca é executada diretamente, mas sempre chamada a partir de programas (EXEs) ou outras DLLs. Uma DLL pode ser compartilhada por vários programas, economizando espaço em disco. O Windows sempre mantém apenas uma cópia da DLL em memória. O Delphi permite criar DLLs, que podem ser utilizadas por um executável feito em Delphi ou mesmo em outra linguagem. Um executável feito em Delphi pode utilizar uma DLL feita em Delphi ou em outra linguagem. Quando você cria a DLL, você define quais as funções e procedimentos que são exportados, isto é, permitem acesso a outros programas. O próprio Windows fornece toda a sua interface de programação diretamente através de três DLLs principais: KERNEL32.DLL, que contém rotinas de gerenciamento de memória e disco, USER32.DLL, com rotinas de gerenciamento de janela e GDI32.DLL (Graphics Device Interface) para rotinas de desenho gráfico. Você normalmente não precisa chamar essas rotinas, pois a VCL do Delphi já faz isso automaticamente.

Page 92: Delphi 4 Com Oracle

O conjunto das rotinas fornecidas pelo Windows para uso dos programas é chamado de API (Application Programming Interface=Interface para Programação de Aplicações). Todas as rotinas do Windows estão disponíveis no Delphi através da unidade Windows.

Criando uma DLL Para criar um novo projeto, que gera uma DLL, clique em File|New..., selecione o ícone e clique Ok. Note que vai aparecer o arquivo de projeto, contendo uma linha inicial:library Project1; A palavra library em vez de program diz que será gerada uma DLL como resultado do projeto. Agora, crie um novo formulário com File|New Form. Altere o Caption para "Forneça a Senha" e Name para "FormSenha". Altere também a propriedade Position para 'poScreenCenter'. Coloque um controle de edição e dois botões, como na figura:

Para o componente Edit, altere o nome para 'editSenha' e a propriedade PasswordChar para o valor "*". Essa propriedade faz com que o controle mostre asteriscos (poderia ser qualquer outro caractere) durante a digitação, mas armazene internamente o valor digitado. Isso é útil para entrada de senhas. Para o botão Ok, altere Default para True, ModalResult para 'mrOk' e Name para 'btnOk'. Para o botão Cancelar, altere Cancel para True, ModalResult = 'mrCancel' e Name para 'btnCancelar'. Vamos criar agora uma função que será exportada pela DLL. Uma função exportada é aquela que pode ser usada fora da DLL. Essa função irá criar dinamicamente o formulário de senha e mostrá-lo na tela. Na unidade do formulário, coloque o seguinte na parte interface (antes ou depois da definição da classe do formulário): function ObtemSenha(senha:ShortString):boolean; export;Esse é apenas o cabeçalho da função, declarando seus parâmetros e tipos de dados. A palavra export depois do cabeçalho determina que a função é exportável, ou seja, ela pode ser exportada pela DLL. O tipo ShortString é o mesmo que string[255], uma string de tamanho fixo, com 255 caracteres. Depois veremos o porquê de usar esse tipo Agora, na parte implementation, defina o corpo da função (note que o cabeçalho agora não tem export): function ObtemSenha(senha:ShortString):boolean; begin FormSenha := TFormSenha.Create(Application); with FormSenha do if ShowModal = mrOk then {usuário clicou Ok} Result := (senha = editSenha.Text) else Result := False; FormSenha.Free; end; A função cria dinamicamente o formulário 'FormSenha' (usando Create) e depois chama o método ShowModal. Se este método retorna 'mrOk', isso indica que o usuário clicou OK. Nesse caso a função compara a senha fornecida com a senha digitada pelo usuário e coloca o resultado dessa comparação na variável Result, que é o valor retornado pela função. Mas se o usuário clicou Cancelar ou fechou o formulário, a função retorna False. Essa função até agora é somente exportável, o que significa que o Delphi gera código de máquina que torna ela compatível com uma chamada de um programa externo. Para que essa função seja exportada, ou seja, que a DLL compilada inclua informação sobre a função, e outros programas possam utilizá-la, devemos alterar o arquivo de projeto e colocar uma declaração de exportação. No arquivo de projeto, acrescente uma declaração, logo depois da lista do uses e antes do begin..end: library Project1; uses ... exports ObtemSenha; begin

end. Agora salve o projeto como VSENHA.PAS e VERSENHA.DPR. Para gerar a DLL compilada, use o menu Project|Compile. Se você tentar executar a DLL, o Delphi também vai compilá-la, mas vai mostrar uma mensagem dizendo que é impossível executar uma DLL. O resultado final do projeto será um arquivo chamado VERSENHA.DLL.

Page 93: Delphi 4 Com Oracle

Utilizando uma DLL Agora salve o grupo de projeto da DLL como 'gpDLL' e crie um novo projeto para um programa executável que usará a nossa DLL. Salve esse projeto como USENHA.PAS e USASENHA.DPR. No formulário coloque o seguinte:

Chame o controle de edição de 'editSenha' e o botão de 'btnTeste'. No código do botão digite o seguinte: begin if ObtemSenha(editSenhaTeste.Text) then Caption := 'Correto!' else Caption := 'Incorreto!'; end; Para que isso funcione é preciso declarar a função 'ObtemSenha' nessa unidade. A declaração informa, além dos parâmetros, qual a DLL onde ela se encontra. Logo acima, depois do implementation, digite: function ObtemSenha(senha:ShortString):boolean; external 'VERSENHA.DLL'; Depois de external é informado o nome da DLL. Note que, normalmente, o Object Pascal não é uma linguagem case-sensitive [que diferencia maiúsculas e minúsculas]. Mas nomes de rotinas de DLL são case-sensitive. Não vai funcionar se você declarar 'obtemsenha' ou 'OBTEMSENHA', por exemplo. Execute o programa. Para testar, digite uma senha e clique em "Testar". Isso vai chamar o outro formulário que está dentro da DLL. Se você digitar a mesma senha e clicar em Ok, o título do formulário vai mudar para "Correto!", senão vai mudar para "Incorreto!".Nota: se você não colocar a extensão de arquivo ".DLL" na diretiva external, o programa vai funcionar no Windows 95, mas não no Windows NT.

Usando strings de tamanho variável com DLLs Uma string de tamanho fixo (ShortString = string[255]) foi usada para passar parâmetros para a DLL. Se você for usar strings de tamanho variável (string, sem decl.de tamanho) em parâmetros de procedimentos/funções exportadas ou funções que retornam strings, você precisa fazer os três passos abaixo: - No arquivo de projeto da DLL, acrescente a unidade ShareMem do Delphi na cláusula uses, antes da primeira unidade, por exemplo: uses ShareMem, SysUtils, Classes,...; - No arquivo de projeto do seu EXE, faça o mesmo. - Copie BORLNDMM.DLL, do diretório do Delphi e instale com seu programa.Note que programas em outras linguagens que utilizarem a DLL não entendem o formato de strings variáveis do Delphi. Nesse caso, se a DLL vai ser usada em outras linguagens, use PChar para passar parâmetros que contém seqüências de caracteres.

Criando uma unidade de importação Uma unidade de importação [import unit] é uma unidade contendo exclusivamente declarações de procedimentos e funções de DLLs. No caso dessa DLL que foi criada, suponhamos, para exemplificar, que existe mais um procedimento chamado DefinirTamanhoSenha. A unidade de importação poderia conter o seguinte:unit ImpSenha;

interface

function ObtemSenha(senha:ShortString):boolean;

procedure DefinirTamanhoSenha(tam:integer);

implementation // repare que ao declarar a função de novo, não é // preciso declarar os parâmetros

Page 94: Delphi 4 Com Oracle

function ObtemSenha; external 'VERSENHA.DLL';

procedure DefinirTamanhoSenha; external 'VERSENHA.DLL';

end. Outros procedimentos e funções exportados pela DLL poderiam ser declarados também nessa unit.

A API do Windows O sistema operacional Windows tem várias rotinas disponíveis para uso dos programas Windows. Essas funções e procedimentos estão dentro de algumas DLLs do Windows, como USER32.DLL, GDI32.DLL e KERNEL32.DLL e outras. O conjunto de rotinas do Windows é chamado de API: Application Programming Interface (interface de programação de aplicações) do Windows. A VCL do Delphi utiliza internamente a API do Windows para: criar e desenhar os componentes, tratar eventos de componentes etc. Normalmente você não precisa chamar as funções de API diretamente. Mas às vezes você vai precisar chamar alguma rotina de API para fazer alguma coisa que não é tratada pela VCL. Por exemplo, para consultar o nome do usuário de rede atual, você pode usar a função WNetGetUser, de USER32.DLL. Para chamar essa função, não é preciso declará-la no programa. O Delphi tem a unidade "Windows", uma unidade de importação com declarações para todas as funções do Windows. Basta usar essa unidade e você terá acesso a qualquer uma dessas funções. Crie um novo projeto. No formulário, altere o Caption para "Consultar usuário". Coloque um botão "Consultar", com nome 'btnConsultar' e um componente Label ao lado, com o nome 'lblUsuario'. Quando for clicado o botão "Consultar", vamos chamar WNetGetUser e mostrar o nome do usuário atual. No evento OnClick do botão, faça: procedure TForm1.btnConsultarClick(Sender:TObject); var vet: array [0..30] of char; n: integer; begin n := sizeof(vet); WNetGetUser(nil,vet,n); lblUsuario.Caption := StrPas(vet); end; Note que uma unidade de formulário já inclui "uses Windows" no topo da interface. A função sizeof no Object Pascal calcula o tamanho de uma variável ou tipo de dados. A função WNetGetUser tem três parâmetros: um "nome de dispositivo" ou "nome da rede" e geralmente não é necessário: você pode colocar nil. Note que o segundo parâmetro é do tipo PChar, que é um ponteiro de caracteres. A função vai usar esse ponteiro para acessar a área de memória com os caracteres e preencher com a informação. Note que o argumento passado é um vetor de caracteres, cuja primeira posição é zero. Esse vetor é compatível com o tipo PChar. O terceiro parâmetro deve indicar o tamanho desse vetor. Note que para converter o vetor de voltar para o formato string, usa-se a função StrPas. Execute o programa e clique no botão "Consultar". O nome de usuário atual na rede deve aparecer. Salve como 'UsuarioRede.pas' e 'UsuarioRedeP.dpr'.

Procura de DLLs Quando um programa executável (ou uma DLL) chama uma DLL, o Windows procura a DLL nos seguintes diretórios até conseguir encontrá-la: - Diretório atual- Diretório do Windows (C:\WINDOWS no Windows 95, ou C:\WINNT no Windows NT).- Diretório SYSTEM do Windows (C:\WINDOWS\SYSTEM ou C:\WINNT\SYSTEM32).- Mesmo diretório do executável. Para instalar uma DLL no sistema do usuário, você pode usar o InstallSHIELD para criar um instalador contendo os arquivos de DLL. Nota: Você pode ver o arquivo fonte da unidade Windows, e de outras unidades de importação, no subdiretório SOURCE\RTL\WIN do diretório de instalação do Delphi.

Page 95: Delphi 4 Com Oracle

Depurando uma DLL Num programa executável, você pode facilmente colocar um breakpoint [ponto de parada] e acompanhar a execução do programa passo-a-passo, consultando valores de variáveis duranta a execução. Você pode fazer o mesmo em uma DLL. Para fazer depuração com uma DLL, você deve especificar um programa EXE que será executado e chamará alguma função da DLL. Para isso, abra o projeto da DLL, VERSENHA.DPR. Clique em Run|Parameters... e digite em "Host application" o nome do programa executável: USASENHA.EXE. Para testar, coloque um ponto de parada dentro da função ObterTexto, na primeira linha após o begin. Agora você pode clicar no botão (Run) para executar o programa. Quando você clicar no botão "Testar" no formulário, a execução vai parar na linha do breakpoint. Você pode teclar [F8[ para acompanhar a execução passo-a-passo, posicionar o cursor sobre uma variável para ver o seu valor etc.

Apêndice B - Criando Help

Conceitos

Características de tópicos

Criando um arquivo de ajuda

Compilando a ajuda

Usando o Help no Delphi

Outros recursos

Conceitos Um arquivo de ajuda [help] é um arquivo contendo texto e imagens que pode ser lido pelo usuário com o recurso de hipertexto, isto é, ao encontrar uma palavra sublinhada, ele pode clicar nela para ver outra parte do help. Um arquivo de ajuda geralmente tem a extensão HLP e é mostrado pelo programa WINHLP32.EXE (ou Winhelp), um aplicativo que faz parte da instalação padrão do Windows. O usuário pode executar o Winhelp diretamente, mas geralmente o help é chamado a partir do programa. Arquivos de ajuda são divididos em tópicos. Cada tópico aparece como uma "página" e pode conter qualquer conteúdo de texto. Por exemplo, num programa que tem um menu, com vários formulários chamados a partir dele, poderia haver um tópico que explica o menu principal e um tópico para cada um dos outros formulários. Geralmente cada tópico tem um título para descrever seu conteúdo.

Características de tópicos Cada tópico tem um identificador [topic ID] que não é visto pelo usuário, mas identifica internamente o tópico para o sistema de help. Cada identificador tem opcionalmente um número de contexto atribuído a ele, que pode ser usado dentro do programa. Um dos tópicos do arquivo é o tópico de conteúdo, o primeiro que o usuário vê ao abrir o arquivo de help. Geralmente a partir dele, existem links de hipertexto para outros tópicos. Um tópico geralmente tem um título, que o identifica em uma lista de tópicos do Winhelp. Opcionalmente ele tem palavras-chave, que podem ser usadas para procurar texto no tópico. Você pode também criar vários tópicos ligados em seqüência: a janela do Winhelp mostra botões [<<] e [>>], que permitem ver o anterior e próximo da seqüência. Nesse caso, cada tópico deve ter um identificador de seqüência mais um número de seqüência. Entre os tópicos podem ser criados links, que permitem chegar a um tópico a partir de outro. Um salto [jump] é o um link onde uma palavra clicada abre o conteúdo de outro tópico na janela do Winhelp, substituindo o anterior. Um pop-up é um link no qual, ao clicar na palavra, o tópico aparece em uma pequena janela retangular.

Page 96: Delphi 4 Com Oracle

Ajuda sensível ao contexto Você pode suportar um arquivo de help de várias formas. A mais fácil é colocar um comando "Ajuda" no seu programa que abre o tópico de conteúdo do seu help. Mas é melhor para o usuário a ajuda sensível ao contexto [context-sensitive help], ou seja, dependendo do local do programa (formulário, controle) onde ele estiver posicionado, ao chamar o help, será apresentado um tópico específico. No formulário de produtos, por exemplo, deve ser mostrado o tópico que explica o formulário de produtos.

Criando um arquivo de ajuda Para criar um arquivo de ajuda, você deve escrever todo o texto em um arquivo no formato RTF (Rich Text Format) em qualquer editor de texto que suporta RTFs. Um arquivo RTF pode conter vários tópicos, que são separados por quebras de página. Esses arquivos serão os arquivos-fonte do help. Para os nossos exemplos, usaremos o Microsoft Word, versão 97 para editar os arquivos. (Note que alguns comandos podem ser diferentes em outras versões do Word e completamente diferentes em outros produtos). Além dos arquivos-fonte RTF, é preciso ter um arquivo de projeto de ajuda [help project file], um arquivo .HPJ que descreve quais são os arquivos-fontes usados, qual é o tópico de conteúdo, quais são os números de contexto de cada tópico e algumas opções globais. Esse arquivo pode ser criado manualmente ou usando o Microsoft Help Workshop (HCRTF.EXE), um programa que é instalado com o Delphi, no subdiretório HELP\TOOLS. Depois você deve compilar seu arquivo HPJ e os arquivos-fonte (*.RTF) com o HCW. Isso vai gerar um arquivo HLP, com o mesmo nome do projeto de ajuda (e.g. SISTEMA.HPJ => SISTEMA.HLP).

Editando arquivos RTF Vamos criar um arquivo de ajuda para o projeto Almoxarifado.DPR. Ele terá três tópicos: um para o menu principal, outro para o cadastro de fabricantes e outro para o cadastro de produtos. Execute o Word e ele vai criar um novo documento. Nota: o Wordpad do Windows 95/NT4 edita RTFs, mas ele não tem os recursos necesários para criar arquivos de Help (como notas de rodapé). Digite o texto do primeiro tópico como no exemplo abaixo: Menu Principal : Este é o menu principal do programa. Aqui você pode escolher uma das seguintes opções: Produtos - abre o cadastro de produtos.Fornecedor - abre o cadastro de fornecedores.Destaque o título do tópico com uma fonte diferente, como preferir. Agora para separar um tópico de outro, você deve fazer uma quebra de página (explícita) no final do texto. A tecla específica do Word para colocar uma quebra de página explícita é [Ctrl+Enter]. Digite os dois próximos tópicos, com uma quebra de página entre eles e uma quebra de página após o segundo tópico: Cadastro de FornecedoresAqui você pode cadastrar fornecedores etc. Cadastro de Produtos Aqui você pode cadastrar produtos etc.

Identificando os tópicos Para cada tópico, você deve definir no mínimo o identificador que será usado internamente. É recomendável também definir um título (o Winhelp não deduz o título automaticamente) que aparece em algumas situações. Opcionalmente você pode colocar palavras-chave e identificadores de seqüência. Cada um desses itens é representado por uma nota de rodapé no arquivo RTF. O caractere da nota de rodapé determina qual item de informação: # - identificador$ - títuloK - palavras-chave+ - nome & número de seqüência Coloque o cursor logo antes do título Menu Principal e clique em Inserir|Notas no menu do Word. Clique em "Personalizada" e digite o caractere #. Clique Ok. Agora, no texto da nota, digite MENU_PRINCIPAL. Esse será o identificador do tópico. Note que o identificador não pode conter espaços. Não tecle Enter após o texto da nota. Clique novamente ao lado do título e em Inserir|Notas. Clique em "Personalizada", digite $ e tecle Ok. A nota "$" identifica o título do tópico. Digite "Menu Principal".

Page 97: Delphi 4 Com Oracle

Agora vamos criar palavras-chave. Clique novamente ao lado do título e insira uma nota com o caractere K (note bem, maiúsculo). Digite o texto: menu;principal;fornecedor;produtos. Até agora você tem três notas, que devem aparecem como o seguinte: # MENU_PRINCIPAL$ Menu PrincipalK menu;principal;fornecedor;produtos Faça o mesmo para os outros tópicos. Para o "Cadastro de Fornecedores", coloque: # CAD_FORNEC$ Cadastro de FornecedoresK fornecedoresPara o "Cadastro de Produtos", crie três notas como: # CAD_PROD$ Cadastro de ProdutosK produtos

Criando links entre tópicos Voltando ao texto do tópico principal, vamos criar links para os outros tópicos. Mas antes de fazer isso, vamos ativar uma opção do editor para exibir o texto oculto. Texto oculto normalmente não aparece na tela, mas você deve editar esse texto para poder criar um link. No Word 97, o comando para mostrar texto oculto é: clique em Ferramentas|Opções, na página Exibir, abaixo de "Caracteres não- imprimíveis", marque a opção "Texto oculto". Para criar um link com outro tópico, você deve selecionar a palavra desejada, e marcá-la com duplo sublinhado e logo após você deve colocar o identificador do tópico desejado, com texto oculto. Vamos fazer, no tópico principal, links para os outros tópicos. Agora volte ao texto do menu principal. Na frase "Produtos - abre o cadastro de produtos.", selecione a primeira palavra Produtos e clique em Formatar|Fonte. Em "Sublinhado", escolha "Duplo" e clique Ok. Agora coloque o cursor imediatamente após a palavra Produtos, sem deixar espaços. Clique em Formatar|Fonte e em "Sublinhado", escolha "(nenhum)". Marque a opção "Oculto" e clique Ok. Agora digite CAD_PROD (o identificador do tópico "Cadastro de Produtos". O texto deve aparecer da seguinte forma: ProdutosCAD_PROD - abre o cadastro de produtos.Faça o mesmo na linha de baixo: selecione a palavra "Fornecedores" e formate-a com texto duplo sublinhado. Após a palavra, formate o texto sem sublinhado e oculto e digite CAD_FORNEC.

Salvando o arquivo Agora vamos salvar o arquivo, com o cuidado de salvar no formato RTF. Clique em Arquivo|Salvar. Na lista "Salvar como tipo", escolha "Rich Text Format (*.rtf)". Salve com o nome de PROD.RTF, no diretório dos arquivos do curso. Depois pode fechar o Microsoft Word.

Vamos definir também qual o tópico de conteúdo, que aparece no início da execução. Para isso, clique no botão Options, e em Default Topic escreva "MENU_PRINCIPAL", exatamente como está digitado dento do arquivo RTF, na nota # do tópico principal. Em Help Title digite "Exemplo de Help", o título que vai aparecer no arquivo. Clique Ok.

Mapeando os números de contexto Vamos definir para cada tópico o número de contexto correspondente. Isso é feito clicando no botão Map. Primeiro clique em Add, digite Topic ID: MENU_PRINCIPAL e Mapped numeric value: 1 e clique Ok. Você verá na lista um item 'MENU_PRINCIPAL=1'. Depois faça o mesmo para CAD_FORNEC (valor = 2) e CAD_PROD (valor = 3) e clique Ok na caixa de diálogo "Map".

Compilando Agora vamos compilar o help. Clique File|Compile ou no botão correspondente. O Help Workshop vai abrir uma janela separada de compilação e mostrar os resultados, como possíveis mensagens de erro. Se estiver tudo Ok, ele vai gerar um arquivo chamado PROD.HLP. Para voltar à janela do projeto, clique em Window|1 Prod.hpj.

Testando o Help Para testar o arquivo de help, clique em File|Run Winhelp. Marque a opção "A double-clicked file icon" e clique no botão View Help. O tópico de conteúdo deve aparecer na tela com links para os outros tópicos. Depois de clicar num link, você pode clicar em "Voltar" para voltar atrás.

Page 98: Delphi 4 Com Oracle

Se você clicar em "Conteúdo", volta para o tópico principal. Se você clicar em "Índice", verá as palavras-chave e, ao escolher uma, verá os tópicos que contêm essa palavra.

Compilando a ajuda Para criar a ajuda, você deve executar o "Microsoft Help Workshop", o programa HCRTF.EXE no subdiretório HELP\TOOLS do Delphi. Para criar um novo projeto de help, clique em File|New, escolha "Help Project" e clique Ok. Selecione o diretório do curso e digite o nome PROD.HPJ para o arquivo. Você deve escolher quais arquivos-fonte RTF serão incluídos. Clique em Files, depois clique no botão Add e escolha o arquivo PROD.RTF criado anteriormente. Depois clique OK. Vamos definir também qual o tópico de conteúdo, que aparece no início da execução. Para isso, clique no botão Options, e em Default Topic escreva "MENU_PRINCIPAL", exatamente como está digitado dento do arquivo RTF, na nota # do tópico principal. Em Help Title digite "Exemplo de Help", o título que vai aparecer no arquivo. Clique Ok.

Mapeando os números de contexto Vamos definir para cada tópico o número de contexto correspondente. Isso é feito clicando no botão Map. Primeiro clique em Add, digite Topic ID: MENU_PRINCIPAL e Mapped numeric value: 1 e clique Ok. Você verá na lista um item 'MENU_PRINCIPAL=1'. Depois faça o mesmo para CAD_FORNEC (valor = 2) e CAD_PROD (valor = 3) e clique Ok na caixa de diálogo "Map".

Compilando Agora vamos compilar o help. Clique File|Compile ou no botão correspondente. O Help Workshop vai abrir uma janela separada de compilação e mostrar os resultados, como possíveis mensagens de erro. Se estiver tudo Ok, ele vai gerar um arquivo chamado PROD.HLP. Para voltar à janela do projeto, clique em Window|1 Prod.hpj.

Testando o Help Para testar o arquivo de help, clique em File|Run Winhelp. Marque a opção "A double-clicked file icon" e clique no botão View Help. O tópico de conteúdo deve aparecer na tela com links para os outros tópicos. Depois de clicar num link, você pode clicar em "Voltar" para voltar atrás. Se você clicar em "Conteúdo", volta para o tópico principal. Se você clicar em "Índice", verá as palavras-chave e, ao escolher uma, verá os tópicos que contêm essa palavra.

Usando o Help no Delphi Agora, retorne ao Delphi e abra o projeto ALMOXARIFADO.DPR. Vamos definir o arquivo de help para ele. Clique em Project|Options e na página Application. Em "Help file", digite PROD.HLP e clique Ok. Com isso, o Delphi vai automaticamente abrir o arquivo de help quando você pressionar a tecla [F1] durante a execução. Mas vamos mudar algumas propriedades antes. Para implementar ajuda sensível ao contexto no Delphi, cada formulário e cada componente tem uma propriedade HelpContext, que é o número de contexto do tópico correspondente. Altere essa propriedade nos três formulários: no principal, coloque o valor 1, no formulário 'FormFornecedor'', o valor 2 e no formulário 'FormProduto', o valor 3. Agora execute o programa. Se você teclar [F1] no formulário principal, verá o tópico principal. Se você abrir o formulário de produtos e teclar [F1], verá o tópico "Cadastro de produtos". Idem para o formulário de fornecedor e seu tópico.

Outros recursos

Usando bitmaps Você pode incluir imagens bitmap (arquivos *.BMP) dentro do seu arquivo de help. Para isso, digite no texto do arquivo RTF um dos seguintes marcadores: {bmc nomearquivo.bmp} ou {bml nomearquivo.bmp} ou ainda

Page 99: Delphi 4 Com Oracle

{bmr nomearquivo.bmp} Usando 'bmc', a imagem será inserida na posição desse marcador, como se fosse um caractere a mais, alinhada na parte inferior com a base do texto: por exemplo. Já usando 'bml', a imagem fica alinhada à esquerda (left) do texto e 'bmr' mostra a imagem alinhada à direita (right).

Usando um arquivo de conteúdo Os novos arquivos de ajuda geralmente têm uma tela de conteúdo, que indica os tópicos de forma hierárquica, como a ajuda do Windows:

Para fazer o mesmo, você pode criar um arquivo de conteúdo [contents file] no Help Workshop. Clique em File|New, escolha "Contents file" e Ok. Um arquivo de conteúdo (.CNT) pode ser um índice para vários arquivos HLP. Clique em "Default filename" e digite PROD.HLP. Agora clique em "Add Below..." [adicionar abaixo], escolha a opção "Heading" e digite "Conteúdo". Clique Ok. Um "Heading" [cabeçalho] aparece como um "livro" no conteúdo. Clique em Add Below novamente e digite as opções como abaixo:

Quando você clicar em Ok, vai aparecer o tópico Menu Principal abaixo do Conteúdo. Continue adicionando outros itens. Coloque em Title o título desejado e em Topic ID o identificador do tópico: CAD_FORNEC e CAD_PROD para os próximos. Depois de terminar, salve o arquivo de conteúdo como PROD.CNT no mesmo diretório de PROD.HLP. Note que ao abrir PROD.HLP no Explorer, o Winhelp vai mostrar automaticamente a divisão de conteúdo.

Usando ajuda "o que é isto" Se um formulário tiver um botão de help (se biHelp está marcado em BorderIcons), os tópicos de help são mostrados de forma diferente, como uma pequena janela do tipo "o que é isto". Se você clicar no botão de help e depois no formulário, também vai ver os itens dessa forma. Essa alternativa é melhor se você tiver um tópico de ajuda para cada controle do formulário. Nesse caso, para ver a ajuda, o usuário clica no botão de help e no controle desejado. Caso a propriedade HelpContext do controle atual seja 0 (zero), o Delphi usa o HelpContext do formulário para localizar o tópico. Caso HelpContext do formulário seja zero, o Delphi mostra o conteúdo padrão.

Chamando comandos explicitamente Às vezes você quer abrir um tópico diretamente a partir do programa, sem que o usuário tecle [F1]. Por exemplo, se você tiver um botão de ajuda no formulário. Para fazer isso, você pode usar o método HelpContext do objeto Application. Por exemplo, para abrir o tópico 3 (cadastro de produtos), use: Application.HelpContext(3); Se você usa muito esse recurso, é recomendável declarar constantes para cada contexto de help usado. Mantenha uma unidade apenas com essas declarações, por exemplo: unit Contextos; interface const hcMENU_PRINCIPAL = 1, hcCAD_FORNEC = 2, hcCAD_PROD = 3;

implementation

end. Nesse caso, você poderia usar: uses Contextos; ... Application.HelpContext(hcCAD_PROD);Ou você pode usar HelpJump, que faz o mesmo, mas com a string de contexto: Application.HelpJump('CAD_PROD');Já o método HelpCommand permite chamar outras funções de help. Ele tem dois parâmetros: 'Command' e 'Data': Command Data Ação

Page 100: Delphi 4 Com Oracle

Help_Contents 0 Mostra o tópico de conteúdo Help_ContextPopup Nº de contexto Mostra o tópico numa janela separada Help_HelpOnHelp 0 Mostra a ajuda do próprio Winhelp ("como usar a ajuda").Help_Index 0 Mostra o índice de palavras chave .

Bibliografia sugeridaOs seguintes livros são uma boa fonte de material para aprofundar-se um pouco mais no conhecimento do Delphi.

LOBO, Rodrigo. Incrementando o Delphi. Ed. Advanced. ISBN: 8586916218.Este livro oferece uma série de dicas, truques, utilitários e componentes para o Delphi, visando tornar o trabalho do programador mais prático e produtivo. Englobando as áreas de banco de dados, multimídia, Internet, arquivos, etc. Inclui CD contendo os componentes e utilitários.

CANTÙ, Marco. Dominando o Delphi 4 "A bíblia". Ed. Makron. ISBN: 8534610460.Esta é a edição completamente revisada e atualizada do livro de programação Delphi mais louvado de todos os tempos. Escrito pelo destacado especialista em Delphi, Marco Cantú, este livro é dedicado a usuários iniciantes e avançados, oferecendo a cobertura mais efetiva e cuidadosa da programação Delphi que se pode encontrar.

PACHECO, Xavier e TEIXEIRA, Steve. Delphi 4 Developer's Guide (Developer's Guide Series). ISBN: 0672312840Este livro é uma referência de nível avançado mostrando aos desenvolvedores o mais importante que eles devem saber sobre o Delphi 4. Os autores lidam com desenvolvedores todos dias e citam técnicas poderosas, dicas e conhecimento técnico das mais avançadas características do Delphi 4. Tópicos incluem: Componentes de nível avançado incluindo links embutidos, características especiais e DLLs, inclusive a criação de suas próprias Bibliotecas de Componentes Visuais (VCL), programação orientada a objeto avançada e object Pascal. O livro discute assuntos sobre desenvolvimento de aplicações e conceitos de estrutura para aplicações de bancos de dados cliente/servidor, desktop, bem como componentes MIDAS (Multi-tier Distributed Applications Services Suite), e como trabalhar com eles.