Criação de Componentes em Delphi 2

Fazer download em pdf ou txt
Fazer download em pdf ou txt
Você está na página 1de 68

CRIAO DE COMPONENTES EM

DELPHI



1. INTRODUO

1.1. Objetivos
Este curso busca oferecer uma aproximao ao desenvolvimento de
componentes de um ponto de vista eminentemente prtico. Em uma poro
de livros de programao em Delphi est o tpico do desenvolvimento de
componentes, mas s de um modo rpido e superficial. O modo certo aquele
dos manuais do Delphi que so completamente dedicados a este tpico, mas,
a meu modo de ver, sofre de falta de exemplos prticos que vo passo
pedaggico a um modo gradual do desenvolvimento de componentes.
Tambm certos tpicos, (o tpico de editores de propriedades, por exemplo)
permanecem muito no ar.

Este curso tentar cobrir estas faltas buscando ser principalmente prtico:
voc comear na teoria bsica de criao de componente e imediatamente
ser aplicado criao de componentes completamente funcionais.

Ao longo do curso aprenderemos a criar componentes de tipos muito diversos.
Comearemos com um visual e no-visuais e iremos progredindo na criao
de componentes grficos, editores de propriedades, componentes de banco de
dados... Tambm ser explicado como criar os arquivos de ajuda para fazer
com que nosso componente seja completamente integrado no ambiente de
desenvolvimento de Delphi.


1.2. Conhecimento Prvio
Mas isto no um curso de programao em Delphi, nem um curso de
programao no guiado a objetos. suposto que o leitor j tenha mais ou
menos uma bagagem destes tpicos e tais conceitos como herana,
descendente. Como condio prvia para a criao de componentes
altamente aconselhvel ter claro os seguintes conceitos:

A programao orientada a objetos e seus aspectos fundamentais
(fabricantes, destrutivo, classes, heranas...);
Domnio do ambiente integrado de desenvolvimento Delphi;
Administrar com facilidade os diferentes componentes standards do Delphi;
Uso do ObjectBrowser para determinar relaes entre os objetos.

Voc pode encontrar explicaes sobre estes tpicos nos prprios manuais do
Delphi como tambm na ajuda on-line.


2. UM POUCO DE TEORIA

2.1. O que so componentes ? A biblioteca visual de componentes
Os componentes so a pedra angular da programao em Delphi. Embora a
maioria dos componentes representam partes visveis da interface de usurio,
tambm existem os componentes no-visuais, como por o exemplo o objeto
Cronmetro e Banco de dados.

Um componente, em sua definio mais simples mais um objeto
descendente tipo TComponent. Todos os componentes descendem na forma
mais primitiva do TComponent, desde que TComponent prov as
caractersticas bsicas que todo o componente deveria ter: capacidade de ser
mostrado na paleta de componentes como tambm de operar em
"Design-time".

Os componentes fazem a programao em Delphi ficar mais facil. Em vez de
ter que operar a nvel de unidades (Units), o usurio de um componente tem
que preencher as propriedades dele e localizar isto na posio requerida da
forma dele simplesmente. Isso tudo: O Delphi se encarrega do resto.

Todos os componentes so parte da hierarquia de Biblioteca de Componentes
de objetos denominada Visual Componnent Library (VCL). Quando um
componente novo criado, este derivado a partir de um componente
existente (bem como o TComponent ou algum outro especializado) e
somado ao VCL.


2.2. Anatomia de um componente. Propriedades, mtodos e eventos
Como j foi mencionado, um componente um objeto, e como tal, consiste
em cdigo e dados. Mas quando se referindo a estes no usaremos estas
condies, mas falaremos bastante de propriedades, mtodos e eventos. Ao
longo deste curso iremos estudar todos estes aspectos a fundo, mas vamos
fazer um pequeno resumo:

Propriedades:
As propriedades provem ao usurio do componente um acesso fcil ao
mesmo. Ao mesmo tempo, permite ao programador do componente
esconder a estrutura subjacente de dados. Algumas vantagens de usar
propriedades:
As propriedades podem ser alteradas em design-time. Deste modo o
usurio do componente pode inicializar os valores das propriedades sem
necessidade de escrever uma linha de cdigo;
As propriedades permitem a validao dos dados ao mesmo tempo em
que so introduzidos. Desta forma, podem ser prevenidos erros
causados por valores invlidos;
assegurado que o primeiro valor colocado nas propriedades so os
certos e evitaro erros.
Eventos
Os Eventos so a conexes existentes entre um certo evento e o cdigo
escrito pelo programador de componentes. Deste modo, por exemplo, antes
do clique de um evento do mouse poderia ser mostrada uma mensagem na
tela. Para o cdigo que executado quando um certo evento o leva a um
lugar denominado Gerenciador de Eventos e normalmente escrito pelo
usurio do componente. Os eventos mais comuns j so parte dos prprios
componentes de Delphi (eventos do mouse, teclado...), mas tambm
possvel definir eventos novos.

Mtodos
Os mtodos so funes de Entrada/Sada de procedimentos que so parte
do componente. O usurio do componente os usa para obter uma certa
ao ou obter um certo valor para o qual voc no pode conseguir por meio
de uma propriedade. Considerando que eles requerem uma execuo de
cdigo, os mtodos esto disponveis somente em run-time.


2.3. Controle de acesso para um componente. Declaraes: Private,
Protected, Public e Published
O Object Pascal tem quatro nveis de controle de acesso para os campos,
propriedades e mtodos de um componente. Este controle de acesso permite
ao programador de componentes em que partes do cdigo especificar as
declaraes do objeto. Deste modo definido a interface do componente.
importante planejar bem esta interface, deste modo nossos componentes
sero facilmente programveis e reutilizveis.

A menos que o oposto seja especificado, os campos, propriedades e mtodos
que so somados a um objeto so do tipo published. Todos os nveis de
controle de acesso operam em nvel de unidades (Units), quer dizer, se uma
parte de um objeto acessvel (ou inacessvel) em uma parte de uma unidade
(Unit), tambm acessvel (ou inacessvel) em qualquer outra parte da
unidade (Unit).

Logo os tipos de controles de acesso so detalhados:

Private: Escondendo os detalhes de implementao.
Declarando uma parte de um componente (um campo, propriedade ou
mtodo) private, voc se priva. Aquela parte do objeto invisvel ao
cdigo externo para a unidade (Unit) no qual o objeto declarado. Dentro
da unidade (Unit) que contm a declarao, o cdigo como o que pode
consentir quela parte do objeto se fosse pblico.

A utilidade principal das declaraes privadas que eles permitem esconder
os detalhes de implementao do componente para o usurio final do
mesmo, visto que eles no podem consentir o acesso parte privada de um
objeto. Deste modo voc pode mudar o implementao interno do objeto
sem afetar ao cdigo que o usurio escreveu.
Protected: Definindo a interface do programador.
Declarar uma parte de um componente como sendo protegido o mesmo
que declarar isto em privado. ( escondido ao cdigo externo da unidade).
A diferena principal entre declarar uma parte de um objeto protegido ou
fazer isto privado so que os descendentes do componente podero fazer
referncia quela parte.

Este parte especialmente til para o criao de componentes.


Public: Definindo a interface para Run-Time.
As partes inteiras de um objeto que declaramos pblico, podero ser
indexados por qualquer cdigo interno ou externo para a prpria unidade
(Unit). Deste modo, a parte pblica identifica a interface a tempo de
execuo de nosso componente. Os mtodos que o usurio do componente
deveria chamar os pblicos deveria ser declarado, como tambm as
propriedades de read-only, para ser s vlido em tempo de execuo.

O pblico declara as propriedades que no aparecero no Object Inspector.
Esta seo talvez mais importante considerar quando estamos projetando
um componente. Quando so projetados componentes, deveriam ser
considerados cuidadosamente que mtodos e propriedades deveriam ser
pblicas. Se o cdigo est correto, isto permitir a alterao as estruturas
de dados e mtodos internos do componente sem ter que jogar o pblico
da interface que continuar sendo o mesmo para o usurio do componente.


Published: Definindo a interface para o Design-Time.
Quando declaramos parte de um objeto publicado o mesmo que a pblica
e tambm gera a informao em run-time.

As propriedades declaradas publicadas aparecem no Object Inspector. Estas
partes definem a interface a design-time de nosso componente.


2.4. Passos necessrios para criar um componente. O expert de
componentes
Os passos necessrios para criar um novo componente so os seguintes:

1. Criar uma unidade (Unit) para o novo componente;
2. Derivar o componente novo a partir de outro existente, o qual servir
como base para somar as caractersticas do novo componente;
3. Somar as propriedades, eventos e mtodos necessrios para o novo
componente;
4. Registrar o componente, inclusive os bitmaps adicionais, arquivos de
ajuda, etc.;
5. Instalar o novo componente na paleta de componentes.

De todos os passos mencionados, o mais importante a escolha do
componente ao qual vai derivar. Este passo crucial, com uma boa escolha do
ascendente podemos economizar muito cdigo e chegar facilmente ao nosso
objetivo.
Como base para a escolha do objeto ascendente, ele serve para fazer
advertncia as normas seguintes:

TComponent - O ponto de partida para os componentes no-visuais;
TWinControl - O ponto de partida se necessrio que o componente
tenha "handles";
TGraphicControl - Um bom ponto de partida para componentes visuais
que no tenham "handles". Esta classe tem os mtodos Paint e Canvas;
TCustomControl - O ponto de partida mais comum. Esta classe tem
"handles" de janelas, eventos e propriedades comuns, e, principalmente,
os mtodos Canvas e Paint.

Bem, j sabemos como determinar o ponto de partida. Vamos ver agora como
criar a unidade (Unit) que abrigar o componente. Existems dois caminhos,
criar a Unit manualmente ou deixar que o Delphi faa o trabalho sujo (Usando
o Expert de Componentes). Se escolhermos a primeira opo, teremos que
fazer tudo manualmente, mas aconselhvel utilizar o Expert do Delphi.

Abrir o expert de componentes no menu Component e New Component...
Aparecer uma caixa de dilogo na qual devemos preencher os campos:
- Class Name: Aqui devemos especificar o nome do novo componente;
- Ancestor Type: Aqui informamos o ascendente a partir do qual
derivaremos o nosso componente;
- Pallete Page: Aqui indicamos a pgina da paleta no qual queremos que o
novo componente aparea.

Uma vez informado estes campos, ao pressionarmos OK veremos o cdigo da
nossa Unit. Se, por exemplo, introduzirmos os seguintes dados no expert de
componentes:
- Class Name: TMiComponente
- Ancestor Type: TComponent
- Pallete Page: Curso

Ao clicarmos em OK, o Delphi nos gerar uma Unit seguinte para
introduzirmos as propriedades e mtodos de nosso componente.

unit Unit1;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs;

type
TMiComponente = class(TComponent)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('Curso', [TMiComponente]);
end;

end.


A partir daqui, tudo se resume em introduzir as propriedades e mtodos
necessrios para a operao de nosso componente. Mas antes de qualquer
coisa, devemos estar atentos em alguns pontos:

Na clusula Uses, o Delphi soma por padro as Units standards. Se
nosso componente no usa alguns deles, poderemos elimin-los desta
clusula. Da mesma forma, se usarmos algum procedimento ou funo
localizada em outra unidade, deveremos adicionar esta unidade na
clusula Uses;
As declaraes das propriedades, campos e mtodos que iremos definir,
sero localizadas na seo apropriada da interface como correspondente
(private, protected, public ou published);
O Delphi declara e define o registro de procedimento automaticamente
para registrar o componente na paleta.
3. UM COMPONENTE NO-VISUAL: Tnif
Chegou o momento de entrarmos em ao. Criaremos o nosso primeiro
componente: TNif. Mas antes de comear a escrever o cdigo devemos analisar o
objetivo do componente e como ser sua implementao.

3.1. Objetivo do componente
O objetivo do componente muito simples: a partir de um dado de entrada, o
nmero de DNI, nosso componente dever calcular a letra do NIF
correspondente. O mtodo de calcular esta letra por meio de uma operao
matemtica muito simples. Esta operao : DNI - ((DNI div 23) * 23). O
resultado desta operao ser um nmero entendido entre 1 e 23. Por meio
de um quadro ns nomearemos uma certa letra a cada nmero, a letra que
corresponde o NIF pedido. Os detalhes completos desta letra eu tenho no
cdigo fonte.


3.2. O projeto do componente
Baseado em nosso objetivo, est claro que nosso componente ser do tipo
no-visual e, ento, derivaremos isto a partir de TComponent que como j foi
visto, a base para a criao de componentes no-visuais.

A forma de introduzir o nmero de DNI ser por meio de uma propriedade
(propriedade DNI) de leitura. O valor desta propriedade ser armazenada em
um campo (FDNI) do tipo LongInt. Esta propriedade ser publicada
(published) de forma que ela aparece deste modo no Object Inspector . A
leitura e escrita de valores nesta propriedade sero diretamente no campo
FDNI, quer dizer, ns deixaremos que o prprio Object Inspector se
encarregar de verificar se o valor introduzido corresponde ao tipo LongInt.

Teremos outra propriedade (NIF) no que a letra calculada ser armazenada
(do tipo Char). Mas esta propriedade ser somente leitura j que o usurio do
componente no poder inserir a letra manualmente, pois o prprio
componente calcula isto. Esta propriedade deveria ser pblica (Public) desde
que seja somente leitura. Este aspecto serve para se salientar isto: as
propriedades somente-leitura deverio ser declaradas na parte pblica. A
funo que se encarrega de calcular a letra do NIF chamar GetNIF.

Sempre sero declarados os campos que armazenam o valor de propriedades
na parte privada (Private) desde que tenhamos certeza de que todos tenham
acesso a ele, mas o usurio do componente, desde que se conscientize pela
propriedade e no pelo campo (isso representa o armazenamento interno da
propriedade).

Relativo aos nomes usados, so continuadas as convenes seguintes:

Os tipos que definirmos sempre comearo com a letra T (de Tipo).
Exemplo: TNif;
Os campos que armazenam os valores de propriedades sempre
comeam com a letra F seguido pelo nome da propriedade que
armazena. Deste modo o campo FDNI est claro que armazena o valor
da propriedade DNI;
Os nomes dos mtodos de leitura dos valores de uma propriedade sero
denominados pelo sufixo Get (para ler) ou Set (para escrever) seguido
pelo nome da propriedade. Exemplo: Mtodo GetNIF.


3.3. Cdigo fonte do componente
unit Nif; { (c)1996 by Luis Roche }

interface

uses
Classes;

type
TNif = class(TComponent)
private
FDNI : LongInt;
function GetNIF : char;
protected
public
property NIF: char read GetNIF;
published
property DNI: LongInt read FDNI write FDNI;
end;

procedure Register;

implementation

function TNIF.GetNIF : char;
var
aux1 : integer;
Const letras : string = 'TRWAGMYFPDXBNJZSQVHLCKE';
begin
aux1:=FDNI - ((FDNI div 23) * 23);
result:=letras[aux1+1];
end;

procedure register;
begin
RegisterComponents('Curso', [TNif]);
end;

end.
3.4. Comentrios sobre o cdigo fonte
Usamos o Expert de Componentes. Na caixa de dilogo que o expert nos
mostrou, informamos TNif como nome da classe (Class Name), TComponent
como Tipo de Ascendente (Ancestor Type) e Curso como Nome da Paleta
(Pallet Name).

De todas as Units que aparecem na clusula Uses, deixaremos somente a Unit
Classes, pois no usaremos qualquer procedimento das outras.

Declaramos o campo FDNI (LongInt) na seo privada. Neste campo, como j
foi visto, armazenaremos o DNI. Na seo Published escrevemos a linha
seguinte:
property DNI: LongInt read FDNI write FDNI;

Deste modo declaramos a propriedade DNI e especificamos que a leitura e
escrita de valores no mesmo so feitos diretamente com o campo FDNI que
usa o Object Inspector.

Declaramos a propriedade NIF Somente-Leitura na parte pblica. Ela serve
para lembrar que as propriedades de leitura s deveriam ir declaradas na
parte pblica e no na published. Especificamos para isso para ler o valor da
propriedade: Usaremos a funo GetNIF, a qual declaramos na seo privada.

Escrevemos a funo que calcula a letra do NIF na parte de implementao da
unidade.

Mantemos a unidade com o nome nif.pas. Visto que todos os componentes
que vamos criar durante este curso esto num mesmo diretrio.


3.5. Criando um bitmap para o componente
Cada componente precisa de um bitmap para represent-lo na paleta de
componentes. Se nenhum for especificado, o Delphi usar um como padro.

O bitmap no includo no cdigo fonte do componente, mas includo
separadamente num arquivo com a extenso DCR (Dynamic Component
Resource). Este arquivo pode ser criado com o prprio editor de imagens que
vem com o Delphi (Image Editor).

O nome do arquivo DCR deve coincidir com o nome da unidade do
componente (PAS), da mesma forma, o nome do bitmap do componente
(BMP). Os arquivos da unidade (PAS), o DCR e o bitmap do componente
devem estar no mesmo diretrio.

Em nosso componente, se foi salvo com o nome de NIF.PAS o arquivo de
recursos deve chamar NIF.DCR. Dentro deste ltimo estar o bitmap para o
qual colocaremos o nome de NIF.BMP. O bitmap que criamos deve ter o
tamanho de 24x24 pixels.


NIF.BMP:
3.6. Instalando o TNif na paleta de componentes
Para instalar o nosso componente na paleta de componentes muito simples.
Siga os passos:

Escolher o menu Component, Install Component;
Clicar na guia "Into new package";
Colocar no campo "Unit file name" o diretrio e o nome da unidade do
nosso componente (NIF.PAS);
No campo "Package file name" colocar o diretrio e o nome do arquivo
com diretivas para instalao do componente (este arquivo ser criado
pelo Delphi). Este nome deve ser diferente do nome da unidade do
componente (Sugesto: TNIF);
No campo "Package description" ( opcional) voc pode colocar uma
breve descrio do componente. (Sugesto: Clculo do NIF);

Ao clicarmos no boto OK o Delphi ir compilar o nosso componente, gerando
o arquivo TNIF.DPK (caso o Package file name seja TNIF).

Se houver erros na compilao, seremos informados.

Caso haja sucesso na compilao, o nosso componente far parte da paleta de
componentes.
4. ADICIONANDO RECURSOS A UM COMPONENTE EXISTENTE
Agora aprenderemos como adicionar recursos e possibilidades para um
componente Delphi existente. Aprenderemos a usar objetos (componentes) em
nosso prprio componente, o que eles so e como so declarados os constructors
e os destructors.

Tudo isto, claro, baseado num exemplo concreto: TBlinkLabel.

4.1. Objetivo do componente
Nosso propsito criar um componente idntico ao o tipo TLabel, mas com
uma capacidade a mais: o de piscar. Quer dizer, nosso componente dever
nos mostrar uma mensagem piscando com uma certa freqncia. Caso o valor
da freqncia for nulo, a mensagem dever permanecer fixa na tela
(emulando o que acontece a um Tlabel normal).


4.2. Usando um componente existente em nosso componente.
Cronmetros
Como h pouco dissemos, nosso componente deveria piscar com uma certa
freqncia. Porque ns precisamos ser possveis medir o tempo que dever
piscar a mensagem. Afortunadamente, o Delphi nos prov um objeto que
molda perfeitamente a nossas exigncias de medio de tempo: TTimer.

Embora no este o lugar de explicar as caractersticas do Cronmetro de
objeto (a informao bsica deste componente pode estar na prpria ajuda
on-line do Delphi), ele serve para parar um momento na propriedade do
cronmetro que ns usaremos: o intervalo da propriedade. Esta propriedade
nos permite especificar o intervalo de tempo que queremos medir (em
milisegundos). Quando o valor estabelecido alcanado o mtodo que est
especificado no evento OnTimer executado.

O mtodo que especificaremos para o evento OnTimer far com que a
mensagem aparea e desaparea.

A teoria muito simples, mas... como adicionamos o objeto cronmetro para
nosso componente ? Em design-time simples. s selecionar o cronmetro
na paleta de componentes e coloc-lo em nosso formulrio.

Mas quando estamos escrevendo um componente no podemos usar este
sistema. Os passos gerais que devem ser seguidos:

Adicionar a unidade que o componente definido na clusula uses de nossa
unidade. Como o objeto cronmetro declarado na unidade ExtCtrls, esta
unidade que deveremos somar s clusulas Uses;
Adicionar o mtodo necessrio para construir o objeto (em nosso caso, o
cronmetro);
Escrever o cdigo necessrio para destruir o objeto no mtodo destructor
de nosso componente.
4.3. Construtores e destruidores
4.3.1. Construtores
Os objetos que declaramos num componente no existem em
memria at serem criado para uma chamada ao mtodo de
construo do objeto.

Um Construtor no mais que um mtodo que prov memria para
o objeto e aponta (por meio de um ponteiro) para ele. Chamando ao
mtodo create, o construtor nomeia a instncia do objeto para uma
varivel.

Ele serve para fazer referncia a todos os componentes herdam um
mtodo denominado create que se encarrega de criar o componente
em memria. De um modo adicional, no mtodo create os valores de
certos campos do componente podem ser inicializados (comumente
lhes nomear um valor por padro).

No mtodo create do componente podemos criar objetos adicionais
que ele precisa para nosso componente. Em nosso caso, no mtodo
create de TBlinkLbl criaremos o cronmetro.


4.3.2. Destruidores
Quando terminarmos de usar um objeto, devemos destru-lo, isto
quer dizer, liberar a memria que o objeto ocupou. Esta operao
feita atraves do mtodo destroy que todos os componentes herdam
de TComponent.

Se criarmos algum objeto adicional, devemos destruir tambm
escrevendo isto no mtodo destroy.

Delphi cria e destri nosso componente automaticamente quando
necessrio, como ja foi dito, os mtodos create e destroy so herdados do
TComponent.

Mas se queremos usar algum objeto em nosso componente, o Delphi no cria
e destroi automaticamente, ns devemos fazer isto manualmente. Este
processo feito para adicionar o cdigo necessrio para os mtodos create e
destroy. importante dizer que escreveremos o mesmo cdigo novamente no
componente, e podem sobrecarregar o cdigo novo.

De um modo geral isto feito do seguinte modo: (um exemplo mais concreto;
isto tem no prprio cdigo fonte do componente)
Na parte pblica de nosso componente declaramos os mtodos create e
destroy seguido pela palavra chave override:

public
constructor create(AOwner: TComponent); override;
destructor destroy; override;

Escrever o cdigo adicional do construtor e do destruidor na seo de
implementao.

constructor TComponent.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
...
end;

destructor TComponent.Destroy;
begin
...
inherited destroy;
end;

A coisa mais importante para se lembrar que quando sobrecarregamos um
construtor, a primeira coisa que deveria ser feita uma ligao ao construtor
original (linha herdada do create). O prximo passo adicionar o cdigo
necessrio para criar o objeto.

Da mesma maneira, quando sobrecarregamos um destruidor, os objetos
criados antes deveriam ser liberados e a ltima linha deveria ser uma
chamada ao destruidor original.


4.4. Implementando o piscador
Em design-time do componente deixamos ainda um passo importante: como
fazer as piscadas de mensagem. Dissemos que quando o evento OnTimer
acontece o mtodo ser executado e mostrar/ocultar a mensagem dando
uma sensao de "pisca-pisca".

A soluo muito simples, desde que nosso componente, descendendo de um
TLabel uma propriedade que determina se o componente deveria ser visvel ou
no. Esta propriedade denominada visible.

Deste modo, nosso mtodo pisca alternar o valor da propriedade booleana
visible quando o evento OnTimer acontece. So mostrados os detalhes
concretos de implementao no e cdigo fonte.
4.5. Outros detalhes na criao do componente. Valores padro
Comentamos que se o valor introduzido na propriedade freqncia for nulo, a
mensagem deveria permanecer fixa na tela. Isto alcanado ativando e
desativando o componente cronmetro quando o valor da velocidade for
introduzido no Object Inspector (ver procedimento SetVelocidad no cdigo
fonte).

Deste modo, se for colocado o valor 0 (zero) na propriedade de Velocidade, o
cronmetro simplesmente desativado e ele usa o valor da propriedade
visible para True. Se o valor introduzido diferente de zero, o cronmetro
habilitado e, por conseguinte, o pisca-pisca.

Um ltimo detalhe dar um valor padro para a propriedade que define a
velocidade. Isto adquirido em dois passos: primeiramente na declarao da
propriedade Velocidade com a palavra chave Default seguida pelo valor (400
em nosso caso) e dentro do construtor o campo associado com a propriedade
inicializado (FVelocidad := 400). Os dois passos so necessrios e no
possvel ter o mesmo resultado com a falta de um deles.

Este detalhe de nomear valores padro mais importante para evitarmos
erros de inicializao e temos certeza de que a propriedade sempre ter um
valor vlido. O Delphi s manter o valor da propriedade se ele for diferente
ao valor padro.


4.6. O cdigo fonte do componente

unit Blinklbl; { (c)1996 by Luis Roche }

interface

uses
Classes, StdCtrls, ExtCtrls;
type
TBlinkLabel = class(TLabel)
private
FVelocidad : integer;
FTimer : TTimer;
procedure SetVelocidad(valor : integer);
protected
procedure parpadea(Sender : TObject);
public
constructor Create(AOwner : TComponent); override; {Constructor}
destructor Destroy; override; {Destructor}
published
property Velocidad : integer read FVelocidad write SetVelocidad default 400;
end;

procedure Register;

implementation

constructor TBlinkLabel.Create(AOwner : TComponent);
begin
inherited Create(AOwner); {Chama o constructor original (herdado)}
FTimer := TTimer.Create(Self); {Criamos o timer}
FVelocidad := 400; {Freqncia (velocidade) padro}
FTimer.Enabled:=True; {Ativamos o timer}
FTimer.OnTimer:=parpadea; {Pegamos o mtodo parpadea (Pisca-Pisca)}
FTimer.Interval:=FVelocidad; {Pegamos o intervalo do timer = freqncia parpadeo}
end;

destructor TBlinkLabel.Destroy;
begin
FTimer.Free; {Liberamos o timer}
inherited destroy; {Chamamos o destructor original (herdado)}
end;

procedure TBlinkLabel.SetVelocidad (valor : integer);
begin
If FVelocidad <> valor then {S se o valor introduzido diferente do padro}
begin
if valor < 0 then FVelocidad:=0;
FVelocidad:=Valor; {Aceita a velocidade}
if FVelocidad=0 then FTimer.Enabled:=False else FTimer.Enabled:=True;
{Se Fvelocidad=0 a menssagem deve estar sempre visible}
FTimer.Interval:=FVelocidad;
Visible:=True;
end;
end;

procedure TBlinkLabel.parpadea(Sender : TObject);
begin
if FTimer.Enabled then Visible := not(Visible); {Alternativamente mostra e oculta a menssagem
se o timer esta activado}
end;

procedure Register; {Registro do componente}
begin
RegisterComponents('Curso', [TBlinkLabel]);
end;

end.


5. O OBJETO CANVAS E O MTODO PAINT: TGradient
Neste captulo criaremos um componente grfico do tipo que usado como fundo
na instalao de aplicaes: TGradient. Mas nosso componente ir mais adiante,
ele ter possibilidades novas para o grfico standard. Aprenderemos a utilizar o
canvas que prepara todos os componentes grficos. Estudaremos o mtodo paint
e como us-lo em nossos componentes.

5.1. Objetivo do componente
Nosso propsito criar um componente no qual ns puxaremos um gradient
semelhante de cores para qual mostrado na maioria dos programas de
instalao de aplicaes (e em outro tipo de programas). Entre as
caractersticas adicionais que nosso componente deve ter necessrio
destaquar que o gradiente de cores no concluir na cor preta, mas poder
comear e terminar em duas cores quaisquer. Tambm, o gradiente poder
estar horizontal ou vertical. Ultimamente, o tamanho de nosso desenho (quer
dizer, do canvas do componente) ser varivel, deste modo podemos
combinar dois ou mais componente para criar efeitos de cores espetaculares.

Nosso componente derivar ento de TGraphicControl do que ns precisamos
que tem Canvas, mas no necessrio que tenha manipulador (o handler).
Esta escolha faz isto baseado no que foi visto no captulo 2.


5.2. A base da programao grfica em Delphi: O objeto Canvas
Tradicionalmente, a programao grfica em janelas foi levada por meio do
uso da interface grfica de dispositivo (GDI). Esta interface uma ferramenta
poderosa que permite o desenho de grficos por meio do uso de pincis,
escovas, retngulos, elipses, etc.

Mas o GDI tem uma inconvenincia: a programao dele muito laboriosa.
Quando eles fazem uso da funo GDI, necessria uma handle, como
tambm criar e destruir as ferramentas de desenho diversos (recursos) isso
usado. Por ltimo , quando concluindo, o contexto de dispositivo deve ser
restabelecido a seu estado original antes de destru-lo.

Delphi encapsula o GDI e faz que no tenhamos que nos preocupar sobre
contextos de dispositivo nenhum e se liberamos ou no os recursos usados.
Deste modo ns podemos nos concentrar em nosso objetivo principal: puxar
os grficos. De todos os modos, se ns queremos isto, ns podemos continuar
usando as funes GDI se nos interessa. Ns temos um grande leque de
possibilidades, Delphi ou GDI, podemos us-los do modo que nos convenha.

Como foi mencionado no pargrafo anterior, o Delphi nos prov uma interface
grfica completo. O objeto principal desta interface o objeto Canvas. O
objeto Canvas se encarrega de ter um contexto de dispositivo vlido, como
tambm de liberar-se quando no usarmos mais. Tambm, o objeto Canvas
(ou simplesmente Canvas) tem propriedades diversas que representam o lpis
atual (pen), escova (brush) e fonte (font).

O canvas administra todos estes recursos, razo por que temos bastante o
que informar aquela classe de caneta e ele se encarrega do resto. Tambm,
deixando o Delphi fazer o servio pesado de criar e liberar os recursos
grficos, em muitos casos um aumento de velocidade acontecer se ns
mesmos administrarmos estes recursos.

A tela de objeto encapsula a programao grfica a trs nveis de
profundidade que o seguinte:

Nvel Operao Ferramentas
Desenho de linhas e formas
Mtodos MoveTo, LineTo,
Retngulo e Elipse
Visualizao e modificao de
textos
Mtodos TextOut, TextHeight,
TextWidth e TextRect
Alto
Recursos de reas Mtodos FillRect e FloodFill
Personalizar textos e grficos Propriedades Pen, Brush e Font
Pixels de manipulao Propriedade Pixels
Mdio
Cpia e unio de imagens
Draw, StretchDraw, BrushCopy,
CopyRect e CopyMode
Baixo Chamadas para funes GDI Handle de Propriedade

No vamos explicar tudo e cada ferramenta disponvel para a criao de
grficos agora, mas faremos isto como nas proximas unidades. Uma descrio
detalhada de cada um deles est na ajuda on-line do Delphi.

Bem, j sabemos que a tela de objeto nos prov uma interface grfica muito
poderosa de criao grfica. Mas agora as perguntas seguintes podem ser
esboadas: Onde o objeto Canvas reside ? Todos os componentes tm isto ?
E se no deste modo, como os componentes incorporam isto ?

A resposta para estas perguntas simples: Todos os objetos derivados de
TGraphicComponent possuem Canvas. Em relao a outros componentes,
depende. No caso de dvida, a coisa mais simples consultar o Object
Browser e verificar se o componente em questo possui canvas ou no (se
declarado em um modo protegido ou pblico). Deste modo por exemplo, o
componente TLabel, TImage e TPanel possuem isto, enquanto tais
componentes como TButton ou TRadioButton no. Este aspecto determinar o
ascendente a ser escolhido quando quisermos desenvolver um componente
que devera ter Canvas.

A forma de consentir o canvas de um objeto muito simples. Vamos supor
que temos um componente (TGradient) derivou da classe TGraphicControl. E
vamos supor que queremos tirar no canvas uma linha do ponto (0,0) at o
ponto (20,20). Para esta tarefa ns deveramos escrever a seguinte linha:

TGradient.Canvas.MoveTo(0,0);
TGradient.Canvas.LineTo(20,20);

Vamos ver agora as propriedades do Canvas que usaremos no
desenvolvimento de nosso componente. Basicamente TGradient deveria puxar
uma srie de retngulos coloridos com uma certa cor. Para isso ns usaremos
o mtodo FillRect. O mtodo FillRect recebe como parmetro um objeto do
tipo TRect com as coordenadas superiores esquerdas e direito inferior para
pintar. Deste modo, o cdigo que usaremos em nosso componente ser
semelhante ao seguinte (ainda no definimos a cor de preenchimento):

Canvas.FillRect(TRect);

Para escolher a cor de preenchimento usaremos a propriedade brush do
canvas. A propriedade brush determina a cor padro que o canvas usa para
preencher formas grficas e fundos. Em nosso caso, usaremos a propriedade
color do brush para escolher a cor que preencher o canvas.

Precisaremos, tambm, usar a propriedade pen do canvas. O objeto pen
determina a classe de lpis que usaremos no canvas para puxar linhas e
pontos. Em nosso caso, usaremos a propriedade estilo do pen que determina o
tipo de linha para puxar (em nosso componente psSolid) e o modo de
propriedade que determina o modo com que o lpis utilizar o canvas (em
nosso componente pmCopy )



5.3. Criando o gradiente de cores
No Delphi, uma cor representada atravs de 4 bytes hexadecimais. O byte
mais alto usado para determinar o ajuste que o Windows faz da paleta e no
veremos isto com mais detalhe. Os outros trs bytes (os trs bytes mais
baixos) eles representam a quantidade de vermelho, verde e azul que forma a
cor. Nosso componente devera calcular um gradiente de cores de um inicial
(FColorDesde no cdigo fonte) at outro fim (FColorHasta). O melhor modo
para calcular este gradiente decompondo as cores RGB (Vermelho, Verde e
Azul) em seu componente. Deste modo, saberemos a quantidade de cada uma
destas trs cores bsicas que iro formar uma certa cor. Por exemplo a cor
vermelha pura representada por 255,0,0 (255 de vermelho, 0 de verde e
azul), e um tom cinza tem os trs valores de vermelho, verde e mesmo azul
(por exemplo: 150,150,150).

A decomposio de uma cor em seus trs componentes bsicos depende de
trs funes: GetRValue, GetGValue e GetBValue que pertencem ao API de
Windows. Estas funes levam como parmetro a cor da qual queremos obter
o decomposio e eles devolvem a "quantidade" respectivamente de
vermelho, verde e azul que compe a cor.

No cdigo fonte, uma vez decompostas as cores inicial e final nas variveis
RGBDesde[0 ..2] e RGBHasta[0 ..2] (0 para cor vermelha, 1 para verde e 2
para azul), o processo de calculo do gradiente o seguinte:

1. Calcular a diferena, em valor absoluto, entre RGBDesde e RGBHasta para
cada uma das cores bsicas e manter isto na varivel RGBDif[0..2],
adicionalmente, no fator varivel manteremos +1 se RGBHasta for maior
que RGBDesde (gradiente superior) e -1 caso contrrio (gradiente
descendente). Este processo feito para cada uma das trs cores bsicas;
2. Entre 0 e 255 (nosso gradiente consistir em 256 cores), calculamos a cor
correspondente a cada requadro a puxar (com o mtodo FillRect) por meio
da expresso:

Vermelho := RGBDesde[0]+Factor[0]*MulDiv(Contador,RGBDif[0],255);
(De um modo semelhante para o verde e o azul)

Nota: MulDiv uma funo da API do Windows que multiplica os
primeiros dois valores passados como parmetros e o resultado
dividido pelo terceiro parmetro que devolve o valor desta diviso
em forma de 16 bits arredondados.
3. Colocamos a cor calculada na propriedade Color do objeto Brush:
Canvas.Brush.Color := RGB(Vermelho, Verde, Azul);

Como fcil supor, a funo RGB leva trs valores de vermelho, verde e
azul, formando a cor que corresponde a estas cores bsicas.
4. O requadro retirado:
Canvas.FillRect(Banda);

Banda uma varivel do tipo TRect que mantm as coordenadas do
requadro para puxar.


Ultimo detalhe: como foi mencionado, nosso gradiente consistir em 256
cores. Porm, dependendo do modo grfico que configurarmos o Windows e
da existncia de outros objetos com as prprias cores, o Windows ajustar as
cores disponveis de forma que o resultado final ser o mais prximo possvel.

5.4. Quando desenhar no Canvas. A mensagem WM_PAINT e o
mtodo Paint.
Quando queremos utilizar o canvas, devemos colocar o cdigo correspondente
dentro do mtodo paint que pertence ao objeto TGraphicControl (o objeto
TCustomControl tambm tem este mtodo). Isto deste modo porque quando
nosso componente deveria ser puxado (por exemplo: o gradiente de cores),
a primeira vez em que h uma resposta para uma aplicao de Windows, o
Windows enviar uma mensagem do tipo WM_PAINT para nosso componente.
O componente, de um modo automtico (herana), faz uma ligao ao mtodo
paint para desenhar o componente (em nosso caso, o gradiente).

Em geral no teremos a necessidade de investigar mais a mensagem
WM_PAINT. Como h pouco explicamos, a nica coisa que nos interessa dele
(pelo menos para nosso componente), que quando esta mensagem
recebida, o Delphi executar o mtodo paint associado ao componente. E
dentro deste mtodo que devemos adicionar o cdigo necessrio para puxar o
gradiente.

Ento, os passos que vimos anteriormente que so necessrios puxar o
gradiente, eles sero localizados dentro do mtodo TGradiente.Paint que ser
declarado como nula. O mtodo paint declarar isto no tipo protegido, desde
que no se possa us-lo fora de nosso componente.


5.5. Outros detalhes no desenvolvimento do componente.
Publicando propriedades herdadas.
Um dos detalhes que veremos agora o alinhamento do componente. O
objeto TGraphicControl possui a propriedade Align (ento nosso componente
herdar isto), mas ser declarado como pblico. interessante declarar isto
como Published de forma que ela aparea no Object Inspector em
design-time. Para isto ns devemos redeclarar a propriedade Align na seo
Published:

published
property align

importante fazer uma advertncia em dois aspectos: o primeiro que uma
redeclarao s pode ser menos restritivo o acesso para uma propriedade (por
exemplo: de protected a public), mas no mais restritivo (de public a
protected). O segundo aspecto ao redeclarar, no necessrio especificar o
tipo da propriedade, apenas indicar seu nome. O que podemos fazer no
momento da redeclarao definir um novo valor default para esta
propriedade.
5.6. O cdigo fonte do componente
unit Gradient;

interface

uses
Classes, Controls, Graphics, WinTypes, WinProcs;

type
TDireccion = (dHorizontal, dVertical); {Tipo de direccin del gradiente}
TGradiente = class(TGraphicControl)
private
FDireccion : TDireccion; {Direccin del gradiente}
FColorDesde, FColorHasta : TColor; {Color del gradiente}
procedure SetDireccion(valor : TDireccion);
procedure SetColorDesde(valor : TColor);
procedure SetColorHasta(valor : TColor);
protected
procedure Paint; override;
public
constructor Create(AOwner : TComponent); override;
published
property Direccion : TDireccion read FDireccion write SetDireccion default dHorizontal;
property ColorDesde : TColor read FColorDesde write SetColorDesde default clBlue;
property ColorHasta : TColor read FColorHasta write SetColorHasta default clBlack;
property Align; {Redeclarao da propriedade como publicada}
end;

procedure Register;

implementation

constructor TGradiente.Create(AOwner : TComponent);
begin
inherited Create(AOwner); {Sempre a primeira coisa a fazer}
FDireccion:=dHorizontal; {Valores padres}
FColorDesde:=clBlue;
FColorHasta:=clBlack;
Width:=100;
Height:=100;
end;

procedure TGradiente.SetDireccion(Valor : TDireccion);
begin
if FDireccion <> valor then
begin
FDireccion := Valor;
Repaint; {Fora o desenho do gradiente}
end;
end;

procedure TGradiente.SetColorDesde(Valor : TColor);
begin
if FColorDesde <> Valor then
begin
FColorDesde := Valor;
Repaint;
end;
end;

procedure TGradiente.SetColorHasta(Valor : TColor);
begin
if FColorHasta <> Valor then
begin
FColorHasta := Valor;
Repaint;
end;
end;

procedure TGradiente.Paint;
var
RGBDesde, RGBHasta, RGBDif : array[0..2] of byte; {Cores inicial e final - diferena de cores}
contador, Vermelho, Verde, Azul : integer;
Banda : TRect; {Coordenadas do requadro a pintar}
Factor : array[0..2] of shortint; {+1 se gradiente crescente e -1 caso decrescente}

begin
RGBDesde[0]:=GetRValue(ColorToRGB(FColorDesde));
RGBDesde[1]:=GetGValue(ColorToRGB(FColorDesde));
RGBDesde[2]:=GetBValue(ColorToRGB(FColorDesde));
RGBHasta[0]:=GetRValue(ColorToRGB(FColorHasta));
RGBHasta[1]:=GetGValue(ColorToRGB(FColorHasta));
RGBHasta[2]:=GetBValue(ColorToRGB(FColorHasta));

for contador:=0 to 2 do {Calculo de RGBDif[] e factor[]}
begin
RGBDif[contador]:=Abs(RGBHasta[contador]-RGBDesde[contador]);
If RGBHasta[contador]>RGBDesde[contador] then
factor[contador]:=1 else factor[contador]:=-1;
end;

Canvas.Pen.Style:=psSolid;
Canvas.Pen.Mode:=pmCopy;
if FDireccion = dHorizontal then
begin
Banda.Left:=0;
Banda.Right:=Width;
for contador:=0 to 255 do
begin
Banda.Top:=MulDiv(contador,height,256);
Banda.Bottom:=MulDIv(contador+1,height,256);
Vermelho:=RGBDesde[0]+factor[0]*MulDiv(contador,RGBDif[0],255);
Verde:=RGBDesde[1]+factor[1]*MulDiv(contador,RGBDif[1],255);
Azul:=RGBDesde[2]+factor[2]*MulDiv(contador,RGBDif[2],255);
Canvas.Brush.Color:=RGB(Vermelho,Verde,Azul);
Canvas.FillRect(Banda);
end;
end;

if FDireccion = dVertical then
begin
Banda.Top:=0;
Banda.Bottom:=Height;
for contador:=0 to 255 do
begin
Banda.Left:=MulDiv(contador,width,256);
Banda.Right:=MulDIv(contador+1,width,256);
Vermelho:=RGBDesde[0]+factor[0]*MulDiv(contador,RGBDif[0],255);
Verde:=RGBDesde[1]+factor[1]*MulDiv(contador,RGBDif[1],255);
Azul:=RGBDesde[2]+factor[2]*MulDiv(contador,RGBDif[2],255);
Canvas.Brush.Color:=RGB(Vermelho,Verde,Azul);
Canvas.FillRect(Banda);
end;
end;
end;
procedure Register;
begin
RegisterComponents('Curso', [TGradiente]);
end;

end.
6. TMultiGrid: CORES, ALINHAMENTO E LINHAS MLTIPLAS
NUM TstringGrid
Nesta unidade criaremos um componente melhorado do tipo StringGrid.
Aprenderemos a criar eventos que nos permitiro dotar de possibilidades novas e
potentes a nossos componentes. Tambm, estudaremos o mtodo OnDrawCell e
nos aprofundaremos nos tratados de tpicos relacionandos com os objetos
Canvas e Brush.


6.1. Objetivo do componente
Como h pouco mencionamos, nosso propsito criar um componente do tipo
StringGrid mas com funcionalidades novas. De forma que a primeira coisa que
deveremos saber o que o componente TStrinGrid padro nos permite fazer e
o que no nos permite. Assim se voc ainda no conhece este objeto,
recomendamos que voc faa uma pequena pausa e olhe na ajuda on-line do
Delphi. As caractersticas novas que implementaremos a nosso componente: a
cor e o alinhamento das celulas ao nvel que quisermos (coluna, linhas e at
mesmo celulas individuais, incluindo alinhamento vertical) como tambm uma
propriedade nova denominada multilinha que nos permitir mostrar mais que
uma linha de texto em cada celula do grid.

A figura que abaixo mostra um exemplo do componente em operao:


6.2. Implementando o alinhamento de clulas. Criando nossos
prprios eventos.
Comearemos com a propriedade de alinhamento. A primeira coisa que
deveremos decidir como implantaremos isto. Fundamentalmente, h trs
possibilidades:

1. Definindo o alinhamento de um modo global. Se optar por esta
implementao, voc apenas teria que definir uma propriedade
denominada, logicamente, Alignment, que controlaria o alinhamento de
todas as clulas do grid. Isto seria muito simples, mas no muito prtico,
desde que o que queremos ter so: As clulas do cabealho centralizadas,
textos alinhados esquerda, ou nmeros direita, etc.
2. Uma soluo um pouco melhor: Definir o alinhamento de colunas. Desde
modo cada coluna pode ser alinhada com independncia da outra, mas
mesmo assim todas as linhas desta coluna tero o mesmo alinhamento
(cabealho e dados). Se optar por esta soluo, voc ter um problema na
sua implementao, pois deveramos criar um editor de propriedades, e
ainda no sabemos como fazer isto (o que ser visto nos prximos
captulos). Este o caso do editor de colunas que o Delphi incorpora.
3. A terceira possibilidade nos oferece um controle total: definio do
alinhamento de cada clula a nvel individual. Deste modo cada clula ter
o seu prprio alinhamento independente da outra. O problema que isto
requer um pouco mais de esforo por parte do usurio do componente.

Como se v, cada uma das trs opes acima tem suas vantagens e
desvantagens. Por isso em nosso componente implementaremos uma
combinao do primeiro e terceiro mtodo. Deste modo, definiremos uma
propriedade de Alinhamento especfica para o alinhamento individual por
celula e outro padro para alinhamento total da grade.

A implementao do alinhamento horizontal a nvel global no tem nenhum
mistrio: Basta definir o propriedade Alignment do tipo TAlignment (j incluida
no Delphi). O campo que ela manter o valor desta propriedade ter o nome:
FAlignment. Para escrevermos o valor na propriedade, usaremos o mtodo
SetAlignment, e a leitura sera feita diretamente no campo FAlignment. Deste
modo definimos a interface da propriedade Alignment perfeitamente.
Definiremos as propriedades para o alinhamento individual por celula quando
estudarmos o evento OnDrawCell.

O alinhamento vertical desenvolvido de um modo semelhante. O nico
aspecto diferencial que o Delphi no incorpora a propriedade do tipo
TVerticalAlignment, de forma que deveremos cri-lo:

TVerticalAlignment = (vaTop, vaCenter, vaBottom);

Veremos agora como implementar a interface do alinhamento das celulas a
nvel individual. Para isso criaremos um novo evento, que ao ser ativado
saberemos o alinhamento de uma celula qualquer.

Como j voc viu no captulo 2, um evento um mecanismo que vncula uma
ao para certo cdigo. Mais concretamente, um evento um ponteiro ao
mtodo.

A implementao do evento feito por meio de propriedades, quer dizer, os
eventos so propriedades. Ao contrrio das propriedades padres, os eventos
no usam mtodos para implementar as partes lidas e escritas. No lugar
delas, as propriedades de evento utilizam um campo privado.

Mas j temos bastante de teoria, vamos trabalhar !. Como j foi mencionado,
criaremos um evento novo que dever ser ativado toda vez que precisarmos
obter o valor do alinhamento de uma celula especfica. A primeira coisa que
devemos fazer ento, definir nosso tipo de evento. Isto se faz por meio
seguinte linha:

TMultiGridAlignmentEvent=procedure(Sender:TObject; ARow,ACol:LongInt;

var HorAlignment: TAlignment; var VerAlignment: TVerticalAlignment) of object;

Os parmetros do evento TMultiGridAlignmentEvent:

1. Sender: Identifica o objeto que faz a ligao;
2. ARow e ACol: Identificam as coordenadas de linha (Arow) e coluna (ACol)
para o alinhamento;
3. var HorAlignment e var VerAlignment: Parmetros para alinhamento
horizontal (HorAlignment) e vertical (VerAlignment);

J definimos o tipo de evento. Agora deveremos criar um campo que contenha
o estado da propriedade OnGetCellAlignment. Isto devera ser feito na parte
Private:

private
...
FOnGetCellAlignment: TMultiGridAlignmentEvent;

Finalmente, definiremos a propriedade na parte Published:

property OnGetCellAlignment: TMultiGridAlignmentEvent read FonGetCellAlignment
write FOnGetCellAlignment;

S ativaremos o evento quando precisarmos, embora um pouco mais tarde
faremos com mais detalhes em nosso componente, a linha abaixo nos mostra
um mecanismo geral:

if Assigned(FOnGetCellAlignment) then
FOnGetCellAlignment(Self,ARow,ACol,HorAlignment,VerAlignment);
Importante: Antes de ativar um evento, conveniente olhar primeiro se este
evento tem um gerenciador de evento nomeado, como o usurio do
componente no tem porque ter escrito este gerenciador. De l a comparao
if Assigned: se h um gerenciador evento, ele chamado, mas se no h nada
feito.


6.3. Implementando a fonte, estilo e cor de clulas
Se voc entendeu a seo anterior, no haver nenhum problema para
entender esta, como a forma de implementar a cor e a atribuio de fonte em
uma certa celula ser feita de um modo semelhante.

Defininimos um novo evento que ser ativado quando necessrio determinar
os atributos da celula. Para isto, ns criaremos o tipo do evento, o campo
privado armazenar isto e a propriedade associada a este evento:

TMultiGridColorEvent=procedure(Sender: TObject; ARow,Acol: LongInt;

AState: TGridDrawState; Abrush: TBrush; AFont:TFont) of object;

...

FOnGetCellColor: TMultiGridColorEvent;

...

property OnGetCellColor: TMultiGridColorEvent read FOnGetCellColor write FOnGetCellColor;

A diferena principal entre este evento e o correspondente ao alinhamento
est determinada pelos parmetros ABruh e AFont. O usurio do componente
dever retornar o brush e a Fonte a celula correspondente nas coordenadas
ARow e ACol. O parmetro AState nos informa do estado da clula (selected,
focus, fixo...).


6.4. Clulas Multi-Linhas
Vamos passar agora para a implementao das celulas multi-linhas.

Em primeiro lugar definiremos a interface. Para isto, criaremos uma
propriedade nova chamada MultiLinha . Esta propriedade ser armazenada no
campo FMultiLinha que ser do tipo boolean. Se FMultiLinha false, nosso
componente se comportar como o StringGrid normal, enquanto se for true,
se tornar um StringGrid Multi-Linhas.

private
FMultiLinha: Boolean;
...

property MultiLinha: Boolean read FMultiLinha write SetMultiLinha default False;

Ele serve para fazer uma chamada ao mtodo SetMultiLinha, se um valor novo
colocada para FMultiLinha o componente redesenhado por meio da
instruo o Invalidate. Esta mesma tcnica usada nos mtodos
SetAlignment, SetVerticalAlignment e SetColor.
6.5. O corao do componente: o mtodo DrawCell
At agora, nos concentramos na interface de nosso componente; chegou o
momento de definir o implementao. O processo inteiro de desenho de uma
celula feita atravs do mtodo DrawCell. Este mtodo o verdadeiro corao
de nosso componente, visto que nele que deve ser escrito todo o cdigo de
clculo e desenho do texto correspondente em uma determinada celula. O
evento OnDrawCell passa os seguintes parmetros ao mtodo DrawCell:

1. ACol e ARow: Coordenadas da clula;
2. ARect: Estrutura de tipo retangular que identifica o canto superior
esquerdo e inferior direito (em pixels) da clula desejada;
3. AState: o estado atual da clula (selecionada, enfocada ou fixa). Em
princpio no usaremos o ltimo valor neste parmetro.

J sabemos onde. Agora falta ver como . A princpio voc pode ficar
assustado com tudo aquilo temos que fazer: calcular o alinhamento horizontal
e vertical, ativar os eventos de alinhamento e colorir, fragmenar o texto de
uma celula em vrias linhas... Mas no h nenhuma razo: o Delphi e as API
do Windows nos ajudam a diminuir tudo isso para 20 ou 30 linhas
compreensiveis. Logo o cdigo que corresponde ao mtodo DrawCell
mostrado:

procedure TMultiGrid.DrawCell(ACol,ARow : LongInt; ARect : TRect; AState : TGridDrawState);
Const
TextAlignments : Array[TAlignment] of Word = (dt_Left, dt_Right, dt_Center);

Var
HorAlignment : TAlignment;
VerAlignment : TVerticalAlignment;
Texto : string;
Altura : integer;
CRect : TRect;
options : integer;

begin
Texto:=Cells[ARow,Acol];
HorAlignment:=FAlignment;
VerAlignment:=FVerticalAlignment;

if Assigned(FOnGetCellAlignment) then
FOnGetCellAlignment(Self,ARow,ACol,HorAlineacion,VerAlignment);

if Assigned(FOnGetCellColor) then
FOnGetCellColor(Self,ARow,ACol,AState,Canvas.Brush,Canvas.Font);

Canvas.FillRect(ARect);
Inc(ARect.Left,2);
Dec(ARect.Right,2);
CRect:=Arect;
options:=TextAlignments[HorAlignment] or dt_VCenter;

if Multilinha then
options:=options or dt_WordBreak;

if not DefaultDrawing then
inherited DrawCell(ACol,ARow,ARect,AState)
else
with ARect,Canvas do begin
Altura:=DrawText(Handle,PChar(Texto),-1,CRect,options or dt_CalcRect);
if FVerticalAlignment = vaCenter then begin
if Altura < Bottom-Top+1 then begin
Top:=(Bottom+Top-Altura) shr 1;
Bottom:=Top+Altura;
end;
end else
if FVerticalAlignment = vaBottom then
if Altura < Bottom-Top+1 then
Top:=Bottom-Altura;
DrawText(Handle,PChar(Texto),-1,ARect,options);
end;
end;

A primeira coisa que faremos manter o contedo da celula para chamar a
varivel Texto. Logo os valores so determinados por padro para as variveis
HorAlignment e VerAlignment porque se o usurio no introduziu um
alinhamento particular para a clula em questo estes alinhamentos padres
sero aplicados.

Agora vem uma das chaves: a chamada para os eventos. Se o usurio
escreveu um gerenciador para o evento de alinhamento, ele o chama. A
mesma coisa acontece para o evento de Cor. Deste modo j temos o
tratamento especfico da clula.

O prximo passo desenhar o fundo da clula por meio do mtodo FillRect
para o qual passamos o requadro de desenho desta clula (ARect).

Faremos uma pausa agora para explicar agora como colocaremos o texto.

Em princpio veja, a coisa lgica seria usar o mtodo TextOut do objeto
Canvas. Este mtodo precisa como parmetros as coordenadas (x ,y) onde
colocar a string com o texto. Mas para nossos propsitos ruim, pois teramos
que calcular a posio correta nas coordenadas (x,y). Tambm teramos que
calcular as divises de palavras necessrias para as clulas multi-linhas, etc.
Em resumo, um rolo !! Mas podemos evitar todo este trabalho graas a uma
funo API do Windows: DrawText.

DrawText precisa dos seguintes parmetros:

1. Um handle para o objeto que queremos desenhar (o Canvas do Grid);
2. Um tipo de cadeia terminada em nulo com o texto a desenhar
(PChar(Texto));
3. Um nmero que indica qual caracter a ser desenhado (-1 para tudo);
4. O retngulo no qual o texto vai se enquadrar (TRect);
5. Uma srie de opes de formatao de texto. Usaremos os controles de
alinhamento de texto (dt_Left, dt_Center, dt_Right, dt_VCenter,
dt_WordBreak) e o de clculo de altura (dt_CalcRect).

H mais opes para o DrawText, se voc quiser mais informaes leia na
ajuda on-line.
Voltemos agora ao fluxo do programa. Depois de encher o fundo da clula com
o FillRect, copiamos na varivel CRect o retngulo original (ARect) e eles
preparam as opes com que chamaremos o DrawText. Aqui surge um
pequeno problema: o HorAlignment do tipo TAlignment (ta_LeftJustify...) e
DrawText no entende este tipo, por isso necessria uma converso entre
este tipo e o do DrawText . Esta converso feita atravs de uma matriz
constante denominado TextAlignments. Logo, se a propriedade Multilinha
true, dt_WordBreak adicionado s opes do DrawText.

Depois verificar se o usurio trocou o valor da propriedade DefaultDrawing.
Se o valor desta propriedade false indica que o usurio se encarrega de todo
o processo, caso contrario o componente se encarrega do desenho.

Se o componente encarregado de tudo, fazemos a primeira chamada ao
DrawText para obter a altura exigida do retngulo da clula. Com esta altura,
sempre que possvel (o multilinha ajusta o texto inteiro na clula), ele
centraliza o texto na celula (ou no topo/rodap). Uma vez feito isto,
chamamos novamente o DrawText de forma que ele pega o lugar, e
colocado o texto no canvas do componente. Veja que para isso usamos ARect
como retngulo nesta ocasio.

Estas so as grandes caracteristicas com a operao do mtodo DrawCell.


6.6. Outros detalhes
Por ltimo existem alguns detalhes que merecem ser comentados:

1. As propriedades de Opes do StringGrid padro so redefinidas por
default para True e as opes goRowSizing para multilinhas do nosso
componente para True por default;
2. Como sempre, no construtor declaramos os valores default para as
propriedades diferentes que ns definimos (alinhamento, cor,
multilinha);
3. Depois, no cdigo fonte, mostrado um exemplo dos eventos de
alinhamento, cores e multilinha.


6.7. Cdigo fonte do componente
unit MultiGrid; { (c) 1997 by Luis Roche }

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids;

type
TVerticalAlignment = (vaTop, vaCenter, vaBottom);
TMultiGridAlignmentEvent=procedure(Sender:TObject;ARow,ACol:LongInt;

var
HorAlignment:TAlignment;var VerAlignment:TVerticalAlignment) of object;
TMultiGridColorEvent=procedure(Sender:TObject;ARow,Acol:LongInt;AState:TGridDrawState;
ABrush:TBrush;AFont:TFont) of object;
TMultiGrid = class(TStringGrid);
private
FAlignment : TAlignment;
FVerticalAlignment : TVerticalAlignment;
FMultiLinha : Boolean;
FOnGetCellAlignment : TMultiGridAlignmentEvent;
FOnGetCellColor : TMultiGridColorEvent;
procedure SetAlignment(Valor : TAlignment);
procedure SetVerticalAlignment(Valor : TVerticalAlignment);
procedure SetMultiLinha(Valor : Boolean);

protected
procedure DrawCell(ACol,ARow : LongInt; ARect : TRect; AState : TGridDrawState); override;

public
constructor Create(AOwner : TComponent); override;

published
property Alignment : TAlignment read FAlignment write SetAlignment default taLeftJustify;
property VerticalAlignment : TVerticalAlignment read FVerticalAlignment
write SetVerticalAlignment default vaCenter;
property MultiLinha : Boolean read FMultiLinha write SetMultiLinha default False;
property OnGetCellAlignment : TMultiGridAlignmentEvent read FonGetCellAlignment
write FOnGetCellAlignment;
property OnGetCellColor : TMultiGridColorEvent read FOnGetCellColor write FOnGetCellColor;
property Options default
[goFixedVertLine,goFixedHorzLine,goVertLine,goHorzLine,goRangeSelect,
goRowSizing,goColSizing];
end;

procedure Register;

implementation

constructor TMultiGrid.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FAlignment:=taLeftJustify;
FVerticalAlignment:=vaCenter;
{FColor:=clWindowText;}
FMultiLinha:=False;
Options:=[goFixedVertLine,goFixedHorzLine,goVertLine,goHorzLine,goRangeSelect,
goRowSizing,goColSizing];
end;

procedure TMultiGrid.SetAlignment(Valor : TAlignment);
begin
if valor <> FAlignment then
begin
FAlignment:=Valor;
Invalidate;
end;
end;

procedure TMultiGrid.SetVerticalAlignment(Valor : TVerticalAlignment);
begin
if valor <> FVerticalAlignment then
begin
FVerticalAlignment:=Valor;
Invalidate;
end;
end;

procedure TMultiGrid.SetMultiLinha(Valor : Boolean);
begin
if valor <> FMultiLinha then
begin
FMultiLinha:=Valor;
Invalidate;
end;
end;

procedure TMultiGrid.DrawCell(ACol,ARow : LongInt; ARect : TRect; AState : TGridDrawState);
Const
TextAlignments : Array[TAlignment] of Word = (dt_Left, dt_Right, dt_Center);
Var
HorAlignment : TAlignment;
VerAlignment : TVerticalAlignment;
Texto : string;
Altura : integer;
CRect : TRect;
Options : integer;
begin
Texto:=Cells[ARow,Acol];
HorAlignment:=FAlignment;
VerAlignment:=FVerticalAlignment;
if Assigned(FOnGetCellAlignment) then
FOnGetCellAlignment(Self,ARow,ACol,HorAlignment,VerAlignment);
if Assigned(FOnGetCellColor) then
FOnGetCellColor(Self,ARow,ACol,AState,Canvas.Brush,Canvas.Font);
Canvas.FillRect(ARect);
Inc(ARect.Left,2);
Dec(ARect.Right,2);
CRect:=Arect;
Options:=TextAlignments[HorAlignment] or dt_VCenter;
if Multilinha then Options:=Options or dt_WordBreak;
if not DefaultDrawing then
inherited DrawCell(ACol,ARow,ARect,AState)
else
with ARect,Canvas do
begin
Altura:=DrawText(Handle,PChar(Texto),-1,CRect, options or dt_CalcRect);
if FVerticalAlignment = vaCenter then
begin
if Altura < Bottom-Top+1 then
begin
Top:=(Bottom+Top-Altura) shr 1;
Bottom:=Top+Altura;
end;
end
else if FVerticalAlignment = vaBottom then
if Altura < Bottom-Top+1 then Top:=Bottom-Altura;
DrawText(Handle,PChar(Texto),-1,ARect,options)
end;
end;

procedure Register;
begin
RegisterComponents('Curso', [TMultiGrid]);
end;

end.

Exemplo de utilizao.
Logo um exemplo de utilizao do nosso componente novo:

procedure TForm1.FormCreate(Sender: TObject);
begin
MultiGrid1.Cells[0,1]:='Janeiro';
MultiGrid1.Cells[0,2]:='Fevereiro';
MultiGrid1.Cells[0,3]:='Total Ano';
MultiGrid1.Cells[0,4]:='Notas';
MultiGrid1.Cells[1,0]:='rea A';
MultiGrid1.Cells[2,0]:='rea B';
MultiGrid1.Cells[3,0]:='Outras Areas';
MultiGrid1.Cells[4,0]:='TOTAL';
MultiGrid1.Cells[1,1]:='1.000.000';
MultiGrid1.Cells[1,2]:='1.250.000';
MultiGrid1.Cells[1,3]:='9.150.000';
MultiGrid1.Cells[1,4]:='Incremento sobre ano anterior';
MultiGrid1.Cells[2,1]:='1.450.000';
MultiGrid1.Cells[2,2]:=' 950.000';
MultiGrid1.Cells[2,3]:='4.150.000';
MultiGrid1.Cells[2,4]:='Decremento';
MultiGrid1.Cells[3,1]:='4.000.000';
MultiGrid1.Cells[3,2]:='3.250.000';
MultiGrid1.Cells[3,3]:='17.250.000';
MultiGrid1.Cells[3,4]:='Incremento sobre ano anterior';
MultiGrid1.Cells[4,1]:='6.450.000';
MultiGrid1.Cells[4,2]:='5.450.000';
MultiGrid1.Cells[4,3]:='30.550.000';
MultiGrid1.Cells[4,4]:='';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
MultiGrid1.Color:=clRed;
end;

procedure TForm1.MultiGrid1GetCellAlignment(Sender: TObject; ARow,
ACol: Longint; var HorAlignment: TAlignment;
var VerAlignment: TVerticalAlignment);
begin
if (ACol in [1..3]) and (ARow in [1..4]) then
HorAlignment:=taRightJustify
else HorAlignment:=taCenter;
end;

procedure TForm1.MultiGrid1GetCellColor(Sender: TObject; ARow,
Acol: Longint; AState: TGridDrawState; Abrush: TBrush; AFont: TFont);
begin
if (ARow=0) then
begin
ABrush.Color:=clMaroon;
AFont.Color:=clWhite;
AFont.Style:=[fsBold];
end
else if (ACol=0) then
begin
AFont.Color:=clBlack;
AFont.Style:=[fsBold];
ABrush.Color:=clYellow;
end
else
begin
AFont.Color:=clBlack;
ABrush.Color:=clYellow;
end;
end;
7. TDBViewer: UM VISUALIZADOR RPIDO PARA BANCO DE
DADOS
Nesta unidade transformaremos uma forma em um componente. Em resumo,
criaremos uma forma para visualizar bancos de dados rapidamente e
integraremos isto dentro de um componente. Deste modo em nossos projetos,
quando queremos visualizar um banco de dados rapidamente, chamamos um
mtodo de nosso componente para realiz-lo.

Esta converso de caixas de dilogo em componentes uma opo muito
poderosa que nos permite reutilizar o cdigo de nossos projetos diversos de um
modo rpido e simples. Sem dvida assim que conhecermos a tcnica para
alcan-lo teremos possibilidades infinitas para converter form's que usamos
freqentemente em componentes. Seria bastante dizer que eu tenho um
componente que encapsula uma aplicao inteira de 10 ou 12 janelas !.

necessario dizer que algumas funes que usaremos aqui no existem no
Delphi 1; se voc for usar o Delphi 1 ento melhor procurar na ajuda on-line
algo semelhante ao que vamos usar.


7.1. Objetivo do componente
Nosso objetivo criar um componente que mostrar uma janela ao usurio
atravs da definio de propriedades e chamadas de mtodos em design-time
para que seja ativado em run-time.

A forma que vamos integrar no componente visualizador / editor de bancos
de dados. Logo o componente mostrado em run-time e suas funes
principais so comentadas.

Por meio de dois Combo-Boxes o usurio define o Alias e o nome da
tabela a ser visualizada;
Um dbGrid mostrado para a consulta e edio dos dados;
Num painel adicional ele poder fazer consultas e buscas por palavras
em qualquer campo de um banco de dados;
O grid tem um menu popup que permite ou no o uso de filtro de
registros.

Criao do componente:
Primeiro criaremos um Form para nosso componente;
Depois criaremos o componente, inclusive propriedades, mtodos, etc.
Por ltimo faremos a conexo entre o componente e o Form criado.


7.2. O projeto do Form
A criao do Form sera feita do modo tradicional, quer dizer, no Delphi,
faremos isso em File|New e escolhemos a forma em branco.

A seo de interface do Form a seguinte:

unit frmView;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, DB,
DBTables, Grids, DBGrids, ExtCtrls, Buttons, Menus;

type
TfrmVisor = class(TForm)
tTable: TTable;
dsDataSource: TDataSource;
dbGrid: TDBGrid;
pTop: TPanel;
pSelectTable: TPanel;
pBusca: TPanel;
gbTables: TGroupBox;
Alias: TLabel;
Table: TLabel;
cbAlias: TComboBox;
cbTables: TComboBox;
gbBusca: TGroupBox;
cbFields: TComboBox;
eSearch: TEdit;
Buscar: TSpeedButton;
BuscarSeguinte: TSpeedButton;
PopupMenu1: TPopupMenu;
Igual1: TMenuItem;
Distinto1: TMenuItem;
Menor1: TMenuItem;
Maior1: TMenuItem;
N1: TMenuItem;
Ativar1: TMenuItem;
Eliminar1: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure FillcbFields;
procedure Inicializar;
procedure cbAliasChange(Sender: TObject);
procedure cbTablesChange(Sender: TObject);
procedure BuscarClick(Sender: TObject);
procedure BuscarSeguinteClick(Sender: TObject);
procedure dbGridColEnter(Sender: TObject);
procedure PopupMenu1Popup(Sender: TObject);
procedure Ativar1Click(Sender: TObject);
procedure Eliminar1Click(Sender: TObject);
procedure Igual1Click(Sender: TObject);
procedure Distinto1Click(Sender: TObject);
procedure Menor1Click(Sender: TObject);
procedure Maior1Click(Sender: TObject);
private

public
FReadOnly, FFiltrar, FBuscar : boolean;
end;

procedure PascalStr(var s : string);
procedure ComaStr(var s : string);
function Condicao(AField : TField; AOp : string) : string;
procedure AddFilter(AField : TField; AOp : string);

var
frmVisor: TfrmVisor;

Seo Pubic: Nela definimos trs variveis de tipo boolean: FReadOnly, FFiltrar
e FBuscar. Estas trs variveis formam a interface entre o Form e o
componente. Deste modo, propriedade ReadOnly que implementaremos no
componente a varivel FReadOnly do Form correspondente e, de um modo
semelhante acontece com as propriedades (AllowSearch) e (AllowFilter) do
componente. Ser o componente, e no o Form que executar os mtodos.

O mtodo de Inicializar o que se encarrega de ajustar a visualizao do Form
de acordo com a varivel FReadOnly , FFiltrar e FBuscar. Este mtodo ser
chamado uma vez pelo prprio componente uma vez que o valor das variveis
forem includas:

1. Ajusta a propriedade ReadOnly do grid de acordo com o valor de
FReadOnly;
2. Mostra ou no o painel de procura de acordo com o valor de FBuscar;
3. Coloca ou no o menu popup para o grid de acordo com o valor de
FFiltrar.

Veremos agora como feita a visualizao. No evento OnCreate do Form voc
comea a encher o ComboBox cbAlias com todos os aliases possveis. Depois
voc ter que encher o ComboBox cbTables com todas as tabelas existentes
no alias com o mtodo (cbAliasChange). Este mtodo fora a chamada para o
mtodo cbTablesChange, o qual se encarrega de ativar o quadro (ele tambm
enche o ComboBox cbFields com os campos da tabela selecionada). Deste
modo, o grid que j conectado ao DataSource e a tabela correspondente.
Mais tarde se o usurio selecionar outro alias ou tabela, o processo se repete e
a nova tabela selecionada mostrada.

Um tratamento de erros na abertura das tabelas recomendvel, porm este
no o objetivo desta lio. Mas voc poderia colocar no mnimo um bloco
Try... Except.
Veremos agora como fazer as buscas de registros. Para isto usaremos o
mtodo BuscarClick que procurar a string do Edit eSearch no campo definido
pelo ComboBox cbFields.. A procura feita atravs dos mtodos do objeto
TTable: FindFirst e FindNext que eu suponho que voc saiba mais do que o
suficiente. Em todo caso, voc achar informao sobre o mesmo na ajuda
on-line do Delphi.

Agora implementaremos o filtro de registros. H diversas formas de fazer isto,
e optei por usar um menu popoup que permite ativar, desativar e adicionar
condies novas para o filtro. Sendo mais claro o menu tem as seguintes
opes: Adicionar uma condio do tipo Campo = valor, Campo > Valor,
Campo < valor e Campo <> Valor, ativar o filtro e eliminar o filtro. As
condies diversas do filtro se unem um ao outro por meio do operador AND.

O mtodo Activar1Click e Eliminar1Click no precisam de muita explicao
desde que eles so limitados para pr a propriedade Filtered do objeto TTable
True ou False como corresponde.

Os mtodos restantes do menu (Igual1Click...) adicionam uma nova condio
para o filtro. Para isto, o procedimento AddFilter usado e recebe como
parmetros o campo onde vai ser colocado o filtro e sua condio do valor
(isso a coluna ativa do dbgrid quando apertado o boto direito do mouse)
e o operador (=, <,> ou <> ). Este mtodo, depois de formatado
corretamente, a string colocada no filtro do objeto TTable, que faz um
refresh na Tabela e ativado o Filtro.

E com isto terminamos a parte que corresponde ao desenvolvimento do Form.
Fcil, no ? De qualquer maneira, se voc tem alguma dvida, veja o cdigo
fonte.


7.3. Criao do componente
Vamos trabalhar agora com a criao do componente, o que muito simples.
Consiste em trs propriedades e dois mtodos, que a esta altura j no devem
ser problema para voc.

1. A propriedade ReadOnly determinar se o grid do Form ser editvel ou
no. Para ele o valor default False, quer dizer, editvel. Os mtodos read
e write limitam-se a ler e escrever no campo FReadOnly do componente
(no confundir com o FReadOnly da forma visto na seo anterior);
2. A propriedade AllowSearch determinar se o painel de procura deve
aparecer no Form. Por default o valor deve ser True. Os mtodos dele so
lidos e escritos diretamente no campo FAllowSearch;
3. A propriedade AllowFilter determinar se permitido ou no o filtro de
registros no Form. Por default o valor True. Os mtodos so iguais ao
anterior;
4. O Construtor limitado a chamar o construtor herdado e nomear os
valores default para as propriedades;
5. O mtodo Execute executar o componente nos mostrando o Form.

E isso tudo. Simples ou no ? Esta simplicidade ser em geral sempre que
voc transformar uma forma em componente. Basta criar as propriedades que
agiro na forma e definir um mtodo que chama a forma e acabou.

Voc no pode esquecer de colocar o nome da sua Unit (do Form criado) na
clusula uses do componente (na seo de implementao). Em nosso caso:

...
implementation

uses frmView;
...


7.4. A conexo entre o componente e o Form
Conforme j foi dito, a execuo do Form levada atravs do mtodo Execute
do componente:

procedure TDBViewer.Execute;
begin
{Criaao do Form}
frmVisor:=TFrmVisor.Create(Application);
try
{Colocar os valores das propriedades do componente nas variaveis
correspondentes do form}
frmVisor.FReadOnly:=FReadOnly;
frmVisor.FBuscar:=FAllowSearch;
frmVisor.FFiltrar:=FAllowFilter;
{Inicializar o form}
frmVisor.Initialize;
{Mostrar a form em modal}
frmVisor.ShowModal;
finally
{Liberar o form}
frmVisor.Free;
end;
end;

O mtodo comea a criar o Form cujo o proprietrio ser a aplicao. Depois
ele levado a conexo entre o Form e o componente. Porque eles partem dos
valores colocados para as variveis FReadOnly , FBuscar e FFiltrar do Form
correspondente ao componente. Uma vez feita esta tarefa, deveremos fazer
com que a forma seja visualizada corretamente baseado nestes valores
(lembre-se que o Form foi criado, mas no foi mostrado ainda). Isto que
chama ao mtodo para Inicializar da forma adquirido (voc viu este mtodo
no desenvolvimento do Form).

S deixamos mostrar o Form de um modo modal e esperar que o usurio se
feche esta forma; momento no qual destruiremos o Form por meio da
chamada para o mtodo Free.

Como ltimo aspecto a destacar temos que avis-lo que o bloco try... finally
nos assegura que os processo foi feito corretamente e que a memria ocupada
ser liberada.
7.5. Instrues de uso do componente e concluses
Uma vez registrado o componente na paleta do modo habitual, o uso dele
muito simples. Quando queremos usar isto em um projeto, basta colocar o
componente DBViewer onde quisermos, colocar os valores nas propriedades e
chamar o mtodo Execute.

Como concluso, pudemos ver que mais simples do que parece transformar
nossos Forms em componentes e cham-los pelo mtodo execute. Este
componente que fizemos foi bem simplificado; espero que voc coloque novas
funes nele ao longo do tempo. Aqui vo algumas idias que podem ser
colocadas: OnOpenTable, OnTableError, opo de impressao dos dados, etc.

Use sua imaginao conforme sua necessidade.


7.6. Cdigo fonte do componente
unit DBView; { (c) 1997 by Luis Roche }

interface

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

type
TDBViewer = class(TComponent)
private
FReadOnly : boolean;
FAllowSearch : boolean;
FAllowFilter : boolean;
protected
public
constructor Create(AOwner : TComponent); override;
procedure Execute;
published
property ReadOnly : boolean read FReadOnly write FReadOnly default False;
property AllowSearch : boolean read FAllowSearch write FAllowSearch default True;
property AllowFilter : boolean read FAllowFilter write FAllowFilter default True;
end;

procedure Register;

implementation

uses frmView;

constructor TDBViewer.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FReadOnly:=False;
FAllowSearch:=True;
FAllowFilter:=True;
end;

procedure TDBViewer.Execute;
begin
frmVisor:=TFrmVisor.Create(Application);
try
frmVisor.FReadOnly:=FReadOnly;
frmVisor.FBuscar:=FAllowSearch;
frmVisor.FFiltrar:=FAllowFilter;
frmVisor.Inicializar;
frmVisor.ShowModal;
finally
frmVisor.Free;
end;
end;

procedure Register;
begin
RegisterComponents('Curso', [TDBViewer]);
end;

end.


7.7. Cdigo fonte do Form
unit frmView;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, DB, DBTables, Grids, DBGrids, ExtCtrls, Buttons, Menus;

type
TfrmVisor = class(TForm)
tTable: TTable;
dsDataSource: TDataSource;
dbGrid: TDBGrid;
pTop: TPanel;
pSelectTable: TPanel;
pBusca: TPanel;
gbTables: TGroupBox;
Alias: TLabel;
Table: TLabel;
cbAlias: TComboBox;
cbTables: TComboBox;
gbBusca: TGroupBox;
cbFields: TComboBox;
eSearch: TEdit;
Buscar: TSpeedButton;
BuscarSeguinte: TSpeedButton;
PopupMenu1: TPopupMenu;
Igual1: TMenuItem;
Distinto1: TMenuItem;
Menor1: TMenuItem;
Maior1: TMenuItem;
N1: TMenuItem;
Ativar1: TMenuItem;
Eliminar1: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure FillcbFields;
procedure Inicializar;
procedure cbAliasChange(Sender: TObject);
procedure cbTablesChange(Sender: TObject);
procedure BuscarClick(Sender: TObject);
procedure BuscarSeguinteClick(Sender: TObject);
procedure dbGridColEnter(Sender: TObject);
procedure PopupMenu1Popup(Sender: TObject);
procedure Ativar1Click(Sender: TObject);
procedure Eliminar1Click(Sender: TObject);
procedure Igual1Click(Sender: TObject);
procedure Distinto1Click(Sender: TObject);
procedure Menor1Click(Sender: TObject);
procedure Maior1Click(Sender: TObject);
private
public
FReadOnly, FFiltrar, FBuscar : boolean;
end;

procedure PascalStr(var s : string);
procedure ComaStr(var s : string);
function Condicao(AField : TField; AOp : string) : string;
procedure AddFilter(AField : TField; AOp : string);

var
frmVisor: TfrmVisor;

implementation

{$R *.DFM}

procedure TfrmVisor.FormCreate(Sender: TObject);
begin
Session.GetAliasNames(cbAlias.Items);
cbAlias.ItemIndex:=0;
cbAliasChange(Sender);
BuscarSeguinte.Enabled:=False;
end;

procedure TfrmVisor.Inicializar;
begin
dbGrid.ReadOnly:=FReadOnly;
pBuscar.Visible:=FBuscar;
if FFiltrar then
dbGrid.PopupMenu:=PopupMenu1
else
dbGrid.PopupMenu:=nil;
end;

procedure TfrmVisor.cbAliasChange(Sender: TObject);
begin
Session.GetTableNames(cbAlias.Items[cbAlias.ItemIndex],'*.*',True,True,cbTables.Items);
cbTables.ItemIndex:=0;
cbTablesChange(Sender);
end;

procedure TfrmVisor.cbTablesChange(Sender: TObject);
begin
tTable.Active:=False;
tTable.DatabaseName:=cbAlias.Text;
tTable.TableName:=cbTables.Text;
tTable.Active:=True;
FillcbFields;
end;

procedure TfrmVisor.FillcbFields;
var
i : integer;

begin
cbFields.Items.Clear;
cbFields.Text:='';
for i:=0 to tTable.FieldCount-1 do
cbFields.Items.Add(tTable.Fields[i].DisplayLabel);
end;

procedure TfrmVisor.BuscarClick(Sender: TObject);
var
F : TField;
s : string;
begin
if cbFields.ItemIndex=-1 then
exit;
with tTable do begin
s:=eSearch.Text;
F:=Fields[cbFields.ItemIndex];
case F.DataType of
FtString, FtDate, FtTime, FtDateTime : PascalStr(s);
FtFloat : ComaStr(s);
end;
Filter:='['+F.FieldName+']='+s;
FindFirst;

BuscarSeguinte.Enabled:=Found;
end;
end;

procedure TfrmVisor.BuscarSeguinteClick(Sender: TObject);
begin
tTable.FindNext;
BuscarSeguinte.Enabled:=tTable.Found;
end;

procedure PascalStr(var s : string);
var
i : integer;
begin
for i:=Length(s) downto 1 do
if s[i]='''' then Insert('''',s,i);
s:=''''+s+'''';
end;

procedure ComaStr(var s : string);
var
i : integer;
begin
for i:=Length(s) downto 1 do
if s[i]=',' then s[i]:='.';
end;

procedure TfrmVisor.dbGridColEnter(Sender: TObject);
begin
cbFields.ItemIndex:=dbGrid.SelectedField.Index;
end;

procedure TfrmVisor.PopupMenu1Popup(Sender: TObject);
begin
with tTable do begin
Ativar1.Checked:=Filtered;
Ativar1.Enabled:=Filter<>'';
Eliminar1.Enabled:=Filter<>'';
end;
end;

procedure TfrmVisor.Ativar1Click(Sender: TObject);
begin
tTable.Filtered:=not tTable.Filtered;
tTable.Refresh;
end;

procedure TfrmVisor.Eliminar1Click(Sender: TObject);
begin
tTable.Filtered:=False;
tTable.Filter:='';
tTable.Refresh;
end;

procedure TfrmVisor.Igual1Click(Sender: TObject);
begin
AddFilter(dbGrid.SelectedField,'=');
end;

procedure TfrmVisor.Distinto1Click(Sender: TObject);
begin
AddFilter(dbGrid.SelectedField,'<>');
end;

procedure TfrmVisor.Menor1Click(Sender: TObject);
begin
AddFilter(dbGrid.SelectedField,'<=');
end;

procedure TfrmVisor.Maior1Click(Sender: TObject);
begin
AddFilter(dbGrid.SelectedField,'>=');
end;

procedure AddFilter(AField : TField; AOp : string);
var
s : string;
begin
s:=Condicao(AField,AOp);
with AField.DataSet do begin
if Filter='' then
Filter:=s
else
Filter:=Filter+' AND ' + s;

Filtered:=True;
Refresh;
end;
end;

function Condicao(AField : TField; AOp : string) : string;
var
Valor : string;
begin
Valor:=AField.AsString;
case AField.DataType of
FtString, FtDate, FtTime, FtDateTime : PascalStr(Valor);
FtFloat : ComaStr(Valor);
end;

Condicao:=Format('([%s] %s %s)',[AField.FieldName,AOp,Valor]);
end;

end.
8. EDITORES DE PROPRIEDADES (I)
Nesta unidade aprenderemos a criar nossos prprios editores de propriedades.

Os editores de propriedades so uma ferramenta poderosa que fazem diferena
entre um componente simplesmente aceitvel e um realmente bom. Por eles
poderemos dotar a nossos componentes de caractersticas novas que vo alm
da validao do valor das propriedades. Graas a eles faremos a vida mais fcil
para os programadores que usam nossos componentes, e isso sempre bom no
?


8.1. Introduo aos editores de propriedades
At agora criamos os componentes sem nos preocupar como foram
introduzidos os valores das diferentes propriedades definidas neles. Chegou o
momento de estudarmos este aspecto. Como j sabemos, para inserir e ler o
valor das propriedades em design-time usamos o Object Inspector. O Object
Inspector se divide em duas colunas: a direita esto os nomes das
propriedades e a esquerda seus valores. Introduzimos um valor novo e ele
aparece. Mas isto s aparncia. Os componentes se interagem com o Object
inspector atravs dos editores de propriedades. Do ponto de vista do usurio,
o Object Inspector o responsvel de publicar as propriedades, mas por trs
h uma srie de objetos, os editores de propriedades que se encarregam de
definir as capacidades de edio das diversas propriedades de um
componente.

Em design-time, quando voc seleciona um componente, o Object Inspector
cria instncias dos editores de propriedades necessrios para publicar as
propriedades definidas no componente selecionado. Quando termina a edio,
o mesmo Object Inspector destri os editores de propriedades criados.

O Delphi tem uma srie de editores de propriedades padres que so o
bastante para a maioria das propriedades com que habitualmente
trabalhamos. Mas o Delphi permite que criemos nossos prprios editores que
podem substituir certas propriedades que so default.


8.2. Os deveres do editor de propriedades
O Editor de Propriedades deve dar resposta a dois aspectos principais:

1. Definir como a propriedade dever ser publicada. Aqui h duas
possibilidades: pode ser modificado o valor da propriedade no prprio
Object Inspector ou voc pode usar uma caixa de dilogo para adicionar
mais flexibilidade edio (por exemplo: Propriedade Cor);
2. Converter o valor da propriedade para um tipo String. O Object
Inspector sempre trabalha com Strings. Embora a propriedade seja do
tipo inteiro ou ponto flutuante, o Object Inspector s usa o tipo String
em suas propriedades. o editor de propriedades que faz todo o
trabalho na representao desta propriedade. Traduzindo: Pode ir da
coisa mais simples (usar a funo IntToStr) at a uma mais complexa
(programa uma rotina para converses). Tudo depender do tipo de
propriedade com que ns estamos trabalhando.
8.3. Passos necessrios para escrever um editor de propriedades
Os passos necessrios para escrever um editor de propriedades so os
seguintes:

1. Criar uma nova Unit no qual definiremos o editor de propriedades. Mais
tarde falaremos mais amplamente sobre este ponto;
2. Colocar a Unit DsgnIntf na clusula Uses do editor de propriedades.
Nesta Unit esto definidos os editores de propriedades default que o
Delphi usa, alm da importante classe TPropertyEditor, que a base de
todos os Editores de Propriedades;
3. Criar uma nova classe descendente de TPropertyEditor ou de algum dos
descendentes dele. Por conveno, o nome dos editores de propriedades
terminam com a palavra Property. Por exemplo: TIntegerProperty,
TStringProperty... Para os principais editores de propriedades logo so
mostrados por default no Delphi:

Editor de Propriedades Tipo
TPropertyEditor
Base de classes para todos os editores de
propriedades
TIntegerProperty Byte, Word, Integer e LongInt
TCharProperty Char
TEnumProperty Tipos enumerados
TSetProperty Sets
TFloatProperty Single, Double, Extended, Comp, Currency
TStringProperty Strings
TClassProperty Qualquer objeto
TMethodProperty Qualquer mtodo (eventos)
TComponentProperty
Para propriedades que fazem referncia a
componentes

4. Implementar os mtodos necessrios para adicionar ao editor de
propriedades as funcionalidades requeridas;
5. Registrar o editor de propriedades na VCL.

Mais tarde aprofundaremos em todos os aspectos dos outros editores de
propriedades, vamos criar agora o nosso primeiro editor de propriedades.
8.4. Editor de propriedades para nmeros binrios
Desenvolveremos nosso primeiro editor de
propriedades que ser editvel no prprio
Object Inspector. Imagine que criamos um
componente com uma propriedade do tipo
inteiro que tenha um valor para ser
visualizado em base binria e no em
base decimal. Se no crissemos um
editor de propriedades e deixssemos que
o editor de propriedades default do Delphi
(TIntegerProperty no caso) fizesse o
trabalho de visualizao, seria em base
decimal e no em base binria, como
queremos. Isto tpico no qual
desenvolvendo um editor de propriedades
simples economiza-se muito trabalho para
o futuro usurio do nosso componente e
evita que ele tenha que fazer a converso
entre decimal e binrio.

Como j mencionamos, temos que decidir
de que classe o nosso editor derivar. Em
nosso caso fcil, a propriedade armazenar um valor do tipo inteiro, assim
ns usaremos IntegerProperty.

Muito bem, nossa propriedade armazena um inteiro, mas no queremos que o
Object Inspector nos mostre diretamente o valor decimal dele, queremos fazer
uma converso deste valor de decimal para binrio e s ento o Object
Inspector mostrar o valor. Para fazer isto, implementaremos (override) na
funo GetValue. Esta funo, definida em TPropertyEditor, chamada pelo
Object Inspector para obter a representao do valor da propriedade em
forma de string. Dentro desta funo deveremos converter o valor da
propriedade decimal para binrio e depois transformar este valor em string,
pois o Object Inspector s mostra Strings. Deste modo, a funo GetValue fica
assim:

unit BinaryPropEd;

interface

uses DsgnIntf;

type
TBinIntegerProperty = class(TIntegerProperty)
public
function GetValue : string; override;
procedure SetValue(const Value : String); override;
end;

procedure Register;

implementation


function TBinIntegerProperty.GetValue : String;
var
Num : Integer;

begin
Num := GetOrdValue;
Result := '';

while Num >= 2 do begin
Result := IntToStr(Num Mod 2)+Result;
Num := Num Div 2;
end;

if Num > 0 then
Result := IntToStr(Num)+Result;

if Length(Result) > 16 then
raise EPropertyError.Create('Error converting '+IntToStr(GetOrdValue)+' to bynary;

Result := '0000000000000000'+Result;
Result := 'B'+Copy(Result, Length(Result)-15, 16);
end;

...

end.

Da implementao desta funo a parte mais importante a primeira linha:
Num := GetOrdValue;

Precisamos obter o valor que est naquele momento na propriedade para
trabalhar nele. Para isto usamos isto o mtodo GetOrdValue, definido
novamente em TPropertyEditor, o qual se encarrega de devolver o valor da
propriedade em forma de ordinal (inteiro). De um modo semelhante, existem
os mtodos GetFloatValue, GetMethodValue, GetVarValue, etc. Para usar com
o tipo de propriedade correspondente.

Uma vez armazenado o valor da propriedade na varivel Num, a converso
comea do valor decimal para binrio, o qual fcil de entender. bom saber
que o nmero mximo de dgitos binrios 16, margem mais do que
suficiente para a maioria das aplicaes.

Por Ultimo temos que devolver um valor do tipo String como resultado da
funo. Porque ns vamos armazenar isto em uma propriedade que aceita
somente String.

Para concluir, colocamos antes da cadeia de string uma letra 'B' para indicar
que base binria.

E isso tudo que esta funo faz. Deste modo o Object Inspetor chama o
mtodo GetValue o qual lhe devolver o string correspondente. Mas partimos
para outro problema: seria bom se pudessemos introduzir o valor da
propriedade tanto em decimal como em binrio, no mesmo ?

Para adquirir esta funcionalidade devemos implementar (override) do mtodo
SetValue, definido na classe TPropertyEditor. Quando o usurio entra um valor
novo no Object Inspetor, ele chama o mtodo SetValue, o qual devera fazer a
traduo inversa ao feito pelo mtodo GetValue. Quer dizer, deveria converter
o string que contm o valor novo da propriedade para o tipo de dados desta
propriedade. Em nosso caso, o string entrar em base decimal ou binrio
(neste ltimo caso, estar a primeira letra da cadeia ' B') e converter isto para
um decimal. Para isto implementaremos o mtodo SetValue incluindo na
clusula Uses a Unit Math (para clculos matemticos) e da seguinte
maneira:

...

type
TBinIntegerProperty = class(TIntegerProperty)
public
function GetValue : string; override;
procedure SetValue(const Value : String); override;
end;

procedure Register;

implementation

...

procedure TBinIntegerProperty.SetValue(const Value : String);
var
Pos : Integer;
Total : LongInt;

begin
if UpperCase(Value[1]) = 'B' then begin
NumText := Trim(Value);

Total := 0;
if Length(NumText) > 0 then
for Pos := Lenght(NumText) DownTo 1 do
Total := Total + (Trunc(StrToInt(Copy(NumText,Pos,1)) *
Power(2, Length(NumText)-Pos))));

SetOrdValue(Total);
end else begin
SetOrdValue(StrToInt(Value));
end;
end;

...

end.

Na implementao deste primeiro mtodo conferimos se o usurio introduziu o
valor novo da propriedade em base decimal ou em base binria. No primeiro
caso, s necessrio converter o string para inteiro por meio da funo
StrToInt(Value) e, depois, usar o mtodo SetOrdValue para armazenar o valor
correspondente. De um modo semelhante, de acordo com o tipo da
propriedade, existem os mtodos SetFloatValue, etc.
No caso de a primeira letra da cadeia for um 'B', a cadeia se torna um valor
binrio para ser convertido em uma valor decimal, e usa o mtodo
SetOrdValue para armazenar o valor novamente na propriedade.

Uma vez implementado estes dois mtodos (GetValue e SetValue) j temos
nosso editor de propriedades acabado; s temos que registr-lo na VCL.


8.5. Inscrio de editor de propriedades
De mesmo modo que devemos registrar no VCL os componentes, temos que
registrar os editores de propriedades tambm. Para isto temos o mtodo
RegisterPropertyEditor (definida na unidade DsgnIntf):

procedure RegisterPropertyEditor(PropertyType: PTypeInfo; ComponentClass: TClass;
cont PropertyName: String; EditorClass: TpropertyEditorClass);

PropertyType faz referncia ao tipo da propriedade para o qual o editor de
propriedades ser aplicado. Para dar um valor a este parmetro normalmente
usaremos a funo TypeInfo, por exemplo, TypeInfo(integer).

ComponentClass permite restringir o uso do editor de propriedades para a
classe especfica. Um valor nulo registra o editor para todos os componentes.

PropertyName especifica o nome da propriedade. Um valor diferente de nulo
s registra o editor para a propriedade especificada, enquanto um valor ' '
registra isto para todas as propriedades.

EditorClass especifica o editor de propriedades que registram (a classe).

Usando estes parmetros, temos a nossa disposio um leque largo de
possibilidades para registrar nosso editor de propriedades. Abaixo temos
alguns exemplos com nosso editor recentemente criado:

RegisterPropertyEditor(TypeInfo(integer), nil, '', TBinIntegerProperty) o
editor de registros de propriedades para todos os componentes que tm
uma propriedade de tipo inteiro. E a forma mais global de registrar um
componente e afeta a todos os componentes registrados no VCL. Se
registrarmos nosso editor deste modo, veremos que todas as
propriedades do tipo inteiro aparecem em binrio ! Tambm podemos
introduzir um valor novo em decimal ou em binrio (colocando a letra B).
Estamos substituindo o editor de propriedades que Delphi usa para o
nosso ! Esta a forma mais global de registrar editor de propriedades.
RegisterPropertyEditor(TypeInfo(integer),TMiComponente,'PropriedadeBin
aria',TBinIntegerProperty) o editor s registra e exclusivamente para a
propriedade 'PropriedadeBinaria' do componente 'TMiComponente'. Esta
a forma restringida de registrar editor de propriedades.


Aconselhamos a voc que registre o editor da forma mais global possvel para
experiment-lo. Ento, uma vez voc viu todas as propriedades... voc o
desinstala.

Voc tambm pode se criar um "componente de mentirinha" e registrar s o
editor para o mesmo. Algo assim:

...

Type
TMiComponente = class(TComponent)
...
property PropriedadeBinaria : integer read FPropBin write FPropBin;
...
end;
...


8.6. Localizao do editor de propriedades
Como j mencionamos, quando mostramos os passos que deveriam ser
continuados criando editor de propriedades, o primeiro deles criar uma Unit
onde localizar o editor. Naquele momento dissemos que no uma escolha to
trivial como pudesse parecer em um pricincipio. Naquela Unit deveria
localizar-se o editor ? Na mesma Unit onde o componente que tem a
propriedade que ser publicada ? Em uma Unit separada ? Temos trs
possveis localizaes:

1. Localizar o editor de propriedades na mesma unidade em que o
componente. Esta a opo mais intuitiva; porm no o mais
aconselhvel, principalmente se o editor usa uma caixa dilogo.

A razo que o Delphi s usa o editor de propriedades em design - time.
De fato, o Form que contm a caixa de dilogo no conectado com a
aplicao, nem o editor de classe de propriedades. Porm, se os recursos
da caixa de dialogo so as unicas coisas associadas a Unit do componente
ento s estaramos aumentando o tamanho do executvel. Ento
estariamos aumentando o tamanho do executvel para nada : (por outro
lado, se o editor de propriedades no usa caixas de dialogo, continua ser
aconselhavel).

2. Localizar o editor de propriedades (se usa um Form ou uma caixa de
dilogo) na mesma Unit da caixa dilogo. Deste modo, a aplicao que
usa o componente no conecta ao editor de propriedades seus recursos
associados.

3. Localizar o editor de propriedades em uma Unit de registro. Uma Unit de
registro uma Unit normal e corrente que contm vrios registros
condenados correspondendo a componentes diferentes e editores de
propriedades que residem em Units diferentes. Esta a opo mais
aconselhvel se o editor de propriedades usado por vrios componentes.
Ento, as ltimas duas opes so dependendo do caso, as mais
aconselhveis.


8.7. Cdigo fonte do editor de propriedades
unit BinaryPropEd;

interface

uses DsgnIntf, Math;

type
TBinIntegerProperty = class(TIntegerProperty)
public
function GetValue : string; override;
procedure SetValue(const Value : String); override;
end;

procedure Register;

implementation

function TBinIntegerProperty.GetValue : String;
var
Num : Integer;

begin
Num := GetOrdValue;
Result := '';

while Num >= 2 do begin
Result := IntToStr(Num Mod 2)+Result;
Num := Num Div 2;
end;

if Num > 0 then
Result := IntToStr(Num)+Result;

if Length(Result) > 16 then
raise EPropertyError.Create('Error converting '+IntToStr(GetOrdValue)+' to bynary;

Result := '0000000000000000'+Result;
Result := 'B'+Copy(Result, Length(Result)-15, 16);
end;

procedure TBinIntegerProperty.SetValue(const Value : String);
var
Pos : Integer;
Total : LongInt;

begin
if UpperCase(Value[1]) = 'B' then begin
NumText := Trim(Value);

Total := 0;
if Length(NumText) > 0 then
for Pos := Lenght(NumText) DownTo 1 do
Total := Total + (Trunc(StrToInt(Copy(NumText,Pos,1)) *
Power(2, Length(NumText)-Pos))));

SetOrdValue(Total);
end else begin
SetOrdValue(StrToInt(Value));
end;
end;

procedure Register;
begin
RegisterPropertyEditor(TypeInfo(Integer),nil,'',TBinIntegerProperty);
end;

end.
9. EDITORES DE PROPRIEDADES (II)
Na unidade anterior aprendemos a operao bsica de editor de propriedades e
desenvolvemos um exemplo que trabalhava no Object Inspector (BinaryPropEd).

9.1. Introduo
Nesta unidade desenvolveremos quatro editores de propriedades e um
componente que ser responsvel por eles.

1. TArqProperty um editor de propriedades do tipo dilogo. Em alguma
ocasio desenvolveremos um componente no qual uma das propriedades
dever armazenar o nome de um arquivo. Em vez de obrigarmos o
usurio a digitar o nome de um arquivo existente usaremos o
TArqProperty para mostrar uma caixa de dilogo (do tipo OpenDialog)
para que ele escolha o arquivo desejado;

2. TAliasProperty um editor de propriedades inteligente de valores da
propriedade idnticos ao valor da propriedade DatabaseName do
compoente TTable: permite a escolha de um alias de banco de dados por
meio de uma lista drop-down no prprio ObjectInspector;

3. Aprofundando um pouco mais no tpico dos editores de propriedades do
tipo caixas de dilogo, criaremos um partindo do zero, ou seja, sem usar
uma caixa de dilogo pr-definida (como no caso do TArqProperty
publicado). Criaremos um editor que nos permite a introduo de
contra-senhas. Imagine isso em uma propriedade de um componente que
ns precisamos manter o valor de uma string do tipo password. Se no
crissemos qualquer editor de propriedades, o usurio do componente
escrever o valor diretamente no Object Inspector, mas o que ser
completamente visvel (no aparecero como acontece com o componente
TEdit). Assim criaremos uma caixa de dilogo que nos permitir escrever
e verificar a contra-senha para validar a propriedade. Este ser o nosso
TPasswordProperty;

4. Por ltimo criaremos um editor de propriedades, TDateTimeProperty,
para a introduo de datas. Deste modo poderemos digitar datas em uma
propriedade do tipo TDateTime (lembrando que as datas so do tipo Ponto
Flutuante) em vez de digitar o nmero equivalente.


9.2. Um Componente de Teste
Antes de comear a desenvolver os editores de propriedades, criaremos um
componente que nos permitir us-los. Este componente chamar Tprueba e
seu cdigo :

unit Unidad9;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DsgnIntf, DB,
PasswordForm;

type
TPrueba = class(TComponent)
private
FArquivo : string;
FAlias : string;
FData : TDateTime;
FPassword : string;
protected
public
constructor Create(AOwner : TComponent); override;
published
property Arquivo : string read FFichero write FFichero;
property Alias : string read FAlias write FAlias;
property Data : TDateTime read FFecha write FFecha;
property Password : string read FPassword write FPassword;
end;

...

implementation

...

constructor TPrueba.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FDAta:=Now;
end;

...

Quatro propriedades de cada um dos editores de propriedades para testar e
um construtor comum para colocar o valor padro de data.

Serve como aviso que pela simplicidade do componente, desenvolveremos o
componente e os quatro editores de propriedades em uma nica unit (h uma
unit adicional necessria para o TPasswordProperty). Faremos isto desta forma
por ser a mais simples, embora no seja a forma mais correta. Geralmente
cada editor de propriedades deveria entrar em uma unit diferente e
independente do prprio componente, conforme j foi visto na unidade 8.
9.3. O Editor de Propriedades TArqProperty
Nos fixamos nesta propriedade e como a implementamos no componente
Tprueba (do tipo String). Tambm no definimos nenhum mtodo Set/Get,
pois o read/write de valores nesta propriedade feito diretamente no campo
associado (FArquivo). Tal como quando um usurio do componente quer
introduzir um valor nesta propriedade, ele ter que fazer isto manualmente, o
que pode acabar sendo uma tarefa tediosa se o arquivo em questo est em
um caminho muito longo. De forma que, por generosidade nossa, decidimos
ajud-lo construindo um editor de propriedades para ele.

O Delphi j incorpora um componente que administra isto muito bem. o
TOpenDialog, de forma que ns veremos como usar isto melhor em nosso
editor.

O primeiro passo decidir de quem nosso editor se derivar. Em nosso caso,
desde que a propriedade do tipo string, decidimos herdar de
TStringProperty. Logo deveremos decidir se a propriedade ser editvel no
prprio editor de objetos ou ser usada uma caixa de dilogo. Neste caso, sem
dvida, deveremos escolher a segunda opo. Claro que isto no tira a chance
de escrever o valor da propriedade diretamente no Object Inspector.

Agora ento, como indicar ao Delphi que se trata de um editor de
propriedades do tipo caixa de dilogos ? Para isto devemos usar outro mtodo
da classe base de todos os editores de propriedades (TPropertyEditor): o
mtodo GetAttributes. Este mtodo uma funo que determina as
caractersticas que o editor de propriedades ter. Estas caractersticas so
devolvidas como resultado desta funo; este resultado do tipo
TPropertyAttributes. O tipo TPropertyAttributes um grupo (Set) e isso
pode ter os seguintes valores:

Atributo Descrio
paValueList Especifica que o editor dever mostrar uma lista com todos os possveis
valores da propriedade. Para encher a lista o mtodo GetValues usado.
paSortList S vlido se paValueList selecionado. Especifica que a lista de valores ser
ordenada.
paSubProperties Indica que o editor define sub-propriedades para mostrar direita (por
exemplo: a propriedade Font do TForm usa isto). Para gerar a lista de
propriedades o mtodo GetProperties usado.
paDialog Indica que o editor de propriedades dever mostrar uma caixa de dilogo em
resposta para o mtodo Edit (como exemplo veja a propriedade Glyph de um
TSpeedButton). Deste modo, quando selecionando a propriedade, ir aparecer
um boto com um "...".
paMultiSelect Possibilita a multi-seleo de opes.
paAutoUpdate O Object Inspector aciona o mtodo SetValue toda vez que o valor da
propriedade for alterado.
paReadOnly O usurio no pode modificar o valor da propriedade.
paRevertable Especifica se a propriedade pode recuperar seu valor original.

Devido a este quadro, fcil de implementar o mtodo GetAttributes em
nosso editor de propriedades:
interface

...

TArqProperty = class(TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure Edit; override;
end;

...

function TArqProperty.GetAttributes : TPropertyAttributes;
begin
Result := [paDialog];
end;

Novamente, por causa da simplicidade, s ativamos o atributo paDialog,
claro que poderamos ativar outro atributo como paMultiSelect, etc. Neste caso
escreveramos [paDialog, paMultiSelect].

S saberemos que o Delphi ativar a caixa de dilogo quando o usurio clica
no boto "..." ou um duplo clique na propriedade. O Delphi invocar o mtodo
Edit de nosso editor de propriedades. Neste mtodo deveremos colocar o
cdigo necessrio para mostrar a caixa de dilogo de abertura de arquivos e
colocar o nome do arquivo na propriedade se o usurio clicar no OK. Este
mtodo que devemos re-implementar (override, ver seo de interface),
deste modo:

...

procedure TArqProperty.Edit;
var
OpenDialog : TOpenDialog; {TOpenDialog est na unidade Dialogs, clausula uses}
begin
OpenDialog:=TOpenDialog.Create(Application); {Criamos a caixa de dilogo}
try
OpenDialog.Filter:='All files|*.*'; {Colocamos suas propriedades iniciais}
if OpenDialog.Execute then {Se o usurio clica OK...}
SetStrValue(OpenDialog.FileName); {Colocamos o novo valor na propriedade}
finally
OpenDialog.Free; {Liberamos a caixa de dilogo}
end;
end;

...

Mais fcil impossvel ! S temos que comentar que para termos certeza da
liberao de recursos (neste caso a caixa de dilogo) usamos um bloco
try...finnaly.

s ! Com 10 ou 15 linhas de cdigo aliviamos o pobre usurio de ficar
sofrendo com tudo isto. Agora registraremos o editor:

procedure Register;
begin
...
RegisterPropertyEditor(TypeInfo(string),TPrueba,'Arquivo',TArqProperty);
...
end;


9.4. O Editor de Propriedades TAliasProperty
Vamos construir agora um editor de propriedades para a propriedade Alias.
Como j sabemos, o componente TTable tem uma propriedade denominada
DatabaseName que especifica o Alias que conecta ao banco de dados.
Selecionar um valor por esta propriedade muito fcil, basta escolhermos um
tem da lista. Este o comportamento que queremos para a nossa
propriedade Alias. Poderamos procurar no cdigo fonte da VCL e tentar
registrar o editor de propriedades da propriedade DatabaseName de forma
que isto incluiria nossa nova propriedade, mas como a construo de um
editor deste tipo muito simples, ns mesmos desenvolveremos isto.

A seo interface de nosso editor a seguinte:

...

TAliasProperty = class (TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure GetValues(Proc : TGetStrProc); override;
end;

...

O mtodo GetAttributes foi conhecido na seo anterior. Basta colocar o
atributo paValueList (indica lista de opes) e o paSortList (para ordernar as
opes alfabeticamente).

function TAliasProperty.GetAttributes : TPropertyAttributes;
begin
Result := [paValueList, paSortList];
end;

Agora preencheremos a lista de Aliases. Para isto, re-implementaremos
(override) o mtodo GetValues. Este mtodo recebe um nico parmetro: um
ponteiro para mtodo. Este ponteiro faz referncia ao mtodo interno Add,
que adiciona um string na lista. Os diversos elementos inseridos na lista pelo
mtodo GetValues que invoca o mtodo referenciado pelo ponteiro e
transforma isto em um valor do tipo string. Soa complicado, no ? No se
preocupe com isso, pois no to difcil; s significa que temos que usar a
sentena Proc(string) para cada elemento a ser adicinado na lista. Em nosso
caso, como queremos adicionar os nomes dos Aliases existentes, faremos uma
ligao a Proc(nome do alias) e adiciona todos os valores. Previamente,
teremos obtido esses aliases existentes por meio do mtodo GetAliasList do
objeto Tsession:

procedure TAliasProperty.GetValues(Proc : TGetStrProc);
var
AliasList : TStringList; {lista com os alias existentes}
i : integer;

begin
try
AliasList := TStringList.Create; {Criamos a lista}
Session.GetAliasNames(AliasList); {Obtemos os alias existentes}
for i:=0 to AliasList.Count - 1 do {Para cada alias...}
Proc(AliasList[i]); {...fazemos a chamada ao mtodo Proc}
finally
AliasList.Free; {Liberamos a lista}
end;
end;

Com isto j construmos nosso novo editor de propriedades. S falta
registr-lo:

procedure Register;
begin
...
RegisterPropertyEditor(TypeInfo(String),TPrueba,'Alias',TAliasProperty);
...
end;
9.5. O Editor de Propriedades TPasswordProperty
Vamos construir um outro editor de propriedades de caixa de dilogos. A
operao dela ser semelhante ao TArqProperty. Colocaremos paDialog para
indicar que editor de caixa de dilogo e no mtodo Edit estar todo o cdigo
do componente.

A diferena principal que nosso componente no usar uma caixa de dilogo
existente, criaremos uma caixa de dilogos, com dois labels e dois edit boxes:

A coisa mais importante nomear a propriedade PasswordChar para o
caracter * (asterisco), de forma que este caracter o que ser apresentado
quando o usurio digitar alguma coisa. Tambm, no evento onCloseQuery
conferir a validade da Contra-Senha digitada.

Os Edits foram nomeados de PW1 e PW2.

O cdigo da unit PasswordForm:

unit PasswordForm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons;

type
TfrmPassword = class(TForm)
lpwd: TLabel;
lVpwd: TLabel;
PW1: TEdit;
PW2: TEdit;
bOK: TBitBtn;
bCancel: TBitBtn;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
public
end;

var
frmPassword: TfrmPassword;

implementation

{$R *.DFM}

procedure TfrmPassword.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
begin
if (ModalResult=mrOK) then
if (PW1.Text = '') then begin
ShowMessage('Deve se inserir a contra-senha');
CanClose:=False;
end
else
if (ModalResult=mrOK) and (PW1.Text <> PW2.Text) then begin
ShowMessage('Verificao invlida. Por favor retente');
CanClose:=False;
end;
end;

end.

J construmos a caixa de dilogo, agora s nos falta encaix-lo no editor de
propriedades. Para isto, fazemos a ligao do form com o mtodo Edit do
editor de propriedades. Vamos ver como o codigo:

function TPasswordProperty.GetAttributes : TPropertyAttributes;
begin
Result := [paDialog];
end;

function TPasswordProperty.GetValue : string;
begin
Result := Format('(%s)',[GetPropType^.Name]);
end;

procedure TPasswordProperty.Edit;
begin
frmPassword := TfrmPassword.Create(Application);
try
frmPassword.Caption:=GetComponent(0).Owner.Name+'.'+
GetComponent(0).Name+'.'+GetName+' - '+frmPassword.Caption;
frmPassword.PW1.Text:=GetStrValue;
frmPassword.PW2.Text:=frmPassword.PW1.Text;
if frmPassword.ShowModal = mrOK then
SetStrValue(frmPassword.PW1.Text)
finally
frmPassword.Free;
end;
end;

S h uma coisa nova: no mtodo GetValue no queremos que seja mostrado
o valor da contra-senha, ento poderamos mostrar outra coisa. Poderia ser
uma cadeia de asteriscos...

Registrando a propriedade:

procedure Register;
begin
...
RegisterPropertyEditor(TypeInfo(String),TPrueba,'Password',TPasswordProperty);
end;
9.6. O Editor de Propriedades TDateTimeProperty
Para terminar esta unidade, desenvolveremos um editor de propriedades
TDateTime.

Como j sabemos, o tipo TDateTime do Delphi do tipo ponto flutuante, de
forma que se temos um componente com uma propriedade de tipo TDateTime
e no registrarmos qualquer editor de propriedades para ela, o usurio ter
que escrever a data em formato decimal (voc imagina que data o nmero
31457 ?). Que ruim ! Principalmente se pensarmos que em seis linhas de
cdigo resolvido o problema:

function TDateTimeProperty.GetValue : string;
begin
Result:=DateTimeToStr(GetFloatValue);
end;

procedure TDateTimeProperty.SetValue(const Value : string);
begin
SetFloatValue(StrToDateTime(Value));
end;

procedure Register;
begin
...
RegisterPropertyEditor(TypeInfo(TDateTime),TPrueba,'Fecha',TDateTimeProperty);
end;

Nada de novo at aqui. S nos limitamos a chamar os mtodos GetFloatValue
e SetFloatValue.


9.7. Concluses
Com isto terminamos o tpico de Editores de Propriedades. Aprendemos a
criar e publicar editores editveis no Object Inspector, que pode ser
preenchido diretamente na propriedade ou por uma caixa de dilogo. O poder
do Editor de Propriedades imenso: por meio dos mtodos SetValue e
GetValue podemos fazer uma verificao do valor da propriedade e agir
depois. Tambm nosso editor pode fazer mil coisas: consultar um arquivo ini,
fazer clculos complexos ou at mesmo operar com um banco de dados !

Mas os editores de propriedades tm duas limitaes:
1. Eles s operam em design-time, aspecto que deveramos lembrar que o
usurio pode querer fazer em run-time. Um exemplo claro as
propriedades do tipo lista. Por exemplo: Os artigos de propriedade de um
TListBox. Em design-time o editor de propriedades especfico permite o
usurio de um modo intuitivo adicionar ou eliminar elementos. Em troca,
em run-time, ele tem os mtodos Add e Delete para operar com a lista.
2. Editor de propriedades s opera com o valor de uma propriedade. Quer
dizer, no podemos alterar o valor de propriedades diferentes ao mesmo
tempo, desde que os mtodos GetValue e SetValue fazem referncia
propriedade que est publicada e no para o resto deles. Ento quando
quisermos alterar vrias propriedades de um componente ao mesmo
tempo usaremos o Editor de Componentes, que ser objeto da prxima
unidade.
9.8. Cdigo Fonte dos Editores de Propriedades

unit Unidad9;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DsgnIntf, DB, PasswordForm;

type
TPrueba = class(TComponent)
private
FArquivo : string;
FAlias : string;
FData : TDateTime;
FPassword : string;
protected
public
constructor Create(AOwner : TComponent); override;
published
property Arquivo : string read FFichero write FFichero;
property Alias : string read FAlias write FAlias;
property Data : TDateTime read FFecha write FFecha;
property Password : string read FPassword write FPassword;
end;

TArqProperty = class(TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure Edit; override;
end;

TAliasProperty = class (TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure GetValues(Proc : TGetStrProc); override;
end;

TDateTimeProperty = class(TFloatProperty)
function GetValue : string; override;
procedure SetValue(const Value : string); override;
end;

TPasswordProperty = class(TPropertyEditor)
function GetAttributes : TPropertyAttributes; override;
function GetValue : string; override;
procedure Edit; override;
end;

procedure Register;

implementation

constructor TPrueba.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FFecha:=Now;
end;

function TFicheroProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paDialog];
end;
procedure TArqProperty.Edit;
var
OpenDialog : TOpenDialog;
begin
OpenDialog:=TOpenDialog.Create(Application);
try
OpenDialog.Filter:='All files|*.*';
if OpenDialog.Execute then
SetStrValue(OpenDialog.FileName);
finally
OpenDialog.Free;
end;
end;


function TAliasProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paValueList, paSortList];
end;

procedure TAliasProperty.GetValues(Proc : TGetStrProc);
Var
AliasList : TStringList;
i : integer;
begin
try
AliasList := TStringList.Create;
Session.GetAliasNames(AliasList);
for i:=0 to AliasList.Count - 1 do
Proc(AliasList[i]);
finally
AliasList.Free;
end;
end;

function TDateTimeProperty.GetValue : string;
begin
Result:=DateTimeToStr(GetFloatValue);
end;

procedure TDateTimeProperty.SetValue(const Value : string);
begin
SetFloatValue(StrToDateTime(Value));
end;

function TPasswordProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paDialog];
end;

function TPasswordProperty.GetValue : string;
begin
Result:=Format('(%s)',[GetPropType^.Name]);
end;

procedure TPasswordProperty.Edit;
begin
frmPassword := TfrmPassword.Create(Application);
try
frmPassword.Caption:=GetComponent(0).Owner.Name+'.'+
GetComponent(0).Name+'.'+GetName+' - '+
frmPassword.Caption;
frmPassword.PW1.Text:=GetStrValue;
frmPassword.PW2.Text:=frmPassword.PW1.Text;
if frmPassword.ShowModal = mrOK then
SetStrValue(frmPassword.PW1.Text)
finally
frmPassword.Free;
end;
end;

procedure Register;
begin
RegisterComponents('Pruebas', [TPrueba]);
RegisterPropertyEditor(TypeInfo(string),TPrueba,'Arquivo,TArqProperty);
RegisterPropertyEditor(TypeInfo(String),TPrueba,'Alias',TAliasProperty);
RegisterPropertyEditor(TypeInfo(TDateTime),TPrueba,'Data',TDateTimeProperty);
RegisterPropertyEditor(TypeInfo(String),TPrueba,'Password',TPasswordProperty);
end;

end.




unit PasswordForm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons;

type
TfrmPassword = class(TForm)
lpwd: TLabel;
lVpwd: TLabel;
PW1: TEdit;
PW2: TEdit;
bOK: TBitBtn;
bCancel: TBitBtn;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
public
end;

var
frmPassword: TfrmPassword;

implementation

{$R *.DFM}

procedure TfrmPassword.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
begin
if (ModalResult=mrOK) then
if (PW1.Text = '') then
begin
ShowMessage('Debe introducir una contrasea');
CanClose:=False;
end
else if (ModalResult=mrOK) and (PW1.Text <> PW2.Text) then
begin
ShowMessage('Verificacin fallida. Por favor reintente');
CanClose:=False;
end;
end;

end.

Você também pode gostar